summaryrefslogtreecommitdiff
path: root/common/src/nxd_tcp_client_socket_connect.c
AgeCommit message (Collapse)Author
2026-08-13Removed bias from the randomised TCP initial sequence number (#408)tinic
* tcp: remove bias from the randomised initial sequence number _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. * Masked the initial sequence number after the addition, not only before The first line of each pair carries an explicit & 0xFFFFFFFF, which documents the intent to keep the sequence number inside 32 bits. While the second line was |=, that mask could not be violated. Now that it is +=, staying inside 32 bits depends on ULONG being exactly 32 bits so the wrap does the masking implicitly. That holds on every port today, ThreadX pinning ULONG to 32 bits everywhere, but it is an implicit dependency where the line above states an explicit one, and the mask on the first line would otherwise read as protecting something it no longer protects. Mask the result as well, at both sites. On a 32-bit ULONG this is a no-op the compiler discards; on a hypothetical port with a wider ULONG it keeps the invariant the first line already claims. No behaviour change. Verified that the library builds and that three regression tests which read nx_tcp_socket_tx_sequence still pass: netx_tcp_4_duplicate_ack_test, netx_tcp_new_reno_algorithm_test2 and netx_tcp_overlapping_packet_test_2. A two-million-draw model of the three lines with a 32-bit ULONG gives a mean of 2146460131 against the uniform 2147483648, so the distribution the PR set out to fix is unaffected. --------- Signed-off-by: Tinic Uro <[email protected]> Co-authored-by: Frédéric Desbiens <[email protected]> Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-03-07Updated copyright headers and version strings. Removed version historyFrédéric Desbiens
2025-03-18Updated version numbers to 6.4.3Frédéric Desbiens
2024-01-29Update copyright.Bo Chen (from Dev Box)
2022-10-26Release 6.2.0Tiejun Zhou
2021-07-28Release 6.1.8v6.1.8_relYuxin Zhou
2020-10-096.1 ReleaseScott Larson
2020-05-11Initial commitPProvost