scp81: fix RAM LOAD block splitting (overlapping 1-byte shifts) (v2.1.12)

The block slicer used the block number as a character offset
(loadfile_tlv[i * 2:(i + 240) * 2]), so every LOAD block after the first was
a 1-byte-shifted copy of the previous one - the cap header repeated every
239 bytes on the wire. A live install accepted three blocks, failed block 4
with SW 6400, then 6985, and INSTALL [for install] answered 6A88. The same
slicing was inherited by the SCP81 helper from the SCP80 path, so
multi-block caps could not install there either; both are fixed.

Tests: blocks are consecutive and reassemble the C4 TLV byte-for-byte
(200 python); findings updated; service worker v149.
This commit is contained in:
2026-09-16 07:51:59 +03:00
parent 92f646df08
commit ff7a1ad5d3
6 changed files with 38 additions and 9 deletions
+7 -4
View File
@@ -21,7 +21,7 @@ from osmocom.construct import GsmOrUcs2Adapter
from osmocom.tlv import BER_TLV_IE
VERSION = '2.1.11'
VERSION = '2.1.12'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -414,9 +414,12 @@ def _cap_apdu_sequence(loadfile_aid, module_aid, loadfile_data, sd_aid='',
ifl_data = _lv(loadfile_aid) + _lv(sd) + '00' + '00' + '00'
apdus = ['80E60200%02X%s00' % (len(ifl_data) // 2, ifl_data)]
loadfile_tlv = 'C4' + _ber_len(len(loadfile_data) // 2) + loadfile_data
total_bytes = len(loadfile_tlv) // 2
blocks = [loadfile_tlv[i * 2:(i + block_size) * 2]
for i in range(0, (total_bytes + block_size - 1) // block_size)]
# Split the TLV into consecutive 240-byte blocks (char offsets, 2 per
# byte). The earlier form indexed with the block number ('i * 2'), which
# produced overlapping 1-byte-shifted copies - the card failed mid-load
# with SW 6400 (live 2026-09-16).
blocks = [loadfile_tlv[off:off + block_size * 2]
for off in range(0, len(loadfile_tlv), block_size * 2)]
for i, block in enumerate(blocks):
p1 = 0x80 if i == len(blocks) - 1 else 0x00
apdus.append('80E8%02X%02X%02X%s00' % (p1, i % 256, len(block) // 2, block))