diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am index 70b4c3a..275f6a9 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 SCONFDIR='"@SCONFDIR"' 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 a36d378..afecd3a 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 461b3e6..32afb52 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 3c76119..7d6928a 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); @@ -231,14 +260,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=", strlen("conf="))) + continue; + + value = argv[i] + strlen("conf="); + *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)); @@ -256,16 +318,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 ee1f2ce..3f1dd4b 100644 --- a/fuzz/wrap.c +++ b/fuzz/wrap.c @@ -35,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"; @@ -56,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)) @@ -65,7 +69,6 @@ WRAP(void *, malloc, (size_t size), NULL, (size)) WRAP(int, gethostname, (char *name, size_t len), -1, (name, len)) WRAP(ssize_t, getline, (char **s, size_t *n, FILE *fp), -1, (s, n, fp)) WRAP(FILE *, fdopen, (int fd, const char *mode), NULL, (fd, mode)) -WRAP(int, fstat, (int fd, struct stat *st), -1, (fd, st)) WRAP(BIO *, BIO_new, (const BIO_METHOD *type), NULL, (type)) WRAP(int, BIO_write, (BIO * b, const void *data, int len), -1, (b, data, len)) WRAP(int, BIO_read, (BIO * b, void *data, int len), -1, (b, data, len)) @@ -83,6 +86,21 @@ extern ssize_t __wrap_read(int fildes, void *buf, size_t nbyte) { return __real_read(fildes, buf, nbyte); } +extern int __real_fstat(int fildes, struct stat *buf); +extern int __wrap_fstat(int fildes, struct stat *buf); +extern int __wrap_fstat(int fildes, struct stat *buf) { + int r; + + assert(fildes >= 0); + assert(buf != NULL); + + r = __real_fstat(fildes, buf); + if (!r) + buf->st_uid = 0; + + return r; +} + extern int __wrap_asprintf(char **strp, const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 2, 3); extern int __wrap_asprintf(char **strp, const char *fmt, ...) { @@ -109,19 +127,26 @@ 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); + + assert((flags & O_ACCMODE) == O_RDONLY); + /* FIXME: special handling for /dev/random */ if (strcmp(pathname, "/dev/urandom") == 0) return __real_open(pathname, flags); - /* 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; + + if (conf_file_path && strcmp(pathname, conf_file_path) == 0) { + assert(*pathname == '/'); /* should not load config from relative path */ + return dup(conf_file_fd); + } + + return dup(authfile_fd); } extern int __wrap_getpwuid_r(uid_t, struct passwd *, char *, size_t,