kunit: Handle test faults
authorMickaël Salaün <mic@digikod.net>
Mon, 8 Apr 2024 07:46:22 +0000 (09:46 +0200)
committerShuah Khan <skhan@linuxfoundation.org>
Mon, 6 May 2024 20:22:02 +0000 (14:22 -0600)
Previously, when a kernel test thread crashed (e.g. NULL pointer
dereference, general protection fault), the KUnit test hanged for 30
seconds and exited with a timeout error.

Fix this issue by waiting on task_struct->vfork_done instead of the
custom kunit_try_catch.try_completion, and track the execution state by
initially setting try_result with -EINTR and only setting it to 0 if
the test passed.

Fix kunit_generic_run_threadfn_adapter() signature by returning 0
instead of calling kthread_complete_and_exit().  Because thread's exit
code is never checked, always set it to 0 to make it clear.  To make
this explicit, export kthread_exit() for KUnit tests built as module.

Fix the -EINTR error message, which couldn't be reached until now.

This is tested with a following patch.

Cc: Brendan Higgins <brendanhiggins@google.com>
Cc: Eric W. Biederman <ebiederm@xmission.com>
Cc: Shuah Khan <skhan@linuxfoundation.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Reviewed-by: David Gow <davidgow@google.com>
Tested-by: Rae Moar <rmoar@google.com>
Signed-off-by: Mickaël Salaün <mic@digikod.net>
Link: https://lore.kernel.org/r/20240408074625.65017-5-mic@digikod.net
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
include/kunit/try-catch.h
kernel/kthread.c
lib/kunit/try-catch.c

index c507dd43119d594d99935b11c9ed0a56f3a04ab1..7c966a1adbd30d7834b78c22a76cacabe2f853ed 100644 (file)
 
 typedef void (*kunit_try_catch_func_t)(void *);
 
-struct completion;
 struct kunit;
 
 /**
  * struct kunit_try_catch - provides a generic way to run code which might fail.
  * @test: The test case that is currently being executed.
- * @try_completion: Completion that the control thread waits on while test runs.
  * @try_result: Contains any errno obtained while running test case.
  * @try: The function, the test case, to attempt to run.
  * @catch: The function called if @try bails out.
@@ -46,7 +44,6 @@ struct kunit;
 struct kunit_try_catch {
        /* private: internal use only. */
        struct kunit *test;
-       struct completion *try_completion;
        int try_result;
        kunit_try_catch_func_t try;
        kunit_try_catch_func_t catch;
index c5e40830c1f2d5e91dea786c24fd70f8b76ee488..f7be976ff88af7cc998c125dc844e42c70ea9676 100644 (file)
@@ -315,6 +315,7 @@ void __noreturn kthread_exit(long result)
        kthread->result = result;
        do_exit(0);
 }
+EXPORT_SYMBOL(kthread_exit);
 
 /**
  * kthread_complete_and_exit - Exit the current kthread.
index cab8b24b5d5a27bc45da38f56a8bc96f08b0d5eb..7a3910dd78a69670eff429aecde6081827b608b0 100644 (file)
@@ -18,7 +18,7 @@
 void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)
 {
        try_catch->try_result = -EFAULT;
-       kthread_complete_and_exit(try_catch->try_completion, -EFAULT);
+       kthread_exit(0);
 }
 EXPORT_SYMBOL_GPL(kunit_try_catch_throw);
 
@@ -26,9 +26,12 @@ static int kunit_generic_run_threadfn_adapter(void *data)
 {
        struct kunit_try_catch *try_catch = data;
 
+       try_catch->try_result = -EINTR;
        try_catch->try(try_catch->context);
+       if (try_catch->try_result == -EINTR)
+               try_catch->try_result = 0;
 
-       kthread_complete_and_exit(try_catch->try_completion, 0);
+       return 0;
 }
 
 static unsigned long kunit_test_timeout(void)
@@ -58,13 +61,11 @@ static unsigned long kunit_test_timeout(void)
 
 void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context)
 {
-       DECLARE_COMPLETION_ONSTACK(try_completion);
        struct kunit *test = try_catch->test;
        struct task_struct *task_struct;
        int exit_code, time_remaining;
 
        try_catch->context = context;
-       try_catch->try_completion = &try_completion;
        try_catch->try_result = 0;
        task_struct = kthread_create(kunit_generic_run_threadfn_adapter,
                                     try_catch, "kunit_try_catch_thread");
@@ -75,8 +76,12 @@ void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context)
        }
        get_task_struct(task_struct);
        wake_up_process(task_struct);
-
-       time_remaining = wait_for_completion_timeout(&try_completion,
+       /*
+        * As for a vfork(2), task_struct->vfork_done (pointing to the
+        * underlying kthread->exited) can be used to wait for the end of a
+        * kernel thread.
+        */
+       time_remaining = wait_for_completion_timeout(task_struct->vfork_done,
                                                     kunit_test_timeout());
        if (time_remaining == 0) {
                try_catch->try_result = -ETIMEDOUT;
@@ -92,7 +97,7 @@ void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context)
        if (exit_code == -EFAULT)
                try_catch->try_result = 0;
        else if (exit_code == -EINTR)
-               kunit_err(test, "wake_up_process() was never called\n");
+               kunit_err(test, "try faulted\n");
        else if (exit_code == -ETIMEDOUT)
                kunit_err(test, "try timed out\n");
        else if (exit_code)