@@ -132,8 +132,119 @@ static void test_nsfs_fs_id(void) {
132132 g_assert_cmpint (buf .f_type , = = , NSFS_MAGIC );
133133}
134134
135+ // Check that sc_running_kernel_is_6_18() agrees with a straightforward
136+ // uname(2) reading done independently here, so that the two never drift
137+ // apart from each other on whatever kernel the test happens to run on.
138+ static void test_sc_running_kernel_is_6_18 (void ) {
139+ struct utsname uts ;
140+ g_assert_cmpint (uname (& uts ), = = , 0 );
141+ int major = 0 , minor = 0 ;
142+ g_assert_cmpint (sscanf (uts .release , "%d.%d" , & major , & minor ), = = , 2 );
143+ bool expected = major == 6 && minor == 18 ;
144+ g_assert_cmpint (sc_running_kernel_is_6_18 (), = = , expected );
145+ }
146+
147+ // Check that sc_ensure_mount_ns_id_ordered() enforces its precondition that
148+ // a helper process has already been forked for this group.
149+ static void test_sc_ensure_mount_ns_id_ordered_no_helper (void ) {
150+ struct sc_mount_ns * group = sc_alloc_mount_ns ();
151+ g_test_queue_free (group );
152+
153+ if (g_test_subprocess ()) {
154+ sc_ensure_mount_ns_id_ordered (group );
155+ g_assert_not_reached ();
156+ }
157+ g_test_trap_subprocess (NULL , 0 , 0 );
158+ g_test_trap_assert_failed ();
159+ g_test_trap_assert_stderr ("*precondition failed: we don't have a helper process*" );
160+ }
161+
162+ // Check that sc_read_mnt_ns_id() dies on a genuine ioctl failure (as opposed
163+ // to ENOTTY/EINVAL, which mean "not supported" and are reported via a false
164+ // return instead, see the comment above the function).
165+ static void test_sc_read_mnt_ns_id_bad_fd (void ) {
166+ if (g_test_subprocess ()) {
167+ uint64_t ns_id = 0 ;
168+ // -1 is never a valid file descriptor, so the ioctl below fails with
169+ // EBADF, which sc_read_mnt_ns_id() must treat as a real error.
170+ sc_read_mnt_ns_id (-1 , & ns_id );
171+ g_assert_not_reached ();
172+ }
173+ g_test_trap_subprocess (NULL , 0 , 0 );
174+ g_test_trap_assert_failed ();
175+ g_test_trap_assert_stderr ("*cannot query mount namespace id*" );
176+ }
177+
178+ // Check that sc_read_mnt_ns_id() can read our own, real mount namespace id.
179+ // This exercises the ioctl wrapper without needing any special privilege:
180+ // every process can read /proc/self/ns/mnt.
181+ static void test_sc_read_mnt_ns_id_self (void ) {
182+ int fd = open ("/proc/self/ns/mnt" , O_RDONLY | O_CLOEXEC );
183+ g_assert_cmpint (fd , != , -1 );
184+ uint64_t ns_id = 0 ;
185+ bool supported = sc_read_mnt_ns_id (fd , & ns_id );
186+ close (fd );
187+ if (!supported ) {
188+ // NS_GET_ID is unavailable on kernels older than the ones affected
189+ // by the ordering bug this whole file works around.
190+ g_test_skip ("NS_GET_ID is not supported by this kernel" );
191+ return ;
192+ }
193+ g_assert_cmpuint (ns_id , != , 0 );
194+ }
195+
196+ // Check the common case end-to-end: a helper forked just before we unshare
197+ // a fresh mount namespace should, overwhelmingly, already be ordered before
198+ // it, and sc_ensure_mount_ns_id_ordered() should return without needing to
199+ // exercise its CPU-sweep fallback (which, if it did trigger, would still be
200+ // expected to terminate -- see the comment above the function). On kernels
201+ // outside the affected 6.18.y series this is a no-op by construction (see
202+ // sc_running_kernel_is_6_18()), so the test still passes there, it just
203+ // doesn't exercise anything beyond that early return.
204+ //
205+ // This needs real CAP_SYS_ADMIN in the same user namespace as the helper
206+ // process, like the real code always has by the time it gets here: using
207+ // unshare(CLONE_NEWUSER) to fake privilege, as some other tests in this
208+ // suite do to run unprivileged, does not work here specifically, because it
209+ // would leave the helper behind in a different (the real, original) user
210+ // namespace, and opening its /proc/<pid>/ns/mnt across that boundary fails
211+ // with EACCES regardless of what capabilities we hold in our new one.
212+ static void test_sc_ensure_mount_ns_id_ordered_common_case (void ) {
213+ if (geteuid () != 0 ) {
214+ g_test_skip ("this test only runs as root" );
215+ return ;
216+ }
217+
218+ struct sc_mount_ns * group = sc_alloc_mount_ns ();
219+ g_test_queue_free (group );
220+
221+ pid_t pid = fork ();
222+ g_assert_cmpint (pid , != , -1 );
223+ if (pid == 0 ) {
224+ // helper: stay in the namespace we were forked in and wait to be
225+ // killed by the parent.
226+ prctl (PR_SET_PDEATHSIG , SIGKILL , 0 , 0 , 0 );
227+ pause ();
228+ _exit (0 );
229+ }
230+ group -> child = pid ;
231+
232+ g_assert_cmpint (unshare (CLONE_NEWNS ), = = , 0 );
233+
234+ sc_ensure_mount_ns_id_ordered (group );
235+
236+ kill (pid , SIGKILL );
237+ int status = 0 ;
238+ waitpid (pid , & status , 0 );
239+ }
240+
135241static void __attribute__((constructor )) init (void ) {
136242 g_test_add_func ("/ns/sc_alloc_mount_ns" , test_sc_alloc_mount_ns );
137243 g_test_add_func ("/ns/sc_open_mount_ns" , test_sc_open_mount_ns );
138244 g_test_add_func ("/ns/nsfs_fs_id" , test_nsfs_fs_id );
245+ g_test_add_func ("/ns/sc_running_kernel_is_6_18" , test_sc_running_kernel_is_6_18 );
246+ g_test_add_func ("/ns/sc_ensure_mount_ns_id_ordered_no_helper" , test_sc_ensure_mount_ns_id_ordered_no_helper );
247+ g_test_add_func ("/ns/sc_read_mnt_ns_id_bad_fd" , test_sc_read_mnt_ns_id_bad_fd );
248+ g_test_add_func ("/ns/sc_read_mnt_ns_id_self" , test_sc_read_mnt_ns_id_self );
249+ g_test_add_func ("/ns/sc_ensure_mount_ns_id_ordered_common_case" , test_sc_ensure_mount_ns_id_ordered_common_case );
139250}
0 commit comments