Skip to content

Commit af300a3

Browse files
committed
Merge tag 'kselftest-fix-vfork-2024-05-12' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux
Pull Kselftest fixes from Mickaël Salaün: "Fix Kselftest's vfork() side effects. As reported by Kernel Test Robot and Sean Christopherson, some tests fail since v6.9-rc1 . This is due to the use of vfork() which introduced some side effects. Similarly, while making it more generic, a previous commit made some Landlock file system tests flaky, and subject to the host's file system mount configuration. This fixes all these side effects by replacing vfork() with clone3() and CLONE_VFORK, which is cleaner (no arbitrary shared memory) and makes the Kselftest framework more robust" Link: https://lore.kernel.org/oe-lkp/[email protected] Link: https://lore.kernel.org/r/[email protected] Link: https://lore.kernel.org/r/[email protected] * tag 'kselftest-fix-vfork-2024-05-12' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux: selftests/harness: Handle TEST_F()'s explicit exit codes selftests/harness: Fix vfork() side effects selftests/harness: Share _metadata between forked processes selftests/pidfd: Fix wrong expectation selftests/harness: Constify fixture variants selftests/landlock: Do not allocate memory in fixture data selftests/harness: Fix interleaved scheduling leading to race conditions selftests/harness: Fix fixture teardown selftests/landlock: Fix FS tests when run on a private mount point selftests/pidfd: Fix config for pidfd_setns_test
2 parents 2842076 + 323feb3 commit af300a3

File tree

4 files changed

+147
-67
lines changed

4 files changed

+147
-67
lines changed

tools/testing/selftests/kselftest_harness.h

+94-33
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
#include <sys/wait.h>
6767
#include <unistd.h>
6868
#include <setjmp.h>
69+
#include <syscall.h>
70+
#include <linux/sched.h>
6971

7072
#include "kselftest.h"
7173

@@ -80,6 +82,17 @@
8082
# define TH_LOG_ENABLED 1
8183
#endif
8284

85+
/* Wait for the child process to end but without sharing memory mapping. */
86+
static inline pid_t clone3_vfork(void)
87+
{
88+
struct clone_args args = {
89+
.flags = CLONE_VFORK,
90+
.exit_signal = SIGCHLD,
91+
};
92+
93+
return syscall(__NR_clone3, &args, sizeof(args));
94+
}
95+
8396
/**
8497
* TH_LOG()
8598
*
@@ -281,6 +294,32 @@
281294
* A bare "return;" statement may be used to return early.
282295
*/
283296
#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) \
284323
void fixture_name##_teardown( \
285324
struct __test_metadata __attribute__((unused)) *_metadata, \
286325
FIXTURE_DATA(fixture_name) __attribute__((unused)) *self, \
@@ -325,7 +364,7 @@
325364
* variant.
326365
*/
327366
#define FIXTURE_VARIANT_ADD(fixture_name, variant_name) \
328-
extern FIXTURE_VARIANT(fixture_name) \
367+
extern const FIXTURE_VARIANT(fixture_name) \
329368
_##fixture_name##_##variant_name##_variant; \
330369
static struct __fixture_variant_metadata \
331370
_##fixture_name##_##variant_name##_object = \
@@ -337,7 +376,7 @@
337376
__register_fixture_variant(&_##fixture_name##_fixture_object, \
338377
&_##fixture_name##_##variant_name##_object); \
339378
} \
340-
FIXTURE_VARIANT(fixture_name) \
379+
const FIXTURE_VARIANT(fixture_name) \
341380
_##fixture_name##_##variant_name##_variant =
342381

