etc.git


etc

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

fuzz_json_recv.c (4612B)

      1 /*
      2  * AFL++ / LibFuzzer harness — RECEIVE command state machine
      3  *
      4  * Target: callback_tty RECEIVE handler, protocol.c:307-354
      5  *
      6  * The existing fuzz_websocket_auth harness sends exactly one RECEIVE call.
      7  * This harness sends up to 4 sequential RECEIVE calls, exercising:
      8  *
      9  *   Phase 1 (pss->process == NULL):
     10  *     JSON_DATA ('{') → parse_window_size() + auth token check + spawn_process()
     11  *     RESIZE_TERMINAL ('1') → parse_window_size(buf+1, len-1) — no process, no-op
     12  *
     13  *   Phase 2 (pss->process != NULL, after JSON_DATA):
     14  *     RESIZE_TERMINAL → parse_window_size(buf+1, len-1) + pty_resize()
     15  *     INPUT ('0') → pty_write() (mock returns -1 → callback returns -1)
     16  *     PAUSE ('2') / RESUME ('3') → pty_pause/pty_resume (mock no-ops)
     17  *
     18  * Key targets:
     19  *   - parse_window_size() with arbitrary JSON (json-c attack surface)
     20  *   - Integer truncation: (uint16_t)json_object_get_int() with extreme values
     21  *   - State machine transitions across multiple messages
     22  *   - Buffer management: pss->buffer malloc/free per RECEIVE, no carry-over
     23  *
     24  * Input format:
     25  *   [num_msgs:1]   1–4 messages (bottom 2 bits + 1)
     26  *   per message:
     27  *     [cmd:1]          command byte ('0', '1', '2', '3', '{', or other)
     28  *     [len:2 BE]       payload length (0 is valid but produces len=1 buffer)
     29  *     [payload...]     raw bytes
     30  */
     31 
     32 #include <stdio.h>
     33 #include <stdlib.h>
     34 #include <string.h>
     35 #include <stdint.h>
     36 #include <stdbool.h>
     37 #include <json-c/json.h>
     38 
     39 #include "fuzz_lws_mock.h"
     40 #include "ttyd-src/src/protocol.c"
     41 
     42 extern struct server _fuzz_server;
     43 extern void          fuzz_server_reset(void);
     44 
     45 #define MAX_MSGS 4
     46 
     47 int fuzz_target(const uint8_t *data, size_t size) {
     48     if (size < 4) return 0;
     49 
     50     int num_msgs = (data[0] & 0x03) + 1;
     51     size_t off   = 1;
     52 
     53     struct {
     54         uint8_t        cmd;
     55         const uint8_t *payload;
     56         size_t         len;
     57     } msgs[MAX_MSGS];
     58     int parsed = 0;
     59 
     60     for (int i = 0; i < num_msgs && off + 3 <= size; i++) {
     61         uint8_t  cmd  = data[off++];
     62         uint16_t plen = (uint16_t)((data[off] << 8) | data[off + 1]);
     63         off += 2;
     64         if (plen > size - off) plen = (uint16_t)(size - off);
     65         msgs[parsed].cmd     = cmd;
     66         msgs[parsed].payload = data + off;
     67         msgs[parsed].len     = plen;
     68         off += plen;
     69         parsed++;
     70     }
     71 
     72     if (parsed == 0) return 0;
     73 
     74     fuzz_lws_reset();
     75     fuzz_server_reset();
     76     _fuzz_server.writable   = true;
     77     _fuzz_server.command    = "bash";
     78     _fuzz_server.prefs_json = "{}";
     79     strncpy(_fuzz_server.terminal_type, "xterm-256color",
     80             sizeof(_fuzz_server.terminal_type) - 1);
     81     fuzz_lws_set_uri_path("/ws", 3);
     82 
     83     struct lws     mock_wsi = {0};
     84     struct pss_tty pss;
     85     memset(&pss, 0, sizeof(pss));
     86 
     87     if (callback_tty(&mock_wsi, LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
     88                      &pss, NULL, 0) != 0)
     89         goto cleanup;
     90 
     91     if (callback_tty(&mock_wsi, LWS_CALLBACK_ESTABLISHED,
     92                      &pss, NULL, 0) != 0)
     93         goto cleanup;
     94 
     95     for (int i = 0; i < 3; i++)
     96         callback_tty(&mock_wsi, LWS_CALLBACK_SERVER_WRITEABLE, &pss, NULL, 0);
     97 
     98     for (int i = 0; i < parsed; i++) {
     99         /* Full RECEIVE buffer: [cmd_byte][payload...] */
    100         size_t buf_len = 1 + msgs[i].len;
    101         char *buf = malloc(buf_len);
    102         if (!buf) break;
    103         buf[0] = (char)msgs[i].cmd;
    104         if (msgs[i].len > 0)
    105             memcpy(buf + 1, msgs[i].payload, msgs[i].len);
    106 
    107         fuzz_lws_set_fragment(1, 0);
    108         int ret = callback_tty(&mock_wsi, LWS_CALLBACK_RECEIVE,
    109                                &pss, buf, buf_len);
    110         free(buf);
    111 
    112         if (ret != 0) break;
    113     }
    114 
    115 cleanup:
    116     fuzz_lws_set_fragment(1, 0);
    117     callback_tty(&mock_wsi, LWS_CALLBACK_CLOSED, &pss, NULL, 0);
    118     return 0;
    119 }
    120 
    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 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    136     return fuzz_target(data, size);
    137 }
    138 
    139 #ifndef LIBFUZZER
    140 int main(int argc, char **argv) {
    141     if (argc != 2) { fprintf(stderr, "Usage: %s <input_file>\n", argv[0]); return 1; }
    142     FILE *f = fopen(argv[1], "rb");
    143     if (!f) { perror("fopen"); return 1; }
    144     fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f);
    145     uint8_t *buf = malloc(sz);
    146     fread(buf, 1, sz, f); fclose(f);
    147     fuzz_target(buf, sz);
    148     free(buf);
    149     return 0;
    150 }
    151 #endif
    152 #endif