etc.git


etc

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

commit c456ff4ae8ae901aca206ed80bd9a0df002631b7
parent 7a8ec4aa46de96798dc7556dbe9e8836b0de4f99
Author: djnn <[email protected]>
Date: Thu, 19 Feb 2026 11:59:25 +0900

fix(ttyd): derefences in harness x.X

Diffstat:
Mfuzzing/ttyd/build_fuzzers.sh | 8++++----
Mfuzzing/ttyd/fuzz_http_parsing.c | 314++++++++++++++++++++++++++++---------------------------------------------------

2 files changed, 113 insertions(+), 209 deletions(-)

diff --git a/fuzzing/ttyd/build_fuzzers.sh b/fuzzing/ttyd/build_fuzzers.sh
@@ -582,9 +582,9 @@ EOF
 pty_buf_t *pty_buf_init(char *base, size_t len)         { return NULL; }
 void       pty_buf_free(pty_buf_t *buf)                  {}
 pty_process *process_init(void *ctx, uv_loop_t *loop,
-                           char *argv[], char *envp[])   { return NULL; }
+                           char *argv[], char *envp[])   { return calloc(1, sizeof(pty_process)); }
 bool       process_running(pty_process *process)         { return false; }
-void       process_free(pty_process *process)            {}
+void       process_free(pty_process *process)            { free(process); }
 int        pty_spawn(pty_process *process,
                      pty_read_cb read_cb,
                      pty_exit_cb exit_cb)                { return -1; }
@@ -976,7 +976,7 @@ $CC $COMMON_CFLAGS fuzz_websocket_auth.c $MOCK_OBJS $JSON_C_LIBS $EXTRA_LIBS -o 
 echo "[+] Built: fuzz_websocket_auth$SUFFIX"
 
 echo "[*] Building fuzz_http_parsing..."
-$CC $COMMON_CFLAGS fuzz_http_parsing.c $MOCK_OBJS $JSON_C_LIBS -o fuzz_http_parsing$SUFFIX
+$CC $COMMON_CFLAGS fuzz_http_parsing.c $MOCK_OBJS $JSON_C_LIBS $EXTRA_LIBS -o fuzz_http_parsing$SUFFIX
 echo "[+] Built: fuzz_http_parsing$SUFFIX"
 
 # CmpLog variants (AFL++ only)
@@ -988,7 +988,7 @@ if [ "$MODE" = "afl" ]; then
             -o ${target}_cmplog
         echo "[+] Built: ${target}_cmplog"
     done
-    AFL_LLVM_CMPLOG=1 $CC $COMMON_CFLAGS fuzz_http_parsing.c $MOCK_OBJS $JSON_C_LIBS \
+    AFL_LLVM_CMPLOG=1 $CC $COMMON_CFLAGS fuzz_http_parsing.c $MOCK_OBJS $JSON_C_LIBS $EXTRA_LIBS \
         -o fuzz_http_parsing_cmplog
     echo "[+] Built: fuzz_http_parsing_cmplog"
 fi
diff --git a/fuzzing/ttyd/fuzz_http_parsing.c b/fuzzing/ttyd/fuzz_http_parsing.c
@@ -1,18 +1,27 @@
 /*
- * AFL++ / LibFuzzer Harness for TTYD HTTP Request Parsing
+ * AFL++ / LibFuzzer harness — TTYD HTTP callback (full request handling)
  *
- * Target: HTTP header parsing, path validation, credential extraction
- * Goal: Find parsing bugs, buffer overflows, injection vulnerabilities
+ * Target: the real callback_http() from ttyd's http.c
+ * Technique: #include the source file directly so static functions are
+ *            accessible (standard OSS-Fuzz approach).
  *
- * Compile with AFL++:
- *   afl-clang-fast -fsanitize=address -g -O1 fuzz_http_parsing.c -o fuzz_http_parsing
+ * Exercises: check_auth(), access_log(), path routing (/token, /index,
+ *            parent redirect, 404), accept_gzip(), uncompress_html(),
+ *            LWS_CALLBACK_HTTP_WRITEABLE response path.
  *
- * Compile with libFuzzer:
- *   clang -fsanitize=fuzzer,address -g -O1 fuzz_http_parsing.c -o fuzz_http_parsing
+ * Input format (structured binary — pairs with http_mutator.so):
+ *   [auth_mode:1][cred_len:2 BE][credential][path_len:2 BE][path][auth_header_value]
  *
- * Run:
- *   afl-fuzz -i input/ -o output/ ./fuzz_http_parsing
- *   ./fuzz_http_parsing  # libFuzzer
+ *   auth_mode 0 — no auth required
+ *   auth_mode 1 — Basic auth  (credential = expected base64 value)
+ *   auth_mode 2 — custom header auth (X-Auth-User)
+ *
+ * Build (handled by build_fuzzers.sh):
+ *   afl-clang-fast -fsanitize=address,undefined -g -O1
+ *     -I fuzz-include -I ttyd-src/src
+ *     fuzz_http_parsing.c
+ *     ttyd-obj/{utils,fuzz_lws_mock,mock_server_globals,mock_pty_stubs}.o
+ *     -ljson-c -lz -o fuzz_http_parsing
  */
 
 #include <stdio.h>