343382
/**
@@ -355,10 +394,11 @@
355394
* Very similar to TEST() except that *self* is the setup instance of fixture's
356395
* datatype exposed for use by the implementation.
357396
*
358-
* The @test_name code is run in a separate process sharing the same memory
359-
* (i.e. vfork), which means that the test process can update its privileges
360-
* without impacting the related FIXTURE_TEARDOWN() (e.g. to remove files from
361-
* 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().
362402
*/
363403
#define TEST_F(fixture_name, test_name) \
364404
__TEST_F_IMPL(fixture_name, test_name, -1, TEST_TIMEOUT_DEFAULT)
@@ -379,53 +419,71 @@
379419
struct __fixture_variant_metadata *variant) \
380420
{ \
381421
/* fixture data is alloced, setup, and torn down per call. */ \
382-
FIXTURE_DATA(fixture_name) self; \
422+
FIXTURE_DATA(fixture_name) self_private, *self = NULL; \
383423
pid_t child = 1; \
384424
int status = 0; \
385-
bool jmp = false; \
386-
memset(&self, 0, sizeof(FIXTURE_DATA(fixture_name))); \
425+
/* Makes sure there is only one teardown, even when child forks again. */ \
426+
bool *teardown = mmap(NULL, sizeof(*teardown), \
427+
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); \
428+
*teardown = false; \
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+
} \
387438
if (setjmp(_metadata->env) == 0) { \
388-
/* Use the same _metadata. */ \
389-
child = vfork(); \
439+
/* _metadata and potentially self are shared with all forks. */ \
440+
child = clone3_vfork(); \
390441
if (child == 0) { \
391-
fixture_name##_setup(_metadata, &self, variant->data); \
442+
fixture_name##_setup(_metadata, self, variant->data); \
392443
/* Let setup failure terminate early. */ \
393444
if (_metadata->exit_code) \
394445
_exit(0); \
395446
_metadata->setup_completed = true; \
396-
fixture_name##_##test_name(_metadata, &self, variant->data); \
447+
fixture_name##_##test_name(_metadata, self, variant->data); \
397448
} else if (child < 0 || child != waitpid(child, &status, 0)) { \
398449
ksft_print_msg("ERROR SPAWNING TEST GRANDCHILD\n"); \
399450
_metadata->exit_code = KSFT_FAIL; \
400451
} \
401452
} \
402-
else \
403-
jmp = true; \
404453
if (child == 0) { \
405-
if (_metadata->setup_completed && !_metadata->teardown_parent && !jmp) \
406-
fixture_name##_teardown(_metadata, &self, variant->data); \
454+
if (_metadata->setup_completed && !fixture_name##_teardown_parent && \
455+
__sync_bool_compare_and_swap(teardown, false, true)) \
456+
fixture_name##_teardown(_metadata, self, variant->data); \
407457
_exit(0); \
408458
} \
409-
if (_metadata->setup_completed && _metadata->teardown_parent) \
410-
fixture_name##_teardown(_metadata, &self, variant->data); \
411-
if (!WIFEXITED(status) && WIFSIGNALED(status)) \
459+
if (_metadata->setup_completed && fixture_name##_teardown_parent && \
460+
__sync_bool_compare_and_swap(teardown, false, true)) \
461+
fixture_name##_teardown(_metadata, self, variant->data); \
462+
munmap(teardown, sizeof(*teardown)); \
463+
if (self && fixture_name##_teardown_parent) \
464+
munmap(self, sizeof(*self)); \
465+
if (WIFEXITED(status)) { \
466+
if (WEXITSTATUS(status)) \
467+
_metadata->exit_code = WEXITSTATUS(status); \
468+
} else if (WIFSIGNALED(status)) { \
412469
/* Forward signal to __wait_for_test(). */ \
413470
kill(getpid(), WTERMSIG(status)); \
471+
} \
414472
__test_check_assert(_metadata); \
415473
} \
416-
static struct __test_metadata \
417-
_##fixture_name##_##test_name##_object = { \
418-
.name = #test_name, \
419-
.fn = &wrapper_##fixture_name##_##test_name, \
420-
.fixture = &_##fixture_name##_fixture_object, \
421-
.termsig = signal, \
422-
.timeout = tmout, \
423-
.teardown_parent = false, \
424-
}; \
474+
static struct __test_metadata *_##fixture_name##_##test_name##_object; \
425475
static void __attribute__((constructor)) \
426476
_register_##fixture_name##_##test_name(void) \
427477
{ \
428-
__register_test(&_##fixture_name##_##test_name##_object); \
478+
struct __test_metadata *object = mmap(NULL, sizeof(*object), \
479+
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); \
480+
object->name = #test_name; \
481+
object->fn = &wrapper_##fixture_name##_##test_name; \
482+
object->fixture = &_##fixture_name##_fixture_object; \
483+
object->termsig = signal; \
484+
object->timeout = tmout; \
485+
_##fixture_name##_##test_name##_object = object; \
486+
__register_test(object); \
429487
} \
430488
static void fixture_name##_##test_name( \
431489
struct __test_metadata __attribute__((unused)) *_metadata, \
@@ -833,11 +891,12 @@ struct __test_xfail {
833891
{ \
834892
.fixture = &_##fixture_name##_fixture_object, \
835893
.variant = &_##fixture_name##_##variant_name##_object, \
836-
.test = &_##fixture_name##_##test_name##_object, \
837894
}; \
838895
static void __attribute__((constructor)) \
839896
_register_##fixture_name##_##variant_name##_##test_name##_xfail(void) \
840897
{ \
898+
_##fixture_name##_##variant_name##_##test_name##_xfail.test = \
899+
_##fixture_name##_##test_name##_object; \
841900
__register_xfail(&_##fixture_name##_##variant_name##_##test_name##_xfail); \
842901
}
843902

@@ -880,7 +939,6 @@ struct __test_metadata {
880939
bool timed_out; /* did this test timeout instead of exiting? */
881940
bool aborted; /* stopped test due to failed ASSERT */
882941
bool setup_completed; /* did setup finish? */
883-
bool teardown_parent; /* run teardown in a parent process */
884942
jmp_buf env; /* for exiting out of test early */
885943
struct __test_results *results;
886944
struct __test_metadata *prev, *next;
@@ -1164,6 +1222,9 @@ void __run_test(struct __fixture_metadata *f,
11641222
/* reset test struct */
11651223
t->exit_code = KSFT_PASS;
11661224
t->trigger = 0;
1225+
t->aborted = false;
1226+
t->setup_completed = false;
1227+
memset(t->env, 0, sizeof(t->env));
11671228
memset(t->results->reason, 0, sizeof(t->results->reason));
11681229

11691230
if (asprintf(&test_name, "%s%s%s.%s", f->name,
@@ -1179,7 +1240,7 @@ void __run_test(struct __fixture_metadata *f,
11791240
fflush(stdout);
11801241
fflush(stderr);
11811242

1182-
t->pid = fork();
1243+
t->pid = clone3_vfork();
11831244
if (t->pid < 0) {
11841245
ksft_print_msg("ERROR SPAWNING TEST CHILD\n");
11851246
t->exit_code = KSFT_FAIL;

0 commit comments

Comments
 (0)