Repository navigation
fix(protocols): bound a frame's dissection at 64 layers (#1610) - #1620
Merged
Merged
Conversation
- Each layer dissects the next from inside its own parse, so deep nesting of any protocol, such as IP in IP or stacked 802.1Q tags, hit the recursion limit, and beholder left the rest raw, silently; #1604's bound restarts at every tunnel. FRAME_LAYER_LIMIT (64) now caps the layers ProtocolBase._parse_next_layer dissects in one frame: past it the rest stays Raw, with one ProtocolWarning. - IPv6 counts the extension headers it walks as layers, as the protocol chain does, and stops its walk at the bound; what a walked ESP decrypts counts on after them. - _make_payload builds each payload one layer further in, so from_data of a cut frame re-parses no deeper than the parse did, where it went past the recursion limit before, and warns of the bound once, not once a layer. - tcp_segment read a TCP 65th layer back out of the Raw the bound leaves, as it does a header the parser rejected (#1518); that Raw is now marked _past_layer_limit, and is not read. - The PEP records recursion with a depth limit as a decision open to revisit.
Owner
Author
|
Verdict: GOOD TO GO at Carried over from the Sonnet cross-review, whose final round found no defect at Checked on the rebased tree, since #1613 and #1614 also landed: the #1610 test file and ten toolkit and engine files (dpkt, scapy, pyshark, pypcapfile, #1581, #1604, #1607, engine agreement) all pass on 3.11 and 3.14, run one at a time. |
Contributor
|
Coverage: 89.72% (unit tier, Python 3.14,
Per-file detail: the |
3 of 13 tasks
JarryShaw
added a commit
that referenced
this pull request
Oct 13, 2026
…arns once (#1619) (#1630) - Each layer from_data rebuilds parses its own octets again, and a layer constructed rather than parsed started at _exthdr_depth 0, so each rebuilt extension header counted its chain afresh: its parse went up to 32 headers past the cut, and warned again on reaching EXTENSION_HEADER_LIMIT. A cut frame rebuilt with 2 warnings over IPv6 and up to 33 over IPv4, not 1. - Internet._make_payload now hands the payload its place in the chain, one past this layer's, as #1620 hands it its place in the frame; a constructed IPv6_Ext takes it up, so the rebuild stops where the parse did. - _next_layer_class does not warn of the bound inside a payload being rebuilt, as _parse_next_layer does not of FRAME_LAYER_LIMIT: the layer being rebuilt warns once, from its own parse.
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.
Please follow the guide below
You will be asked some questions, please read them carefully and answer honestly
Put an
xinto all the boxes [ ] relevant to your pull request (like that [x])Use Preview tab to see how your pull request will actually look like
Searched for similar pull requests
Followed the coding style (
make pylint,make mypy,make isort)make testpasses, and a test case covers the changeAdded a changelog entry under
docs/source/changelog/and regeneratedCHANGELOG.md, if the change is user-visible — N/A — centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657What is the purpose of your pull request?
Tick the commit type your subject line carries.
fix— corrects a defectfeat— adds a featureperf— changes performance, not behaviourrefactor— changes neither behaviour nor performancetest— tests onlydocs— documentation onlyci— workflows or build toolingrelease— bumps the version or rolls up a distributionchore— anything elseDescription of your pull request and other information
Closes #1610 for the default engine, on top of #1608. dpkt, scapy, pyshark and pypcapfile follow in #1609.
protocol.py:2323, about 9 to 12 frames a layer), so deep nesting of any protocol hit the recursion limit.beholder(decorators.py:128) only logged theRecursionErrorand left the restRaw.FRAME_LAYER_LIMIT = 64inpcapkit/protocols/protocol.py, counted per frame inProtocolBase._parse_next_layer. Past it, the rest staysRaw, with oneProtocolWarningthat names the bound. Tunnels do not restart the count. IPv6 counts the extension headers it walks, since they are protocol-chain entries too. A walked ESP still dissects its decrypted payload, and those layers count on after the headers walked before it.sys.setrecursionlimitis not touched.ProtocolBase._make_payloadis the step of everyfrom_datathat rebuilds the payload. It now raises the layer count by one around that nestedfrom_data. Each rebuilt layer parses its own octets again, and before this it counted from zero. Sofrom_dataof a cut frame dissected 64 more layers below every layer, which went past the recursion limit under pytest (depth 966 against 721 for the parse). Each of those re-parses also warned of the bound, 65 warnings for one frame. The payload's re-parse no longer warns, so a rebuilt cut frame warns once, as its parse does. fix(protocols): bound the IPv6 extension-header chain at 32 headers (#1604) #1608's chain bound still warns 2 to 33 times on a rebuild; that is fix(protocols): rebuilding a frame cut at the extension-chain bound warns more than once #1619.Rawit leaves keeps every octet. Below 64 layers the bound is never reached, so nothing differs. At 65 and 1,000 layers,from_data(info.to_dict()).dataequals the capture, and the rebuilt protocol chain equals the parsed one. All 201 files undertests/protocols/pass on 3.11 and 3.14, run one at a time.Rawthe bound leaves carries anerror, as a TCP header the parser rejected does, and the IP above it carries TCP. Sotcp_segment's fix(engines): TCP trace flows disagree between engines on snapped frames and on IPv4 total_length=0 #1518 fall-back (toolkit/pcap.py:157, reached through fix(toolkit): key tunnelled TCP by the innermost IP header (#1581) #1605's innermost-IP walk) read it back out. The bound now marks thatRawwithProtocolBase._past_layer_limit, and the fall-back declines it. A rejected header below the bound is read as before. fix(protocols): bound the IPv6 extension-header chain at 32 headers (#1604) #1608's extension-header bound cannot leak this way, because the layer it cuts is always an extension header, never TCP, so itserrorconvention is unchanged.