SAIP: make _split_der robust to truncated/malformed input

decode_saip_profile (used by make_profile --inspect on user files) could
IndexError on a truncated tail. _split_der now stops at the last complete DER
element and returns the valid prefix. Tests: +1; 109 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
A ashkan@beigi.net 2026-07-02 02:56:20 +00:00
parent b64e9dbd54
commit 9a36b1a5fa
2 changed files with 23 additions and 2 deletions

View File

@ -142,7 +142,11 @@ def create_saip_profile(
def _split_der(data: bytes):
"""Yield byte-range slices of consecutive top-level DER TLVs in ``data``."""
"""Yield byte-range slices of consecutive top-level DER TLVs in ``data``.
Stops cleanly at the last complete element so a truncated or malformed tail
yields the valid prefix instead of raising.
"""
i, n = 0, len(data)
while i < n:
start = i
@ -151,15 +155,21 @@ def _split_der(data: bytes):
if first & 0x1F == 0x1F: # high-tag-number form: skip continuation octets
while i < n and data[i] & 0x80:
i += 1
i += 1
i += 1 # final tag byte
if i >= n: # no room for a length octet
return
length_byte = data[i]
i += 1
if length_byte & 0x80: # long-form length
num = length_byte & 0x7F
if i + num > n:
return
length = int.from_bytes(data[i:i + num], "big")
i += num
else:
length = length_byte
if i + length > n: # truncated content
return
i += length
yield data[start:i]

View File

@ -80,6 +80,17 @@ class TestSaipEncoding:
assert by["header"]["major-version"] == 3
assert by["mf"]["ef-iccid"] == [("fillFileContent", enc_iccid(iccid))]
@saip_required
def test_truncated_profile_yields_prefix_not_crash(self):
pkg = saip.create_saip_profile("302720000000001", generate_ki(),
b"\x22" * 16, generate_iccid())
full = saip.decode_saip_profile(pkg)
# Cutting mid-element must not raise; it returns the complete prefix.
partial = saip.decode_saip_profile(pkg[:-3])
assert len(partial) < len(full)
assert [c for c, _ in partial] == [c for c, _ in full][:len(partial)]
assert saip.decode_saip_profile(b"\xa0\x82") == [] # bogus length, no crash
@saip_required
def test_imsi_embedded_in_usim(self):
imsi = "302720123456789"