TCP-MD5 signature option writes past 40-byte opt[] stack buffer with no bounds check
Summary
When kernel built TCP_SIGNATURE option and socket has TF_SIGNATURE tcp_output appends 20-byte signature option into fixed u_char opt[TCP_MAXOLEN](40-byte) stack buffer at :786-798 WITHOUT checking optlen+20<=40 WITHOUT reserving signature space in preceding option writers. MSS/window-scale/SACK-permitted/timestamp writers(:719-766) and tcp_sack_fill_report(tcp_sack.c:920) can drive optlen to 24-40 before signature block. Signature overruns opt[] by 4-20 bytes corrupting adjacent stack locals optlen hdrlen ipoptlen and saved registers. KASSERT optlen<=TCP_MAXOLEN(:801) no-op production. SYN: MSS(4)+win(4)+sack_perm(4)+ts(12)=24 signature writes opt[24..43] 4-byte overrun. Established SACK+ts fills to 40 signature writes opt[40..59] 20-byte overrun. setsockopt(TCP_SIGNATURE_ENABLE) NO privilege check any uid TCPS_CLOSED. Remote: TCP-MD5 server SACK-reporting ACK overruns.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2597 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| poc.c | trigger-source | setsockopt(TCP_SIGNATURE_ENABLE) reachability probe + SYN-emit trigger (used only if TCP_SIGNATURE is compiled in) | 4.8 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o poc poc.c | 329 B | view raw |
| run.sh | run-script | ./poc 18097 | 478 B | view raw |
| build.log | build-log | PoC compile on default GENERIC (BUILD_EXIT=0) | 62 B | view raw |
| run.log | run-log | PoC run on default GENERIC -> setsockopt ENOPROTOOPT, RUN_EXIT=5 | 392 B | view raw |
| env.txt | environment | uname, cc version, TCP_SIGNATURE build state, IPsec SADB absence proof | 850 B | view raw |
| tcp_signature_unbuildable.txt | evidence | Proof that enabling options TCP_SIGNATURE fails to compile: IPSEC_DIR_OUTBOUND undeclared (tcp_output.c:1058) and struct secasvar/key_allocsa undeclared (tcp_subr.c) β the IPsec SADB subsystem was removed from DragonFly | 1.5 KB | view raw |
| fix.diff | suggested-fix | bound-check optlen+TCPOLEN_SIGNATURE+2<=TCP_MAXOLEN before appending the signature option (defense-in-depth; applies+checks cleanly but cannot be boot-validated because the surrounding #ifdef TCP_SIGNATURE code is unbuildable) | 1.4 KB | view raw |
| VERDICT.md | verdict | full narrative: real source defect in dead/unbuildable code | 6.0 KB | β raw |
DF-2597 β TCP-MD5 (TCP_SIGNATURE) signature option overruns 40-byte opt[] stack buffer
Verdict: NOT REPRODUCED β real source-level defect in DEAD, UNBUILDABLE code
The cited overflow (sys/netinet/tcp_output.c:779-800) is a genuine source
defect β the 20-byte signature option (TCPOLEN_SIGNATURE=18 + NOP/EOL=2) is
appended to the 40-byte u_char opt[TCP_MAXOLEN] stack buffer with no bound
check, so any prior options (MSS+window-scale+SACK-permitted+timestamp on a
SYN β optlen=24, or timestamp + SACK blocks in ESTABLISHED β optlen up to 40)
push the signature write 4β20 bytes past the end of opt[], corrupting the
adjacent stack frame (optlen, hdrlen, ipoptlen, saved registers).
But the code path is completely unreachable on every kernel buildable from current DragonFly master, for two compounding reasons:
(1) TCP_SIGNATURE is not in the default GENERIC kernel
options TCP_SIGNATURE is commented out in sys/conf/options:271 and is
absent from sys/config/X86_64_GENERIC. The vulnerable block is wrapped in
#ifdef TCP_SIGNATURE, and so is the only way to arm it from userspace β the
case TCP_SIGNATURE_ENABLE: handler in sys/netinet/tcp_usrreq.c:1556. On the
running audit-source kernel (6.5-DEVELOPMENT #0, INVARIANTS ON):
[*] setsockopt(TCP_SIGNATURE_ENABLE) -> -1 errno=42 (Protocol not available)
setsockopt falls through to default: error = ENOPROTOOPT, TF_SIGNATURE
can never be set, and tcp_output.c:779-800 is compiled out entirely. Runtime
unreachable from any unprivileged (or even privileged) action on the default
kernel. Verified: nm /boot/kernel/kernel | grep -ci tcpsignature = 0.
(2) TCP_SIGNATURE is UNBUILDABLE on current master β its dependency (IPsec SADB) was deleted
The TCP-MD5 feature (tcpsignature_compute in sys/netinet/tcp_subr.c:2199,
and the digest-write call site at tcp_output.c:1058) depends on the KAME/IPsec
SADB subsystem: key_allocsa(), struct secasvar, key_freesav(),
key_sa_recordxfer(), _KEYBUF()/_KEYLEN(), and IPSEC_DIR_OUTBOUND. None
of these exist anywhere in the current sys/ tree β the IPsec subsystem was
removed (no sys/netproto/ipsec/, no key.h, no ipsec.h). Attempting to
build a kernel with options TCP_SIGNATURE fails to compile:
/usr/src/sys/netinet/tcp_output.c:1058:37: error: 'IPSEC_DIR_OUTBOUND' undeclared
and even forcing past that, tcp_subr.c fails:
tcp_subr.c:2328:29: error: dereferencing pointer to incomplete type 'struct secasvar' tcp_subr.c:2236:9: warning: implicit declaration of function 'key_allocsa' tcp_subr.c:2331:2: warning: implicit declaration of function 'key_freesav'
So no kernel (default or custom) can be built from current master source with this code path live. The TCP_SIGNATURE code is orphaned dead code that references a subsystem that no longer exists in the OS.
Conclusion / classification
This is the valid hard blocker from Phase 6: the vulnerable code path is
dead/unreachable at runtime AND no harness can exercise it β here elevated to
the code cannot even be compiled into any kernel. The bug is a latent
source-level defect (a missing optlen + TCPOLEN_SIGNATURE + 2 <= TCP_MAXOLEN
bound check) that would be a real High-severity stack overflow if and only if
the IPsec SADB subsystem were present and options TCP_SIGNATURE were enabled.
On the audited kernel it has zero runtime impact.
Because there is no live memory-corruption primitive (the code never runs, never even links), there is no escalation chain to develop β Phase 6 escalation applies only to a live write primitive, which does not exist here.
Mechanism trace (source-confirmed, were the code reachable)
tcp_output()declaresu_char opt[TCP_MAXOLEN]on its stack βTCP_MAXOLEN = 60 - sizeof(tcphdr) = 40(sys/netinet/tcp.h:149,:148).- On a SYN with default sysctls (
tcp_do_rfc1323=1setsTF_REQ_SCALE|TF_REQ_TSTMPintcp_subr.c:759;tcp_do_sackdefault) the option writers attcp_output.c:719-766produce: MSS(4)+window-scale(4)+SACK-permitted(4)+timestamp(12) = optlen=24 (TCPOLEN_MAXSEG=4tcp.h:86,TCPOLEN_WINDOW=3 padded to 4,TCPOLEN_SACK_PERMITTED_ALIGNED=4tcp.h:93,TCPOLEN_TSTAMP_APPA=12tcp.h:101). - In ESTABLISHED with timestamp + 3 SACK blocks (
tcp_sack_fill_reportattcp_sack.c:920addsTCPOLEN_SACK_ALIGNED=4 + 3ΓTCPOLEN_SACK_BLOCK=8, bounded to β€40), optlen reaches 40. - The signature block at
tcp_output.c:779-800then unconditionally writes TCPOPT_SIGNATURE+TCPOLEN_SIGNATURE (2) + 16 zero bytes + NOP+EOL (2) = 20 bytes atopt+optlenwith no check, overrunningopt[]by 4 bytes (SYN) to 20 bytes (ESTABLISHED). The post-hocKASSERT(optlen <= TCP_MAXOLEN)at:801is a debug-only trip, not a guard.
Fix-validation status: not_testable
Phase 8 requires a clean before/after on a kernel that runs the vulnerable
code. Here the vulnerable code cannot be compiled into any kernel (the IPsec
dependency is gone), so there is no "before" baseline to reproduce against and no
"after" patched kernel to test. fix.diff applies cleanly (git apply --check
OK) and is a correct bound check, but it cannot be validated by building and
booting, because the surrounding #ifdef TCP_SIGNATURE code does not compile on
current master.
What fix.diff does (defense-in-depth for if IPsec/TCP-MD5 is reintroduced)
Wraps the signature-append block in a bound check:
if ((tp->t_flags & TF_SIGNATURE) && optlen + TCPOLEN_SIGNATURE + 2 <= TCP_MAXOLEN).
When there is no room, the signature is omitted (the segment fails MD5
verification on the peer β graceful degradation) rather than overrunning opt[].
Matches the spirit of the finding markdown's recommended fix; supersedes it with a
precise, line-accurate guard.
Reproduce
cd findings/poc/DF-2597 && ./build.sh && ./run.sh # Expected on the default audit-source kernel: # [*] setsockopt(TCP_SIGNATURE_ENABLE) -> -1 errno=42 (Protocol not available) # [!] RESULT: bug UNREACHABLE on this kernel (latent code defect). # RUN_EXIT=5
Fix verification
not_testablenot_testable: the vulnerable code cannot be compiled into ANY kernel on current master (IPsec SADB subsystem removed), so no unpatched baseline reproduces and no patched kernel can be compared. fix.diff applies cleanly and is a correct bound check but cannot be boot-validated.
baseline (default GENERIC #0): setsockopt(TCP_SIGNATURE_ENABLE) -> errno=42 ENOPROTOOPT, nm|grep -ci tcpsignature=0, RUN_EXIT=5. attempted TCP_SIGNATURE kernel build: error 'IPSEC_DIR_OUTBOUND undeclared' at tcp_output.c:1058. => no before/after possible.
Confirmed kernel references
Detail
Exploit chain
none β no live memory-corruption primitive exists. The vulnerable code never compiles, never links, never runs on any kernel buildable from current DragonFly master.
Evidence (decisive lines)
[+] listener on 127.0.0.1:18097. setsockopt(TCP_SIGNATURE_ENABLE) -> -1 errno=42 (Protocol not available). TCP_SIGNATURE NOT compiled into this kernel. RUN_EXIT=5. TCP_SIGNATURE build attempt on master: error 'IPSEC_DIR_OUTBOUND undeclared' at tcp_output.c:1058; struct secasvar incomplete type. IPsec SADB subsystem present in sys/: ABSENT.
PoC changes
Wrote poc.c from scratch (PoC dir empty): setsockopt(TCP_SIGNATURE_ENABLE) reachability probe; authored fix.diff bound-checking optlen+TCPOLEN_SIGNATURE+2<=TCP_MAXOLEN. git apply --check passes.
Verified recommended fix
In sys/netinet/tcp_output.c wrap the signature-append block (line 779) in 'if ((tp->t_flags & TF_SIGNATURE) && optlen + TCPOLEN_SIGNATURE + 2 <= TCP_MAXOLEN)' so the option can never overrun the 40-byte opt[] stack buffer. Defense-in-depth for currently-dead code; full git-apply-able diff in findings/poc/DF-2597/fix.diff.
Verdict
NOT REPRODUCED β real source-level defect in DEAD, UNBUILDABLE code. The cited overflow is genuine: tcp_output.c:786-800 appends the 20-byte TCP-MD5 signature option into the 40-byte u_char opt[TCP_MAXOLEN] stack buffer with NO bound check, so on a SYN with default options optlen=24 -> 4-byte overrun; in ESTABLISHED with timestamp+3 SACK blocks optlen=40 -> 20-byte overrun. BUT the entire block is #ifdef TCP_SIGNATURE, and TCP_SIGNATURE is NOT in the default GENERIC kernel and is UNBUILDABLE because its IPsec SADB dependency was removed entirely (no sys/netproto/ipsec/). setsockopt(TCP_SIGNATURE_ENABLE) returns ENOPROTOOPT (errno 42) at runtime. Classification: latent source defect behind a dead/unreachable, unbuildable code path.
No comments yet.