@@ -20,230 +29,125 @@
 #include <string.h>
 #include <stdint.h>
 #include <stdbool.h>
-#include <ctype.h>
-#include <unistd.h>
-
-#define MAX_PATH_LEN 128
-#define MAX_HEADER_LEN 256
-
-// Simulated HTTP parsing
-typedef struct {
-    char path[MAX_PATH_LEN];
-    char auth_header[MAX_HEADER_LEN];
-    char origin_header[MAX_HEADER_LEN];
-    char host_header[MAX_HEADER_LEN];
-} http_request_t;
-
-// Parse HTTP request from fuzzer input
-bool parse_http_request(const uint8_t *data, size_t size, http_request_t *req) {
-    if (size == 0 || data == NULL || req == NULL) return false;
-
-    memset(req, 0, sizeof(http_request_t));
-
-    // Simple HTTP request parsing
-    // Format: METHOD PATH\r\nHeader: Value\r\n\r\n
-
-    const char *ptr = (const char *)data;
-    const char *end = ptr + size;
-
-    // Parse request line
-    const char *path_start = strchr(ptr, ' ');
-    if (!path_start || path_start >= end) return false;
-    path_start++;
-
-    const char *path_end = strchr(path_start, ' ');
-    if (!path_end || path_end >= end) path_end = strchr(path_start, '\r');
-    if (!path_end || path_end >= end) path_end = end;
-
-    size_t path_len = path_end - path_start;
-    if (path_len >= MAX_PATH_LEN) path_len = MAX_PATH_LEN - 1;
-    memcpy(req->path, path_start, path_len);
-    req->path[path_len] = '\0';
-
-    // Parse headers
-    ptr = path_end;
-    while (ptr < end) {
-        // Find header line
-        const char *line_start = strchr(ptr, '\n');
-        if (!line_start || line_start >= end) break;
-        line_start++;
-
-        if (line_start[0] == '\r' || line_start[0] == '\n') break;  // End of headers
-
-        const char *colon = strchr(line_start, ':');
-        if (!colon || colon >= end) {
-            ptr = line_start;
-            continue;
-        }
-
-        // Extract header name and value
-        size_t name_len = colon - line_start;
-        const char *value_start = colon + 1;
-        while (value_start < end && isspace(*value_start)) value_start++;
-
-        const char *value_end = strchr(value_start, '\r');
-        if (!value_end || value_end >= end) value_end = end;
-
-        size_t value_len = value_end - value_start;
-
-        // Check for specific headers
-        if (name_len == 13 && strncasecmp(line_start, "Authorization", 13) == 0) {
-            if (value_len >= MAX_HEADER_LEN) value_len = MAX_HEADER_LEN - 1;
-            memcpy(req->auth_header, value_start, value_len);
-            req->auth_header[value_len] = '\0';
-        } else if (name_len == 6 && strncasecmp(line_start, "Origin", 6) == 0) {
-            if (value_len >= MAX_HEADER_LEN) value_len = MAX_HEADER_LEN - 1;
-            memcpy(req->origin_header, value_start, value_len);
-            req->origin_header[value_len] = '\0';
-        } else if (name_len == 4 && strncasecmp(line_start, "Host", 4) == 0) {
-            if (value_len >= MAX_HEADER_LEN) value_len = MAX_HEADER_LEN - 1;
-            memcpy(req->host_header, value_start, value_len);
-            req->host_header[value_len] = '\0';
-        }
-
-        ptr = value_end;
-    }
-
-    return true;
-}
-
-// Validate path (check for path traversal, etc.)
-bool validate_path(const char *path) {
-    if (path == NULL || path[0] == '\0') return false;
-
-    // Check for path traversal sequences
-    if (strstr(path, "..") != NULL) return false;
-    if (strstr(path, "//") != NULL) return false;
-
-    // Check for null bytes
-    size_t len = strlen(path);
-    for (size_t i = 0; i < len; i++) {
-        if (path[i] == '\0') return false;
-    }
-
-    return true;
-}
-
-// Check origin vs host (CSRF protection)
-bool check_origin(const char *origin, const char *host) {
-    if (origin == NULL || host == NULL) return false;
-    if (origin[0] == '\0' || host[0] == '\0') return false;
-
-    // Extract hostname from origin (http://host:port)
-    const char *host_start = strstr(origin, "://");
-    if (host_start) host_start += 3;
-    else host_start = origin;
-
-    // Compare with Host header
-    return strncasecmp(host_start, host, strlen(host)) == 0;
-}
 
