diff --git a/platformio.ini b/platformio.ini index c289d1133..75ee95672 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 000000000..015b17616 --- /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)