From 83cd96bc2fd90e5f5a8b0ceaed2022a16a017a1c Mon Sep 17 00:00:00 2001 From: Justin Mitchell Date: Fri, 10 Apr 2026 15:13:59 -0500 Subject: [PATCH] fix: Wild pointer crash in JPEGDEC MCU_SKIP handling (#1627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a PlatformIO pre-build script to patch JPEGDEC library. When decoding progressive JPEGs with AC coefficients, MCU_SKIP (-8) causes array index 0xFFFFF8, creating a wild pointer ~33MB past the sMCUs array. The patch redirects pMCU to sMCUs[0] when MCU_SKIP is active, preventing store-access faults while maintaining correct behavior for JPEG_SCALE_EIGHTH decoding. Devices with larger framebuffers (like the x3) (792×528 = 52,272 bytes vs 800×480 = 48,000 bytes) have less free heap, shifting the allocation and changing where the wild pointer lands. Commit 8628297 guarded the DC coefficient write (pMCU[0]) with if (iMCU >= 0), which prevents crashes for progressive JPEGs whose first scan is DC-only (iScanEnd == 0). However, if the first scan includes AC coefficients (iScanEnd > 0), the AC decode loop still writes through the wild pointer and crashes. --- platformio.ini | 1 + scripts/patch_jpegdec.py | 68 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 scripts/patch_jpegdec.py diff --git a/platformio.ini b/platformio.ini index c289d113..75ee9567 100644 --- a/platformio.ini +++ b/platformio.ini @@ -51,6 +51,7 @@ extra_scripts = pre:scripts/build_html.py pre:scripts/gen_i18n.py pre:scripts/git_branch.py + pre:scripts/patch_jpegdec.py ; Libraries lib_deps = diff --git a/scripts/patch_jpegdec.py b/scripts/patch_jpegdec.py new file mode 100644 index 00000000..015b1761 --- /dev/null +++ b/scripts/patch_jpegdec.py @@ -0,0 +1,68 @@ +""" +PlatformIO pre-build script: patch JPEGDEC for MCU_SKIP wild pointer crash. + +Problem: + JPEGDecodeMCU_P computes pMCU = &sMCUs[iMCU & 0xffffff]. When iMCU is + MCU_SKIP (-8), the bitmask produces index 0xFFFFF8 (16 777 208), creating a + pointer ~33 MB past the 392-entry sMCUs array. If the progressive JPEG's + first scan includes AC coefficients (iScanEnd > 0), the AC decode loop writes + through this wild pointer and crashes with a store-access fault. + + Upstream commit 8628297 guarded the DC coefficient write (pMCU[0]) but not the + AC coefficient writes at indices 1-63. + +Fix: + Redirect pMCU to sMCUs[0] when MCU_SKIP is active. Writes to sMCUs[1..63] + are harmless: for JPEG_SCALE_EIGHTH only sMCUs[0] is read for output, and + the DC write at sMCUs[0] is already guarded by the existing `if (iMCU >= 0)` + check. + +Applied idempotently — safe to run on every build. +""" + +Import("env") +import os + + +def patch_jpegdec(env): + libdeps_dir = os.path.join(env["PROJECT_DIR"], ".pio", "libdeps") + if not os.path.isdir(libdeps_dir): + return + for env_dir in os.listdir(libdeps_dir): + jpeg_inl = os.path.join(libdeps_dir, env_dir, "JPEGDEC", "src", "jpeg.inl") + if os.path.isfile(jpeg_inl): + _apply_mcu_skip_pointer_fix(jpeg_inl) + + +def _apply_mcu_skip_pointer_fix(filepath): + MARKER = "// CrossPoint patch: safe pMCU for MCU_SKIP" + with open(filepath, "r") as f: + content = f.read() + + if MARKER in content: + return # already patched + + # The wild-pointer line in JPEGDecodeMCU_P: + OLD = " signed short *pMCU = &pJPEG->sMCUs[iMCU & 0xffffff];" + + NEW = ( + " " + MARKER + "\n" + " signed short *pMCU = (iMCU < 0) ? pJPEG->sMCUs\n" + " : &pJPEG->sMCUs[iMCU & 0xffffff];" + ) + + if OLD not in content: + print( + "WARNING: JPEGDEC MCU_SKIP pointer patch target not found in %s " + "— library may have been updated" % filepath + ) + return + + content = content.replace(OLD, NEW, 1) + with open(filepath, "w") as f: + f.write(content) + print("Patched JPEGDEC: safe pMCU for MCU_SKIP in JPEGDecodeMCU_P: %s" % filepath) + + +# Run immediately at script import time (before compilation). +patch_jpegdec(env)