diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index bc65d3b98dcbfa..77b825aee6b24f 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -67,6 +67,8 @@ obj-$(CONFIG_PKCS7_TEST_KEY) += pkcs7_test_key.o pkcs7_test_key-y := \ pkcs7_key_type.o +obj-y += tests/ + # # Signed PE binary-wrapped key handling # diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c index 6e3ffdac83ace7..f7368bdfebd52a 100644 --- a/crypto/asymmetric_keys/pkcs7_parser.c +++ b/crypto/asymmetric_keys/pkcs7_parser.c @@ -151,7 +151,7 @@ struct pkcs7_message *pkcs7_parse_message(const void *data, size_t datalen) ctx->ppsinfo = &ctx->msg->signed_infos; /* Attempt to decode the signature */ - ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen); + ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen + 1 /* Intentional bug! */ ); if (ret < 0) { msg = ERR_PTR(ret); goto out; diff --git a/crypto/asymmetric_keys/tests/Makefile b/crypto/asymmetric_keys/tests/Makefile new file mode 100644 index 00000000000000..26f4f26f932036 --- /dev/null +++ b/crypto/asymmetric_keys/tests/Makefile @@ -0,0 +1,4 @@ +pkcs7-test-y := $(and $(CONFIG_KUNIT),$(filter y, $(CONFIG_PKCS7_MESSAGE_PARSER))) +rsa-helper-test-y := $(and $(CONFIG_KUNIT),$(filter y, $(CONFIG_CRYPTO_RSA))) +obj-$(pkcs7-test-y) += pkcs7_test.o +obj-$(rsa-helper-test-y) += rsa_helper_test.o diff --git a/crypto/asymmetric_keys/tests/pkcs7_test.c b/crypto/asymmetric_keys/tests/pkcs7_test.c new file mode 100644 index 00000000000000..763b206958a6da --- /dev/null +++ b/crypto/asymmetric_keys/tests/pkcs7_test.c @@ -0,0 +1,24 @@ +#include +#include + +static void fuzz_pkcs7_parse_message(struct kunit *test, char *data, + size_t datalen) +{ + struct pkcs7_message *msg; + + msg = pkcs7_parse_message(data, datalen); + if (msg && !IS_ERR(msg)) + pkcs7_free_message(msg); +} + +static struct kunit_case pkcs7_test_cases[] = { + KUNIT_CASE_FUZZ(fuzz_pkcs7_parse_message), + {} +}; + +static struct kunit_suite pkcs7_test_suite = { + .name = "pkcs7", + .test_cases = pkcs7_test_cases, +}; + +kunit_test_suites(&pkcs7_test_suite); diff --git a/crypto/asymmetric_keys/tests/rsa_helper_test.c b/crypto/asymmetric_keys/tests/rsa_helper_test.c new file mode 100644 index 00000000000000..60a364d1bc1f47 --- /dev/null +++ b/crypto/asymmetric_keys/tests/rsa_helper_test.c @@ -0,0 +1,31 @@ +#include +#include + +static void fuzz_rsa_parse_pub_key(struct kunit *test, char *data, + size_t datalen) +{ + struct rsa_key out; + + rsa_parse_priv_key(&out, data, datalen); +} + +static void fuzz_rsa_parse_priv_key(struct kunit *test, char *data, + size_t datalen) +{ + struct rsa_key out; + + rsa_parse_priv_key(&out, data, datalen); +} + +static struct kunit_case rsa_helper_test_cases[] = { + KUNIT_CASE_FUZZ(fuzz_rsa_parse_pub_key), + KUNIT_CASE_FUZZ(fuzz_rsa_parse_priv_key), + {} +}; + +static struct kunit_suite rsa_helper_test_suite = { + .name = "rsa_helper", + .test_cases = rsa_helper_test_cases, +}; + +kunit_test_suites(&rsa_helper_test_suite); diff --git a/include/kunit/test.h b/include/kunit/test.h index 9cd1594ab697d9..b5aca1481bbcb2 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -80,9 +80,15 @@ enum kunit_speed { KUNIT_SPEED_MAX = KUNIT_SPEED_NORMAL, }; +enum kunit_case_type { + KUNIT_NORMAL, + KUNIT_FUZZ, +}; + /* Holds attributes for each test case and suite */ struct kunit_attributes { enum kunit_speed speed; + enum kunit_case_type type; }; /** @@ -127,6 +133,7 @@ struct kunit_attributes { */ struct kunit_case { void (*run_case)(struct kunit *test); + void (*fuzz_case)(struct kunit *test, char *input, size_t input_len); const char *name; const void* (*generate_params)(struct kunit *test, const void *prev, char *desc); @@ -161,9 +168,16 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) * &struct kunit_case object from it. See the documentation for * &struct kunit_case for an example on how to use it. */ -#define KUNIT_CASE(test_name) \ - { .run_case = test_name, .name = #test_name, \ - .module_name = KBUILD_MODNAME} +#define KUNIT_CASE(test_name) \ + { .run_case = test_name, \ + .name = #test_name, \ + .module_name = KBUILD_MODNAME } + +#define KUNIT_CASE_FUZZ(test_name) \ + { .fuzz_case = test_name, \ + .name = #test_name, \ + .attr.type = KUNIT_FUZZ, \ + .module_name = KBUILD_MODNAME } /** * KUNIT_CASE_ATTR - A helper for creating a &struct kunit_case @@ -285,6 +299,8 @@ struct kunit_suite { struct string_stream *log; int suite_init_err; bool is_init; + char *fuzz_input; + size_t fuzz_input_len; }; /* Stores an array of suites, end points one past the end */ @@ -464,8 +480,8 @@ static inline int kunit_run_all_tests(void) #define kunit_test_init_section_suite(suite) \ kunit_test_init_section_suites(&suite) -#define kunit_suite_for_each_test_case(suite, test_case) \ - for (test_case = suite->test_cases; test_case->run_case; test_case++) +#define kunit_suite_for_each_test_case(suite, test_case) \ + for (test_case = suite->test_cases; test_case->name; test_case++) enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite); diff --git a/lib/kunit/debugfs.c b/lib/kunit/debugfs.c index 9c326f1837bdfb..3f29180c0aa5d2 100644 --- a/lib/kunit/debugfs.c +++ b/lib/kunit/debugfs.c @@ -4,8 +4,13 @@ * Author: Alan Maguire */ +#include "test_internal.h" #include #include +#include + +#define KFUZZ_IOC_MAGIC 'K' +#define KFUZZ_IOC_RUN _IO(KFUZZ_IOC_MAGIC, 1) #include #include @@ -13,9 +18,10 @@ #include "string-stream.h" #include "debugfs.h" -#define KUNIT_DEBUGFS_ROOT "kunit" -#define KUNIT_DEBUGFS_RESULTS "results" -#define KUNIT_DEBUGFS_RUN "run" +#define KUNIT_DEBUGFS_ROOT "kunit" +#define KUNIT_DEBUGFS_RESULTS "results" +#define KUNIT_DEBUGFS_RUN "run" +#define KUNIT_DEBUGFS_FUZZ "fuzz" /* * Create a debugfs representation of test suites: @@ -114,8 +120,29 @@ static int debugfs_print_run(struct seq_file *seq, void *v) struct kunit_suite *suite = (struct kunit_suite *)seq->private; seq_puts(seq, "Write to this file to trigger the test suite to run.\n"); - seq_printf(seq, "usage: echo \"any string\" > /sys/kernel/debugfs/kunit/%s/run\n", - suite->name); + seq_printf( + seq, + "usage: echo \"any string\" > /sys/kernel/debugfs/kunit/%s/run\n", + suite->name); + return 0; +} + +static int debugfs_print_fuzz(struct seq_file *seq, void *v) +{ + struct kunit_case *test_case; + struct kunit_suite *suite; + + suite = (struct kunit_suite *)seq->private; + + size_t i = 0; + seq_puts(seq, "available fuzz harnesses:\n"); + kunit_suite_for_each_test_case(suite, test_case) { + if (test_case->attr.type != KUNIT_FUZZ) + continue; + seq_printf(seq, " %zu: %s\n", i, test_case->name); + i++; + } + return 0; } @@ -139,17 +166,91 @@ static int debugfs_run_open(struct inode *inode, struct file *file) * * Note: what is written to this file will not be saved. */ -static ssize_t debugfs_run(struct file *file, - const char __user *buf, size_t count, loff_t *ppos) +static ssize_t debugfs_run(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) { struct inode *f_inode = file->f_inode; - struct kunit_suite *suite = (struct kunit_suite *) f_inode->i_private; + struct kunit_suite *suite = (struct kunit_suite *)f_inode->i_private; __kunit_test_suites_init(&suite, 1, true); return count; } +static ssize_t debugfs_fuzz(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + void *input_buff; + + struct inode *f_inode = file->f_inode; + struct kunit_suite *suite = (struct kunit_suite *)f_inode->i_private; + + input_buff = kzalloc(count, GFP_KERNEL); + if (!input_buff) + return -ENOMEM; + + if (copy_from_user(input_buff, buf, count)) { + kfree(input_buff); + return -EFAULT; + } + + /* Free the previously allocated input buffer if it exists. */ + if (suite->fuzz_input) + kfree(suite->fuzz_input); + suite->fuzz_input = input_buff; + suite->fuzz_input_len = count; + return count; +} + +static struct kunit_case *find_nth_fuzz_harness(struct kunit_suite *suite, + unsigned int n) +{ + struct kunit_case *test_case; + unsigned int i = 0; + + kunit_suite_for_each_test_case(suite, test_case) { + if (test_case->attr.type != KUNIT_FUZZ) + continue; + if (i == n) + return test_case; + i++; + } + return NULL; +} + +static long kfuzz_unlocked_ioctl_invoke(struct file *file, unsigned int cmd, + unsigned long arg) +{ + /* XXX: not sure this is the correct way to do this? */ + struct kunit test = { .param_value = NULL, .param_index = 0 }; + + struct kunit_case *harness; + size_t harness_id; + + struct inode *f_inode = file->f_inode; + struct kunit_suite *suite = (struct kunit_suite *)f_inode->i_private; + + if (cmd != KFUZZ_IOC_RUN) + return -ENOTTY; + + harness_id = arg; + + harness = find_nth_fuzz_harness(suite, harness_id); + if (!harness) + return -EINVAL; + + kunit_init_test(&test, harness->name, harness->log); + kunit_run_case_catch_errors(suite, harness, &test); + + return 0; +} + +static int debugfs_fuzz_open(struct inode *inode, struct file *file) +{ + struct kunit_suite *suite = (struct kunit_suite *)inode->i_private; + return single_open(file, debugfs_print_fuzz, suite); +} + static const struct file_operations debugfs_results_fops = { .open = debugfs_results_open, .read = seq_read, @@ -165,10 +266,20 @@ static const struct file_operations debugfs_run_fops = { .release = debugfs_release, }; +static const struct file_operations debugfs_fuzz_fops = { + .open = debugfs_fuzz_open, + .read = seq_read, + .write = debugfs_fuzz, + .llseek = seq_lseek, + .release = debugfs_release, + .unlocked_ioctl = kfuzz_unlocked_ioctl_invoke, +}; + void kunit_debugfs_create_suite(struct kunit_suite *suite) { struct kunit_case *test_case; struct string_stream *stream; + bool has_fuzz_harness = false; /* If suite log already allocated, do not create new debugfs files. */ if (suite->log) @@ -194,19 +305,26 @@ void kunit_debugfs_create_suite(struct kunit_suite *suite) string_stream_set_append_newlines(stream, true); test_case->log = stream; + if (test_case->attr.type == KUNIT_FUZZ) + has_fuzz_harness = true; } suite->debugfs = debugfs_create_dir(suite->name, debugfs_rootdir); debugfs_create_file(KUNIT_DEBUGFS_RESULTS, S_IFREG | 0444, - suite->debugfs, - suite, &debugfs_results_fops); + suite->debugfs, suite, &debugfs_results_fops); - /* Do not create file to re-run test if test runs on init */ + /* + * Do not create file to re-run test if test runs on init, or if the + * suite represents a set of fuzzing harnesses. + */ if (!suite->is_init) { debugfs_create_file(KUNIT_DEBUGFS_RUN, S_IFREG | 0644, - suite->debugfs, - suite, &debugfs_run_fops); + suite->debugfs, suite, &debugfs_run_fops); + } + if (has_fuzz_harness) { + debugfs_create_file(KUNIT_DEBUGFS_FUZZ, S_IFREG | 0644, + suite->debugfs, suite, &debugfs_fuzz_fops); } return; diff --git a/lib/kunit/kunit-example-test.c b/lib/kunit/kunit-example-test.c index 0bae7b7ca0b05d..5bac1c6f7e4bf4 100644 --- a/lib/kunit/kunit-example-test.c +++ b/lib/kunit/kunit-example-test.c @@ -489,6 +489,26 @@ static void example_params_test_with_init_dynamic_arr(struct kunit *test) KUNIT_EXPECT_EQ(test, param_val - param_val, 0); } +static void example_fuzz_harness_always_fails(struct kunit *test, char *data, + size_t datalen) +{ + kunit_info(test, "i am a fuzz harness and got %zu bytes of data!", + datalen); + KUNIT_ASSERT_EQ(test, true, false); +} + +static void example_fuzz_harness_oob(struct kunit *test, char *data, + size_t datalen) +{ + size_t i; + volatile char c; + + for (i = 0; i < datalen; i++) + READ_ONCE(data[i]); + + c = *(data - 1); +} + /* * Here we make a list of all the test cases we want to add to the test suite * below. @@ -514,6 +534,8 @@ static struct kunit_case example_test_cases[] = { kunit_array_gen_params, example_param_init_dynamic_arr, example_param_exit_dynamic_arr), KUNIT_CASE_SLOW(example_slow_test), + KUNIT_CASE_FUZZ(example_fuzz_harness_always_fails), + KUNIT_CASE_FUZZ(example_fuzz_harness_oob), {} }; diff --git a/lib/kunit/test.c b/lib/kunit/test.c index 41e1c89799b6a7..5c1029c5c97caa 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -24,6 +24,7 @@ #include "hooks-impl.h" #include "string-stream.h" #include "try-catch-impl.h" +#include "test_internal.h" static DEFINE_MUTEX(kunit_run_lock); @@ -445,7 +446,11 @@ static void kunit_run_case_internal(struct kunit *test, ktime_get_ts64(&start); - test_case->run_case(test); + if (test_case->attr.type == KUNIT_FUZZ) + test_case->fuzz_case(test, suite->fuzz_input, + suite->fuzz_input_len); + else + test_case->run_case(test); ktime_get_ts64(&end); @@ -565,9 +570,9 @@ static void kunit_catch_run_case(void *data) * Performs all logic to run a test case. It also catches most errors that * occur in a test case and reports them as failures. */ -static void kunit_run_case_catch_errors(struct kunit_suite *suite, - struct kunit_case *test_case, - struct kunit *test) +void kunit_run_case_catch_errors(struct kunit_suite *suite, + struct kunit_case *test_case, + struct kunit *test) { struct kunit_try_catch_context context; struct kunit_try_catch *try_catch; @@ -763,6 +768,10 @@ kunit_run_one_test(struct kunit_suite *suite, struct kunit_case *test_case, /* Test marked as skip */ test.status = KUNIT_SKIPPED; kunit_update_stats(¶m_stats, test.status); + } else if (test_case->attr.type == KUNIT_FUZZ) { + /* Skip fuzzing harnesses. */ + test_case->status = KUNIT_SKIPPED; + kunit_update_stats(¶m_stats, test.status); } else if (!test_case->generate_params) { /* Non-parameterised test. */ test_case->status = KUNIT_SKIPPED; diff --git a/lib/kunit/test_internal.h b/lib/kunit/test_internal.h new file mode 100644 index 00000000000000..2ed830e25f1a00 --- /dev/null +++ b/lib/kunit/test_internal.h @@ -0,0 +1,10 @@ +#ifndef KUNIT_TEST_INTERNAL_H +#define KUNIT_TEST_INTERNAL_H + +#include + +void kunit_run_case_catch_errors(struct kunit_suite *suite, + struct kunit_case *test_case, + struct kunit *test); + +#endif /* KUNIT_TEST_INTERNAL_H */