etc~ scripts and random things |
| git clone https://github.com/djnnvx/etc.git |
| log | files | refs | archive |
fuzz_http_parsing.c (5661B)
1 /* 2 * AFL++ / LibFuzzer harness — TTYD HTTP callback (full request handling) 3 * 4 * Target: the real callback_http() from ttyd's http.c 5 * Technique: #include the source file directly so static functions are 6 * accessible (standard OSS-Fuzz approach). 7 * 8 * Exercises: check_auth(), access_log(), path routing (/token, /index, 9 * parent redirect, 404), accept_gzip(), uncompress_html(), 10 * LWS_CALLBACK_HTTP_WRITEABLE response path. 11 * 12 * Input format (structured binary — pairs with http_mutator.so): 13 * [auth_mode:1][cred_len:2 BE][credential][path_len:2 BE][path][auth_header_value] 14 * 15 * auth_mode 0 — no auth required 16 * auth_mode 1 — Basic auth (credential = expected base64 value) 17 * auth_mode 2 — custom header auth (X-Auth-User) 18 * 19 * Build (handled by build_fuzzers.sh): 20 * afl-clang-fast -fsanitize=address,undefined -g -O1 21 * -I fuzz-include -I ttyd-src/src 22 * fuzz_http_parsing.c 23 * ttyd-obj/{utils,fuzz_lws_mock,mock_server_globals,mock_pty_stubs}.o 24 * -ljson-c -lz -o fuzz_http_parsing 25 */ 26 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <stdint.h> 31 #include <stdbool.h> 32 33 /* Mock lws control state — must come before including the real ttyd source */ 34 #include "fuzz_lws_mock.h" 35 36 /* Pull in the real ttyd HTTP handler (callback_http, check_auth, etc.) */ 37 #include "ttyd-src/src/http.c" 38 39 /* Provided by mock_server_globals.c */ 40 extern struct server _fuzz_server; 41 extern void fuzz_server_reset(void); 42 43 /* ── fuzz target ─────────────────────────────────────────────────────────── */ 44 45 int fuzz_target(const uint8_t *data, size_t size) { 46 /* Need at least: auth_mode(1) + cred_len(2) + path_len(2) = 5 bytes */ 47 if (size < 5) return 0; 48 49 /* Parse structured input */ 50 uint8_t auth_mode = data[0] % 3; 51 uint16_t cred_len = (uint16_t)((data[1] << 8) | data[2]); 52 if ((size_t)cred_len > size - 5) cred_len = (uint16_t)(size - 5); 53 54 size_t off = 3 + cred_len; 55 if (off + 2 > size) return 0; 56 57 uint16_t path_len = (uint16_t)((data[off] << 8) | data[off + 1]); 58 off += 2; 59 if ((size_t)path_len > size - off) path_len = (uint16_t)(size - off); 60 61 /* Reset all fuzzer-controlled state */ 62 fuzz_lws_reset(); 63 fuzz_server_reset(); 64 65 /* Set up the credential the server expects */ 66 static char cred_buf[65536]; 67 if (auth_mode == 1 && cred_len > 0) { 68 memcpy(cred_buf, data + 3, cred_len); 69 cred_buf[cred_len] = '\0'; 70 _fuzz_server.credential = cred_buf; 71 _fuzz_server.auth_header = NULL; 72 } else if (auth_mode == 2) { 73 _fuzz_server.credential = NULL; 74 _fuzz_server.auth_header = (char *)"X-Auth-User"; 75 } 76 /* auth_mode 0: no auth, both remain NULL */ 77 78 /* Extract the URL path (null-terminated for callback_http) */ 79 static char path_buf[4096]; 80 int n = path_len < (int)sizeof(path_buf) - 1 ? path_len : (int)sizeof(path_buf) - 1; 81 memcpy(path_buf, data + off, n); 82 path_buf[n] = '\0'; 83 off += path_len; 84 85 /* Remaining bytes are the Authorization header value */ 86 size_t auth_len = (off < size) ? size - off : 0; 87 fuzz_lws_set_auth((const char *)(data + off), (int)auth_len); 88 89 /* For custom-header mode, same bytes go to the custom header mock */ 90 if (auth_mode == 2) 91 fuzz_lws_set_custom((const char *)(data + off), (int)auth_len); 92 93 /* ── Exercise the REAL callback_http() ────────────────────────────── */ 94 struct lws mock_wsi = {0}; 95 struct pss_http pss = {0}; 96 97 /* LWS_CALLBACK_HTTP: main request handling (auth, path routing, etc.) */ 98 int ret = callback_http(&mock_wsi, LWS_CALLBACK_HTTP, 99 &pss, path_buf, (size_t)n); 100 101 /* LWS_CALLBACK_HTTP_WRITEABLE: exercise response body transmission 102 * (only meaningful if the HTTP handler set up a buffer) */ 103 if (ret == 0 && pss.buffer != NULL && pss.len > 0) { 104 (void)callback_http(&mock_wsi, LWS_CALLBACK_HTTP_WRITEABLE, 105 &pss, NULL, 0); 106 /* pss_buffer_free() inside WRITEABLE already freed the buffer — 107 * null it so the cleanup below doesn't double-free. */ 108 pss.buffer = NULL; 109 } 110 111 /* Clean up any strdup'd buffer from /token path if WRITEABLE was skipped */ 112 if (pss.buffer != NULL && pss.buffer != (char *)index_html && 113 pss.buffer != html_cache) { 114 free(pss.buffer); 115 } 116 117 return 0; 118 } 119 120 /* ── AFL++ persistent mode ───────────────────────────────────────────────── */ 121 #ifdef __AFL_FUZZ_TESTCASE_LEN 122 __AFL_FUZZ_INIT(); 123 int main(void) { 124 #ifdef __AFL_HAVE_MANUAL_CONTROL 125 __AFL_INIT(); 126 #endif 127 unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; 128 while (__AFL_LOOP(10000)) { 129 fuzz_target(buf, __AFL_FUZZ_TESTCASE_LEN); 130 } 131 return 0; 132 } 133 134 #else 135 /* LibFuzzer */ 136 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 137 return fuzz_target(data, size); 138 } 139 140 #ifndef LIBFUZZER 141 /* Standalone file-based testing */ 142 int main(int argc, char **argv) { 143 if (argc != 2) { fprintf(stderr, "Usage: %s <input_file>\n", argv[0]); return 1; } 144 FILE *f = fopen(argv[1], "rb"); 145 if (!f) { perror("fopen"); return 1; } 146 fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f); 147 uint8_t *buf = malloc(sz); 148 fread(buf, 1, sz, f); fclose(f); 149 fuzz_target(buf, sz); 150 free(buf); 151 return 0; 152 } 153 #endif 154 #endif