2020-08-20 22:17:36

by Uriel Guajardo

[permalink] [raw]
Subject: [PATCH 1/2] kunit: expect failures from dynamic analysis tools

Adds support to KUnit for failure expectation for specific tools. Uses
named KUnit resources to keep track of whether or not a failure expectation has
been set by the user.

- Adds a generic KUNIT_EXPECT_TOOL_FAIL macro that can expect failure from any
supported tool that uses the same identifying name

- Adds kunit_fail_from_tool which is used to flag failures for specific
tools.

Requires "kunit: suppport failure from dynamic analysis tools":
https://lore.kernel.org/linux-kselftest/[email protected]/

Signed-off-by: Uriel Guajardo <[email protected]>
---
include/kunit/test-bug.h | 53 ++++++++++++++++++++++++++++++++++++++++
include/kunit/test.h | 5 ++++
lib/kunit/test.c | 46 +++++++++++++++++++++++++++++-----
3 files changed, 98 insertions(+), 6 deletions(-)

diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
index 283c19ec328f..383198f70cb5 100644
--- a/include/kunit/test-bug.h
+++ b/include/kunit/test-bug.h
@@ -9,16 +9,69 @@
#ifndef _KUNIT_TEST_BUG_H
#define _KUNIT_TEST_BUG_H

+/**
+ * struct kunit_expectation - represents expectations in KUnit, specifically
+ * used to keep track of failure expectations from analysis tools
+ */
+struct kunit_expectation {
+ bool expected;
+ bool found;
+};
+
#if IS_ENABLED(CONFIG_KUNIT)

extern void kunit_fail_current_test(void);

