Skip to content

Commit f453cc3

Browse files
committed
selftests/harness: Fix vfork() side effects
Setting the time namespace with CLONE_NEWTIME returns -EUSERS if the calling thread shares memory with another thread (because of the shared vDSO), which is the case when it is created with vfork(). Fix pidfd_setns_test by replacing test harness's vfork() call with a clone3() call with CLONE_VFORK, and an explicit sharing of the _metadata and self objects. Replace _metadata->teardown_parent with a new FIXTURE_TEARDOWN_PARENT() helper that can replace FIXTURE_TEARDOWN(). This is a cleaner approach and it enables to selectively share the fixture data between the child process running tests and the parent process running the fixture teardown. This also avoids updating several tests to not rely on the self object's copy-on-write property (e.g. storing the returned value of a fork() call). Cc: Christian Brauner <[email protected]> Cc: David S. Miller <[email protected]> Cc: Günther Noack <[email protected]> Cc: Jakub Kicinski <[email protected]> Cc: Mark Brown <[email protected]> Cc: Shuah Khan <[email protected]> Cc: Will Drewry <[email protected]> Reported-by: kernel test robot <[email protected]> Closes: https://lore.kernel.org/oe-lkp/[email protected] Fixes: 0710a1a ("selftests/harness: Merge TEST_F_FORK() into TEST_F()") Reviewed-by: Kees Cook <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mickaël Salaün <[email protected]>
1 parent 24cf65a commit f453cc3

File tree

2 files changed

+57
-25
lines changed

2 files changed

+57
-25
lines changed

