diff --git a/src/defrag.c b/src/defrag.c index de1d884ac7..d64a4e9cbb 100644 --- a/src/defrag.c +++ b/src/defrag.c @@ -651,7 +651,7 @@ DefragInsertFrag(ThreadVars *tv, DecodeThreadVars *dtv, DefragTracker *tracker, if (!RB_EMPTY(&tracker->fragment_tree)) { Frag key = { - .offset = frag_offset - 1, + .offset = frag_offset ? frag_offset - 1 : 0, }; next = RB_NFIND(IP_FRAGMENTS, &tracker->fragment_tree, &key); if (next == NULL) { @@ -3143,6 +3143,117 @@ static int DefragBsdMissingFragmentIpv6Test(void) PASS; } +/** + * \test Reassemble a datagram where the fragment at offset 0 comes in last, + * with the other fragments already in the tree. This is the RB_NFIND + * search key case from ticket 8232. + */ +static int DefragOutOfOrderFirstFragmentTest(void) +{ + Packet *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL; + Packet *reassembled = NULL; + int id = 8232; + + DefragInit(); + default_policy = DEFRAG_POLICY_BSD; + + /* four fragments, no overlaps, offset 0 delivered last */ + p1 = BuildIpv4TestPacket(IPPROTO_ICMP, id, 24 >> 3, 0, 'D', 8); + FAIL_IF_NULL(p1); + p2 = BuildIpv4TestPacket(IPPROTO_ICMP, id, 8 >> 3, 1, 'B', 8); + FAIL_IF_NULL(p2); + p3 = BuildIpv4TestPacket(IPPROTO_ICMP, id, 16 >> 3, 1, 'C', 8); + FAIL_IF_NULL(p3); + p4 = BuildIpv4TestPacket(IPPROTO_ICMP, id, 0, 1, 'A', 8); + FAIL_IF_NULL(p4); + + FAIL_IF(Defrag(&test_tv, &test_dtv, p1) != NULL); + FAIL_IF(Defrag(&test_tv, &test_dtv, p2) != NULL); + FAIL_IF(Defrag(&test_tv, &test_dtv, p3) != NULL); + + reassembled = Defrag(&test_tv, &test_dtv, p4); + FAIL_IF_NULL(reassembled); + + FAIL_IF(IPV4_GET_RAW_HLEN(PacketGetIPv4(reassembled)) != 20); + FAIL_IF(IPV4_GET_RAW_IPLEN(PacketGetIPv4(reassembled)) != 20 + 32); + + for (int i = 20 + 0; i < 20 + 8; i++) + FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'A'); + for (int i = 20 + 8; i < 20 + 16; i++) + FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'B'); + for (int i = 20 + 16; i < 20 + 24; i++) + FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'C'); + for (int i = 20 + 24; i < 20 + 32; i++) + FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'D'); + + PacketFree(p1); + PacketFree(p2); + PacketFree(p3); + PacketFree(p4); + PacketFree(reassembled); + + DefragDestroy(); + PASS; +} + +/** + * \test A fragment arrives at an offset that is already in the tree, with an + * earlier fragment overlapping it from the left. Keeps the frag_offset - 1 + * search key honest: with frag_offset the seek skips the overlapping + * fragment and 16..24 comes out as 'B' instead of 'D'. + */ +static int DefragSameOffsetPrecedingOverlapTest(void) +{ + Packet *packets[5] = { NULL }; + Packet *reassembled = NULL; + int id = 8232; + + DefragInit(); + default_policy = DEFRAG_POLICY_BSD; + + /* B 8..24 goes in first so it keeps offset 8. A only sets its ltrim later, + * otherwise BSD would bake the trim into the stored offset. */ + FAIL_IF_NOT(BuildIpv4TestPacketWithContent( + &packets[0], IPPROTO_ICMP, id, 8 >> 3, 1, (uint8_t *)"BBBBBBBBBBBBBBBB", 16)); + /* A 0..16, overlaps B and D from the left */ + FAIL_IF_NOT(BuildIpv4TestPacketWithContent( + &packets[1], IPPROTO_ICMP, id, 0, 1, (uint8_t *)"AAAAAAAAAAAAAAAA", 16)); + /* C 24..32, the node RB_NFIND(8) lands on */ + FAIL_IF_NOT(BuildIpv4TestPacketWithContent( + &packets[2], IPPROTO_ICMP, id, 24 >> 3, 1, (uint8_t *)"CCCCCCCC", 8)); + /* D 8..24, same offset as B, has to be reconciled against A */ + FAIL_IF_NOT(BuildIpv4TestPacketWithContent( + &packets[3], IPPROTO_ICMP, id, 8 >> 3, 1, (uint8_t *)"DDDDDDDDDDDDDDDD", 16)); + /* E 32..40, last fragment, kicks off reassembly */ + FAIL_IF_NOT(BuildIpv4TestPacketWithContent( + &packets[4], IPPROTO_ICMP, id, 32 >> 3, 0, (uint8_t *)"EEEEEEEE", 8)); + + for (int i = 0; i < 4; i++) + FAIL_IF(Defrag(&test_tv, &test_dtv, packets[i]) != NULL); + reassembled = Defrag(&test_tv, &test_dtv, packets[4]); + FAIL_IF_NULL(reassembled); + + FAIL_IF(IPV4_GET_RAW_IPLEN(PacketGetIPv4(reassembled)) != 20 + 40); + + // clang-format off + const uint8_t expected[] = { + 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', + 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', + 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', + 'C', 'C', 'C', 'C', 'C', 'C', 'C', 'C', + 'E', 'E', 'E', 'E', 'E', 'E', 'E', 'E', + }; + // clang-format on + FAIL_IF(memcmp(expected, GET_PKT_DATA(reassembled) + 20, sizeof(expected)) != 0); + + for (int i = 0; i < 5; i++) + PacketFree(packets[i]); + PacketFree(reassembled); + + DefragDestroy(); + PASS; +} + #endif /* UNITTESTS */ void DefragRegisterTests(void) @@ -3195,5 +3306,7 @@ void DefragRegisterTests(void) DefragBsdSubsequentOverlapsStartOfOriginalIpv6Test_2); UtRegisterTest("DefragBsdMissingFragmentIpv4Test", DefragBsdMissingFragmentIpv4Test); UtRegisterTest("DefragBsdMissingFragmentIpv6Test", DefragBsdMissingFragmentIpv6Test); + UtRegisterTest("DefragOutOfOrderFirstFragmentTest", DefragOutOfOrderFirstFragmentTest); + UtRegisterTest("DefragSameOffsetPrecedingOverlapTest", DefragSameOffsetPrecedingOverlapTest); #endif /* UNITTESTS */ }