1/* Copyright (C) 2021 Yubico AB - See COPYING */
2#include <assert.h>
3#include <err.h>
4#include <errno.h>
5#include <pwd.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <sys/mman.h>
10#include <sys/stat.h>
11#include <sys/types.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
23size_t LLVMFuzzerMutate(uint8_t *, size_t, size_t);
24int LLVMFuzzerInitialize(int *, char ***);
25int LLVMFuzzerTestOneInput(const uint8_t *, size_t);
26size_t LLVMFuzzerCustomMutator(uint8_t *, size_t, size_t, unsigned int);
27
28struct 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
37struct conv_appdata {
38  char *str;
39  char *save;
40};
41
42/* fuzzer configuration */
43static unsigned int flags = MUTATE_ALL;
44
45/* it is far easier for the fuzzer to guess the native format */
46static const char dummy_authfile[] = AUTHFILE_SSH;
47
48/* module configuration split by fuzzer on semicolon */
49static const char *dummy_conf = "sshformat;pinverification=0;manual;";
50
51/* conversation dummy for manual authentication */
52static 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 */
60static 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
67static 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
82static 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
90static 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
100static 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
119static 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
136static void mutate_blob(struct blob *blob) {
137  blob->len =
138    LLVMFuzzerMutate((uint8_t *) blob->body, blob->len, sizeof(blob->body));
139}
140
141static 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
148static 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
161static 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
169static int conv_cb(int num_msg, const struct pam_message **msg,
170                   struct pam_response **resp_p, void *appdata_ptr) {
171  struct conv_appdata *conv = appdata_ptr;
172  struct pam_response *resp = NULL;
173  const char *str = NULL;
174
175  assert(num_msg == 1);
176  assert(resp_p != NULL);
177
178  consume(msg[0]->msg, strlen(msg[0]->msg));
179
180  if ((*resp_p = resp = calloc(1, sizeof(*resp))) == NULL)
181    return PAM_CONV_ERR;
182
183  if (msg[0]->msg_style == PAM_PROMPT_ECHO_OFF ||
184      msg[0]->msg_style == PAM_PROMPT_ECHO_ON) {
185    str = strtok_r(conv->save ? NULL : conv->str, "\n", &conv->save);
186    if (str != NULL && (resp->resp = strdup(str)) == NULL) {
187      free(resp);
188      return PAM_CONV_ERR;
189    }
190  }
191
192  return PAM_SUCCESS;
193}
194
195static void prepare_argv(char *s, const char **argv, int *argc) {
196  const char *delim = ";";
197  char *token, *save;
198  int size = *argc;
199
200  *argc = 0;
201
202  token = strtok_r(s, delim, &save);
203  while (token != NULL && *argc < size) {
204    argv[(*argc)++] = token;
205    token = strtok_r(NULL, delim, &save);
206  }
207}
208
209static void prepare_conv(struct pam_conv *conv, struct conv_appdata *data,
210                         char *str) {
211  data->str = str;
212  conv->conv = conv_cb;
213  conv->appdata_ptr = data;
214}
215
216static int prepare_authfile(const unsigned char *data, size_t len) {
217  int fd;
218  ssize_t r;
219
220  if ((fd = memfd_create("u2f_keys", MFD_CLOEXEC)) == -1)
221    return -1;
222
223  if ((r = write(fd, data, len)) == -1 || (size_t) r != len ||
224      lseek(fd, 0, SEEK_SET) == -1) {
225    close(fd);
226    return -1;
227  }
228
229  return fd;
230}
231
232int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
233
234  struct param *param = NULL;
235  struct pam_conv conv;
236  struct conv_appdata conv_data;
237  const char *argv[32];
238  int argc = 32;
239  int fd = -1;
240
241  memset(&argv, 0, sizeof(*argv));
242  memset(&conv, 0, sizeof(conv));
243  memset(&conv_data, 0, sizeof(conv_data));
244
245  if ((param = unpack(data, size)) == NULL)
246    goto err;
247
248  /* init libfido2's fuzzing prng */
249  prng_init(param->seed);
250
251  /* configure wrappers */
252  prepare_conv(&conv, &conv_data, param->conv);
253  set_conv(&conv);
254  set_user(param->user);
255  set_wiredata(param->wiredata.body, param->wiredata.len);
256
257  if ((fd = prepare_authfile(param->authfile.body, param->authfile.len)) == -1)
258    goto err;
259  set_authfile(fd);
260
261  prepare_argv(param->conf, &argv[0], &argc);
262  pam_sm_authenticate((void *) FUZZ_PAM_HANDLE, 0, argc, argv);
263
264err:
265  if (fd != -1)
266    close(fd);
267  free(param);
268  return 0;
269}
270
271size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t maxsize,
272                               unsigned int seed) {
273  size_t blob_len;
274  struct param *p = NULL;
275
276  if ((p = unpack(data, size)) == NULL)
277    return pack_dummy(data, maxsize);
278
279  mutate(p, seed);
280  blob_len = pack(data, maxsize, p);
281  free(p);
282
283  return blob_len;
284}
285
286static void parse_mutate_flags(const char *opt, unsigned int *mutate_flags) {
287  const char *f;
288
289  if ((f = strchr(opt, '=')) == NULL || strlen(++f) == 0)
290    errx(1, "usage: --pam-u2f-mutate=<flag>");
291
292  if (strcmp(f, "seed") == 0)
293    *mutate_flags |= MUTATE_SEED;
294  else if (strcmp(f, "param") == 0)
295    *mutate_flags |= MUTATE_PARAM;
296  else if (strcmp(f, "wiredata") == 0)
297    *mutate_flags |= MUTATE_WIREDATA;
298  else
299    errx(1, "--pam-u2f-mutate: unknown flag '%s'", f);
300}
301
302int LLVMFuzzerInitialize(int *argc, char ***argv) {
303  unsigned int mutate_flags = 0;
304
305  for (int i = 0; i < *argc; i++) {
306    if (strncmp((*argv)[i], "--pam-u2f-mutate=", 17) == 0) {
307      parse_mutate_flags((*argv)[i], &mutate_flags);
308    }
309  }
310
311  if (mutate_flags)
312    flags = mutate_flags;
313
314  return 0;
315}
316