Home | History | Annotate | Line # | Download | only in fuzz
      1 /* Copyright (C) 2021-2022 Yubico AB - See COPYING */
      2 #include <sys/types.h>
      3 #include <sys/mman.h>
      4 #include <sys/stat.h>
      5 #include <assert.h>
      6 #include <err.h>
      7 #include <errno.h>
      8 #include <pwd.h>
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 #include <string.h>
     12 #include <unistd.h>
     13 
     14 #include "fuzz/fuzz.h"
     15 #include "fuzz/wiredata.h"
     16 #include "fuzz/authfile.h"
     17 
     18 #define MUTATE_SEED 0x01
     19 #define MUTATE_PARAM 0x02
     20 #define MUTATE_WIREDATA 0x04
     21 #define MUTATE_ALL (MUTATE_SEED | MUTATE_PARAM | MUTATE_WIREDATA)
     22 
     23 size_t LLVMFuzzerMutate(uint8_t *, size_t, size_t);
     24 int LLVMFuzzerInitialize(int *, char ***);
     25 int LLVMFuzzerTestOneInput(const uint8_t *, size_t);
     26 size_t LLVMFuzzerCustomMutator(uint8_t *, size_t, size_t, unsigned int);
     27 
     28 struct param {
     29   uint32_t seed;
     30   char user[MAXSTR];
     31   char conf[MAXSTR];
     32   char conv[MAXSTR];
     33   struct blob authfile;
     34   struct blob wiredata;
     35 };
     36 
     37 struct conv_appdata {
     38   char *str;
     39   char *save;
     40 };
     41 
     42 /* fuzzer configuration */
     43 static unsigned int flags = MUTATE_ALL;
     44 
     45 /* it is far easier for the fuzzer to guess the native format */
     46 static const char dummy_authfile[] = AUTHFILE_SSH;
     47 
     48 /* module configuration split by fuzzer on semicolon */
     49 static const char *dummy_conf = "sshformat;pinverification=0;manual;";
     50 
     51 /* conversation dummy for manual authentication */
     52 static const char *dummy_conv =
     53   "94/ZgCC5htEl9SRmTRfUffKCzU/2ScRJYNFSlC5U+ik=\n"
     54   "ssh:\n"
     55   "WCXjBhDooWIRWWD+HsIj5lKcn0tugCANy15cMhyK8eKxvwEAAAAP\n"
     56   "MEQCIDBrIO3J/B9Y7LJca3A7t0m76WcxoATJe0NG/"
     57   "ZsjOMq2AiAdBGrjMalfVtzEe0rjWfnRrGhMFyRyaRuPfCHVYdIWdg==\n";
     58 
     59 /* wiredata collected from an authenticator during authentication */
     60 static unsigned char dummy_wiredata[] = {
     61   WIREDATA_CTAP_INIT,
     62   WIREDATA_CTAP_CBOR_INFO,
     63   WIREDATA_CTAP_CBOR_ASSERT_DISCOVER,
     64   WIREDATA_CTAP_CBOR_ASSERT_AUTHENTICATE,
     65 };
     66 
     67 static size_t pack(uint8_t *data, size_t len, const struct param *p) {
     68   size_t ilen = len;
     69 
     70   if (pack_u32(&data, &len, p->seed) != 1 ||
     71       pack_string(&data, &len, p->user) != 1 ||
     72       pack_string(&data, &len, p->conf) != 1 ||
     73       pack_string(&data, &len, p->conv) != 1 ||
     74       pack_blob(&data, &len, &p->authfile) != 1 ||
     75       pack_blob(&data, &len, &p->wiredata) != 1) {
     76     return 0;
     77   }
     78 
     79   return ilen - len;
     80 }
     81 
     82 static int set_blob(struct blob *blob, const void *data, size_t len) {
     83   if (len > MAXBLOB)
     84     return 0;
     85   memcpy(blob->body, data, len);
     86   blob->len = len;
     87   return 1;
     88 }
     89 
     90 static int set_string(char *dst, const char *src, size_t size) {
     91   int n;
     92 
     93   /* FIXME: use strlcpy */
     94   n = snprintf(dst, size, "%s", src);
     95   if (n < 0 || (size_t) n >= size)
     96     return 0;
     97   return 1;
     98 }
     99 
    100 static size_t pack_dummy(uint8_t *data, size_t len) {
    101   struct param dummy;
    102   size_t r;
    103 
    104   memset(&dummy, 0, sizeof(dummy));
    105   if (!set_string(dummy.user, "user", MAXSTR) ||
    106       !set_string(dummy.conf, dummy_conf, MAXSTR) ||
    107       !set_string(dummy.conv, dummy_conv, MAXSTR) ||
    108       !set_blob(&dummy.authfile, dummy_authfile, sizeof(dummy_authfile)) ||
    109       !set_blob(&dummy.wiredata, dummy_wiredata, sizeof(dummy_wiredata))) {
    110     assert(0); /* dummy couldn't be prepared */
    111     return 0;
    112   }
    113 
    114   r = pack(data, len, &dummy);
    115   assert(r != 0); /* dummy couldn't be packed */
    116   return r;
    117 }
    118 
    119 static struct param *unpack(const uint8_t *data, size_t len) {
    120   struct param *p = NULL;
    121 
    122   if ((p = calloc(1, sizeof(*p))) == NULL ||
    123       unpack_u32(&data, &len, &p->seed) != 1 ||
    124       unpack_string(&data, &len, p->user) != 1 ||
    125       unpack_string(&data, &len, p->conf) != 1 ||
    126       unpack_string(&data, &len, p->conv) != 1 ||
    127       unpack_blob(&data, &len, &p->authfile) != 1 ||
    128       unpack_blob(&data, &len, &p->wiredata) != 1) {
    129     free(p);
    130     return NULL;
    131   }
    132 
    133   return p;
    134 }
    135 
    136 static void mutate_blob(struct blob *blob) {
    137   blob->len =
    138     LLVMFuzzerMutate((uint8_t *) blob->body, blob->len, sizeof(blob->body));
    139 }
    140 
    141 static void mutate_string(char *s, size_t maxlen) {
    142   size_t len;
    143 
    144   len = LLVMFuzzerMutate((uint8_t *) s, strlen(s), maxlen);
    145   s[len - 1] = '\0';
    146 }
    147 
    148 static void mutate(struct param *p, uint32_t seed) {
    149   if (flags & MUTATE_SEED)
    150     p->seed = seed;
    151   if (flags & MUTATE_PARAM) {
    152     mutate_string(p->user, MAXSTR);
    153     mutate_string(p->conf, MAXSTR);
    154     mutate_string(p->conv, MAXSTR);
    155     mutate_blob(&p->authfile);
    156   }
    157   if (flags & MUTATE_WIREDATA)
    158     mutate_blob(&p->wiredata);
    159 }
    160 
    161 static void consume(const void *body, size_t len) {
    162   const volatile uint8_t *ptr = body;
    163   volatile uint8_t x = 0;
    164 
    165   while (len--)
    166     x ^= *ptr++;
    167 
    168   (void) x;
    169 }
    170 
    171 static int conv_cb(int num_msg, const struct pam_message **msg,
    172                    struct pam_response **resp_p, void *appdata_ptr) {
    173   struct conv_appdata *conv = appdata_ptr;
    174   struct pam_response *resp = NULL;
    175   const char *str = NULL;
    176 
    177   assert(num_msg == 1);
    178   assert(resp_p != NULL);
    179 
    180   consume(msg[0]->msg, strlen(msg[0]->msg));
    181 
    182   if ((*resp_p = resp = calloc(1, sizeof(*resp))) == NULL)
    183     return PAM_CONV_ERR;
    184 
    185   if (msg[0]->msg_style == PAM_PROMPT_ECHO_OFF ||
    186       msg[0]->msg_style == PAM_PROMPT_ECHO_ON) {
    187     str = strtok_r(conv->save ? NULL : conv->str, "\n", &conv->save);
    188     if (str != NULL && (resp->resp = strdup(str)) == NULL) {
    189       free(resp);
    190       return PAM_CONV_ERR;
    191     }
    192   }
    193 
    194   return PAM_SUCCESS;
    195 }
    196 
    197 static void prepare_argv(char *s, const char **argv, int *argc) {
    198   const char *delim = ";";
    199   char *token, *save;
    200   int size = *argc;
    201 
    202   *argc = 0;
    203 
    204   token = strtok_r(s, delim, &save);
    205   while (token != NULL && *argc < size) {
    206     argv[(*argc)++] = token;
    207     token = strtok_r(NULL, delim, &save);
    208   }
    209 }
    210 
    211 static void prepare_conv(struct pam_conv *conv, struct conv_appdata *data,
    212                          char *str) {
    213   data->str = str;
    214   conv->conv = conv_cb;
    215   conv->appdata_ptr = data;
    216 }
    217 
    218 static int prepare_authfile(const unsigned char *data, size_t len) {
    219   int fd;
    220   ssize_t r;
    221 
    222   if ((fd = memfd_create("u2f_keys", MFD_CLOEXEC)) == -1)
    223     return -1;
    224 
    225   if ((r = write(fd, data, len)) == -1 || (size_t) r != len ||
    226       lseek(fd, 0, SEEK_SET) == -1) {
    227     close(fd);
    228     return -1;
    229   }
    230 
    231   return fd;
    232 }
    233 
    234 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    235 
    236   struct param *param = NULL;
    237   struct pam_conv conv;
    238   struct conv_appdata conv_data;
    239   const char *argv[32];
    240   int argc = 32;
    241   int fd = -1;
    242 
    243   memset(&argv, 0, sizeof(*argv));
    244   memset(&conv, 0, sizeof(conv));
    245   memset(&conv_data, 0, sizeof(conv_data));
    246 
    247   if ((param = unpack(data, size)) == NULL)
    248     goto err;
    249 
    250   /* init libfido2's fuzzing prng */
    251   prng_init(param->seed);
    252 
    253   /* configure wrappers */
    254   prepare_conv(&conv, &conv_data, param->conv);
    255   set_conv(&conv);
    256   set_user(param->user);
    257   set_wiredata(param->wiredata.body, param->wiredata.len);
    258 
    259   if ((fd = prepare_authfile(param->authfile.body, param->authfile.len)) == -1)
    260     goto err;
    261   set_authfile(fd);
    262 
    263   prepare_argv(param->conf, &argv[0], &argc);
    264   pam_sm_authenticate((void *) FUZZ_PAM_HANDLE, 0, argc, argv);
    265 
    266 err:
    267   if (fd != -1)
    268     close(fd);
    269   free(param);
    270   return 0;
    271 }
    272 
    273 size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t maxsize,
    274                                unsigned int seed) {
    275   size_t blob_len;
    276   struct param *p = NULL;
    277 
    278   if ((p = unpack(data, size)) == NULL)
    279     return pack_dummy(data, maxsize);
    280 
    281   mutate(p, seed);
    282   blob_len = pack(data, maxsize, p);
    283   free(p);
    284 
    285   return blob_len;
    286 }
    287 
    288 static void parse_mutate_flags(const char *opt, unsigned int *mutate_flags) {
    289   const char *f;
    290 
    291   if ((f = strchr(opt, '=')) == NULL || strlen(++f) == 0)
    292     errx(1, "usage: --pam-u2f-mutate=<flag>");
    293 
    294   if (strcmp(f, "seed") == 0)
    295     *mutate_flags |= MUTATE_SEED;
    296   else if (strcmp(f, "param") == 0)
    297     *mutate_flags |= MUTATE_PARAM;
    298   else if (strcmp(f, "wiredata") == 0)
    299     *mutate_flags |= MUTATE_WIREDATA;
    300   else
    301     errx(1, "--pam-u2f-mutate: unknown flag '%s'", f);
    302 }
    303 
    304 int LLVMFuzzerInitialize(int *argc, char ***argv) {
    305   unsigned int mutate_flags = 0;
    306 
    307   for (int i = 0; i < *argc; i++) {
    308     if (strncmp((*argv)[i], "--pam-u2f-mutate=", 17) == 0) {
    309       parse_mutate_flags((*argv)[i], &mutate_flags);
    310     }
    311   }
    312 
    313   if (mutate_flags)
    314     flags = mutate_flags;
    315 
    316   return 0;
    317 }
    318