fix: reject a bool IP address in-library instead of silently corrupting the packet - #500
Merged
Merged
Conversation
…tion Closes #491, the unfixed IPv4 sibling of #469/#481. - `_IPAddressField.pre_process`, `IPv4InterfaceField.pre_process`, and `IPv6InterfaceField.pre_process` (pcapkit/corekit/fields/ipaddress.py) now reject a `bool` value before dispatching into `ipaddress.ip_address`/ `ip_interface`, via a new shared `_reject_bool` helper. `bool` is an `int` subclass, and `ipaddress.ip_address()` treats any `int` below `2**32` as IPv4 -- so `True`/`False` used to be silently packed as `0.0.0.1`/ `0.0.0.0` on an IPv4-typed field, with no exception and no warning. On an IPv6-typed field the same conversion happened to raise instead (version mismatch), which is the asymmetry that let this slip past #481's guard for MH's `_make_opt_mn_id` -- that PR fixed the mechanism at one call site, not at its shared root. - `SecurityAssociation.__init__` (pcapkit/protocols/internet/esp.py) calls `ipaddress.ip_address()` directly on `destination`, bypassing the Field abstraction entirely; it gets the same guard, raising `ProtocolError`. - Both guards point the caller at `int(...)`, matching #481's message shape. tests/corekit/test_fields_ipaddress.py sweeps `{True, False}` across all four field classes (IPv4/IPv6 address and interface) and reproduces the original corruption through the public `IPv4.make(src=True, dst=False)` API. tests/protocols/internet/test_esp_unit.py adds the equivalent case for `SecurityAssociation`. Build: tests/corekit/test_fields_ipaddress.py -> 14 passed, 25 subtests. tests/protocols/internet/test_esp_unit.py -> 30 passed, 38 subtests. Full tests/corekit + tests/protocols/internet -> 254 passed, 662 subtests, 0 failed.
This was referenced Sep 19, 2026
Closed
JarryShaw
added a commit
that referenced
this pull request
Sep 20, 2026
…508) - add `parse_ip_address()` to `pcapkit.corekit.fields.ipaddress`, which calls the existing `_reject_bool` before converting, and takes an optional `version` so a caller that pins the address family widens an `int` to the right one - route the seven `_make_*` sites that convert a caller-supplied address *before* the schema is built through it: `MH._make_opt_bid`, `MH._make_opt_lmaa`, `MH._make_fid_suboption`, `MH._make_opt_dmnp`, `MH._make_opt_lma_up`, `HIP._make_param_locator_set` and `TCP._make_mptcp_addaddr`. Each derives its option length or family flag from the converted address, so a bare `ipaddress.ip_address(True)` became `0.0.0.1` and #500's field-level guard could no longer tell it from a real address - leave `SwitchField` untouched: its `pre_process` delegates to the resolved field and is not reached on the pack path at all, so the guard #508 proposes putting there would be dead code - drop the now-unused `import ipaddress` from `tcp.py`, and document `parse_ip_address` on the ipaddress fields page Adds tests over three files, including one that re-derives the ten address-typed `SwitchField` declarations from `Schema.__fields__` so a new one cannot be added unnoticed. CI-equivalent unit suite: 1056 passed, with only the pre-existing `test_docstring_contract` failure that main already has.
This was referenced Sep 20, 2026
JarryShaw
added a commit
that referenced
this pull request
Sep 20, 2026
- `TSOption.post_process` converted `ts_data` entries to addresses with a bare
`ipaddress.ip_address`, which takes a `bool` as the `int` it subclasses, so
`ts_data=[True, 5]` packed and reported `IPv4Address('0.0.0.1')` with no
exception. It runs on the packing path too, and `IPv4.make` accepts a
caller-built option schema, so this was reachable from the public API. All
three conversions now go through `parse_ip_address`, the fifth site of the
defect #481, #500, #539 and #540 fixed before it.
- `quick_start_data_selector` sized the nested Quick-Start suboption with a
hardcoded `SchemaField(length=5)` -- the width of a Request's `ttl` and
`nonce` alone. A well-formed 8-octet option decoded its nonce as 55 rather
than 933982136 and left three octets to be read as a fabricated option, so
the datagram failed with `ProtocolError`. The length now comes from
`quick_start_option_length`, computed from the resolved suboption, and
`QuickStartReportOption` gains the RFC 4782 section 3.1 `Not Used` octet it
was missing, which had made it seven octets wide against the `length=8` both
`_make_opt_qs` and `_read_opt_qs` use.
- `_make_opt_ts` passed `data=` where the schema field is `ts_data`, so every
timestamp was dropped with an `UnknownFieldWarning` and the Timestamp option
was unbuildable through `make`. The `TYPE_CHECKING` `__init__` stub that
advertised `data` is corrected too.
Three new tests, each shown to fail without its fix; `ipv4-option/TS` deleted
from `EXPECTED_FAILURES` now that it round-trips. Full unit tier green, 1107
passed with 2666 subtests; both changed modules at 100% statement and branch
coverage.
JarryShaw
added a commit
that referenced
this pull request
Sep 20, 2026
- `TSOption.post_process` converted `ts_data` entries to addresses with a bare
`ipaddress.ip_address`, which takes a `bool` as the `int` it subclasses, so
`ts_data=[True, 5]` packed and reported `IPv4Address('0.0.0.1')` with no
exception. It runs on the packing path too, and `IPv4.make` accepts a
caller-built option schema, so this was reachable from the public API. All
three conversions now go through `parse_ip_address`, the fifth site of the
defect #481, #500, #539 and #540 fixed before it.
- `quick_start_data_selector` sized the nested Quick-Start suboption with a
hardcoded `SchemaField(length=5)` -- the width of a Request's `ttl` and
`nonce` alone. A well-formed 8-octet option decoded its nonce as 55 rather
than 933982136 and left three octets to be read as a fabricated option, so
the datagram failed with `ProtocolError`. The length now comes from
`quick_start_option_length`, computed from the resolved suboption, and
`QuickStartReportOption` gains the RFC 4782 section 3.1 `Not Used` octet it
was missing, which had made it seven octets wide against the `length=8` both
`_make_opt_qs` and `_read_opt_qs` use.
- `_make_opt_ts` passed `data=` where the schema field is `ts_data`, so every
timestamp was dropped with an `UnknownFieldWarning` and the Timestamp option
was unbuildable through `make`. The `TYPE_CHECKING` `__init__` stub that
advertised `data` is corrected too.
Three new tests, each shown to fail without its fix; `ipv4-option/TS` deleted
from `EXPECTED_FAILURES` now that it round-trips. Full unit tier green, 1107
passed with 2666 subtests; both changed modules at 100% statement and branch
coverage.
7 tasks done
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.
Fixes #491 — the unfixed shared root of #469/#481, which fixed the same mechanism at exactly one call site.
The defect
boolis anintsubclass, andipaddress.ip_address()treats anyintbelow2**32as IPv4. So on an IPv4-typed field aboolwas silently accepted and corrupted the packet — no exception, no warning:The IPv4/IPv6 asymmetry is why #481 looked sufficient. On an IPv6-typed field the same conversion happens to raise, because the resulting
IPv4Address's version mismatches — so the IPv6 half masked the IPv4 half, and fixingMH._make_opt_mn_idalone left the root untouched.The fix
A shared
_reject_bool(value, description)helper inpcapkit/corekit/fields/ipaddress.py, following the module's existing_reraise_as_field_value_errorpattern, called as the first statement of_IPAddressField.pre_process,IPv4InterfaceField.pre_processandIPv6InterfaceField.pre_process— before anyipaddress.*dispatch.That placement is deliberate and matches #481's: a correct check in the wrong position does not fire, and that exact mistake has been made twice in this repository's history with a passing test each time.
pcapkit/protocols/internet/esp.pygets the equivalent guard inSecurityAssociation.__init__, ahead of its directipaddress.ip_address()call, which sits outside the Field abstraction entirely and had no version check at all.Exception choice
FieldValueErrorin the fields module,ProtocolErrorinesp.py— each module's existing convention, andProtocolErroris what #481 used for the same mechanism in a protocol module.Deliberately not
pcapkit.utilities.exceptions.BoolError: its docstring is "The argument(s) must bebooltype" — it is for the inverse case, where a bool was expected and something else arrived. Using it here would invert its meaning.Verification
Every public field class in the module, enumerated rather than spot-checked, with
pcapkit.__file__asserted against this branch before import:Both original reproductions:
The escape hatch the message promises actually works, and ordinary input is unaffected:
Revert-proof. Neutering the three field guards and the esp guard in place:
Restored: 44 passed, 63 subtests passed.
The new tests sweep
{True, False}across the address and interface types for both IP versions, because the IPv6-raises/IPv4-corrupts asymmetry is precisely what let this survive #481.Scope deliberately left out
The roughly ten further IPv4-typed maker sites listed in #491 as "confirmed by inspection but not executed" are untouched here:
RROption/LSROption/SSROptioninipv4.py, the dual-stack option makers inmh.py, andSCTP._make_param_ipv4. They share this mechanism and very likely still corrupt, but those files are being edited concurrently for #490/#493/#494, and splitting the change across them would have produced conflicting edits to the same lines.With the root guarded, every one of those sites that routes through an
IPv4AddressFieldis now covered automatically.SCTP._make_param_ipv4and any site callingipaddress.*directly are not, and want a follow-up pass once the other streams land.