Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 141 additions & 10 deletions include/linux/kftf.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ethan Graham <ethangraham@google.com>");
MODULE_DESCRIPTION("Kernel Fuzz Testing Framework (KFTF)");

/* forward decl */
static void *kftf_parse_input(void *input, size_t input_size);

/**
* struct kftf_test case defines a single fuzz test case. These should not
* be created manually. Instead the user should use the FUZZ_TEST macro defined
Expand Down Expand Up @@ -88,7 +91,7 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len,
size_t len, loff_t *off); \
static ssize_t _read_metadata_callback_##func( \
struct file *filp, char __user *buf, size_t len, loff_t *off); \
static void _fuzz_test_logic_##func(func_arg_type arg); \
static void _fuzz_test_logic_##func(func_arg_type *arg); \
/* test case struct initialization */ \
const struct kftf_test_case __fuzz_test__##func \
__attribute__((__section__(".kftf_test"), __used__)) = { \
Expand All @@ -111,20 +114,31 @@ write_input_cb_common(struct file *filp, const char __user *buf, size_t len,
const char __user *buf, \
size_t len, loff_t *off) \
{ \
pr_info("[ENTER] %s\n", __FUNCTION__); \
int err; \
func_arg_type arg; \
err = write_input_cb_common(filp, buf, len, off, &arg, \
sizeof(arg)); \
void *buffer = kmalloc(len, GFP_KERNEL); \
if (!buffer || IS_ERR(buffer)) \
return PTR_ERR(buffer); \
err = write_input_cb_common(filp, buf, len, off, buffer, len); \
if (err != 0) { \
kfree(buffer); \
return err; \
} \
void *payload = kftf_parse_input(buffer, len); \
if (!payload) { \
kfree(buffer); \
return -1; \
} \
func_arg_type *arg = payload; \
/* call the user's logic on the provided arg. */ \
/* NOTE: define some success/failure return types? */ \
pr_info("invoking fuzz logic for %s\n", #func); \
_fuzz_test_logic_##func(arg); \
kfree(buffer); \
kfree(payload); \
return len; \
} \
static void _fuzz_test_logic_##func(func_arg_type arg)
static void _fuzz_test_logic_##func(func_arg_type *arg)

/**
* Reports a bug with a predictable prefix so that it can be parsed by a
Expand Down Expand Up @@ -195,34 +209,38 @@ static_assert(sizeof(struct kftf_constraint) == 64,
};

#define KFTF_EXPECT_EQ(arg_type, field, val) \
if (arg.field != val) \
if (arg->field != val) \
return; \
__KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_EQ)

#define KFTF_EXPECT_NE(arg_type, field, val) \
if (arg.field == val) \
if (arg->field == val) \
return; \
__KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_NE)

#define KFTF_EXPECT_LE(arg_type, field, val) \
if (arg.field > val) \
if (arg->field > val) \
return; \
__KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_LE)

#define KFTF_EXPECT_GT(arg_type, field, val) \
if (arg.field <= val) \
if (arg->field <= val) \
return; \
__KFTF_DEFINE_CONSTRAINT(arg_type, field, val, 0x0, EXPECT_GT)

#define KFTF_EXPECT_NOT_NULL(arg_type, field) \
KFTF_EXPECT_NE(arg_type, field, 0x0)

#define KFTF_EXPECT_IN_RANGE(arg_type, field, lower_bound, upper_bound) \
if (arg.field < lower_bound || arg.field > upper_bound) \
if (arg->field < lower_bound || arg->field > upper_bound) \
return; \
__KFTF_DEFINE_CONSTRAINT(arg_type, field, lower_bound, upper_bound, \
EXPECT_IN_RANGE)

#define KFTF_EXPECT_LEN(expected_len, actual_len) \
if ((expected_len) != (actual_len)) \
return;

enum kftf_annotation_attribute : uint8_t {
ATTRIBUTE_LEN = 0,
ATTRIBUTE_STRING,
Expand Down Expand Up @@ -256,4 +274,117 @@ struct kftf_annotation {
#define KFTF_ANNOTATE_LEN(arg_type, field, linked_field) \
__KFTF_ANNOTATE(arg_type, field, linked_field, ATTRIBUTE_LEN)

struct reloc_entry {
uintptr_t pointer; /* offset from the beginning of the payload */
uintptr_t value; /* difference between the pointed to address and the address itself */
};

/*
* How many integers of padding in the relocation table between the header
* information and the relocation entries
*/
#define RELOC_TABLE_PADDING 2

struct reloc_table {
int num_entries;
uint32_t max_alignment;
int padding[RELOC_TABLE_PADDING];
struct reloc_entry entries[];
};
static_assert(offsetof(struct reloc_table, entries) %
sizeof(struct reloc_entry) ==
0);

/**
* This value should be known the fuzz engine.
*/
static const uintptr_t nullPtr = (uintptr_t)-1;

/* XXX: wasn't building before without attribute unused, but it is used in
* several locations - weird... */
__attribute__((unused)) static void *kftf_parse_input(void *input,
size_t input_size)
{
size_t i;
void *payload_start, *out;
uintptr_t *ptr_location;
size_t payload_len, alloc_size, entries_size, header_size;
struct reloc_table *rt;
struct reloc_entry re;
if (input_size > KMALLOC_MAX_SIZE)
return NULL;

if (input_size < sizeof(struct reloc_table)) {
pr_warn("got misformed input in %s\n", __FUNCTION__);
return NULL;
}
rt = input;

if (check_mul_overflow(rt->num_entries, sizeof(struct reloc_entry),
&entries_size))
return NULL;
header_size = offsetof(struct reloc_table, entries) + entries_size;
if (header_size > input_size)
return NULL;

payload_start = (char *)input + header_size;
if (payload_start >= input + input_size)
return NULL;

if (!is_power_of_2(rt->max_alignment) || rt->max_alignment > PAGE_SIZE)
return NULL;

payload_len = input_size - (payload_start - input);

/*
* Check input for out-of-bounds pointers before before allocating
* aligned output buffer.
*/
for (i = 0; i < rt->num_entries; i++) {
re = rt->entries[i];
if (re.pointer > payload_len ||
re.pointer + sizeof(uintptr_t) > payload_len)
return NULL;

if (re.value == nullPtr)
continue;

if (re.pointer + re.value >= payload_len ||
re.pointer + re.value + sizeof(uintptr_t) > payload_len)
return NULL;
}

/*
* To guarantee correct alignment of structures within the payload, we
* allocate a new buffer that is aligned to the next power of two
* greater than either the size of the payload + 1 or the maximum
* alignment of the nested structures. We add one to the payload length
* and call kzalloc to ensure that the payload is padded by trailing
* zeros to prevent false-positives on non-null terminated strings.
*/
alloc_size =
MAX(roundup_pow_of_two(payload_len + 1), rt->max_alignment);
out = kzalloc(alloc_size, GFP_KERNEL);
if (!out)
return NULL;

memcpy(out, payload_start, payload_len);

/*
* Iterate through entries in the relocation table and patch the
* pointers.
*/
for (i = 0; i < rt->num_entries; i++) {
re = rt->entries[i];
ptr_location = (uintptr_t *)(out + re.pointer);
if (re.value == nullPtr) {
*ptr_location = (uintptr_t)NULL;
} else {
*ptr_location = (uintptr_t)ptr_location + re.value;
}
}

return out;
}

#endif /* KFTF_H */
34 changes: 33 additions & 1 deletion lib/kftf/kftf_tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,39 @@ FUZZ_TEST(kftf_fuzzable, struct kftf_simple_arg)
KFTF_EXPECT_NOT_NULL(kftf_simple_arg, first);
KFTF_EXPECT_IN_RANGE(kftf_simple_arg, second, 'a', 'z');
KFTF_EXPECT_IN_RANGE(kftf_simple_arg, third, 'a', 'z');
kftf_fuzzable(arg.first, arg.second, arg.third);
kftf_fuzzable(arg->first, arg->second, arg->third);
}