tools/testing/selftests/kselftest_harness.h

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,32 @@ static inline pid_t clone3_vfork(void)
294294
* A bare "return;" statement may be used to return early.
295295
*/
296296
#define FIXTURE_TEARDOWN(fixture_name) \
297+
static const bool fixture_name##_teardown_parent; \
298+
__FIXTURE_TEARDOWN(fixture_name)
299+
300+
/**
301+
* FIXTURE_TEARDOWN_PARENT()
302+
* *_metadata* is included so that EXPECT_*, ASSERT_* etc. work correctly.
303+
*
304+
* @fixture_name: fixture name
305+
*
306+
* .. code-block:: c
307+
*
308+
* FIXTURE_TEARDOWN_PARENT(fixture_name) { implementation }
309+
*
310+
* Same as FIXTURE_TEARDOWN() but run this code in a parent process. This
311+
* enables the test process to drop its privileges without impacting the
312+
* related FIXTURE_TEARDOWN_PARENT() (e.g. to remove files from a directory
313+
* where write access was dropped).
314+
*
315+
* To make it possible for the parent process to use *self*, share (MAP_SHARED)
316+
* the fixture data between all forked processes.
317+
*/
318+
#define FIXTURE_TEARDOWN_PARENT(fixture_name) \
319+
static const bool fixture_name##_teardown_parent = true; \
320+
__FIXTURE_TEARDOWN(fixture_name)
321+
322+
#define __FIXTURE_TEARDOWN(fixture_name) \
297323
void fixture_name##_teardown( \
298324
struct __test_metadata __attribute__((unused)) *_metadata, \
299325
FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \
@@ -368,10 +394,11 @@ static inline pid_t clone3_vfork(void)
368394
* Very similar to TEST() except that *self* is the setup instance of fixture's
369395
* datatype exposed for use by the implementation.
370396
*
371-
* The @test_name code is run in a separate process sharing the same memory
372-
* (i.e. vfork), which means that the test process can update its privileges
373-
* without impacting the related FIXTURE_TEARDOWN() (e.g. to remove files from
374-
* a directory where write access was dropped).
397+
* The _metadata object is shared (MAP_SHARED) with all the potential forked
398+
* processes, which enables them to use EXCEPT_*() and ASSERT_*().
399+
*
400+
* The *self* object is only shared with the potential forked processes if
401+
* FIXTURE_TEARDOWN_PARENT() is used instead of FIXTURE_TEARDOWN().
375402
*/
376403
#define TEST_F(fixture_name, test_name) \
377404
__TEST_F_IMPL(fixture_name, test_name, -1, TEST_TIMEOUT_DEFAULT)
@@ -392,39 +419,49 @@ static inline pid_t clone3_vfork(void)
392419
struct __fixture_variant_metadata *variant) \
393420
{ \
394421
/* fixture data is alloced, setup, and torn down per call. */ \
395-
FIXTURE_DATA(fixture_name) self; \
422+
FIXTURE_DATA(fixture_name) self_private, *self = NULL; \
396423
pid_t child = 1; \
397424
int status = 0; \
398425
/* Makes sure there is only one teardown, even when child forks again. */ \
399426
bool *teardown = mmap(NULL, sizeof(*teardown), \
400427
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); \
401428
*teardown = false; \
402-
memset(&self, 0, sizeof(FIXTURE_DATA(fixture_name))); \
429+
if (sizeof(*self) > 0) { \
430+
if (fixture_name##_teardown_parent) { \
431+
self = mmap(NULL, sizeof(*self), PROT_READ | PROT_WRITE, \
432+
MAP_SHARED | MAP_ANONYMOUS, -1, 0); \
433+
} else { \
434+
memset(&self_private, 0, sizeof(self_private)); \
435+
self = &self_private; \
436+
} \
437+
} \
403438
if (setjmp(_metadata->env) == 0) { \
404-
/* Use the same _metadata. */ \
405-
child = vfork(); \
439+
/* _metadata and potentially self are shared with all forks. */ \
440+
child = clone3_vfork(); \
406441
if (child == 0) { \
407-
fixture_name##_setup(_metadata, &self, variant->data); \
442+
fixture_name##_setup(_metadata, self, variant->data); \
408443
/* Let setup failure terminate early. */ \
409444
if (_metadata->exit_code) \
410445
_exit(0); \
411446
_metadata->setup_completed = true; \
412-
fixture_name##_##test_name(_metadata, &self, variant->data); \
447+
fixture_name##_##test_name(_metadata, self, variant->data); \
413448
} else if (child < 0 || child != waitpid(child, &status, 0)) { \
414449
ksft_print_msg("ERROR SPAWNING TEST GRANDCHILD\n"); \
415450
_metadata->exit_code = KSFT_FAIL; \
416451
} \
417452
} \
418453
if (child == 0) { \
419-
if (_metadata->setup_completed && !_metadata->teardown_parent && \
454+
if (_metadata->setup_completed && !fixture_name##_teardown_parent && \
420455
__sync_bool_compare_and_swap(teardown, false, true)) \
421-
fixture_name##_teardown(_metadata, &self, variant->data); \
456+
fixture_name##_teardown(_metadata, self, variant->data); \
422457
_exit(0); \
423458
} \
424-
if (_metadata->setup_completed && _metadata->teardown_parent && \
459+
if (_metadata->setup_completed && fixture_name##_teardown_parent && \
425460
__sync_bool_compare_and_swap(teardown, false, true)) \
426-
fixture_name##_teardown(_metadata, &self, variant->data); \
461+
fixture_name##_teardown(_metadata, self, variant->data); \
427462
munmap(teardown, sizeof(*teardown)); \
463+
if (self && fixture_name##_teardown_parent) \
464+
munmap(self, sizeof(*self)); \
428465
if (!WIFEXITED(status) && WIFSIGNALED(status)) \
429466
/* Forward signal to __wait_for_test(). */ \
430467
kill(getpid(), WTERMSIG(status)); \
@@ -898,7 +935,6 @@ struct __test_metadata {
898935
bool timed_out; /* did this test timeout instead of exiting? */
899936
bool aborted; /* stopped test due to failed ASSERT */
900937
bool setup_completed; /* did setup finish? */
901-
bool teardown_parent; /* run teardown in a parent process */
902938
jmp_buf env; /* for exiting out of test early */
903939
struct __test_results *results;
904940
struct __test_metadata *prev, *next;

tools/testing/selftests/landlock/fs_test.c

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,6 @@ static void prepare_layout_opt(struct __test_metadata *const _metadata,
286286

287287
static void prepare_layout(struct __test_metadata *const _metadata)
288288
{
289-
_metadata->teardown_parent = true;
290-
291289
prepare_layout_opt(_metadata, &mnt_tmp);
292290
}
293291

@@ -316,7 +314,7 @@ FIXTURE_SETUP(layout0)
316314
prepare_layout(_metadata);
317315
}
318316

319-
FIXTURE_TEARDOWN(layout0)
317+
FIXTURE_TEARDOWN_PARENT(layout0)
320318
{
321319
cleanup_layout(_metadata);
322320
}
@@ -379,7 +377,7 @@ FIXTURE_SETUP(layout1)
379377
create_layout1(_metadata);
380378
}
381379

382-
FIXTURE_TEARDOWN(layout1)
380+
FIXTURE_TEARDOWN_PARENT(layout1)
383381
{
384382
remove_layout1(_metadata);
385383

@@ -3692,7 +3690,7 @@ FIXTURE_SETUP(ftruncate)
36923690
create_file(_metadata, file1_s1d1);
36933691
}
36943692

3695-
FIXTURE_TEARDOWN(ftruncate)
3693+
FIXTURE_TEARDOWN_PARENT(ftruncate)
36963694
{
36973695
EXPECT_EQ(0, remove_path(file1_s1d1));
36983696
cleanup_layout(_metadata);
@@ -3870,7 +3868,7 @@ FIXTURE_SETUP(layout1_bind)
38703868
clear_cap(_metadata, CAP_SYS_ADMIN);
38713869
}
38723870

3873-
FIXTURE_TEARDOWN(layout1_bind)
3871+
FIXTURE_TEARDOWN_PARENT(layout1_bind)
38743872
{
38753873
/* umount(dir_s2d2)) is handled by namespace lifetime. */
38763874

@@ -4275,7 +4273,7 @@ FIXTURE_SETUP(layout2_overlay)
42754273
clear_cap(_metadata, CAP_SYS_ADMIN);
42764274
}
42774275

4278-
FIXTURE_TEARDOWN(layout2_overlay)
4276+
FIXTURE_TEARDOWN_PARENT(layout2_overlay)
42794277
{
42804278
if (self->skip_test)
42814279
SKIP(return, "overlayfs is not supported (teardown)");
@@ -4708,8 +4706,6 @@ FIXTURE_SETUP(layout3_fs)
47084706
SKIP(return, "this filesystem is not supported (setup)");
47094707
}
47104708

4711-
_metadata->teardown_parent = true;
4712-
47134709
prepare_layout_opt(_metadata, &variant->mnt);
47144710

47154711
/* Creates directory when required. */
@@ -4743,7 +4739,7 @@ FIXTURE_SETUP(layout3_fs)
47434739
free(dir_path);
47444740
}
47454741

4746-
FIXTURE_TEARDOWN(layout3_fs)
4742+
FIXTURE_TEARDOWN_PARENT(layout3_fs)
47474743
{
47484744
if (self->skip_test)
47494745
SKIP(return, "this filesystem is not supported (teardown)");

0 commit comments

Comments
 (0)