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