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.
This commit is contained in:
+76
-19
@@ -1,23 +1,29 @@
|
||||
"""
|
||||
PlatformIO pre-build script: patch JPEGDEC for MCU_SKIP wild pointer crash.
|
||||
PlatformIO pre-build script: patch JPEGDEC for safe MCU_SKIP handling.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Upstream commit 8628297 guarded the DC coefficient write (pMCU[0]) but not the
|
||||
AC coefficient writes at indices 1-63.
|
||||
Two patches, both required:
|
||||
|
||||
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.
|
||||
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).
|
||||
|
||||
Applied idempotently — safe to run on every build.
|
||||
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")
|
||||
@@ -32,6 +38,7 @@ def patch_jpegdec(env):
|
||||
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):
|
||||
@@ -40,9 +47,8 @@ def _apply_mcu_skip_pointer_fix(filepath):
|
||||
content = f.read()
|
||||
|
||||
if MARKER in content:
|
||||
return # already patched
|
||||
return
|
||||
|
||||
# The wild-pointer line in JPEGDecodeMCU_P:
|
||||
OLD = " signed short *pMCU = &pJPEG->sMCUs[iMCU & 0xffffff];"
|
||||
|
||||
NEW = (
|
||||
@@ -54,7 +60,7 @@ def _apply_mcu_skip_pointer_fix(filepath):
|
||||
if OLD not in content:
|
||||
print(
|
||||
"WARNING: JPEGDEC MCU_SKIP pointer patch target not found in %s "
|
||||
"— library may have been updated" % filepath
|
||||
"-- library may have been updated" % filepath
|
||||
)
|
||||
return
|
||||
|
||||
@@ -64,5 +70,56 @@ def _apply_mcu_skip_pointer_fix(filepath):
|
||||
print("Patched JPEGDEC: safe pMCU for MCU_SKIP in JPEGDecodeMCU_P: %s" % filepath)
|
||||
|
||||
|
||||
# Run immediately at script import time (before compilation).
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user