etc.git


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