commit 9e5f87fc60536d280097a6da9171afc181146175
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2026-06-10T16:19:17Z |
| subject | Added fuzzer for stream_sniff.c |
commit 9e5f87fc60536d280097a6da9171afc181146175
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date: 2026-06-10T16:19:17Z
Added fuzzer for stream_sniff.c
---
fuzz/Makefile | 20 +--
fuzz/README.md | 6 +-
fuzz/devtools/gen_seeds_stream_sniff.py | 140 +++++++++++++++++++++
fuzz/fuzz_stream_sniff.c | 71 +++++++++++
fuzz/seeds/stream_sniff/junk-empty | 0
fuzz/seeds/stream_sniff/junk-http-get | 3 +
fuzz/seeds/stream_sniff/junk-random-small | Bin 0 -> 5 bytes
fuzz/seeds/stream_sniff/junk-ssh-banner | 1 +
fuzz/seeds/stream_sniff/partial-1 | 1 +
fuzz/seeds/stream_sniff/partial-16 | Bin 0 -> 16 bytes
fuzz/seeds/stream_sniff/partial-2 | 1 +
fuzz/seeds/stream_sniff/partial-3 | 1 +
fuzz/seeds/stream_sniff/partial-32 | Bin 0 -> 32 bytes
fuzz/seeds/stream_sniff/partial-4 | Bin 0 -> 4 bytes
fuzz/seeds/stream_sniff/partial-5 | Bin 0 -> 5 bytes
fuzz/seeds/stream_sniff/partial-6 | Bin 0 -> 6 bytes
fuzz/seeds/stream_sniff/partial-64 | Bin 0 -> 64 bytes
fuzz/seeds/stream_sniff/partial-9 | Bin 0 -> 9 bytes
fuzz/seeds/stream_sniff/tls-alert-not-handshake | Bin 0 -> 7 bytes
fuzz/seeds/stream_sniff/tls-appdata-not-handshake | Bin 0 -> 10 bytes
fuzz/seeds/stream_sniff/tls-bad-handshake-len-huge | Bin 0 -> 100 bytes
.../seeds/stream_sniff/tls-bad-handshake-len-small | Bin 0 -> 100 bytes
fuzz/seeds/stream_sniff/tls-no-sni | Bin 0 -> 80 bytes
fuzz/seeds/stream_sniff/tls-record-says-too-big | Bin 0 -> 9 bytes
fuzz/seeds/stream_sniff/tls-record-says-zero-len | Bin 0 -> 5 bytes
.../stream_sniff/tls-sni-declared-len-too-large | Bin 0 -> 92 bytes
fuzz/seeds/stream_sniff/tls-sni-empty | Bin 0 -> 89 bytes
fuzz/seeds/stream_sniff/tls-sni-example-com | Bin 0 -> 100 bytes
fuzz/seeds/stream_sniff/tls-sni-idn-punycode | Bin 0 -> 111 bytes
fuzz/seeds/stream_sniff/tls-sni-localhost | Bin 0 -> 98 bytes
fuzz/seeds/stream_sniff/tls-sni-long-valid-253 | Bin 0 -> 342 bytes
fuzz/seeds/stream_sniff/tls-sni-too-long-254 | Bin 0 -> 343 bytes
32 files changed, 227 insertions(+), 17 deletions(-)
diff --git a/fuzz/Makefile b/fuzz/Makefile
index e6aebe7..ae91969 100644
--- a/fuzz/Makefile
+++ b/fuzz/Makefile
@@ -12,7 +12,7 @@ OUT := $(PROJECT_ROOT)/bin/fuzz
CORPUS := $(PROJECT_ROOT)/corpus
SEEDS := seeds
-TARGETS := proxy_v2_build file_conf stream_conn
+TARGETS := proxy_v2_build file_conf stream_conn stream_sniff
BINS := $(addprefix $(OUT)/fuzz_,$(TARGETS))
PROJECT_SRCS := $(filter-out $(SRC_DIR)/tinyproxy.c,$(wildcard $(SRC_DIR)/*.c))
@@ -28,27 +28,16 @@ COMMON_FUZZ_SRC := \
all: $(BINS)
-$(OUT)/fuzz_proxy_v2_build: fuzz_proxy_v2_build.c $(PROJECT_SRCS)
- $(CC) $(CFLAGS) $(FUZZ_CFLAGS) $(CPPFLAGS) \
- -I$(SRC_DIR) \
- $^ \
- -o $@ \
- $(LDFLAGS) $(FUZZ_LDFLAGS) $(LDLIBS)
+$(OUT):
+ mkdir -p $(OUT)
-$(OUT)/fuzz_file_conf: fuzz_file_conf.c $(PROJECT_SRCS)
+$(OUT)/fuzz_%: fuzz_%.c $(PROJECT_SRCS) | $(OUT)
$(CC) $(CFLAGS) $(FUZZ_CFLAGS) $(CPPFLAGS) \
-I$(SRC_DIR) \
$^ \
-o $@ \
$(LDFLAGS) $(FUZZ_LDFLAGS) $(LDLIBS)
-$(OUT)/fuzz_stream_conn: fuzz_stream_conn.c $(PROJECT_SRCS)
- $(CC) $(CFLAGS) $(FUZZ_CFLAGS) $(CPPFLAGS) \
- -I$(SRC_DIR) \
- $^ \
- -o $@ \
- $(LDFLAGS) $(LDLIBS)
-
seed-%: FORCE
mkdir -p $(CORPUS)/$*
if [ -d "$(SEEDS)/$*" ]; then \
@@ -56,7 +45,6 @@ seed-%: FORCE
fi
run-%: $(OUT)/fuzz_% seed-%
- mkdir -p $(OUT)
$(OUT)/fuzz_$* \
$(CORPUS)/$* \
-max_total_time=$${MAX_TOTAL_TIME:-60} \
diff --git a/fuzz/README.md b/fuzz/README.md
index 8e28774..dcc742e 100755
--- a/fuzz/README.md
+++ b/fuzz/README.md
@@ -2,5 +2,9 @@ macos
Fuzz for 8 hours
```
-LIBEVENT_SRC=libevent2/ MAX_TOTAL_TIME=28800 MAX_LEN=65536 CC="$(brew --prefix llvm)/bin/clang" caffeinate make -C fuzz run-file_conf
+caffeinate make -C fuzz run-file_conf \
+ LIBEVENT_SRC=libevent2/ \
+ MAX_TOTAL_TIME=28800 \
+ MAX_LEN=65536 \
+ CC="$(brew --prefix llvm)/bin/clang"
```
diff --git a/fuzz/devtools/gen_seeds_stream_sniff.py b/fuzz/devtools/gen_seeds_stream_sniff.py
new file mode 100755
index 0000000..c3b55ad
--- /dev/null
+++ b/fuzz/devtools/gen_seeds_stream_sniff.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+
+from pathlib import Path
+import struct
+
+OUT = Path("seeds/stream_sniff")
+OUT.mkdir(parents=True, exist_ok=True)
+
+
+def write(name, data):
+ (OUT / name).write_bytes(data)
+
+
+def ext_sni(hostname: bytes) -> bytes:
+ # extension_type: server_name = 0
+ # extension_data:
+ # server_name_list length
+ # name_type = host_name
+ # host_name length
+ # host_name bytes
+ server_name = b"\x00" + struct.pack("!H", len(hostname)) + hostname
+ server_name_list = struct.pack("!H", len(server_name)) + server_name
+ return struct.pack("!HH", 0, len(server_name_list)) + server_name_list
+
+
+def ext_supported_versions() -> bytes:
+ # supported_versions extension, client side:
+ # extension_type = 43
+ # extension_data = length-prefixed list of versions
+ body = b"\x04\x03\x04\x03\x03" # TLS 1.3, TLS 1.2
+ return struct.pack("!HH", 43, len(body)) + body
+
+
+def ext_alpn(proto: bytes = b"http/1.1") -> bytes:
+ # ALPN extension:
+ # protocol_name_list length
+ # protocol_name length
+ # protocol_name
+ name = bytes([len(proto)]) + proto
+ body = struct.pack("!H", len(name)) + name
+ return struct.pack("!HH", 16, len(body)) + body
+
+
+def client_hello(hostname: bytes | None = b"example.com", extra_exts: bytes = b"") -> bytes:
+ legacy_version = b"\x03\x03" # TLS 1.2
+ random = bytes(range(32))
+ session_id = b""
+
+ cipher_suites = b"".join([
+ b"\x13\x01", # TLS_AES_128_GCM_SHA256
+ b"\x13\x02", # TLS_AES_256_GCM_SHA384
+ b"\xc0\x2f", # TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ ])
+
+ compression_methods = b"\x00"
+
+ extensions = b""
+ if hostname is not None:
+ extensions += ext_sni(hostname)
+ extensions += ext_supported_versions()
+ extensions += ext_alpn()
+ extensions += extra_exts
+
+ body = (
+ legacy_version +
+ random +
+ bytes([len(session_id)]) + session_id +
+ struct.pack("!H", len(cipher_suites)) + cipher_suites +
+ bytes([len(compression_methods)]) + compression_methods +
+ struct.pack("!H", len(extensions)) + extensions
+ )
+
+ handshake = (
+ b"\x01" + # HandshakeType client_hello
+ len(body).to_bytes(3, "big") + # handshake length
+ body
+ )
+
+ record = (
+ b"\x16" + # ContentType handshake
+ b"\x03\x01" + # TLS record legacy version
+ struct.pack("!H", len(handshake)) +
+ handshake
+ )
+
+ return record
+
+
+# 1. Non-TLS / junk
+write("junk-empty", b"")
+write("junk-http-get", b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+write("junk-ssh-banner", b"SSH-2.0-OpenSSH_9.9\r\n")
+write("junk-random-small", bytes([0x00, 0xff, 0x13, 0x37, 0x42]))
+
+# 2. Partial TLS records
+good = client_hello(b"example.com")
+for n in [1, 2, 3, 4, 5, 6, 9, 16, 32, 64]:
+ write(f"partial-{n}", good[:n])
+
+# 3. Valid-ish ClientHello with SNI
+write("tls-sni-example-com", client_hello(b"example.com"))
+write("tls-sni-localhost", client_hello(b"localhost"))
+write("tls-sni-long-valid-253", client_hello(b"a" * 253))
+write("tls-sni-idn-punycode", client_hello(b"xn--r8jz45g.xn--zckzah"))
+
+# 4. ClientHello without SNI
+write("tls-no-sni", client_hello(None))
+
+# 5. Malformed TLS-ish things
+write("tls-record-says-zero-len", b"\x16\x03\x01\x00\x00")
+write("tls-record-says-too-big", b"\x16\x03\x01\xff\xff" + b"\x01\x00\x00\x00")
+write("tls-alert-not-handshake", b"\x15\x03\x03\x00\x02\x02\x28")
+write("tls-appdata-not-handshake", b"\x17\x03\x03\x00\x05hello")
+
+# 6. Bad handshake length fields
+bad = bytearray(good)
+bad[6:9] = b"\xff\xff\xff" # handshake length too large
+write("tls-bad-handshake-len-huge", bytes(bad))
+
+bad = bytearray(good)
+bad[6:9] = b"\x00\x00\x01" # handshake length too small
+write("tls-bad-handshake-len-small", bytes(bad))
+
+# 7. SNI edge cases
+
+# Declared SNI hostname length larger than available.
+trunc_sni = bytearray(client_hello(b"abc"))
+idx = trunc_sni.find(b"\x00\x03abc")
+if idx != -1:
+ trunc_sni[idx:idx + 2] = b"\x01\x00"
+write("tls-sni-declared-len-too-large", bytes(trunc_sni))
+
+# Actual hostname is 254 bytes; should hit NAME_TOO_LONG or parse error,
+# depending on your implementation.
+write("tls-sni-too-long-254", client_hello(b"a" * 254))
+
+# Empty hostname.
+write("tls-sni-empty", client_hello(b""))
+
+print(f"wrote seeds to {OUT}")
\ No newline at end of file
diff --git a/fuzz/fuzz_stream_sniff.c b/fuzz/fuzz_stream_sniff.c
new file mode 100644
index 0000000..f17f1e8
--- /dev/null
+++ b/fuzz/fuzz_stream_sniff.c
@@ -0,0 +1,71 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <event2/buffer.h>
+
+#include "stream_sniff.h"
+
+static void check_invariants(const struct stream_sniff *s)
+{
+ if (s->sni[STREAM_SNI_MAX] != '\0') {
+ abort();
+ }
+
+ if (strlen(s->sni) > STREAM_SNI_MAX) {
+ abort();
+ }
+
+ if (s->observed > STREAM_SNIFF_MAX) {
+ abort();
+ }
+
+ if (s->done) {
+ switch (s->status) {
+ case STREAM_SNIFF_PARSED:
+ case STREAM_SNIFF_MISSING:
+ case STREAM_SNIFF_NOT_TLS:
+ case STREAM_SNIFF_TRUNCATED:
+ case STREAM_SNIFF_NAME_TOO_LONG:
+ case STREAM_SNIFF_PARSE_ERROR:
+ break;
+
+ case STREAM_SNIFF_NOT_OBSERVED:
+ case STREAM_SNIFF_NEED_MORE:
+ default:
+ abort();
+ }
+ }
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct evbuffer *input;
+ struct stream_sniff s;
+
+ memset(&s, 0, sizeof(s));
+
+ input = evbuffer_new();
+ if (input == NULL) {
+ return 0;
+ }
+
+ if (size > 0 && evbuffer_add(input, data, size) != 0) {
+ evbuffer_free(input);
+ return 0;
+ }
+
+ stream_sniff_peek_client_input(&s, input);
+
+ /*
+ * Also fuzz the log helper after parsing.
+ * This catches cases where status/sni state is inconsistent.
+ */
+ (void)stream_sniff_log_sni(&s);
+
+ check_invariants(&s);
+
+ evbuffer_free(input);
+ return 0;
+}
diff --git a/fuzz/seeds/stream_sniff/junk-empty b/fuzz/seeds/stream_sniff/junk-empty
new file mode 100644
index 0000000..e69de29
diff --git a/fuzz/seeds/stream_sniff/junk-http-get b/fuzz/seeds/stream_sniff/junk-http-get
new file mode 100644
index 0000000..7fba85d
--- /dev/null
+++ b/fuzz/seeds/stream_sniff/junk-http-get
@@ -0,0 +1,3 @@
+GET / HTTP/1.1
+Host: example.com
+
diff --git a/fuzz/seeds/stream_sniff/junk-random-small b/fuzz/seeds/stream_sniff/junk-random-small
new file mode 100644
index 0000000..1b978ae
Binary files /dev/null and b/fuzz/seeds/stream_sniff/junk-random-small differ
diff --git a/fuzz/seeds/stream_sniff/junk-ssh-banner b/fuzz/seeds/stream_sniff/junk-ssh-banner
new file mode 100644
index 0000000..3aea5ae
--- /dev/null
+++ b/fuzz/seeds/stream_sniff/junk-ssh-banner
@@ -0,0 +1 @@
+SSH-2.0-OpenSSH_9.9
diff --git a/fuzz/seeds/stream_sniff/partial-1 b/fuzz/seeds/stream_sniff/partial-1
new file mode 100644
index 0000000..127620c
--- /dev/null
+++ b/fuzz/seeds/stream_sniff/partial-1
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/fuzz/seeds/stream_sniff/partial-16 b/fuzz/seeds/stream_sniff/partial-16
new file mode 100644
index 0000000..6f03231
Binary files /dev/null and b/fuzz/seeds/stream_sniff/partial-16 differ
diff --git a/fuzz/seeds/stream_sniff/partial-2 b/fuzz/seeds/stream_sniff/partial-2
new file mode 100644
index 0000000..10cc57b
--- /dev/null
+++ b/fuzz/seeds/stream_sniff/partial-2
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/fuzz/seeds/stream_sniff/partial-3 b/fuzz/seeds/stream_sniff/partial-3
new file mode 100644
index 0000000..94db382
--- /dev/null
+++ b/fuzz/seeds/stream_sniff/partial-3
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/fuzz/seeds/stream_sniff/partial-32 b/fuzz/seeds/stream_sniff/partial-32
new file mode 100644
index 0000000..52de33e
Binary files /dev/null and b/fuzz/seeds/stream_sniff/partial-32 differ
diff --git a/fuzz/seeds/stream_sniff/partial-4 b/fuzz/seeds/stream_sniff/partial-4
new file mode 100644
index 0000000..bb7fa08
Binary files /dev/null and b/fuzz/seeds/stream_sniff/partial-4 differ
diff --git a/fuzz/seeds/stream_sniff/partial-5 b/fuzz/seeds/stream_sniff/partial-5
new file mode 100644
index 0000000..a5bf948
Binary files /dev/null and b/fuzz/seeds/stream_sniff/partial-5 differ
diff --git a/fuzz/seeds/stream_sniff/partial-6 b/fuzz/seeds/stream_sniff/partial-6
new file mode 100644
index 0000000..c3a4b3e
Binary files /dev/null and b/fuzz/seeds/stream_sniff/partial-6 differ
diff --git a/fuzz/seeds/stream_sniff/partial-64 b/fuzz/seeds/stream_sniff/partial-64
new file mode 100644
index 0000000..53795db
Binary files /dev/null and b/fuzz/seeds/stream_sniff/partial-64 differ
diff --git a/fuzz/seeds/stream_sniff/partial-9 b/fuzz/seeds/stream_sniff/partial-9
new file mode 100644
index 0000000..564828d
Binary files /dev/null and b/fuzz/seeds/stream_sniff/partial-9 differ
diff --git a/fuzz/seeds/stream_sniff/tls-alert-not-handshake b/fuzz/seeds/stream_sniff/tls-alert-not-handshake
new file mode 100644
index 0000000..f3a5df3
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-alert-not-handshake differ
diff --git a/fuzz/seeds/stream_sniff/tls-appdata-not-handshake b/fuzz/seeds/stream_sniff/tls-appdata-not-handshake
new file mode 100644
index 0000000..4b1c117
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-appdata-not-handshake differ
diff --git a/fuzz/seeds/stream_sniff/tls-bad-handshake-len-huge b/fuzz/seeds/stream_sniff/tls-bad-handshake-len-huge
new file mode 100644
index 0000000..63cfb04
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-bad-handshake-len-huge differ
diff --git a/fuzz/seeds/stream_sniff/tls-bad-handshake-len-small b/fuzz/seeds/stream_sniff/tls-bad-handshake-len-small
new file mode 100644
index 0000000..ea7513f
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-bad-handshake-len-small differ
diff --git a/fuzz/seeds/stream_sniff/tls-no-sni b/fuzz/seeds/stream_sniff/tls-no-sni
new file mode 100644
index 0000000..5d4fa02
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-no-sni differ
diff --git a/fuzz/seeds/stream_sniff/tls-record-says-too-big b/fuzz/seeds/stream_sniff/tls-record-says-too-big
new file mode 100644
index 0000000..711a7eb
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-record-says-too-big differ
diff --git a/fuzz/seeds/stream_sniff/tls-record-says-zero-len b/fuzz/seeds/stream_sniff/tls-record-says-zero-len
new file mode 100644
index 0000000..f582c8b
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-record-says-zero-len differ
diff --git a/fuzz/seeds/stream_sniff/tls-sni-declared-len-too-large b/fuzz/seeds/stream_sniff/tls-sni-declared-len-too-large
new file mode 100644
index 0000000..b43eee2
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-sni-declared-len-too-large differ
diff --git a/fuzz/seeds/stream_sniff/tls-sni-empty b/fuzz/seeds/stream_sniff/tls-sni-empty
new file mode 100644
index 0000000..e253b91
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-sni-empty differ
diff --git a/fuzz/seeds/stream_sniff/tls-sni-example-com b/fuzz/seeds/stream_sniff/tls-sni-example-com
new file mode 100644
index 0000000..5cd4956
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-sni-example-com differ
diff --git a/fuzz/seeds/stream_sniff/tls-sni-idn-punycode b/fuzz/seeds/stream_sniff/tls-sni-idn-punycode
new file mode 100644
index 0000000..fc17f92
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-sni-idn-punycode differ
diff --git a/fuzz/seeds/stream_sniff/tls-sni-localhost b/fuzz/seeds/stream_sniff/tls-sni-localhost
new file mode 100644
index 0000000..e4ed54d
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-sni-localhost differ
diff --git a/fuzz/seeds/stream_sniff/tls-sni-long-valid-253 b/fuzz/seeds/stream_sniff/tls-sni-long-valid-253
new file mode 100644
index 0000000..18f06fc
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-sni-long-valid-253 differ
diff --git a/fuzz/seeds/stream_sniff/tls-sni-too-long-254 b/fuzz/seeds/stream_sniff/tls-sni-too-long-254
new file mode 100644
index 0000000..246ecf6
Binary files /dev/null and b/fuzz/seeds/stream_sniff/tls-sni-too-long-254 differ