defrag: drop unsigned underflow in RB_NFIND search key#15403
Conversation
|
Do you have a pcap based test case? If not, can you craft one? |
|
NOTE: This PR may contain new authors. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #15403 +/- ##
==========================================
- Coverage 82.66% 82.63% -0.03%
==========================================
Files 996 996
Lines 271604 271609 +5
==========================================
- Hits 224512 224442 -70
- Misses 47092 47167 +75
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Crafted the PCAP and added another PR OISF/suricata-verify#3098 |
c7dd282 to
a8f49b6
Compare
|
The CI failures (CodeQL cpp and rust) are both failing at the |
|
I am not sure there is a bug if the test pass as is. How did you do the fuzzing ? Which sanitizers ? |
|
The original underflow was reported by Sergey Zhidkih in Redmine #8232. The report says it surfaced during fuzzing, but I have no visibility into the specific fuzzer or sanitizer setup that was used on their side. You are right that on a normal release build the algorithm still produces correct output. uint16_t wraparound is defined in C, RB_NFIND with key 65535 always returns NULL on a real fragment tree (no IPv4 fragment can have offset 65535, since the 13-bit field shifted by 3 caps at 65528), and the existing RB_MIN fallback walks forward and reaches the same end state. So depending on where the line is drawn, this is either a latent issue worth fixing (sanitizer finding, dead What the PR adds beyond removing the If you would rather drop the SV test and keep this as a pure cleanup commit, that works too. Just let me know which way you want it. |
|
I would prefer to have the right test (with the right sanitizer if needed) that fails now and passes with this PR. |
catenacyber
left a comment
There was a problem hiding this comment.
I think it needs a better and smaller patch
|
This probably needs multiple PRs to demonstrate.
|
a8f49b6 to
e31c44a
Compare
|
@catenacyber thanks, your version is cleaner. Force-pushed @jasonish on the multi-PR demonstrator idea, with the simpler patch shape the SV test (OISF/suricata-verify#3098) no longer fails on the pre-fix code under If you want me to put that pair together, I can open it as a small companion PR. Otherwise this PR stands as a code-quality cleanup that silences the sanitizer finding and tightens intent. |
Simply ran fuzz_decodepcapfile.
The actual sanitizer report is |
|
@Rx1513 thanks for the details. You are right that the sanitizer trip is On whether the The current branch took @catenacyber's earlier suggestion of wrapping the call with @catenacyber which shape do you prefer? |
|
I think the test would be to run CI with If just |
The seek into the fragment tree used frag_offset - 1 as the key for RB_NFIND. frag_offset is uint16_t, the subtraction promotes to int, and on the first fragment of an IP datagram the int result -1 is then narrowed to uint16_t when assigned to the key field, wrapping to 65535. Builds with -fsanitize=implicit-conversion flag this narrowing. The wrap is harmless in practice because no fragment can have offset 65535 (IPv4 limits the field to 8191 << 3 = 65528, and IPv6 follows the same alignment), so RB_NFIND returns NULL and the existing RB_MIN fallback walks the tree forward to reach the same end state. The path is just reached by accident. The -1 is also functionally a no-op even on inputs where it does not underflow. Fragment offsets are always multiples of 8, so RB_NFIND with key = frag_offset - 1 and with key = frag_offset return the same node on every realistic input. Drop the -1 entirely. The 35 defrag unit tests pass unchanged. Bug: OISF#8232.
e31c44a to
67efa92
Compare
|
Force-pushed |
|
With
Don't you ? |
|
@catenacyber No, fuzz_decodepcapfile only runs PostConfLoadedSetup and then decodes pcaps. It never calls SigLoadSignatures so the port-interval code isn't reached, which is why I didn't see that trip. But yeah, port2 + 1 with port2 == 65535 is the same class of bug. I can take it as a follow-up PR after this one lands, plus another adding -fsanitize=implicit-conversion to a CI job so we catch the rest in one pass. |
|
I meant for SV tests with To sum up, I think what this PR is fixing here is just some UBSAN report. Do you agree with this ? |
Agreed, the CI gate is the right end state. This fix is correct on its own (the a. Land this as the first PR in a series: fix the other Either works for me. Let me know what you want. |
Make sure these boxes are checked accordingly before submitting your Pull Request. Thank you
Contribution style:
https://docs.suricata.io/en/latest/devguide/contributing/contribution-process.html
Our Contribution agreements:
https://suricata.io/about/contribution-agreement/ (note: this is only required once)
Changes (if applicable):
(including schema descriptions)
https://redmine.openinfosecfoundation.org/projects/suricata/issues
Link to ticket: https://redmine.openinfosecfoundation.org/issues/8232
Describe changes:
DefragInsertFragseeds the RB tree search withfrag_offset - 1as the key.frag_offsetisuint16_t, so the first fragment of any IP datagram (frag_offset == 0) wraps the key to 65535. RB_NFIND then returns NULL and the existing RB_MIN fallback walks the tree forward. The end result is correct but the path is reached by accident, and the underflow is what fuzzer integer-overflow sanitizers flag.-1is functionally a no-op even on inputs where it does not underflow. Fragment offsets are always multiples of 8 (IPV4_GET_RAW_FRAGOFFSET << 3for IPv4, the IPv6 fragment header offset field for IPv6), so no fragment can ever haveoffset == frag_offset - 1. RB_NFIND returns the same neighbour for keysfrag_offsetandfrag_offset - 1on every realistic input.- 1. The RB_PREV / RB_MIN fallback logic that follows is intentionally left intact. In the no-strict-greater case the existing code starts at the tree minimum and walks forward, which is the only safe choice given the defrag policies do not enforce a non-overlapping invariant onoffset + data_len(overlaps are handled throughltrim, not by trimmingdata_len). Switching toRB_MAXwould miss overlaps where an earlier long fragment extends past later ones.Verification:
--enable-unittests, including every Sturges-Novak overlap-evasion pattern for BSD, Linux, Windows, Solaris, First, and Last policies on IPv4 and IPv6, the BSD subsequent-overlap-of-original tests, the missing-fragment tests, and the trailing no-MF-fragment tests.Provide values to any of the below to override the defaults.
link to the pull request in the respective
_BRANCHvariable.SV_REPO=https://github.com/ssam18/suricata-verify
SV_BRANCH=bug-8232-defrag-first-frag-regress
SU_REPO=
SU_BRANCH=