tcp: remove bias from the randomised initial sequence number - #408
Open
tinic wants to merge 1 commit into
Open
Conversation
_nxd_tcp_client_socket_connect() and _nx_tcp_server_socket_accept() compose a
fresh ISN from two random draws with a bitwise OR:
socket_ptr -> nx_tcp_socket_tx_sequence = (((ULONG)NX_RAND()) << NX_SHIFT_BY_16) & 0xFFFFFFFF;
socket_ptr -> nx_tcp_socket_tx_sequence |= (ULONG)NX_RAND();
The two draws overlap in bits 16-30, and an OR of two random bits is 1 with
probability 3/4. rand() returns 0..RAND_MAX, so with the usual RAND_MAX of
0x7FFFFFFF the ISN has 17 bits at P(1) = 1/2 and 15 bits at P(1) = 3/4. That
is 29.2 bits of Shannon entropy and 17 + 15*log2(4/3) = 23.2 bits of
min-entropy: the likeliest ISN comes up 2^(32-23.2) = 438 times more often
than under a uniform distribution, and an attacker who enumerates the dense
upper values first faces 2^23 rather than 2^32.
This is a property of the composition, not of the generator. A port that
points NX_RAND at a CSPRNG loses the same nine bits, because the loss is
entirely in the overlap between the two draws.
Adding instead of ORing removes it, and matches the else branch immediately
below -- the path every reused socket takes, which already adds. With a
31-bit draw the sum is exactly uniform over the whole 32-bit space:
(a << 16) is uniform over the 65536 multiples of 65536, and adding an
independent uniform value over [0, 2^31) leaves every 32-bit residue equally
likely. Measured over 2e7 samples of a 31-bit uniform source:
Shannon min-entropy likeliest ISN vs uniform
with | 29.168 23.220 440x
with + 32.000 31.991 1x
The consequence is blind in-window injection: an off-path attacker needs a
sequence number inside the receive window to land a spoofed RST or segment,
and nine bits of bias is nine bits fewer guesses. RFC 6528 asks that an ISN
not be predictable. This does not implement 6528's four-tuple hash; it only
removes a bias in a value the code already meant to be random.
Found while auditing SYN traces from an m68k AmigaOS port: bits 16-30 were
set in 35 of 45 SYNs (0.78, predicted 0.75) before the change and 69 of 135
(0.51, predicted 0.50) after.
Signed-off-by: Tinic Uro <tinicuro@gmail.com>
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.
_nxd_tcp_client_socket_connect() and _nx_tcp_server_socket_accept() compose a fresh ISN from two random draws with a bitwise OR:
The two draws overlap in bits 16-30, and an OR of two random bits is 1 with probability 3/4. rand() returns 0..RAND_MAX, so with the usual RAND_MAX of 0x7FFFFFFF the ISN has 17 bits at P(1) = 1/2 and 15 bits at P(1) = 3/4. That is 29.2 bits of Shannon entropy and 17 + 15*log2(4/3) = 23.2 bits of min-entropy: the likeliest ISN comes up 2^(32-23.2) = 438 times more often than under a uniform distribution, and an attacker who enumerates the dense upper values first faces 2^23 rather than 2^32.
This is a property of the composition, not of the generator. A port that points NX_RAND at a CSPRNG loses the same nine bits, because the loss is entirely in the overlap between the two draws.
Adding instead of ORing removes it, and matches the else branch immediately below -- the path every reused socket takes, which already adds. With a 31-bit draw the sum is exactly uniform over the whole 32-bit space: (a << 16) is uniform over the 65536 multiples of 65536, and adding an independent uniform value over [0, 2^31) leaves every 32-bit residue equally likely. Measured over 2e7 samples of a 31-bit uniform source:
The consequence is blind in-window injection: an off-path attacker needs a sequence number inside the receive window to land a spoofed RST or segment, and nine bits of bias is nine bits fewer guesses. RFC 6528 asks that an ISN not be predictable. This does not implement 6528's four-tuple hash; it only removes a bias in a value the code already meant to be random.
Found while auditing SYN traces from an m68k AmigaOS port: bits 16-30 were set in 35 of 45 SYNs (0.78, predicted 0.75) before the change and 69 of 135 (0.51, predicted 0.50) after.