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