struct my_fun_func_arg {
const char *string;
char *buffer;
size_t buffer_size;
};

volatile char __w;
static void my_fun_func(const char *string, char *buffer, size_t buffer_size)
{
size_t i;
/* string should be NULL terminated! */
pr_info("string length = %zu",
strlen(string) + 1 /* null terminated str */);
pr_info("buffer_size = %zu\n", buffer_size);

for (i = 0; i < buffer_size; i++) {
buffer[i]++;
__w = buffer[i]; // avoid inlining
}
}

FUZZ_TEST(my_memncpy, struct my_fun_func_arg)
{
pr_info("[ENTER] %s\n", __FUNCTION__);

KFTF_ANNOTATE_STRING(my_fun_func_arg, string);
KFTF_ANNOTATE_LEN(my_fun_func_arg, buffer_size, buffer);
KFTF_EXPECT_NOT_NULL(my_fun_func_arg, string);
KFTF_EXPECT_NOT_NULL(my_fun_func_arg, buffer);
my_fun_func(arg->string, arg->buffer, arg->buffer_size);
}

#endif /* KFTF_TESTS_H */
18 changes: 1 addition & 17 deletions lib/math/int_sqrt.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@
#include <linux/bitops.h>
#include <linux/limits.h>
#include <linux/math.h>
#include <linux/kftf.h>

struct int_sqrt_arg {
unsigned long x;
};

FUZZ_TEST(int_sqrt, struct int_sqrt_arg)
{
unsigned long res = int_sqrt(arg.x);
pr_info("fuzz_arg.x = %lu, res = %lu", arg.x, res);
}

/**
* int_sqrt - computes the integer square root
Expand All @@ -32,11 +21,6 @@ unsigned long int_sqrt(unsigned long x)
{
unsigned long b, m, y = 0;

// deliberate bug
if (x == 0) {
char c = *(char *)x;
pr_info("should crash here! %c\n", c);
}
if (x <= 1)
return x;

Expand Down Expand Up @@ -67,7 +51,7 @@ u32 int_sqrt64(u64 x)
u64 b, m, y = 0;

if (x <= ULONG_MAX)
return int_sqrt((unsigned long)x);
return int_sqrt((unsigned long) x);

m = 1ULL << ((fls64(x) - 1) & ~1ULL);
while (m != 0) {
Expand Down