Skip to content

KUnit Fuzz Harnesses (PoC)#18

Open
ethangraham2001 wants to merge 2 commits intomasterfrom
kfuzztest-kunit
Open

KUnit Fuzz Harnesses (PoC)#18
ethangraham2001 wants to merge 2 commits intomasterfrom
kfuzztest-kunit

Conversation

@ethangraham2001
Copy link
Copy Markdown
Owner

@ethangraham2001 ethangraham2001 commented Mar 25, 2026

An idea that was floated around in the mailing list when the first KFuzzTest RFC was sent up was the idea of integrating KFuzzTest into KUnit rather than maintaining it as a separate framework.

At the time it was decided that it would be pragmatic to keep KFuzzTest as a standalone framework, after having some time to reflect, it seemed like it would be a good idea to try it out. There are a few benefits to this.

  • Fuzzing harnesses get to reuse a lot of existing KUnit functionality, such as mocking, logging, try-catch, debugfs.
  • An existing userbase, lowering barrier for adoption.

These two patches implement a simple PoC for fuzzing harnesses in KUnit as a special type of KUnit test. Much of the design is taken directly from KFuzzTest, allowing a userspace fuzzer (or arbitrary program) to send inputs via debugfs to invoke a fuzz test. The flow looks like so

  • write() data into /sys/kernel/debug/kunit/suite-name/fuzz
  • ioctl(..., handle_id) the same file to invoke the fuzz test on the data previously sent.

Here, handle_id represents the logical index in the array of available fuzz targets for the suite. This can be easily read from debugfs by reading from the [...]/kunit/suite-name/fuzz file, like so:

cat /sys/kernel/debug/kunit/rsa_helper/fuzz
available fuzz harnesses:
  0: fuzz_rsa_parse_pub_key
  1: fuzz_rsa_parse_priv_key

I'm not super familiar with the KUnit codebase so this implementation is pretty hacky, but it seems to work reasonably well in practice. Using the same pkcs7 PoC from previous patch series, a fuzzer was able to trigger an injected bug as demonstrated by a KASAN report.

Commit 2 shows how the previously macro-defined fuzz targets can be implemented similarly as KUnit tests - these examples likely aren't the best however, as they don't use any specific KUnit functionality.

I used this fuzzer to trigger the bug: https://github.com/ethangraham2001/KFuzz-Uring/tree/kunit-fuzz-poc

Note that throughput is significantly lower than with the simple write() based approach from the previous KFuzzTest implementation, seeing as we are spawning a thread for every invocation - maybe a different model should be considered for the fuzzing harnesses. The number of inputs/sec is still higher than syzkaller however, but it's a less smart fuzzer and therefore difficult to faithfully compare.

There are a couple of samples built into the KUnit examples; one of these contains an OOB that can very easily be triggered.

#include <fcntl.h>
#include <linux/ioctl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define KFUZZ_IOC_MAGIC 'K'
#define KFUZZ_IOC_RUN _IO(KFUZZ_IOC_MAGIC, 1)

int main(void) {
  const char data[] = "Hello, world!";
  int fd = open("/sys/kernel/debug/kunit/example/fuzz", O_WRONLY, 0644);

  ssize_t written = write(fd, data, sizeof(data));
  if (written < sizeof(data)) {
    perror("unable to write data.\n");
    return 1;
  }

  // Handler ID for the OOB example.
  unsigned int handler_id = 1;
  if (ioctl(fd, KFUZZ_IOC_RUN, handler_id) < 0) {
    perror("unable to run ioctl");
    return 1;
  }

  close(fd);
  return 0;
}
$ ./main
[  138.626662]     # example_fuzz_harness_oob: initializing
[  138.628764] ==================================================================
[  138.630090] BUG: KASAN: slab-out-of-bounds in example_fuzz_harness_oob+0xf1/0x100
[  138.631372] Read of size 1 at addr ffff888111c7755f by task kunit_try_catch/403

Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Demonstrates the proposed KUnit fuzz harness API.

Signed-off-by: Ethan Graham <ethan.w.s.graham@gmail.com>
Comment thread include/kunit/test.h
Comment on lines -164 to +174
#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 }
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My formatter got excited and git add -p wouldn't let me split this.

Comment thread lib/kunit/debugfs.c
Comment on lines +317 to +320
/*
* Do not create file to re-run test if test runs on init, or if the
* suite represents a set of fuzzing harnesses.
*/
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops - this comment is from another attempt. Behavior is unchanged here, ignore the comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant