xref: /netbsd-src/external/bsd/pam-u2f/dist/fuzz/fuzz_auth.c (revision d16b7486a53dcb8072b60ec6fcb4373a2d0c27b7)
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 
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 
169 static 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 
195 static 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 
209 static 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 
216 static 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 
232 int 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 
264 err:
265   if (fd != -1)
266     close(fd);
267   free(param);
268   return 0;
269 }
270 
271 size_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 
286 static 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 
302 int 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