2019-09-23 17:02:39 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* An API to allow a function, that may fail, to be executed, and recover in a
|
|
|
|
* controlled manner.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019, Google LLC.
|
|
|
|
* Author: Brendan Higgins <brendanhiggins@google.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <kunit/test.h>
|
|
|
|
#include <linux/completion.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/kthread.h>
|
2024-04-08 15:46:20 +08:00
|
|
|
#include <linux/sched/task.h>
|
2019-09-23 17:02:39 +08:00
|
|
|
|
2020-01-07 06:28:19 +08:00
|
|
|
#include "try-catch-impl.h"
|
|
|
|
|
2019-09-23 17:02:39 +08:00
|
|
|
void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)
|
|
|
|
{
|
|
|
|
try_catch->try_result = -EFAULT;
|
2024-04-08 15:46:22 +08:00
|
|
|
kthread_exit(0);
|
2019-09-23 17:02:39 +08:00
|
|
|
}
|
2020-01-07 06:28:20 +08:00
|
|
|
EXPORT_SYMBOL_GPL(kunit_try_catch_throw);
|
2019-09-23 17:02:39 +08:00
|
|
|
|
|
|
|
static int kunit_generic_run_threadfn_adapter(void *data)
|
|
|
|
{
|
|
|
|
struct kunit_try_catch *try_catch = data;
|
|
|
|
|
2024-04-08 15:46:22 +08:00
|
|
|
try_catch->try_result = -EINTR;
|
2019-09-23 17:02:39 +08:00
|
|
|
try_catch->try(try_catch->context);
|
2024-04-08 15:46:22 +08:00
|
|
|
if (try_catch->try_result == -EINTR)
|
|
|
|
try_catch->try_result = 0;
|
2019-09-23 17:02:39 +08:00
|
|
|
|
2024-04-08 15:46:22 +08:00
|
|
|
return 0;
|
2019-09-23 17:02:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long kunit_test_timeout(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* TODO(brendanhiggins@google.com): We should probably have some type of
|
|
|
|
* variable timeout here. The only question is what that timeout value
|
|
|
|
* should be.
|
|
|
|
*
|
|
|
|
* The intention has always been, at some point, to be able to label
|
|
|
|
* tests with some type of size bucket (unit/small, integration/medium,
|
|
|
|
* large/system/end-to-end, etc), where each size bucket would get a
|
|
|
|
* default timeout value kind of like what Bazel does:
|
|
|
|
* https://docs.bazel.build/versions/master/be/common-definitions.html#test.size
|
|
|
|
* There is still some debate to be had on exactly how we do this. (For
|
|
|
|
* one, we probably want to have some sort of test runner level
|
|
|
|
* timeout.)
|
|
|
|
*
|
|
|
|
* For more background on this topic, see:
|
|
|
|
* https://mike-bland.com/2011/11/01/small-medium-large.html
|
2020-01-07 06:28:21 +08:00
|
|
|
*
|
|
|
|
* If tests timeout due to exceeding sysctl_hung_task_timeout_secs,
|
|
|
|
* the task will be killed and an oops generated.
|
2019-09-23 17:02:39 +08:00
|
|
|
*/
|
2022-03-23 05:48:19 +08:00
|
|
|
return 300 * msecs_to_jiffies(MSEC_PER_SEC); /* 5 min */
|
2019-09-23 17:02:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void kunit_try_catch_run(struct kunit_try_catch *try_catch, void *context)
|
|
|
|
{
|
|
|
|
struct kunit *test = try_catch->test;
|
|
|
|
struct task_struct *task_struct;
|
2024-04-12 10:59:01 +08:00
|
|
|
struct completion *task_done;
|
2019-09-23 17:02:39 +08:00
|
|
|
int exit_code, time_remaining;
|
|
|
|
|
|
|
|
try_catch->context = context;
|
|
|
|
try_catch->try_result = 0;
|
2024-04-08 15:46:20 +08:00
|
|
|
task_struct = kthread_create(kunit_generic_run_threadfn_adapter,
|
|
|
|
try_catch, "kunit_try_catch_thread");
|
2019-09-23 17:02:39 +08:00
|
|
|
if (IS_ERR(task_struct)) {
|
2024-04-08 15:46:19 +08:00
|
|
|
try_catch->try_result = PTR_ERR(task_struct);
|
2019-09-23 17:02:39 +08:00
|
|
|
try_catch->catch(try_catch->context);
|
|
|
|
return;
|
|
|
|
}
|
2024-04-08 15:46:20 +08:00
|
|
|
get_task_struct(task_struct);
|
2024-04-08 15:46:22 +08:00
|
|
|
/*
|
|
|
|
* 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
|
2024-04-12 10:59:01 +08:00
|
|
|
* kernel thread. It is set to NULL when the thread exits, so we
|
|
|
|
* keep a copy here.
|
2024-04-08 15:46:22 +08:00
|
|
|
*/
|
2024-04-12 10:59:01 +08:00
|
|
|
task_done = task_struct->vfork_done;
|
|
|
|
wake_up_process(task_struct);
|
|
|
|
|
|
|
|
time_remaining = wait_for_completion_timeout(task_done,
|
2019-09-23 17:02:39 +08:00
|
|
|
kunit_test_timeout());
|
|
|
|
if (time_remaining == 0) {
|
|
|
|
try_catch->try_result = -ETIMEDOUT;
|
kunit: fix UAF when run kfence test case test_gfpzero
Patch series "kunit: fix a UAF bug and do some optimization", v2.
This series is to fix UAF (use after free) when running kfence test case
test_gfpzero, which is time costly. This UAF bug can be easily triggered
by setting CONFIG_KFENCE_NUM_OBJECTS = 65535. Furthermore, some
optimization for kunit tests has been done.
This patch (of 3):
Kunit will create a new thread to run an actual test case, and the main
process will wait for the completion of the actual test thread until
overtime. The variable "struct kunit test" has local property in function
kunit_try_catch_run, and will be used in the test case thread. Task
kunit_try_catch_run will free "struct kunit test" when kunit runs
overtime, but the actual test case is still run and an UAF bug will be
triggered.
The above problem has been both observed in a physical machine and qemu
platform when running kfence kunit tests. The problem can be triggered
when setting CONFIG_KFENCE_NUM_OBJECTS = 65535. Under this setting, the
test case test_gfpzero will cost hours and kunit will run to overtime.
The follows show the panic log.
BUG: unable to handle page fault for address: ffffffff82d882e9
Call Trace:
kunit_log_append+0x58/0xd0
...
test_alloc.constprop.0.cold+0x6b/0x8a [kfence_test]
test_gfpzero.cold+0x61/0x8ab [kfence_test]
kunit_try_run_case+0x4c/0x70
kunit_generic_run_threadfn_adapter+0x11/0x20
kthread+0x166/0x190
ret_from_fork+0x22/0x30
Kernel panic - not syncing: Fatal exception
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS
Ubuntu-1.8.2-1ubuntu1 04/01/2014
To solve this problem, the test case thread should be stopped when the
kunit frame runs overtime. The stop signal will send in function
kunit_try_catch_run, and test_gfpzero will handle it.
Link: https://lkml.kernel.org/r/20220309083753.1561921-1-liupeng256@huawei.com
Link: https://lkml.kernel.org/r/20220309083753.1561921-2-liupeng256@huawei.com
Signed-off-by: Peng Liu <liupeng256@huawei.com>
Reviewed-by: Marco Elver <elver@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Tested-by: Brendan Higgins <brendanhiggins@google.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Wang Kefeng <wangkefeng.wang@huawei.com>
Cc: Daniel Latypov <dlatypov@google.com>
Cc: David Gow <davidgow@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2022-03-23 05:48:16 +08:00
|
|
|
kthread_stop(task_struct);
|
2019-09-23 17:02:39 +08:00
|
|
|
}
|
|
|
|
|
2024-04-08 15:46:20 +08:00
|
|
|
put_task_struct(task_struct);
|
2019-09-23 17:02:39 +08:00
|
|
|
exit_code = try_catch->try_result;
|
|
|
|
|
|
|
|
if (!exit_code)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (exit_code == -EFAULT)
|
|
|
|
try_catch->try_result = 0;
|
2024-04-08 15:46:24 +08:00
|
|
|
else if (exit_code == -EINTR) {
|
|
|
|
if (test->last_seen.file)
|
|
|
|
kunit_err(test, "try faulted: last line seen %s:%d\n",
|
|
|
|
test->last_seen.file, test->last_seen.line);
|
|
|
|
else
|
|
|
|
kunit_err(test, "try faulted\n");
|
|
|
|
} else if (exit_code == -ETIMEDOUT)
|
2024-04-08 15:46:21 +08:00
|
|
|
kunit_err(test, "try timed out\n");
|
2019-09-23 17:02:39 +08:00
|
|
|
else if (exit_code)
|
|
|
|
kunit_err(test, "Unknown error: %d\n", exit_code);
|
|
|
|
|
|
|
|
try_catch->catch(try_catch->context);
|
|
|
|
}
|
2020-01-07 06:28:20 +08:00
|
|
|
EXPORT_SYMBOL_GPL(kunit_try_catch_run);
|