-// Extract and validate Basic auth
-bool extract_basic_auth(const char *auth_header, char *username, char *password, size_t max_len) {
-    if (auth_header == NULL || username == NULL || password == NULL) return false;
+/* Mock lws control state — must come before including the real ttyd source */
+#include "fuzz_lws_mock.h"
 
-    // Check for "Basic " prefix
-    if (strncmp(auth_header, "Basic ", 6) != 0) return false;
+/* Pull in the real ttyd HTTP handler (callback_http, check_auth, etc.) */
+#include "ttyd-src/src/http.c"
 
-    const char *encoded = auth_header + 6;
-
-    // Simple base64 decode (not production quality)
-    // For fuzzing purposes, just extract the string
-    size_t len = strlen(encoded);
-    if (len >= max_len) len = max_len - 1;
-
-    // Look for colon separator in decoded value (simplified)
-    const char *colon = strchr(encoded, ':');
-    if (colon) {
-        size_t user_len = colon - encoded;
-        if (user_len >= max_len) user_len = max_len - 1;
-        memcpy(username, encoded, user_len);
-        username[user_len] = '\0';
-
-        size_t pass_len = len - user_len - 1;
-        if (pass_len >= max_len) pass_len = max_len - 1;
-        memcpy(password, colon + 1, pass_len);
-        password[pass_len] = '\0';
-    }
+/* Provided by mock_server_globals.c */
+extern struct server  _fuzz_server;
+extern void           fuzz_server_reset(void);
 
-    return true;
-}
+/* ── fuzz target ─────────────────────────────────────────────────────────── */
 
