Commit f453cc30 authored by Mickaël Salaün's avatar Mickaël Salaün

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 <brauner@kernel.org>
Cc: David S. Miller <davem@davemloft.net>
Cc: Günther Noack <gnoack@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Mark Brown <broonie@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: Will Drewry <wad@chromium.org>
Reported-by: default avatarkernel test robot <oliver.sang@intel.com>
Closes: https://lore.kernel.org/oe-lkp/202403291015.1fcfa957-oliver.sang@intel.com
Fixes: 0710a1a7 ("selftests/harness: Merge TEST_F_FORK() into TEST_F()")
Reviewed-by: default avatarKees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20240511171445.904356-10-mic@digikod.netSigned-off-by: default avatarMickaël Salaün <mic@digikod.net>
parent 24cf65a6
...@@ -294,6 +294,32 @@ static inline pid_t clone3_vfork(void) ...@@ -294,6 +294,32 @@ static inline pid_t clone3_vfork(void)
* A bare "return;" statement may be used to return early. * A bare "return;" statement may be used to return early.
*/ */
#define FIXTURE_TEARDOWN(fixture_name) \ #define FIXTURE_TEARDOWN(fixture_name) \
static const bool fixture_name##_teardown_parent; \
__FIXTURE_TEARDOWN(fixture_name)
/**
* FIXTURE_TEARDOWN_PARENT()
* *_metadata* is included so that EXPECT_*, ASSERT_* etc. work correctly.
*
* @fixture_name: fixture name
*
* .. code-block:: c
*
* FIXTURE_TEARDOWN_PARENT(fixture_name) { implementation }
*
* Same as FIXTURE_TEARDOWN() but run this code in a parent process. This
* enables the test process to drop its privileges without impacting the
* related FIXTURE_TEARDOWN_PARENT() (e.g. to remove files from a directory
* where write access was dropped).
*
* To make it possible for the parent process to use *self*, share (MAP_SHARED)
* the fixture data between all forked processes.
*/
#define FIXTURE_TEARDOWN_PARENT(fixture_name) \
static const bool fixture_name##_teardown_parent = true; \
__FIXTURE_TEARDOWN(fixture_name)
#define __FIXTURE_TEARDOWN(fixture_name) \
void fixture_name##_teardown( \ void fixture_name##_teardown( \
struct __test_metadata __attribute__((unused)) *_metadata, \ struct __test_metadata __attribute__((unused)) *_metadata, \
FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \ FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \
...@@ -368,10 +394,11 @@ static inline pid_t clone3_vfork(void) ...@@ -368,10 +394,11 @@ static inline pid_t clone3_vfork(void)
* Very similar to TEST() except that *self* is the setup instance of fixture's * Very similar to TEST() except that *self* is the setup instance of fixture's
* datatype exposed for use by the implementation. * datatype exposed for use by the implementation.
* *
* The @test_name code is run in a separate process sharing the same memory * The _metadata object is shared (MAP_SHARED) with all the potential forked
* (i.e. vfork), which means that the test process can update its privileges * processes, which enables them to use EXCEPT_*() and ASSERT_*().
* without impacting the related FIXTURE_TEARDOWN() (e.g. to remove files from *
* a directory where write access was dropped). * The *self* object is only shared with the potential forked processes if
* FIXTURE_TEARDOWN_PARENT() is used instead of FIXTURE_TEARDOWN().
*/ */
#define TEST_F(fixture_name, test_name) \ #define TEST_F(fixture_name, test_name) \
__TEST_F_IMPL(fixture_name, test_name, -1, TEST_TIMEOUT_DEFAULT) __TEST_F_IMPL(fixture_name, test_name, -1, TEST_TIMEOUT_DEFAULT)
...@@ -392,39 +419,49 @@ static inline pid_t clone3_vfork(void) ...@@ -392,39 +419,49 @@ static inline pid_t clone3_vfork(void)
struct __fixture_variant_metadata *variant) \ struct __fixture_variant_metadata *variant) \
{ \ { \
/* fixture data is alloced, setup, and torn down per call. */ \ /* fixture data is alloced, setup, and torn down per call. */ \
FIXTURE_DATA(fixture_name) self; \ FIXTURE_DATA(fixture_name) self_private, *self = NULL; \
pid_t child = 1; \ pid_t child = 1; \
int status = 0; \ int status = 0; \
/* Makes sure there is only one teardown, even when child forks again. */ \ /* Makes sure there is only one teardown, even when child forks again. */ \
bool *teardown = mmap(NULL, sizeof(*teardown), \ bool *teardown = mmap(NULL, sizeof(*teardown), \
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); \ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); \
*teardown = false; \ *teardown = false; \
memset(&self, 0, sizeof(FIXTURE_DATA(fixture_name))); \ if (sizeof(*self) > 0) { \
if (fixture_name##_teardown_parent) { \
self = mmap(NULL, sizeof(*self), PROT_READ | PROT_WRITE, \
MAP_SHARED | MAP_ANONYMOUS, -1, 0); \
} else { \
memset(&self_private, 0, sizeof(self_private)); \
self = &self_private; \
} \
} \
if (setjmp(_metadata->env) == 0) { \ if (setjmp(_metadata->env) == 0) { \
/* Use the same _metadata. */ \ /* _metadata and potentially self are shared with all forks. */ \
child = vfork(); \ child = clone3_vfork(); \
if (child == 0) { \ if (child == 0) { \
fixture_name##_setup(_metadata, &self, variant->data); \ fixture_name##_setup(_metadata, self, variant->data); \
/* Let setup failure terminate early. */ \ /* Let setup failure terminate early. */ \
if (_metadata->exit_code) \ if (_metadata->exit_code) \
_exit(0); \ _exit(0); \
_metadata->setup_completed = true; \ _metadata->setup_completed = true; \
fixture_name##_##test_name(_metadata, &self, variant->data); \ fixture_name##_##test_name(_metadata, self, variant->data); \
} else if (child < 0 || child != waitpid(child, &status, 0)) { \ } else if (child < 0 || child != waitpid(child, &status, 0)) { \
ksft_print_msg("ERROR SPAWNING TEST GRANDCHILD\n"); \ ksft_print_msg("ERROR SPAWNING TEST GRANDCHILD\n"); \
_metadata->exit_code = KSFT_FAIL; \ _metadata->exit_code = KSFT_FAIL; \
} \ } \
} \ } \
if (child == 0) { \ if (child == 0) { \
if (_metadata->setup_completed && !_metadata->teardown_parent && \ if (_metadata->setup_completed && !fixture_name##_teardown_parent && \
__sync_bool_compare_and_swap(teardown, false, true)) \ __sync_bool_compare_and_swap(teardown, false, true)) \
fixture_name##_teardown(_metadata, &self, variant->data); \ fixture_name##_teardown(_metadata, self, variant->data); \
_exit(0); \ _exit(0); \
} \ } \
if (_metadata->setup_completed && _metadata->teardown_parent && \ if (_metadata->setup_completed && fixture_name##_teardown_parent && \
__sync_bool_compare_and_swap(teardown, false, true)) \ __sync_bool_compare_and_swap(teardown, false, true)) \
fixture_name##_teardown(_metadata, &self, variant->data); \ fixture_name##_teardown(_metadata, self, variant->data); \
munmap(teardown, sizeof(*teardown)); \ munmap(teardown, sizeof(*teardown)); \
if (self && fixture_name##_teardown_parent) \
munmap(self, sizeof(*self)); \
if (!WIFEXITED(status) && WIFSIGNALED(status)) \ if (!WIFEXITED(status) && WIFSIGNALED(status)) \
/* Forward signal to __wait_for_test(). */ \ /* Forward signal to __wait_for_test(). */ \
kill(getpid(), WTERMSIG(status)); \ kill(getpid(), WTERMSIG(status)); \
...@@ -898,7 +935,6 @@ struct __test_metadata { ...@@ -898,7 +935,6 @@ struct __test_metadata {
bool timed_out; /* did this test timeout instead of exiting? */ bool timed_out; /* did this test timeout instead of exiting? */
bool aborted; /* stopped test due to failed ASSERT */ bool aborted; /* stopped test due to failed ASSERT */
bool setup_completed; /* did setup finish? */ bool setup_completed; /* did setup finish? */
bool teardown_parent; /* run teardown in a parent process */
jmp_buf env; /* for exiting out of test early */ jmp_buf env; /* for exiting out of test early */
struct __test_results *results; struct __test_results *results;
struct __test_metadata *prev, *next; struct __test_metadata *prev, *next;
......
...@@ -286,8 +286,6 @@ static void prepare_layout_opt(struct __test_metadata *const _metadata, ...@@ -286,8 +286,6 @@ static void prepare_layout_opt(struct __test_metadata *const _metadata,
static void prepare_layout(struct __test_metadata *const _metadata) static void prepare_layout(struct __test_metadata *const _metadata)
{ {
_metadata->teardown_parent = true;
prepare_layout_opt(_metadata, &mnt_tmp); prepare_layout_opt(_metadata, &mnt_tmp);
} }
...@@ -316,7 +314,7 @@ FIXTURE_SETUP(layout0) ...@@ -316,7 +314,7 @@ FIXTURE_SETUP(layout0)
prepare_layout(_metadata); prepare_layout(_metadata);
} }
FIXTURE_TEARDOWN(layout0) FIXTURE_TEARDOWN_PARENT(layout0)
{ {
cleanup_layout(_metadata); cleanup_layout(_metadata);
} }
...@@ -379,7 +377,7 @@ FIXTURE_SETUP(layout1) ...@@ -379,7 +377,7 @@ FIXTURE_SETUP(layout1)
create_layout1(_metadata); create_layout1(_metadata);
} }
FIXTURE_TEARDOWN(layout1) FIXTURE_TEARDOWN_PARENT(layout1)
{ {
remove_layout1(_metadata); remove_layout1(_metadata);
...@@ -3692,7 +3690,7 @@ FIXTURE_SETUP(ftruncate) ...@@ -3692,7 +3690,7 @@ FIXTURE_SETUP(ftruncate)
create_file(_metadata, file1_s1d1); create_file(_metadata, file1_s1d1);
} }
FIXTURE_TEARDOWN(ftruncate) FIXTURE_TEARDOWN_PARENT(ftruncate)
{ {
EXPECT_EQ(0, remove_path(file1_s1d1)); EXPECT_EQ(0, remove_path(file1_s1d1));
cleanup_layout(_metadata); cleanup_layout(_metadata);
...@@ -3870,7 +3868,7 @@ FIXTURE_SETUP(layout1_bind) ...@@ -3870,7 +3868,7 @@ FIXTURE_SETUP(layout1_bind)
clear_cap(_metadata, CAP_SYS_ADMIN); clear_cap(_metadata, CAP_SYS_ADMIN);
} }
FIXTURE_TEARDOWN(layout1_bind) FIXTURE_TEARDOWN_PARENT(layout1_bind)
{ {
/* umount(dir_s2d2)) is handled by namespace lifetime. */ /* umount(dir_s2d2)) is handled by namespace lifetime. */
...@@ -4275,7 +4273,7 @@ FIXTURE_SETUP(layout2_overlay) ...@@ -4275,7 +4273,7 @@ FIXTURE_SETUP(layout2_overlay)
clear_cap(_metadata, CAP_SYS_ADMIN); clear_cap(_metadata, CAP_SYS_ADMIN);
} }
FIXTURE_TEARDOWN(layout2_overlay) FIXTURE_TEARDOWN_PARENT(layout2_overlay)
{ {
if (self->skip_test) if (self->skip_test)
SKIP(return, "overlayfs is not supported (teardown)"); SKIP(return, "overlayfs is not supported (teardown)");
...@@ -4708,8 +4706,6 @@ FIXTURE_SETUP(layout3_fs) ...@@ -4708,8 +4706,6 @@ FIXTURE_SETUP(layout3_fs)
SKIP(return, "this filesystem is not supported (setup)"); SKIP(return, "this filesystem is not supported (setup)");
} }
_metadata->teardown_parent = true;
prepare_layout_opt(_metadata, &variant->mnt); prepare_layout_opt(_metadata, &variant->mnt);
/* Creates directory when required. */ /* Creates directory when required. */
...@@ -4743,7 +4739,7 @@ FIXTURE_SETUP(layout3_fs) ...@@ -4743,7 +4739,7 @@ FIXTURE_SETUP(layout3_fs)
free(dir_path); free(dir_path);
} }
FIXTURE_TEARDOWN(layout3_fs) FIXTURE_TEARDOWN_PARENT(layout3_fs)
{ {
if (self->skip_test) if (self->skip_test)
SKIP(return, "this filesystem is not supported (teardown)"); SKIP(return, "this filesystem is not supported (teardown)");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment