From 3eea550115f708e2dd58f383030e52af16d9dde1 Mon Sep 17 00:00:00 2001 From: Giovanni Simoni Date: Wed, 11 Dec 2024 09:01:20 +0100 Subject: [PATCH] Integrate cfg with libfuzzer testing - split-input format: add trailing blob for config file The corpus needs some update. - __wrap_open extended to handle the configuration file. The configuration file, mutated by the fuzzer, is made available to the cfg.c implementation, analogously to the handling of authfile_fd. The conf_file_path variable will hold the expected file name. If the conf_file= option does not appear in the fuzzed argv, the conf_file_path variable is set with the CFG_DEFAULT_PATH. conf_file_path might also be NULL if the test does not involve the use of a configuration file: this happens in the fuzz_format_parsers case. --- fuzz/Makefile.am | 1 + fuzz/export.sym | 2 ++ fuzz/fuzz.h | 2 ++ fuzz/fuzz_auth.c | 90 +++++++++++++++++++++++++++++++++++++++++++----- fuzz/wrap.c | 15 ++++++++ 5 files changed, 101 insertions(+), 9 deletions(-) diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am index 70b4c3a0..cfb65567 100644 --- a/fuzz/Makefile.am +++ b/fuzz/Makefile.am @@ -1,6 +1,7 @@ # Copyright (C) 2020 Yubico AB - See COPYING AM_CFLAGS = $(CWFLAGS) $(CSFLAGS) -fsanitize=fuzzer AM_CPPFLAGS = $(LIBFIDO2_CFLAGS) $(LIBCRYPTO_CFLAGS) -I$(srcdir)/.. +AM_CPPFLAGS += -D SYSCONFDIR='"$(sysconfdir)"' AM_LDFLAGS = -no-install -fsanitize=fuzzer fuzz_format_parsers_SOURCES = fuzz_format_parsers.c diff --git a/fuzz/export.sym b/fuzz/export.sym index a36d378c..afecd3ae 100644 --- a/fuzz/export.sym +++ b/fuzz/export.sym @@ -5,3 +5,5 @@ set_authfile set_conv set_user set_wiredata +set_conf_file_fd +set_conf_file_path diff --git a/fuzz/fuzz.h b/fuzz/fuzz.h index 461b3e6d..32afb523 100644 --- a/fuzz/fuzz.h +++ b/fuzz/fuzz.h @@ -21,6 +21,8 @@ void set_wiredata(uint8_t *, size_t); void set_user(const char *); void set_conv(struct pam_conv *); void set_authfile(int); +void set_conf_file_path(const char *); +void set_conf_file_fd(int); int pack_u32(uint8_t **, size_t *, uint32_t); int unpack_u32(const uint8_t **, size_t *, uint32_t *); diff --git a/fuzz/fuzz_auth.c b/fuzz/fuzz_auth.c index e6882e5a..7c1574c0 100644 --- a/fuzz/fuzz_auth.c +++ b/fuzz/fuzz_auth.c @@ -11,6 +11,7 @@ #include #include +#include "cfg.h" #include "fuzz/fuzz.h" #include "fuzz/wiredata.h" #include "fuzz/authfile.h" @@ -32,6 +33,7 @@ struct param { char conv[MAXSTR]; struct blob authfile; struct blob wiredata; + struct blob conf_file; }; struct conv_appdata { @@ -48,6 +50,29 @@ static const char dummy_authfile[] = AUTHFILE_SSH; /* module configuration split by fuzzer on semicolon */ static const char *dummy_conf = "sshformat;pinverification=0;manual;"; +/* module configuration file */ +static const char dummy_conf_file[] = "max_devices=10\n" + "manual\n" + "debug\n" + "nouserok\n" + "openasuser\n" + "alwaysok\n" + "interactive\n" + "cue\n" + "nodetect\n" + "expand\n" + "userpresence=0\n" + "userverification=0\n" + "pinverification=0\n" + "authfile=/foo/bar\n" + "sshformat\n" + "authpending_file=/baz/quux\n" + "origin=pam://lolcalhost\n" + "appid=pam://lolcalhost\n" + "prompt=hello\n" + "cue_prompt=howdy\n" + "debug_file=stdout\n"; + /* conversation dummy for manual authentication */ static const char *dummy_conv = "94/ZgCC5htEl9SRmTRfUffKCzU/2ScRJYNFSlC5U+ik=\n" @@ -72,7 +97,8 @@ static size_t pack(uint8_t *data, size_t len, const struct param *p) { pack_string(&data, &len, p->conf) != 1 || pack_string(&data, &len, p->conv) != 1 || pack_blob(&data, &len, &p->authfile) != 1 || - pack_blob(&data, &len, &p->wiredata) != 1) { + pack_blob(&data, &len, &p->wiredata) != 1 || + pack_blob(&data, &len, &p->conf_file) != 1) { return 0; } @@ -106,7 +132,8 @@ static size_t pack_dummy(uint8_t *data, size_t len) { !set_string(dummy.conf, dummy_conf, MAXSTR) || !set_string(dummy.conv, dummy_conv, MAXSTR) || !set_blob(&dummy.authfile, dummy_authfile, sizeof(dummy_authfile)) || - !set_blob(&dummy.wiredata, dummy_wiredata, sizeof(dummy_wiredata))) { + !set_blob(&dummy.wiredata, dummy_wiredata, sizeof(dummy_wiredata)) || + !set_blob(&dummy.conf_file, dummy_conf_file, sizeof(dummy_conf_file))) { assert(0); /* dummy couldn't be prepared */ return 0; } @@ -125,7 +152,8 @@ static struct param *unpack(const uint8_t *data, size_t len) { unpack_string(&data, &len, p->conf) != 1 || unpack_string(&data, &len, p->conv) != 1 || unpack_blob(&data, &len, &p->authfile) != 1 || - unpack_blob(&data, &len, &p->wiredata) != 1) { + unpack_blob(&data, &len, &p->wiredata) != 1 || + unpack_blob(&data, &len, &p->conf_file) != 1) { free(p); return NULL; } @@ -153,6 +181,7 @@ static void mutate(struct param *p, uint32_t seed) { mutate_string(p->conf, MAXSTR); mutate_string(p->conv, MAXSTR); mutate_blob(&p->authfile); + mutate_blob(&p->conf_file); } if (flags & MUTATE_WIREDATA) mutate_blob(&p->wiredata); @@ -229,14 +258,47 @@ static int prepare_authfile(const unsigned char *data, size_t len) { return fd; } +static int prepare_conf_file(const struct blob *conf_file, int argc, + const char **argv, const char **conf_file_path) { + int i, fd; + ssize_t w; + + *conf_file_path = CFG_DEFAULT_PATH; + for (i = 0; i < argc; i++) { + const char *value; + + if (strncmp(argv[i], "conf_file=", strlen("conf_file="))) + continue; + + value = argv[i] + strlen("conf_file="); + *conf_file_path = value; + } + + if ((fd = memfd_create("pam_u2f.conf", MFD_CLOEXEC)) == -1) + return -1; + + w = write(fd, conf_file->body, conf_file->len); + if (w == -1 || (size_t) w != conf_file->len) + goto fail; + + if (lseek(fd, 0, SEEK_SET) == -1) + goto fail; + + return fd; + +fail: + close(fd); + return -1; +} + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct param *param = NULL; struct pam_conv conv; struct conv_appdata conv_data; - const char *argv[32]; + const char *argv[32], *conf_file_path; int argc = 32; - int fd = -1; + int authfile_fd = -1, conf_file_fd = -1; memset(&argv, 0, sizeof(*argv)); memset(&conv, 0, sizeof(conv)); @@ -254,16 +316,26 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { set_user(param->user); set_wiredata(param->wiredata.body, param->wiredata.len); - if ((fd = prepare_authfile(param->authfile.body, param->authfile.len)) == -1) + if ((authfile_fd = + prepare_authfile(param->authfile.body, param->authfile.len)) == -1) goto err; - set_authfile(fd); + set_authfile(authfile_fd); prepare_argv(param->conf, &argv[0], &argc); + + if ((conf_file_fd = prepare_conf_file(¶m->conf_file, argc, argv, + &conf_file_path)) == -1) + goto err; + set_conf_file_path(conf_file_path); + set_conf_file_fd(conf_file_fd); + pam_sm_authenticate((void *) FUZZ_PAM_HANDLE, 0, argc, argv); err: - if (fd != -1) - close(fd); + if (authfile_fd != -1) + close(authfile_fd); + if (conf_file_fd != -1) + close(conf_file_fd); free(param); return 0; } diff --git a/fuzz/wrap.c b/fuzz/wrap.c index 8f7fd9fd..31e026aa 100644 --- a/fuzz/wrap.c +++ b/fuzz/wrap.c @@ -16,6 +16,7 @@ #include #include "drop_privs.h" +#include "cfg.h" #include "fuzz/fuzz.h" extern int prng_up; @@ -34,6 +35,8 @@ static const char *user_ptr = NULL; static struct pam_conv *conv_ptr = NULL; static uint8_t *wiredata_ptr = NULL; static size_t wiredata_len = 0; +static const char *conf_file_path = NULL; +static int conf_file_fd = -1; static int authfile_fd = -1; static char env[] = "value"; @@ -55,6 +58,8 @@ void set_wiredata(uint8_t *data, size_t len) { } void set_user(const char *user) { user_ptr = user; } void set_conv(struct pam_conv *conv) { conv_ptr = conv; } +void set_conf_file_path(const char *path) { conf_file_path = path; } +void set_conf_file_fd(int fd) { conf_file_fd = fd; } void set_authfile(int fd) { authfile_fd = fd; } WRAP(int, close, (int fd), -1, (fd)) @@ -100,17 +105,27 @@ extern uid_t __wrap_geteuid(void) { extern int __real_open(const char *pathname, int flags); extern int __wrap_open(const char *pathname, int flags); extern int __wrap_open(const char *pathname, int flags) { + if (prng_up && uniform_random(400) < 1) return -1; + /* open write-only files as /dev/null */ if ((flags & O_ACCMODE) == O_WRONLY) return __real_open("/dev/null", flags); + /* FIXME: special handling for /dev/random */ if (strcmp(pathname, "/dev/urandom") == 0) return __real_open(pathname, flags); + + if (conf_file_path && strcmp(pathname, conf_file_path) == 0) { + assert(*pathname == '/'); /* should not load config from relative path */ + return dup(conf_file_fd); + } + /* open read-only files using a shared fd for the authfile */ if ((flags & O_ACCMODE) == O_RDONLY) return dup(authfile_fd); + assert(0); /* unsupported */ return -1; }