etc.git


etc

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

fuzz_prefs_message.c (4101B)

      1 /*
      2  * AFL++ / LibFuzzer harness — send_initial_message() stack overflow
      3  *
      4  * Target: protocol.c:26 (SET_WINDOW_TITLE) and protocol.c:29 (SET_PREFERENCES)
      5  *
      6  *   unsigned char message[LWS_PRE + 1 + 4096];   // 4129 bytes
      7  *   unsigned char *p = &message[LWS_PRE];          // 4097 bytes of headroom
      8  *
      9  *   SET_WINDOW_TITLE:
     10  *     n = sprintf((char *)p, "%c%s (%s)", cmd, server->command, hostname);
     11  *     Overflows when server->command > ~3965 bytes (hostname takes up to 127).
     12  *
     13  *   SET_PREFERENCES:
     14  *     n = sprintf((char *)p, "%c%s", cmd, server->prefs_json);
     15  *     Overflows when server->prefs_json > 4095 bytes.
     16  *
     17  * In real ttyd, both fields are admin-controlled (CLI flags --pref, command
     18  * argv).  This harness makes them fuzzer-controlled to find the overflow and
     19  * any downstream UB (wrong return value → unexpected -1 from send_initial_message
     20  * → lws_close_reason path, etc.).
     21  *
     22  * Input format:
     23  *   [prefs_len:2 BE][prefs_json bytes][cmd_len:2 BE][command bytes]
     24  */
     25 
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 #include <stdint.h>
     30 #include <stdbool.h>
     31 #include <json-c/json.h>
     32 
     33 #include "fuzz_lws_mock.h"
     34 #include "ttyd-src/src/protocol.c"
     35 
     36 extern struct server _fuzz_server;
     37 extern void          fuzz_server_reset(void);
     38 
     39 static char prefs_buf[65536];
     40 static char cmd_buf[65536];
     41 
     42 int fuzz_target(const uint8_t *data, size_t size) {
     43     if (size < 4) return 0;
     44 
     45     /* Parse prefs_json */
     46     uint16_t prefs_len = (uint16_t)((data[0] << 8) | data[1]);
     47     size_t off = 2;
     48     if (prefs_len > size - off)          prefs_len = (uint16_t)(size - off);
     49     if (prefs_len > sizeof(prefs_buf)-1) prefs_len = sizeof(prefs_buf) - 1;
     50     memcpy(prefs_buf, data + off, prefs_len);
     51     prefs_buf[prefs_len] = '\0';
     52     off += prefs_len;
     53 
     54     /* Parse command */
     55     if (off + 2 > size) return 0;
     56     uint16_t cmd_len = (uint16_t)((data[off] << 8) | data[off + 1]);
     57     off += 2;
     58     if (cmd_len > size - off)          cmd_len = (uint16_t)(size - off);
     59     if (cmd_len > sizeof(cmd_buf) - 1) cmd_len = sizeof(cmd_buf) - 1;
     60     memcpy(cmd_buf, data + off, cmd_len);
     61     cmd_buf[cmd_len] = '\0';
     62 
     63     fuzz_lws_reset();
     64     fuzz_server_reset();
     65     _fuzz_server.prefs_json = prefs_buf;
     66     _fuzz_server.command    = cmd_buf;
     67     strncpy(_fuzz_server.terminal_type, "xterm-256color",
     68             sizeof(_fuzz_server.terminal_type) - 1);
     69     fuzz_lws_set_uri_path("/ws", 3);
     70 
     71     struct lws     mock_wsi = {0};
     72     struct pss_tty pss;
     73     memset(&pss, 0, sizeof(pss));
     74 
     75     /* FILTER: no auth, no origin — always passes */
     76     if (callback_tty(&mock_wsi, LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
     77                      &pss, NULL, 0) != 0)
     78         goto cleanup;
     79 
     80     if (callback_tty(&mock_wsi, LWS_CALLBACK_ESTABLISHED,
     81                      &pss, NULL, 0) != 0)
     82         goto cleanup;
     83 
     84     /*
     85      * Drive through:
     86      *   WRITEABLE call 1 → SET_WINDOW_TITLE  (sprintf with server->command)
     87      *   WRITEABLE call 2 → SET_PREFERENCES   (sprintf with server->prefs_json)
     88      *   WRITEABLE call 3 → sets initialized=true, calls pty_resume(NULL)
     89      */
     90     for (int i = 0; i < 3; i++)
     91         callback_tty(&mock_wsi, LWS_CALLBACK_SERVER_WRITEABLE, &pss, NULL, 0);
     92 
     93 cleanup:
     94     callback_tty(&mock_wsi, LWS_CALLBACK_CLOSED, &pss, NULL, 0);
     95     return 0;
     96 }
     97 
     98 #ifdef __AFL_FUZZ_TESTCASE_LEN
     99 __AFL_FUZZ_INIT();
    100 int main(void) {
    101 #ifdef __AFL_HAVE_MANUAL_CONTROL
    102     __AFL_INIT();
    103 #endif
    104     unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
    105     while (__AFL_LOOP(10000)) {
    106         fuzz_target(buf, __AFL_FUZZ_TESTCASE_LEN);
    107     }
    108     return 0;
    109 }
    110 
    111 #else
    112 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    113     return fuzz_target(data, size);
    114 }
    115 
    116 #ifndef LIBFUZZER
    117 int main(int argc, char **argv) {
    118     if (argc != 2) { fprintf(stderr, "Usage: %s <input_file>\n", argv[0]); return 1; }
    119     FILE *f = fopen(argv[1], "rb");
    120     if (!f) { perror("fopen"); return 1; }
    121     fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f);
    122     uint8_t *buf = malloc(sz);
    123     fread(buf, 1, sz, f); fclose(f);
    124     fuzz_target(buf, sz);
    125     free(buf);
    126     return 0;
    127 }
    128 #endif
    129 #endif