protocols: floor CALIPSO, MPL and REG_INFO's wire-derived lengths at zero - #460
Conversation
…zero (#455) Three more wire-derived length expressions shared the unguarded-underflow shape #438/#449 fixed elsewhere -- a crafted or truncated option drives the subtraction negative and it reaches struct as a format like '-1s'. - CALIPSOOption.pad (hopopt.py, ipv6_opts.py): `len - 8 - cmpt_len * 4` could go negative and hit struct.calcsize as a bare struct.error. Added calipso_pad_len, raising FieldValueError below zero, replacing the duplicated lambda in both files. - MPLOption.pad (hopopt.py, ipv6_opts.py): same crash shape for `len - 2 - <Seed-ID length>`. Added mpl_opt_pad_len analogously. - RegInfoParameter.reg_info (hip.py): `len - 2` fed a ListField, whose `while length > 0` loop silently returns [] on a negative length instead of raising -- a malformed REG_INFO parsed "successfully" with no diagnostic. Added reg_info_list_len, mirroring #449's registration_type_list_len for REG_REQUEST/RESPONSE/FAILED. Follows #449's precedent for smf_i_dpd_id_len: a named function per module, since hopopt.py and ipv6_opts.py don't import from each other. Tests cover both symptoms distinctly and are confirmed failing on the pre-fix tree (struct.error for CALIPSO/MPL, silent reg_type=() for REG_INFO). Full suite: 982 passed, 17 skipped (999 collected, baseline 994 at da24227); mypy clean.
|
Standing in for Copilot on this one (out of tokens). Reviewed at head CIAll 23 checks settled, no red: 21 The three claimed sites, verified at this sha
Reproduction, both directions (schema files reverted to
|
| Parameter | Field | Line (main) |
Consumed before list | Crafted Length=0 result |
|---|---|---|---|---|
NATTraversalModeParameter |
modes |
454 | reserved: PaddingField(length=2) |
no exception, mode_id=() |
TransportFormatListParameter |
formats |
808 | (none -- first field, yet still -2) |
no exception, tf_type=() |
ESPTransformParameter |
suites |
826 | reserved: PaddingField(length=2) |
no exception, suite_id=() |
HIPTransportModeParameter |
mode |
941 | port: UInt16Field() |
no exception, mode_id=() |
Each is a ListField(length=lambda pkt: pkt['len'] - 2, ...) against the same unconstrained wire-controlled Parameter.len: UInt16Field(), so each hits the identical while length > 0 silent-empty-list path this PR fixes for RegInfoParameter. TransportFormatListParameter.formats is odd in a different way worth separating out -- it subtracts 2 despite being the very first field in the schema with nothing structurally consumed before it -- but that's a question about whether -2 is the right constant, not about whether the expression can go negative; it can, on the same wire-controlled len.
Separately, I checked the two lines that don't belong to this list of four: hip.py:420 (HIPTransformParameter.suites) and hip.py:635 (HITSuiteListParameter.suites) use a bare length=lambda pkt: pkt['len'], no subtraction, so they cannot underflow the same way -- confirming that if any earlier characterization conflated those two line numbers with the four real candidates above, that conflation was wrong, but the four candidates themselves are real and share the exact shape.
Left this as an out-of-scope inline note rather than a blocker, per the PR's stated scope (the three sites #455 names) -- a follow-up issue mirroring this fix would be the right vehicle for the four.
Verdict
Every claim in the PR body checked out against direct reproduction and my own measurements, no test or type-check regressions, CI fully green, one-commit-per-file diff matches the stated intent. Nothing here blocks merge.
GOOD TO MERGE at fe98da602bb23537763896ec3b24072d7b03e46b.
Summary
Closes #455.
Three more wire-derived length expressions shared the unguarded-underflow shape #438/#449 fixed elsewhere: a crafted or truncated option drives a subtraction negative, and the negative length reaches
structas a format like'-1s'.The three sites (re-derived against
da2422728, since #449 shifted the line numbers the issue quoted):pcapkit/protocols/schema/internet/hopopt.py:432andpcapkit/protocols/schema/internet/ipv6_opts.py:432--CALIPSOOption.pad,pkt['len'] - 8 - pkt['cmpt_len'] * 4pcapkit/protocols/schema/internet/hopopt.py:696andpcapkit/protocols/schema/internet/ipv6_opts.py:701--MPLOption.pad,pkt['len'] - 2 - <Seed-ID length>pcapkit/protocols/schema/internet/hip.py:733--RegInfoParameter.reg_info,pkt['len'] - 2Two distinct symptoms:
PaddingFieldreachesstruct.calcsizewith a negative length and raises a barestruct.error: bad char in struct format.RegInfoParameter.reg_infois aListField, whosewhile length > 0loop returns an empty list on a negative length with no exception at all -- a malformedREG_INFOparses "successfully" toreg_type=().The fix follows the precedent PR #449 set for
smf_i_dpd_id_len: a named function per module (sincehopopt.pyandipv6_opts.pydon't import from each other) that raisesFieldValueErrorfrompcapkit.utilities.exceptionswhen the computed length would go negative.calipso_pad_lenandmpl_opt_pad_len, added to bothhopopt.pyandipv6_opts.py, replacing the identical duplicated lambdas each file carried forCALIPSOOption.padandMPLOption.pad.reg_info_list_len, added tohip.py, mirroringregistration_type_list_len(added by protocols: drop ipv6_opts' stray SMF_DPD test field, fix two length underflows #449 for the siblingREG_REQUEST/REG_RESPONSE/REG_FAILEDparameters, which read a singlelifetimeoctet whereREG_INFOreadsmin_lifetime+max_lifetime, hence the different offset).Test plan
tests/protocols/internet/test_ipv6_extension_unit.py: added_assert_calipso_option_rejects_underflowing_lengthand_assert_mpl_option_rejects_underflowing_length, each exercised against bothHOPOPTandIPv6_Opts. Confirmed failing on the pre-fix tree withstruct.error: bad char in struct format(frompcapkit/corekit/fields/field.py:109), and passing after the fix withFieldValueError.tests/protocols/internet/test_hip_unit.py: addedtest_hip_reg_info_parameter_rejects_underflowing_length, mirroring the existingtest_hip_registration_parameters_reject_underflowing_length(protocols: drop ipv6_opts' stray SMF_DPD test field, fix two length underflows #449/A wire-derived length underflows into a struct format string, raising bare struct.error on untrusted input #438). Confirmed failing on the pre-fix tree withAssertionError: FieldValueError not raised(i.e. the silentreg_type=()case), and passing after the fix.tests/protocols/test_option_roundtrip_unit.py: ran explicitly per the coordination note in the issue -- 7 passed, 299 subtests passed, noEXPECTED_FAILURESentry flipped (CALIPSO/MPL have none;REG_INFO's existing entry is an unrelatedRECONSTRUCT/tuple-vs-list gap, untouched by this change).mypyclean on all three modified schema modules.examples/generators/make_samples.pyregenerated once): baselineda2422728collects 994 tests; with this branch's 5 new tests, 999 collected, 982 passed, 17 skipped, 0 failed.🤖 Generated with Claude Code