-// Fuzzing target
 int fuzz_target(const uint8_t *data, size_t size) {
-    if (size < 10) return 0;
-
-    http_request_t req;
-    if (!parse_http_request(data, size, &req)) {
-        return 0;
+    /* Need at least: auth_mode(1) + cred_len(2) + path_len(2) = 5 bytes */
+    if (size < 5) return 0;
+
+    /* Parse structured input */
+    uint8_t  auth_mode = data[0] % 3;
+    uint16_t cred_len  = (uint16_t)((data[1] << 8) | data[2]);
+    if ((size_t)cred_len > size - 5) cred_len = (uint16_t)(size - 5);
+
+    size_t off = 3 + cred_len;
+    if (off + 2 > size) return 0;
+
+    uint16_t path_len = (uint16_t)((data[off] << 8) | data[off + 1]);
+    off += 2;
+    if ((size_t)path_len > size - off) path_len = (uint16_t)(size - off);
+
+    /* Reset all fuzzer-controlled state */
+    fuzz_lws_reset();
+    fuzz_server_reset();
+
+    /* Set up the credential the server expects */
+    static char cred_buf[65536];
+    if (auth_mode == 1 && cred_len > 0) {
+        memcpy(cred_buf, data + 3, cred_len);
+        cred_buf[cred_len] = '\0';
+        _fuzz_server.credential  = cred_buf;
+        _fuzz_server.auth_header = NULL;
+    } else if (auth_mode == 2) {
+        _fuzz_server.credential  = NULL;
+        _fuzz_server.auth_header = (char *)"X-Auth-User";
+    }
+    /* auth_mode 0: no auth, both remain NULL */
+
+    /* Extract the URL path (null-terminated for callback_http) */
+    static char path_buf[4096];
+    int n = path_len < (int)sizeof(path_buf) - 1 ? path_len : (int)sizeof(path_buf) - 1;
+    memcpy(path_buf, data + off, n);
+    path_buf[n] = '\0';
+    off += path_len;
+
+    /* Remaining bytes are the Authorization header value */
+    size_t auth_len = (off < size) ? size - off : 0;
+    fuzz_lws_set_auth((const char *)(data + off), (int)auth_len);
+
+    /* For custom-header mode, same bytes go to the custom header mock */
+    if (auth_mode == 2)
+        fuzz_lws_set_custom((const char *)(data + off), (int)auth_len);
+
+    /* ── Exercise the REAL callback_http() ────────────────────────────── */
+    struct lws      mock_wsi = {0};
+    struct pss_http pss      = {0};
+
+    /* LWS_CALLBACK_HTTP: main request handling (auth, path routing, etc.) */
+    int ret = callback_http(&mock_wsi, LWS_CALLBACK_HTTP,
+                            &pss, path_buf, (size_t)n);
+
+    /* LWS_CALLBACK_HTTP_WRITEABLE: exercise response body transmission
+     * (only meaningful if the HTTP handler set up a buffer) */
+    if (ret == 0 && pss.buffer != NULL && pss.len > 0) {
+        (void)callback_http(&mock_wsi, LWS_CALLBACK_HTTP_WRITEABLE,
+                            &pss, NULL, 0);
+        /* pss_buffer_free() inside WRITEABLE already freed the buffer —
+         * null it so the cleanup below doesn't double-free. */
+        pss.buffer = NULL;
     }
 
-    // Validate parsed data
-    bool path_valid = validate_path(req.path);
-    bool origin_valid = check_origin(req.origin_header, req.host_header);
-
-    char username[128] = {0};
-    char password[128] = {0};
-    bool auth_valid = extract_basic_auth(req.auth_header, username, password, sizeof(username));
-
-    // Exercise various code paths
-    if (path_valid && origin_valid && auth_valid) {
-        // All validations passed
+    /* Clean up any strdup'd buffer from /token path if WRITEABLE was skipped */
+    if (pss.buffer != NULL && pss.buffer != (char *)index_html &&
+        pss.buffer != html_cache) {
+        free(pss.buffer);
     }
 
     return 0;
 }
 
+/* ── AFL++ persistent mode ───────────────────────────────────────────────── */
 #ifdef __AFL_FUZZ_TESTCASE_LEN
-// AFL++ persistent mode
 __AFL_FUZZ_INIT();
-
 int main(void) {
-    #ifdef __AFL_HAVE_MANUAL_CONTROL
-        __AFL_INIT();
-    #endif
-
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+    __AFL_INIT();
+#endif
     unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
-
     while (__AFL_LOOP(10000)) {
-        int len = __AFL_FUZZ_TESTCASE_LEN;
-        fuzz_target(buf, len);
+        fuzz_target(buf, __AFL_FUZZ_TESTCASE_LEN);
     }
-
     return 0;
 }
+
 #else
-// LibFuzzer mode
+/* LibFuzzer */
 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
     return fuzz_target(data, size);
 }
 
-// Standalone mode for testing
 #ifndef LIBFUZZER
+/* Standalone file-based testing */
 int main(int argc, char **argv) {
-    if (argc != 2) {
-        fprintf(stderr, "Usage: %s <input_file>\n", argv[0]);
-        return 1;
-    }
-
+    if (argc != 2) { fprintf(stderr, "Usage: %s <input_file>\n", argv[0]); return 1; }
     FILE *f = fopen(argv[1], "rb");
-    if (!f) {
-        perror("fopen");
-        return 1;
-    }
-
-    fseek(f, 0, SEEK_END);
-    long size = ftell(f);
-    fseek(f, 0, SEEK_SET);
-
-    uint8_t *data = malloc(size);
-    fread(data, 1, size, f);
-    fclose(f);
-
-    fuzz_target(data, size);
-    free(data);
-
-    printf("Test completed successfully\n");
+    if (!f) { perror("fopen"); return 1; }
+    fseek(f, 0, SEEK_END); long sz = ftell(f); rewind(f);
+    uint8_t *buf = malloc(sz);
+    fread(buf, 1, sz, f); fclose(f);
+    fuzz_target(buf, sz);
+    free(buf);
     return 0;
 }
 #endif