From 9ce750fdc6f6e1fc752dd30c537de364c7c45b75 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Thu, 2 Jul 2026 14:35:07 +0200 Subject: [PATCH] F-5965: skip wb_diff match candidates whose offset MSB collides with ESC A matched block's source offset is encoded as off[0..2] right after the ESC (0x7f) header marker. When the match offset falls in [0x7f0000, 0x7fffff], off[0] is 0x7f, so the header begins with ESC ESC -- the same two bytes wb_patch uses to decode an escaped literal 0x7f. The decoder then emits a single literal byte, consumes only 2 of the 6 header bytes, and desyncs the rest of the stream, breaking the wb_patch(wb_diff(A,B)) == B roundtrip for base images >= ~8MB (a supported MMU/Linux delta-update configuration). Make wb_diff skip any candidate match whose offset's most-significant byte equals ESC, in both the forward (base-image) and backward (previously-patched-image) search paths, so the ambiguous header is never produced; the position falls back to literal encoding instead. --- src/delta.c | 16 ++++++++++++++++ tools/unit-tests/unit-delta.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/delta.c b/src/delta.c index 236a6c09..67c44e5f 100644 --- a/src/delta.c +++ b/src/delta.c @@ -303,6 +303,15 @@ int wb_diff(WB_DIFF_CTX *ctx, uint8_t *patch, uint32_t len) blk_start = pa - ctx->src_a; if (blk_start > BLOCK_OFF_MAX) return -1; + if (((blk_start >> 16) & 0xFF) == ESC) { + /* The most-significant offset byte would collide with + * the escaped-literal marker (ESC ESC), making the + * header indistinguishable from a literal ESC byte on + * decode. Skip this candidate and keep searching. + */ + pa++; + continue; + } b_start = ctx->off_b; pa+= BLOCK_HDR_SIZE; ctx->off_b += BLOCK_HDR_SIZE; @@ -367,6 +376,13 @@ int wb_diff(WB_DIFF_CTX *ctx, uint8_t *patch, uint32_t len) blk_start = pb - ctx->src_b; if (blk_start > BLOCK_OFF_MAX) return -1; + if (((blk_start >> 16) & 0xFF) == ESC) { + /* Same ESC-collision hazard as the forward-match + * path: skip this candidate and keep searching. + */ + pb++; + continue; + } pb+= BLOCK_HDR_SIZE; ctx->off_b += BLOCK_HDR_SIZE; while ((pb < pb_limit) && diff --git a/tools/unit-tests/unit-delta.c b/tools/unit-tests/unit-delta.c index b870b32c..6f630ac4 100644 --- a/tools/unit-tests/unit-delta.c +++ b/tools/unit-tests/unit-delta.c @@ -533,6 +533,34 @@ START_TEST(test_wb_patch_and_diff_multi_sector_images) } END_TEST +START_TEST(test_wb_patch_and_diff_match_offset_msb_equals_esc) +{ + /* A matched block whose 24-bit source offset has 0x7f (ESC) as its + * most-significant byte would encode a header starting with the same + * two bytes (ESC, ESC) used to escape a literal ESC byte, making it + * indistinguishable on decode. wb_diff must never emit such a header; + * base images large enough to expose an offset in [0x7f0000,0x7fffff] + * are a supported configuration (e.g. MMU/Linux delta updates). + */ + static uint8_t src_a[0x800000]; + uint8_t src_b[7]; + const uint32_t src_off = 0x7f0010; + + memset(src_a, 0, sizeof(src_a)); + src_a[src_off] = 0x11; + src_a[src_off + 1] = 0x22; + src_a[src_off + 2] = 0x33; + src_a[src_off + 3] = 0x44; + src_a[src_off + 4] = 0x55; + src_a[src_off + 5] = 0x66; + + memcpy(src_b, src_a + src_off, 6); + src_b[6] = 0xAA; + + (void)run_roundtrip_case(src_a, sizeof(src_a), src_b, sizeof(src_b), 64); +} +END_TEST + START_TEST(test_wb_diff_get_sector_size_rejects_values_above_16bit) { #if HAVE_POSIX_FORK @@ -690,6 +718,7 @@ Suite *patch_diff_suite(void) tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_completely_different_images); tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_all_escape_images); tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_multi_sector_images); + tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_match_offset_msb_equals_esc); tcase_add_test(tc_wolfboot_delta, test_wb_diff_get_sector_size_accepts_16bit_limit); tcase_add_test(tc_wolfboot_delta, test_wb_diff_get_sector_size_rejects_values_above_16bit); tcase_add_test(tc_wolfboot_delta, test_wb_patch_and_diff_size_changing_update);