nstun/tcp: validate guest ACK numbers against the send window#280
Open
g0w6y wants to merge 1 commit into
Open
Conversation
The guest is untrusted and fully controls the acknowledgement number of
every TCP segment it emits. tcp_process_data() accepted any forward ACK
(acked_bytes > 0) without checking that it stayed within the send window,
so the guest could acknowledge data that was never sent.
Each such ACK advances tx_acked_offset by up to 2^31-1. When tx_buffer is
empty the erase step below is a no-op, so tx_acked_offset accumulates and
can be driven past 2^32 with a few pure ACK segments. push_to_guest() then
computes:
int32_t available = tx_buffer.size() - tx_acked_offset;
const uint8_t* data = tx_buffer.data() + tx_acked_offset + in_flight;
available is an int32_t truncation of a size_t subtraction, so an oversized
tx_acked_offset wraps it back positive and defeats the in_flight >= available
guard. data then points at least 2 GB past the heap buffer and
tcp_send_packet() reads up to NSTUN_MTU bytes from it.
The nstun network loop runs in the nsjail parent (supervisor) process, and
SIGSEGV is not handled, so the out of bounds read lets an untrusted jailed
process crash its own supervisor. The minimum out of bounds distance is 2 GB,
so under ASLR the access reliably faults rather than returning data.
Add the RFC 793 receive check (SEG.ACK <= SND.NXT): reject ACKs whose
sequence is beyond seq_to_guest. This keeps tx_acked_offset within
tx_buffer.size(), which the framing in push_to_guest() relies on.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
nstuntreats packets coming from the jailed process as untrusted and validatesthem at the IP and L4 layers. The TCP acknowledgement path is missing one of
those checks:
tcp_process_data()accepts any forward ACK without verifying itstays within the send window, which lets the guest acknowledge data that was
never sent and drive an out of bounds heap read in
push_to_guest().Root cause
In
nstun/tcp.cc, the ACK is processed as:ackis fully controlled by the guest. The code checks that the ACK movesforward (
acked_bytes > 0) but never that it stays at or below what wasactually sent (
SEG.ACK <= SND.NXTin RFC 793 terms). Each accepted ACKadvances
tx_acked_offsetby up to2^31 - 1. Whiletx_bufferis empty theerase step is a no op, so a few pure ACK segments push
tx_acked_offsetpast2^32.The forwarding path then trusts the invariant
tx_acked_offset <= tx_buffer.size():availableis anint32_ttruncation of asize_tsubtraction, so anoversized
tx_acked_offsetwraps it back to a positive value and thein_flight >= availableguard passes.datais then formed from the full64 bit
tx_acked_offsetand points at least 2 GB past the buffer, andtcp_send_packet()reads up toNSTUN_MTUbytes from it.Impact
The nstun network loop runs in the nsjail parent (supervisor) process
(
initParent->nstun_init_parent), and SIGSEGV is not handled. A jailedprocess can therefore crash its own supervisor by opening a TCP flow and
sending a few crafted ACK segments. The minimum out of bounds distance is 2 GB,
so under ASLR the access reliably faults, which makes this a denial of service
of the supervisor rather than a stable disclosure primitive. The only write
that uses the offset (the
eraseabove) is clamped, so there is no out ofbounds write.
Fix
Add the missing RFC 793 receive check so an ACK beyond
seq_to_guestisrejected. This keeps
tx_acked_offsetwithintx_buffer.size(), which theframing math in
push_to_guest()depends on. The change is confined to thesingle ACK acceptance condition; duplicate ACK handling and the FIN paths are
unaffected, and the same
tcp_process_data()function serves both IPv4 andIPv6, so both are covered.
Testing
The accounting and the pointer arithmetic were reproduced in isolation using
the exact variable types from the code. Before the change, three pure ACK
segments drive
tx_acked_offsetto0x100000000; a subsequent 512 byte hostwrite makes
availablewrap to a positive512, the guard passes, and theread pointer lands
0xFFFFFE00bytes past a 512 byte buffer. After the changethe out of window ACKs are rejected,
tx_acked_offsetstays0, and the readpointer remains inside the buffer. A search over the reachable offsets confirms
the minimum out of bounds distance is
0x80000001bytes.