+/**
+ * kunit_fail_from_tool() - Fails the currently running KUnit tests under the
+ * given tool name.
+ *
+ * Note: Uses a named KUnit resource to track state. Do not use the name
+ * KUNIT_TOOL_{tool} for KUnit resources outside of here.
+ */
+#define kunit_fail_from_tool(tool) \
+ if (current->kunit_test) \
+ kunit_tool_fail(current->kunit_test, #tool,\
+ "KUNIT_TOOL_" #tool)
+
+/**
+ * kunit_tool_expectation() - Returns the kunit_expectation for the given
+ * tool. If it cannot find the expectation, it creates an expectation and
+ * returns it.
+ *
+ * Note: Uses a named KUnit resource to track state. Do not use the name
+ * KUNIT_TOOL_{tool} for KUnit resources outside of here.
+ */
+#define kunit_tool_expectation(test, tool) \
+ kunit_find_expectation(test, "KUNIT_TOOL_" #tool)
+
+
+/**
+ * KUNIT_EXPECT_TOOL_FAIL() - Fails the currently running KUnit test if the
+ * condition does not cause an error within the given tool.
+ *
+ * Note: 'tool' must be consistent with the name specified in
+ * kunit_fail_from_tool(). If the tool fails KUnit using another name, KUnit
+ * will treat it as a separate tool.
+ */
+#define KUNIT_EXPECT_TOOL_FAIL(test, condition, tool) do { \
+ struct kunit_expectation *data = kunit_tool_expectation(test, tool);\
+ data->expected = true; \
+ data->found = false; \
+ condition; \
+ KUNIT_EXPECT_EQ(test, data->expected, data->found); \
+ data->expected = false; \
+ data->found = false; \
+} while (0)
+
#else

static inline void kunit_fail_current_test(void)
{
}

+#define kunit_fail_from_tool(tool) do { } while (0)
+
#endif

#endif /* _KUNIT_TEST_BUG_H */
diff --git a/include/kunit/test.h b/include/kunit/test.h
index 81bf43a1abda..3da8e17ee32b 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -511,6 +511,11 @@ static inline int kunit_destroy_named_resource(struct kunit *test,
*/
void kunit_remove_resource(struct kunit *test, struct kunit_resource *res);

+void kunit_tool_fail(struct kunit *test, char *tool_name, char *resource_name);
+
+struct kunit_expectation *kunit_find_expectation(struct kunit *test,
+ char *resource_name);
+
/**
* kunit_kmalloc() - Like kmalloc() except the allocation is *test managed*.
* @test: The test context object.
diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index d8189d827368..458d1ad2daf2 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -22,6 +22,45 @@ void kunit_fail_current_test(void)
kunit_set_failure(current->kunit_test);
}

+static void kunit_data_free(struct kunit_resource *res)
+{
+ kfree(res->data);
+}
+
+struct kunit_expectation *kunit_find_expectation(struct kunit *test,
+ char *resource_name)
+{
+ struct kunit_resource *resource;
+ struct kunit_expectation *expectation;
+
+ struct kunit_resource *existing = kunit_find_named_resource(
+ test, resource_name);
+ if (!existing) {
+ expectation = kzalloc(sizeof(*expectation), GFP_KERNEL);
+ resource = kunit_alloc_and_get_resource(test, NULL,
+ kunit_data_free, GFP_KERNEL,
+ expectation);
+ resource->name = resource_name;
+ kunit_put_resource(resource);
+ return expectation;
+ }
+ kunit_put_resource(existing);
+ return existing->data;
+}
+EXPORT_SYMBOL_GPL(kunit_find_expectation);
+
+void kunit_tool_fail(struct kunit *test, char *tool_name, char *resource_name)
+{
+ struct kunit_expectation *data = kunit_find_expectation(test,
+ resource_name);
+ if (!data->expected) {
+ kunit_warn(test, "Dynamic analysis tool failure from %s",
+ tool_name);
+ return kunit_fail_current_test();
+ }
+ data->found = true;
+}
+
static void kunit_print_tap_version(void)
{
static bool kunit_has_printed_tap_version;
@@ -538,11 +577,6 @@ static int kunit_kmalloc_init(struct kunit_resource *res, void *context)
return 0;
}

-static void kunit_kmalloc_free(struct kunit_resource *res)
-{
- kfree(res->data);
-}
-
void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp)
{
struct kunit_kmalloc_params params = {
@@ -552,7 +586,7 @@ void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp)

return kunit_alloc_resource(test,
kunit_kmalloc_init,
- kunit_kmalloc_free,
+ kunit_data_free,
gfp,
&params);
}
--
2.28.0.297.g1956fa8f8d-goog


2020-08-20 23:59:04

by Uriel Guajardo

[permalink] [raw]
Subject: [PATCH 2/2] kunit: port UBSAN tests to KUnit

From: Uriel Guajardo <[email protected]>

UBSAN tests are ported to the KUnit testing framework using the tool failure
expectation feature added to KUnit.

I've commented out test_ubsan_divrem_overflow and
test_ubsan_out_of_bounds since they both consistently crash the kernel.
The former gives me a divide error and the latter gives a corrupted kernel
stack error. If this is only on my end, let me know.

Kconfig file is reformatted according the KUnit naming guidelines:
https://lore.kernel.org/linux-kselftest/[email protected]/

Requires "kunit: UBSAN integration":
https://lore.kernel.org/linux-kselftest/[email protected]/

Signed-off-by: Uriel Guajardo <[email protected]>
---
lib/Kconfig.ubsan | 12 ++++--
lib/Makefile | 2 +-
lib/test_ubsan.c | 96 ++++++++++++++++++++++++-----------------------
lib/ubsan.c | 2 +-
4 files changed, 59 insertions(+), 53 deletions(-)

diff --git a/lib/Kconfig.ubsan b/lib/Kconfig.ubsan
index 774315de555a..aab5fbecd19a 100644
--- a/lib/Kconfig.ubsan
+++ b/lib/Kconfig.ubsan
@@ -80,11 +80,15 @@ config UBSAN_ALIGNMENT
Enabling this option on architectures that support unaligned
accesses may produce a lot of false positives.

-config TEST_UBSAN
- tristate "Module for testing for undefined behavior detection"
- depends on m
+config UBSAN_KUNIT_TEST
+ tristate "Tests for undefined behavior detection" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
help
- This is a test module for UBSAN.
+ This is a test suite for UBSAN using the KUnit testing framework.
It triggers various undefined behavior, and detect it.

+ For more information on KUnit and unit tests in general, please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit
+
endif # if UBSAN
diff --git a/lib/Makefile b/lib/Makefile
index b1c42c10073b..8b1a134f5bf1 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -67,7 +67,7 @@ obj-$(CONFIG_TEST_IDA) += test_ida.o
obj-$(CONFIG_TEST_KASAN) += test_kasan.o
CFLAGS_test_kasan.o += -fno-builtin
CFLAGS_test_kasan.o += $(call cc-disable-warning, vla)
-obj-$(CONFIG_TEST_UBSAN) += test_ubsan.o
+obj-$(CONFIG_UBSAN_KUNIT_TEST) += test_ubsan.o
CFLAGS_test_ubsan.o += $(call cc-disable-warning, vla)
UBSAN_SANITIZE_test_ubsan.o := y
obj-$(CONFIG_TEST_KSTRTOX) += test-kstrtox.o
diff --git a/lib/test_ubsan.c b/lib/test_ubsan.c
index 9ea10adf7a66..ec724cddf005 100644
--- a/lib/test_ubsan.c
+++ b/lib/test_ubsan.c
@@ -2,63 +2,65 @@
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <kunit/test.h>

-typedef void(*test_ubsan_fp)(void);
+#define KUNIT_EXPECT_UBSAN_FAIL(test, condition) \
+ KUNIT_EXPECT_TOOL_FAIL(test, condition, UBSAN)

-static void test_ubsan_add_overflow(void)
+static void test_ubsan_add_overflow(struct kunit *test)
{
volatile int val = INT_MAX;

- val += 2;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val += 2);
}

-static void test_ubsan_sub_overflow(void)
+static void test_ubsan_sub_overflow(struct kunit *test)
{
volatile int val = INT_MIN;
volatile int val2 = 2;

- val -= val2;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val -= val2);
}

-static void test_ubsan_mul_overflow(void)
+static void test_ubsan_mul_overflow(struct kunit *test)
{
volatile int val = INT_MAX / 2;

- val *= 3;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val *= 3);
}

-static void test_ubsan_negate_overflow(void)
+static void test_ubsan_negate_overflow(struct kunit *test)
{
volatile int val = INT_MIN;

- val = -val;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val = -val);
}

-static void test_ubsan_divrem_overflow(void)
+static void test_ubsan_divrem_overflow(struct kunit *test)
{
volatile int val = 16;
volatile int val2 = 0;

- val /= val2;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val /= val2);
}

-static void test_ubsan_shift_out_of_bounds(void)
+static void test_ubsan_shift_out_of_bounds(struct kunit *test)
{
volatile int val = -1;
int val2 = 10;

- val2 <<= val;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val2 <<= val);
}

-static void test_ubsan_out_of_bounds(void)
+static void test_ubsan_out_of_bounds(struct kunit *test)
{
volatile int i = 4, j = 5;
volatile int arr[4];

- arr[j] = i;
+ KUNIT_EXPECT_UBSAN_FAIL(test, arr[j] = i);
}

-static void test_ubsan_load_invalid_value(void)
+static void test_ubsan_load_invalid_value(struct kunit *test)
{
volatile char *dst, *src;
bool val, val2, *ptr;
@@ -69,10 +71,10 @@ static void test_ubsan_load_invalid_value(void)
*dst = *src;

ptr = &val2;
- val2 = val;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val2 = val);
}

-static void test_ubsan_null_ptr_deref(void)
+static void test_ubsan_null_ptr_deref(struct kunit *test)
{
volatile int *ptr = NULL;
int val;
@@ -80,56 +82,56 @@ static void test_ubsan_null_ptr_deref(void)
val = *ptr;
}

-static void test_ubsan_misaligned_access(void)
+static void test_ubsan_misaligned_access(struct kunit *test)
{
volatile char arr[5] __aligned(4) = {1, 2, 3, 4, 5};
volatile int *ptr, val = 6;

ptr = (int *)(arr + 1);
- *ptr = val;
+ KUNIT_EXPECT_UBSAN_FAIL(test, *ptr = val);
}

-static void test_ubsan_object_size_mismatch(void)
+static void test_ubsan_object_size_mismatch(struct kunit *test)
{
/* "((aligned(8)))" helps this not into be misaligned for ptr-access. */
volatile int val __aligned(8) = 4;
volatile long long *ptr, val2;

ptr = (long long *)&val;
- val2 = *ptr;
+ KUNIT_EXPECT_UBSAN_FAIL(test, val2 = *ptr);
}

-static const test_ubsan_fp test_ubsan_array[] = {
- test_ubsan_add_overflow,
- test_ubsan_sub_overflow,
- test_ubsan_mul_overflow,
- test_ubsan_negate_overflow,
- test_ubsan_divrem_overflow,
- test_ubsan_shift_out_of_bounds,
- test_ubsan_out_of_bounds,
- test_ubsan_load_invalid_value,
- //test_ubsan_null_ptr_deref, /* exclude it because there is a crash */
- test_ubsan_misaligned_access,
- test_ubsan_object_size_mismatch,
+static struct kunit_case ubsan_test_cases[] = {
+ KUNIT_CASE(test_ubsan_add_overflow),
+ KUNIT_CASE(test_ubsan_sub_overflow),
+ KUNIT_CASE(test_ubsan_mul_overflow),
+ KUNIT_CASE(test_ubsan_negate_overflow),
+ //KUNIT_CASE(test_ubsan_divrem_overflow), /* exclude because it crashes*/
+ KUNIT_CASE(test_ubsan_shift_out_of_bounds),
+ //KUNIT_CASE(test_ubsan_out_of_bounds), /* exclude because it crashes */
+ KUNIT_CASE(test_ubsan_load_invalid_value),
+ //KUNIT_CASE(test_ubsan_null_ptr_deref), /* exclude because it crashes */
+ KUNIT_CASE(test_ubsan_misaligned_access),
+ KUNIT_CASE(test_ubsan_object_size_mismatch),
+ {}
};

-static int __init test_ubsan_init(void)
+static int test_ubsan_init(struct kunit *test)
{
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE(test_ubsan_array); i++)
- test_ubsan_array[i]();
-
- (void)test_ubsan_null_ptr_deref; /* to avoid unsed-function warning */
+ /* Avoid unused-function warnings */
+ (void)test_ubsan_divrem_overflow;
+ (void)test_ubsan_out_of_bounds;
+ (void)test_ubsan_null_ptr_deref;
return 0;
}
-module_init(test_ubsan_init);

-static void __exit test_ubsan_exit(void)
-{
- /* do nothing */
-}
-module_exit(test_ubsan_exit);
+static struct kunit_suite ubsan_test_suite = {
+ .name = "ubsan",
+ .init = test_ubsan_init,
+ .test_cases = ubsan_test_cases,
+};
+
+kunit_test_suites(&ubsan_test_suite);

MODULE_AUTHOR("Jinbum Park <[email protected]>");
MODULE_LICENSE("GPL v2");
diff --git a/lib/ubsan.c b/lib/ubsan.c
index 1460e2c828c8..9fe357eb1e81 100644
--- a/lib/ubsan.c
+++ b/lib/ubsan.c
@@ -138,7 +138,7 @@ static void ubsan_prologue(struct source_location *loc, const char *reason)
{
current->in_ubsan++;

- kunit_fail_current_test();
+ kunit_fail_from_tool(UBSAN);
pr_err("========================================"
"========================================\n");
pr_err("UBSAN: %s in %s:%d:%d\n", reason, loc->file_name,
--
2.28.0.297.g1956fa8f8d-goog