Files
Crosspoint/scripts/patch_jpegdec.py
T
Jeremy Klein 597edeebd8 fix: guard DC writes in JPEGDEC MCU_SKIP path
EIGHT_BIT_GRAYSCALE decode of a 3-component progressive JPEG calls
JPEGDecodeMCU_P with MCU_SKIP for Cb and Cr after every Y MCU. The
existing safe-pMCU patch redirects the wild pointer to &sMCUs[0] but
leaves the DC store unguarded, so each chroma skip overwrites the
just-decoded Y DC with the chroma DC predictor. Output reads sMCUs[0],
gets the trailing Cr DC (~0), and renders an all-black image.

Add `if (iMCU >= 0)` guards to the two pMCU[0] writes (main DC store
and successive-approximation update). The pointer redirect stays as the
AC wild-pointer defence; the new guards stop the silent corruption at
sMCUs[0]. The two fixes are independent and both required.
2026-05-19 20:05:07 +02:00

126 lines
3.9 KiB
Python

"""
PlatformIO pre-build script: patch JPEGDEC for safe MCU_SKIP handling.
When iMCU is MCU_SKIP (-8), JPEGDecodeMCU_P computes pMCU as
&sMCUs[iMCU & 0xffffff] = &sMCUs[0xFFFFF8], a wild pointer ~33 MB past
the array. EIGHT_BIT_GRAYSCALE decoding of a 3-component progressive
JPEG calls JPEGDecodeMCU_P with MCU_SKIP twice per Y MCU (Cb then Cr),
so every MCU exercises the wild pointer.
Two patches, both required:
1. Redirect pMCU to &sMCUs[0] when iMCU < 0. Without this, the AC
decode loop (`pMCU[iIndex] = ...`) store-faults on any progressive
JPEG whose first scan carries AC coefficients (iScanEnd > 0).
2. Guard the two pMCU[0] DC writes with `if (iMCU >= 0)`. Without
this, patch 1 just relocates the corruption: chroma-skip DC writes
land at sMCUs[0] and clobber the freshly-decoded Y DC, producing
all-black output for progressive JPEGs at JPEG_SCALE_EIGHTH grayscale.
The AC loop body (`pMCU[iIndex] = ...` writes and matching reads) is
not separately guarded. It is unreachable on the JPEG_SCALE_EIGHTH
DC-only first-scan path, and guarding the writes without also skipping
bit consumption would desync the bitstream for the next MCU.
Both patches are idempotent.
"""
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)
_apply_dc_write_guards(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
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)
def _apply_dc_write_guards(filepath):
MARKER = "// CrossPoint patch: guard pMCU DC writes for MCU_SKIP"
with open(filepath, "r") as f:
content = f.read()
if MARKER in content:
return
OLD_DC = """\
pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
}
// Now get the other 63 AC coefficients"""
NEW_DC = """\
""" + MARKER + """
if (iMCU >= 0)
pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
}
// Now get the other 63 AC coefficients"""
OLD_SA = """\
pMCU[0] |= iPositive;
}
goto mcu_done; // that's it"""
NEW_SA = """\
if (iMCU >= 0)
pMCU[0] |= iPositive;
}
goto mcu_done; // that's it"""
if OLD_DC not in content:
print(
"WARNING: JPEGDEC DC write guard target not found in %s "
"-- library may have been updated" % filepath
)
return
content = content.replace(OLD_DC, NEW_DC, 1)
if OLD_SA in content:
content = content.replace(OLD_SA, NEW_SA, 1)
else:
print(
"WARNING: JPEGDEC successive-approximation DC guard target not found in %s "
"-- continuing without that half of the patch" % filepath
)
with open(filepath, "w") as f:
f.write(content)
print("Patched JPEGDEC: guard pMCU[0] DC writes for MCU_SKIP in JPEGDecodeMCU_P: %s" % filepath)
patch_jpegdec(env)