Skip to content

Commit

Permalink
fuzz: Integrate cfg with libfuzzer testing
Browse files Browse the repository at this point in the history
- split-input format: add trailing blob for config file

  The corpus needs some update.

- wrappers (-Wl,--wrap) integrate fuzzing of the configuration file.

  The configuration file, mutated by the fuzzer, is made
  available to the cfg.c implementation.

  The mock-up works under the assumption that only the cfg.c
  module works by opening "/" with open(3), and follows up with an
  alternation of openat(3) and fstat(3) calls.
  • Loading branch information
dacav committed Jan 5, 2025
1 parent 5041e54 commit 4f98c7a
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 14 deletions.
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pam_u2f_la_LDFLAGS += -Wl,--wrap=strdup
pam_u2f_la_LDFLAGS += -Wl,--wrap=calloc
pam_u2f_la_LDFLAGS += -Wl,--wrap=malloc
pam_u2f_la_LDFLAGS += -Wl,--wrap=open
pam_u2f_la_LDFLAGS += -Wl,--wrap=openat
pam_u2f_la_LDFLAGS += -Wl,--wrap=close
pam_u2f_la_LDFLAGS += -Wl,--wrap=fdopen
pam_u2f_la_LDFLAGS += -Wl,--wrap=fstat
Expand Down
1 change: 1 addition & 0 deletions fuzz/Makefile.am
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions fuzz/export.sym
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ set_authfile
set_conv
set_user
set_wiredata
set_conf_file_fd
2 changes: 2 additions & 0 deletions fuzz/fuzz.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand Down
74 changes: 66 additions & 8 deletions fuzz/fuzz_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <string.h>
#include <unistd.h>

#include "cfg.h"
#include "fuzz/fuzz.h"
#include "fuzz/wiredata.h"
#include "fuzz/authfile.h"
Expand All @@ -32,6 +33,7 @@ struct param {
char conv[MAXSTR];
struct blob authfile;
struct blob wiredata;
struct blob conf_file;
};

struct conv_appdata {
Expand All @@ -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"
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -231,14 +260,35 @@ static int prepare_authfile(const unsigned char *data, size_t len) {
return fd;
}

static int prepare_conf_file(const struct blob *conf_file) {
int fd;
ssize_t w;

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];
int argc = 32;
int fd = -1;
int authfile_fd = -1, conf_file_fd = -1;

memset(&argv, 0, sizeof(*argv));
memset(&conv, 0, sizeof(conv));
Expand All @@ -256,16 +306,24 @@ 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(&param->conf_file)) == -1)
goto err;
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;
}
Expand Down
54 changes: 48 additions & 6 deletions fuzz/wrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ 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 int latest_conf_path_fd = -1;
static int latest_conf_file_fd = -1;
static int conf_file_fd = -1;
static int authfile_fd = -1;
static char env[] = "value";

Expand All @@ -56,6 +59,7 @@ 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_fd(int fd) { conf_file_fd = fd; }
void set_authfile(int fd) { authfile_fd = fd; }

WRAP(int, close, (int fd), -1, (fd))
Expand All @@ -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))
Expand All @@ -83,6 +86,23 @@ 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 && (fildes == latest_conf_file_fd || fildes == latest_conf_path_fd)) {
buf->st_uid = 0;
buf->st_mode &= ~(S_IWGRP | S_IWOTH);
}

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, ...) {
Expand All @@ -106,22 +126,44 @@ extern uid_t __wrap_geteuid(void) {
return (uniform_random(10) < 1) ? 0 : 1008;
}

extern int __real_openat(int fd, const char *path, int oflag, ...);
extern int __wrap_openat(int fd, const char *path, int oflag, ...);
extern int __wrap_openat(int fd, const char *path, int oflag, ...) {
(void) path;

assert(fd == latest_conf_path_fd);
if (oflag & O_DIRECTORY) {
latest_conf_path_fd = dup(latest_conf_path_fd);
return latest_conf_path_fd;
}

latest_conf_file_fd = dup(conf_file_fd);
return latest_conf_file_fd;
}

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 (strcmp(pathname, "/") == 0) {
latest_conf_path_fd = __real_open("/", flags);
return latest_conf_path_fd;
}

return dup(authfile_fd);
}

extern int __wrap_getpwuid_r(uid_t, struct passwd *, char *, size_t,
Expand Down

0 comments on commit 4f98c7a

Please sign in to comment.