defrag: regression test for first-fragment RB tree search#3098
Conversation
Adds bug-8232-defrag-first-frag-out-of-order under tests/defrag/. The pcap carries an IPv4 TCP packet with a 40 byte payload, fragmented into eight 8 byte fragments. The fragments are written in reverse order on the wire so the offset-0 fragment lands while the RB tree is already populated, exercising the neighbour search at src/defrag.c that was seeded with frag_offset - 1 before the fix in OISF/suricata PR #15403. The reassembled output is identical with or without the fix (no offset ever lives at frag_offset - 1 since fragment offsets are 8-byte aligned), so this test does not fail on the pre-fix code. It is a regression check that future changes to the defrag neighbour search preserve reassembly correctness for this ordering pattern. Bug: #8232.
|
@ssam18 why does it pass as-is? |
|
@victorjulien @inashivb thanks for the review. To Victor's "why does it pass as-is?": with the original code the uint16_t key wraps to 65535 when frag_offset is 0. No real fragment offset can be that high (IPv4 13-bit field shifted by 3 caps at 65528, IPv6 follows the same alignment), so RB_NFIND returns NULL either way and the existing RB_MIN fallback walks the tree forward. The reassembled output is the same, so the test as originally written only exercised the affected path without distinguishing the two states. To inashivb's suggestion to add I pushed an update to OISF/suricata#15403 that takes the same idea but restructures the block so the assertion stays valid on the fixed code: if (!RB_EMPTY(&tracker->fragment_tree)) {
if (frag_offset == 0) {
prev = RB_MIN(IP_FRAGMENTS, &tracker->fragment_tree);
next = IP_FRAGMENTS_RB_NEXT(prev);
} else {
DEBUG_VALIDATE_BUG_ON(frag_offset == 0);
Frag key = { .offset = frag_offset };
next = RB_NFIND(IP_FRAGMENTS, &tracker->fragment_tree, &key);
...
}
}The first-fragment branch uses the same RB_MIN initialisation that the no-strict-greater fallback uses, so behaviour is identical to the previous code on every input. The BUG_ON in the else branch is a real invariant now (the else is only reached for non-zero frag_offset). Verified locally with On the fixed code under the same debug-validation build the test passes. All 35 defrag unit tests still pass on the fixed code under |
Companion test for OISF/suricata#15403 (Redmine #8232).
What it tests
Adds
tests/defrag/bug-8232-defrag-first-frag-out-of-order/.The pcap, generated by
frag-ip-tcp-payload.pywith scapy, carries an IPv4 TCP packet with a 40 byte payload, fragmented into eight 8 byte fragments. The fragments are written in reverse order on the wire so the offset-0 fragment lands while the RB tree is already populated. That exercises the neighbour search atsrc/defrag.c:657whose key was changed fromfrag_offset - 1tofrag_offsetin the companion PR.The rule alerts on the reassembled TCP payload via a content match. The test expects exactly one alert.
Honest caveat
The reassembled output is identical with or without the suricata-side fix. Fragment offsets are always 8-byte aligned (
IPV4_GET_RAW_FRAGOFFSET << 3for IPv4, the fragment header offset field for IPv6), so no node ever lives atfrag_offset - 1, andRB_NFINDreturns the same neighbour for keysfrag_offsetandfrag_offset - 1on every realistic input. The fix only removes auint16_tunderflow onfrag_offset == 0, which used to wrap the key to 65535 and reach the same end state via an unintended fallback.So this test does not fail on the pre-fix code. It is regression coverage of the affected code path so future changes to the defrag neighbour search keep reassembly correct in this ordering pattern, not a fail-on-old/pass-on-new demonstrator. Verified locally that the test passes both with and without the
defrag.cchange.Verification
Ran locally against a Suricata built from the companion PR branch and against the pre-fix code: