Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/pcapkit/protocols/internet/hip.rst
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ Auxiliary Functions
.. autofunction:: pcapkit.protocols.schema.internet.hip.locator_value_selector
.. autofunction:: pcapkit.protocols.schema.internet.hip.host_id_hi_selector
.. autofunction:: pcapkit.protocols.schema.internet.hip.registration_type_list_len
.. autofunction:: pcapkit.protocols.schema.internet.hip.reg_info_list_len

Data Models
-----------
Expand Down
2 changes: 2 additions & 0 deletions docs/source/pcapkit/protocols/internet/hopopt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ Auxiliary Functions

.. autofunction:: pcapkit.protocols.schema.internet.hopopt.mpl_opt_seed_id_len
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.pad_opt_data_len
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.calipso_pad_len
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.mpl_opt_pad_len

.. autofunction:: pcapkit.protocols.schema.internet.hopopt.smf_dpd_data_selector
.. autofunction:: pcapkit.protocols.schema.internet.hopopt.smf_i_dpd_tid_selector
Expand Down
2 changes: 2 additions & 0 deletions docs/source/pcapkit/protocols/internet/ipv6_opts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ Auxiliary Functions

.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.mpl_opt_seed_id_len
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.pad_opt_data_len
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.calipso_pad_len
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.mpl_opt_pad_len

.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.smf_dpd_data_selector
.. autofunction:: pcapkit.protocols.schema.internet.ipv6_opts.smf_i_dpd_tid_selector
Expand Down
28 changes: 27 additions & 1 deletion pcapkit/protocols/schema/internet/hip.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,32 @@ def registration_type_list_len(pkt: 'dict[str, Any]') -> 'int':
return length


def reg_info_list_len(pkt: 'dict[str, Any]') -> 'int':
"""Return ``REG_INFO`` registration type list length.

Used by the ``reg_info`` field of :class:`RegInfoParameter`, which follows
a pair of ``min_lifetime`` and ``max_lifetime`` octets with a list of
registration type octets sized by the remainder of the parameter.

Args:
pkt: Parameter unpacked schema.

Returns:
Registration type list length.

Raises:
FieldValueError: If the parameter's ``Length`` on the wire is too
short to hold the ``min_lifetime`` and ``max_lifetime`` octets
already read, which would otherwise underflow the list length
below zero.

"""
length = pkt['len'] - 2
if length < 0:
raise FieldValueError(f'HIP: invalid parameter length: {pkt["len"]}')
return length


class Parameter(EnumSchema[Enum_Parameter]):
"""Base schema for HIP parameters."""

Expand Down Expand Up @@ -704,7 +730,7 @@ class RegInfoParameter(Parameter, code=Enum_Parameter.REG_INFO):
max_lifetime: 'int' = UInt8Field()
#: Registration types.
reg_info: 'list[Enum_Registration]' = ListField(
length=lambda pkt: pkt['len'] - 2,
length=reg_info_list_len,
Comment thread
JarryShaw marked this conversation as resolved.
item_type=EnumField(length=1, namespace=Enum_Registration),
)
#: Padding.
Expand Down
49 changes: 45 additions & 4 deletions pcapkit/protocols/schema/internet/hopopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,49 @@ def pad_opt_data_len(pkt: 'dict[str, Any]') -> 'int':
return length


def calipso_pad_len(pkt: 'dict[str, Any]') -> 'int':
"""Return CALIPSO option padding length.

Args:
pkt: CALIPSO option unpacked schema.

Returns:
CALIPSO option padding length.

Raises:
FieldValueError: If ``Opt Data Len`` on the wire is too short to hold
the fixed header and the compartment bitmap declared by
``cmpt_len``, which would otherwise underflow the padding length
below zero.

"""
length = pkt['len'] - 8 - pkt['cmpt_len'] * 4
if length < 0:
raise FieldValueError(f'HOPOPT: invalid CALIPSO option length: {pkt["len"]}')
return length


def mpl_opt_pad_len(pkt: 'dict[str, Any]') -> 'int':
"""Return MPL option padding length.

Args:
pkt: MPL option unpacked schema.

Returns:
MPL option padding length.

Raises:
FieldValueError: If ``Opt Data Len`` on the wire is too short to hold
the fixed header and the Seed-ID declared by ``flags.type``, which
would otherwise underflow the padding length below zero.

"""
length = pkt['len'] - 2 - (0 if pkt['flags']['type'] == 0 else mpl_opt_seed_id_len(pkt))
if length < 0:
raise FieldValueError(f'HOPOPT: invalid MPL option length: {pkt["len"]}')
return length


class Option(EnumSchema[Enum_Option]):
"""Header schema for HOPOPT options."""

Expand Down Expand Up @@ -389,7 +432,7 @@ class CALIPSOOption(Option, code=Enum_Option.CALIPSO):
lambda pkt: pkt['cmpt_len'] > 0,
)
#: Padding.
pad: 'bytes' = PaddingField(length=lambda pkt: pkt['len'] - 8 - pkt['cmpt_len'] * 4)
pad: 'bytes' = PaddingField(length=calipso_pad_len)
Comment thread
JarryShaw marked this conversation as resolved.

if TYPE_CHECKING:
def __init__(self, type: 'Enum_Option', len: 'int', domain: 'int', cmpt_len: 'int',
Expand Down Expand Up @@ -650,9 +693,7 @@ class MPLOption(Option, code=Enum_Option.MPL_Option):
lambda pkt: pkt['flags']['type'] != Enum_SeedID.IPV6_SOURCE_ADDRESS,
)
#: Reserved data (padding).
pad: 'bytes' = PaddingField(length=lambda pkt: pkt['len'] - 2 - (
0 if pkt['flags']['type'] == 0 else mpl_opt_seed_id_len(pkt)
))
pad: 'bytes' = PaddingField(length=mpl_opt_pad_len)

def post_process(self, packet: 'dict[str, Any]') -> 'Schema':
"""Revise ``schema`` data after unpacking process.
Expand Down
49 changes: 45 additions & 4 deletions pcapkit/protocols/schema/internet/ipv6_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,49 @@ def pad_opt_data_len(pkt: 'dict[str, Any]') -> 'int':
return length


def calipso_pad_len(pkt: 'dict[str, Any]') -> 'int':
"""Return CALIPSO option padding length.

Args:
pkt: CALIPSO option unpacked schema.

Returns:
CALIPSO option padding length.

Raises:
FieldValueError: If ``Opt Data Len`` on the wire is too short to hold
the fixed header and the compartment bitmap declared by
``cmpt_len``, which would otherwise underflow the padding length
below zero.

"""
length = pkt['len'] - 8 - pkt['cmpt_len'] * 4
if length < 0:
raise FieldValueError(f'IPv6-Opts: invalid CALIPSO option length: {pkt["len"]}')
return length


def mpl_opt_pad_len(pkt: 'dict[str, Any]') -> 'int':
"""Return MPL option padding length.

Args:
pkt: MPL option unpacked schema.

Returns:
MPL option padding length.

Raises:
FieldValueError: If ``Opt Data Len`` on the wire is too short to hold
the fixed header and the Seed-ID declared by ``flags.type``, which
would otherwise underflow the padding length below zero.

"""
length = pkt['len'] - 2 - (0 if pkt['flags']['type'] == 0 else mpl_opt_seed_id_len(pkt))
if length < 0:
raise FieldValueError(f'IPv6-Opts: invalid MPL option length: {pkt["len"]}')
return length


class Option(EnumSchema[Enum_Option]):
"""Header schema for IPv6-Opts options."""

Expand Down Expand Up @@ -389,7 +432,7 @@ class CALIPSOOption(Option, code=Enum_Option.CALIPSO):
lambda pkt: pkt['cmpt_len'] > 0,
)
#: Padding.
pad: 'bytes' = PaddingField(length=lambda pkt: pkt['len'] - 8 - pkt['cmpt_len'] * 4)
pad: 'bytes' = PaddingField(length=calipso_pad_len)

if TYPE_CHECKING:
def __init__(self, type: 'Enum_Option', len: 'int', domain: 'int', cmpt_len: 'int',
Expand Down Expand Up @@ -655,9 +698,7 @@ class MPLOption(Option, code=Enum_Option.MPL_Option):
lambda pkt: pkt['flags']['type'] != Enum_SeedID.IPV6_SOURCE_ADDRESS,
)
#: Reserved data (padding).
pad: 'bytes' = PaddingField(length=lambda pkt: pkt['len'] - 2 - (
0 if pkt['flags']['type'] == 0 else mpl_opt_seed_id_len(pkt)
))
pad: 'bytes' = PaddingField(length=mpl_opt_pad_len)

def post_process(self, packet: 'dict[str, Any]') -> 'Schema':
"""Revise ``schema`` data after unpacking process.
Expand Down
39 changes: 39 additions & 0 deletions tests/protocols/internet/test_hip_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,45 @@ def test_hip_registration_parameters_reject_underflowing_length(self) -> None:
with self.assertRaisesRegex(FieldValueError, 'invalid parameter length'):
HIP(raw, len(raw), extension=True)

def test_hip_reg_info_parameter_rejects_underflowing_length(self) -> None:
"""#455: a ``REG_INFO`` parameter's ``Length`` too small for its own
``min_lifetime``/``max_lifetime`` octets must raise, not silently
drop the registration list.

``reg_info`` sizes its list of registration-type octets as
``Length - 2`` -- the two octets are ``min_lifetime`` and
``max_lifetime``, which ``REG_INFO`` carries in place of the single
``lifetime`` octet #438 fixed for ``reg_request``/``reg_response``/
``reg_failed``. Nothing floored that at zero either, so a peer
declaring ``Length = 0`` drove the list length to ``-2``. Unlike a
:class:`~pcapkit.corekit.fields.strings.BytesField`,
:class:`~pcapkit.corekit.fields.collections.ListField` never reaches
:func:`struct.calcsize` for a negative length -- its own ``while
length > 0`` loop just returns an empty list instead -- so this
parsed to an empty ``reg_type`` with no exception and no diagnostic:
``proto.info.parameters[930].reg_type == ()``, confirmed against the
pre-fix tree at ``da2422728``, rather than rejecting the malformed
``Length``.

"""
from pcapkit.protocols.internet.hip import HIP
from pcapkit.utilities.exceptions import FieldValueError

# next(1) len(1)=5 pkt(1) ver(1)=0x01 (the reserved bit that must be 1)
# checksum(2) control(2) shit(16) rhit(16) -- the fixed 40-octet header,
# declaring one 8-octet parameter to follow: (5 - 4) * 8 == 8.
fixed = bytes([0x3b, 0x05, 0x00, 0x01]) + bytes(2) + bytes(2) + bytes(16) + bytes(16)
self.assertEqual(len(fixed), 40)

# type(2)=930 (REG_INFO) len(2)=0, min_lifetime(1) max_lifetime(1),
# then 2 octets padding out the 8-octet parameter area the outer
# header declared.
param = (930).to_bytes(2, 'big') + (0).to_bytes(2, 'big') + bytes(2) + bytes(2)
raw = fixed + param

with self.assertRaisesRegex(FieldValueError, 'invalid parameter length'):
HIP(raw, len(raw), extension=True)

def test_hip_schema_selectors_and_encrypted_parameter_branches(self) -> None:
from pcapkit.const.hip.cipher import Cipher
from pcapkit.const.hip.hi_algorithm import HIAlgorithm
Expand Down
71 changes: 71 additions & 0 deletions tests/protocols/internet/test_ipv6_extension_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,77 @@ def test_ipv6_opts_identification_based_dpd_option_rejects_underflowing_length(s

self._assert_identification_based_dpd_option_rejects_underflowing_length(IPv6_Opts)

def _assert_calipso_option_rejects_underflowing_length(self, protocol_cls: type) -> None:
"""#455: ``Opt Data Len`` too small for CALIPSO's own fixed fields must raise, not crash.

This is the issue's own reproduction: a CALIPSO option declaring
``Opt Data Len = 0`` and ``Cmpt Len = 0``. ``pad``'s length is
``Opt Data Len - 8 - Cmpt Len * 4`` -- the octets left over after the
option's own ``domain``, ``cmpt_len``, ``level`` and ``checksum``
fields (8 octets fixed) and its compartment bitmap (``Cmpt Len * 4``
octets) -- and nothing floored that at zero, so it drove the padding
length to ``-8``. That reached :func:`struct.calcsize` as the template
``'-8s'`` and raised a bare ``struct.error: bad char in struct
format`` -- not one of pcapkit's own exception types, and uncatchable
through :mod:`pcapkit.utilities.exceptions`.

"""
from pcapkit.utilities.exceptions import FieldValueError

# next(1) hdr_ext_len(1)=1 -> 16-octet extension header; option
# type(1)=CALIPSO(0x07) len(1)=0, then domain(4) cmpt_len(1) level(1)
# checksum(2) all zero, with four trailing zero octets.
raw = bytes.fromhex('3b0007000000000000000000000000')

with self.assertRaisesRegex(FieldValueError, 'invalid CALIPSO option length'):
with time_limit(5):
protocol_cls(raw, extension=True)

def test_hopopt_calipso_option_rejects_underflowing_length(self) -> None:
from pcapkit.protocols.internet.hopopt import HOPOPT

self._assert_calipso_option_rejects_underflowing_length(HOPOPT)

def test_ipv6_opts_calipso_option_rejects_underflowing_length(self) -> None:
from pcapkit.protocols.internet.ipv6_opts import IPv6_Opts

self._assert_calipso_option_rejects_underflowing_length(IPv6_Opts)

def _assert_mpl_option_rejects_underflowing_length(self, protocol_cls: type) -> None:
"""#455: ``Opt Data Len`` too small for MPL's own ``flags``/``seq`` octets must raise, not crash.

An MPL option declaring ``Opt Data Len = 0`` with Seed-ID type ``0``
(no Seed-ID octets). ``pad``'s length is ``Opt Data Len - 2 -
<Seed-ID length>`` -- the two octets are the ``flags`` and ``seq``
fields the option always carries -- and nothing floored that at zero,
so it drove the padding length to ``-2``. That reached
:func:`struct.calcsize` as the template ``'-2s'`` and raised a bare
``struct.error: bad char in struct format`` -- not one of pcapkit's
own exception types, and uncatchable through
:mod:`pcapkit.utilities.exceptions`.

"""
from pcapkit.utilities.exceptions import FieldValueError

# next(1) hdr_ext_len(1)=0 -> 8-octet extension header; option
# type(1)=MPL_Option(0x6d) len(1)=0, flags(1)=0 (Seed-ID type 0),
# seq(1)=0, with two trailing zero octets.
raw = bytes.fromhex('3b006d0000000000')

with self.assertRaisesRegex(FieldValueError, 'invalid MPL option length'):
with time_limit(5):
protocol_cls(raw, extension=True)

def test_hopopt_mpl_option_rejects_underflowing_length(self) -> None:
from pcapkit.protocols.internet.hopopt import HOPOPT

self._assert_mpl_option_rejects_underflowing_length(HOPOPT)

def test_ipv6_opts_mpl_option_rejects_underflowing_length(self) -> None:
from pcapkit.protocols.internet.ipv6_opts import IPv6_Opts

self._assert_mpl_option_rejects_underflowing_length(IPv6_Opts)

def _assert_a_truncated_option_area_is_diagnosed(self, protocol_cls: type) -> None:
"""An option area with nothing behind it is an error, not a hang.

Expand Down
Loading