etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

fuzz_ws_fragments.c (4539B)

      1 /*
      2  * AFL++ / LibFuzzer harness — WebSocket fragmented message accumulation
      3  *
      4  * Target: callback_tty RECEIVE path, protocol.c:284-293
      5  *
      6  *   if (pss->buffer == NULL) {
      7  *       pss->buffer = xmalloc(len);
      8  *       pss->len = len;
      9  *       memcpy(pss->buffer, in, len);
     10  *   } else {
     11  *       pss->buffer = xrealloc(pss->buffer, pss->len + len);  <-- integer overflow
     12  *       memcpy(pss->buffer + pss->len, in, len);
     13  *       pss->len += len;
     14  *   }
     15  *
     16  * The existing fuzz_websocket_auth harness never reaches the xrealloc branch:
     17  * lws_is_final_fragment() and lws_remaining_packet_payload() are hardcoded to
     18  * 1 and 0 respectively, so the early-return at protocol.c:303 is never taken.
     19  *
     20  * This harness makes both values fuzzer-controlled so AFL++ can explore:
     21  *   - Multi-fragment accumulation across N RECEIVE calls
     22  *   - Integer overflow: pss->len + len → xrealloc(buf, 0) → heap overflow
     23  *   - Command dispatch on the fully-assembled multi-fragment message
     24  *
     25  * Input format:
     26  *   [num_frags:1]   number of fragments, 1–8 (bottom 3 bits)
     27  *   [cmd:1]         WebSocket command byte prepended to the first fragment
     28  *   [payload...]    raw bytes split evenly across fragments
     29  */
     30 
     31 #include <stdio.h>
     32 #include <stdlib.h>
     33 #include <string.h>
     34 #include <stdint.h>
     35 #include <stdbool.h>
     36 #include <json-c/json.h>
     37 
     38 #include "fuzz_lws_mock.h"
     39 #include "ttyd-src/src/protocol.c"
     40 
     41 extern struct server _fuzz_server;
     42 extern void          fuzz_server_reset(void);
     43 
     44 int fuzz_target(const uint8_t *data, size_t size) {
     45     /* need: num_frags(1) + cmd(1) + at least 1 payload byte */
     46     if (size < 3) return 0;
     47 
     48     int num_frags = (data[0] & 0x07) + 1;   /* 1–8 */
     49     uint8_t cmd   = data[1];
     50     const uint8_t *payload = data + 2;
     51     size_t payload_size    = size - 2;
     52 
     53     /* Build the full virtual message: [cmd][payload...] */
     54     size_t total = payload_size + 1;
     55     char *msg = malloc(total);
     56     if (!msg) return 0;
     57     msg[0] = (char)cmd;
     58     memcpy(msg + 1, payload, payload_size);
     59 
     60     fuzz_lws_reset();
     61     fuzz_server_reset();
     62     _fuzz_server.writable    = true;
     63     _fuzz_server.command     = "bash";
     64     _fuzz_server.prefs_json  = "{}";
     65     strncpy(_fuzz_server.terminal_type, "xterm-256color",
     66             sizeof(_fuzz_server.terminal_type) - 1);
     67     fuzz_lws_set_uri_path("/ws", 3);
     68 
     69     struct lws     mock_wsi = {0};
     70     struct pss_tty pss;
     71     memset(&pss, 0, sizeof(pss));
     72 
     73     if (callback_tty(&mock_wsi, LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
     74                      &pss, NULL, 0) != 0)
     75         goto cleanup;
     76 
     77     if (callback_tty(&mock_wsi, LWS_CALLBACK_ESTABLISHED,
     78                      &pss, NULL, 0) != 0)
     79         goto cleanup;
     80 
     81     /* Burn through the two initial messages + initialization sentinel */
     82     for (int i = 0; i < 3; i++)
     83         callback_tty(&mock_wsi, LWS_CALLBACK_SERVER_WRITEABLE, &pss, NULL, 0);
     84 
     85     /* Split msg into num_frags fragments and send each as a RECEIVE call */
     86     size_t frag_size = total / (size_t)num_frags;
     87     if (frag_size == 0) frag_size = 1;
     88 
     89     size_t off = 0;
     90     for (int i = 0; i < num_frags && off < total; i++) {
     91         size_t this_len = (i == num_frags - 1) ? (total - off) : frag_size;
     92         if (off + this_len > total) this_len = total - off;
     93 
     94         int is_final  = (i == num_frags - 1) ? 1 : 0;
     95         size_t remaining = is_final ? 0 : (total - off - this_len);
     96 
     97         fuzz_lws_set_fragment(is_final, remaining);
     98         int ret = callback_tty(&mock_wsi, LWS_CALLBACK_RECEIVE,
     99                                &pss, msg + off, this_len);
    100         if (ret != 0) break;
    101         off += this_len;
    102     }
    103 
    104 cleanup:
    105     free(msg);
    106     fuzz_lws_set_fragment(1, 0);
    107     callback_tty(&mock_wsi, LWS_CALLBACK_CLOSED, &pss, NULL, 0);
    108     return 0;
    109 }
    110 
    111 #ifdef __AFL_FUZZ_TESTCASE_LEN
    112 __AFL_FUZZ_INIT();
    113 int main(void) {
    114 #ifdef __AFL_HAVE_MANUAL_CONTROL
    115     __AFL_INIT();
    116 #endif
    117     unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
    118     while (__AFL_LOOP(10000)) {
    119         fuzz_target(buf, __AFL_FUZZ_TESTCASE_LEN);
    120     }
    121     return 0;
    122 }
    123 
    124 #else
    125 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    126     return fuzz_target(data, size);
    127 }
    128 
    129 #ifndef LIBFUZZER
    130 int main(int argc, char **argv) {
    131     if (argc != 2) { fprintf(stderr, "Usage: %s <input_file>\n", argv[0]); return 1; }
    132     FILE *f = fopen(argv[1], "rb");
    133     if (!f) { perror("fopen"); return 1; }
    134     fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f);
    135     uint8_t *buf = malloc(sz);
    136     fread(buf, 1, sz, f); fclose(f);
    137     fuzz_target(buf, sz);
    138     free(buf);
    139     return 0;
    140 }
    141 #endif
    142 #endif