refactor: apply JPEGDEC patches via git apply, not string replace

The previous patch_jpegdec.py used in-place string replacement with a
single shared marker for two distinct DC writes (main store and
successive-approximation update). If only one of the two anchors
matched, the file was written half-patched and the shared marker locked
the partial state in for every subsequent run.

Generate the fixes as `git format-patch` artifacts under
scripts/jpegdec_patches/, then apply them in lexical order via
`git apply`. Idempotency is decided by git itself: `--check --reverse`
succeeds means already applied; `--check` succeeds means appliable;
neither aborts the build rather than leaving a half-patched file.

No behaviour change to the patched JPEGDEC source: same redirect, same
DC guards, same intent. Just stops the pre-build script from doing
something it has no business doing.
This commit is contained in:
Jeremy Klein
2026-05-19 20:05:07 +02:00
committed by jpirnay
parent 597edeebd8
commit 9e68ced046
3 changed files with 128 additions and 99 deletions
@@ -0,0 +1,28 @@
From 5dff5afab0c68d0d0c4385d72e6f0c030b613960 Mon Sep 17 00:00:00 2001
From: patch <patch@local>
Date: Mon, 18 May 2026 20:57:54 -0700
Subject: [PATCH 1/2] Redirect pMCU to sMCUs[0] when iMCU < 0 (MCU_SKIP)
---
src/jpeg.inl | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/jpeg.inl b/src/jpeg.inl
index a60b548..26bcf6f 100644
--- a/src/jpeg.inl
+++ b/src/jpeg.inl
@@ -1824,7 +1824,10 @@ static int JPEGDecodeMCU_P(JPEGIMAGE *pJPEG, int iMCU, int *iDCPredictor)
unsigned short *pFast;
uint32_t usHuff; // this prevents an unnecessary & 65535 for shorts
signed int iPositive, iNegative, iCoeff;
- signed short *pMCU = &pJPEG->sMCUs[iMCU & 0xffffff];
+ // CrossPoint patch: redirect pMCU to sMCUs[0] when MCU_SKIP to avoid
+ // a wild pointer (~33 MB past sMCUs) that store-faults on AC writes.
+ signed short *pMCU = (iMCU < 0) ? pJPEG->sMCUs
+ : &pJPEG->sMCUs[iMCU & 0xffffff];
uint32_t ulBitOff;
my_ulong ulCode, ulBits, ulTemp; // local copies to allow compiler to use register vars
uint8_t *pBuf;
--
2.50.1 (Apple Git-155)
@@ -0,0 +1,41 @@
From f6238b54c2de34c8e29b0d371a7e902d9cf579bf Mon Sep 17 00:00:00 2001
From: patch <patch@local>
Date: Mon, 18 May 2026 20:58:22 -0700
Subject: [PATCH 2/2] Guard pMCU[0] DC writes against MCU_SKIP
---
src/jpeg.inl | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/jpeg.inl b/src/jpeg.inl
index 26bcf6f..1bd38b2 100644
--- a/src/jpeg.inl
+++ b/src/jpeg.inl
@@ -1855,7 +1855,11 @@ static int JPEGDecodeMCU_P(JPEGIMAGE *pJPEG, int iMCU, int *iDCPredictor)
{
// (*iDCPredictor) |= iPositive; // in case the scan is run more than once
// pMCU[0] = *iDCPredictor; // store in MCU[0]
- pMCU[0] |= iPositive;
+ // CrossPoint patch: guard against MCU_SKIP. The pMCU
+ // redirect makes &sMCUs[0] safe to dereference, but
+ // writing here would clobber the just-decoded Y DC.
+ if (iMCU >= 0)
+ pMCU[0] |= iPositive;
}
goto mcu_done; // that's it
}
@@ -1887,7 +1891,10 @@ static int JPEGDecodeMCU_P(JPEGIMAGE *pJPEG, int iMCU, int *iDCPredictor)
ulCode <<= pJPEG->cApproxBitsLow; // successive approximation shift value
(*iDCPredictor) += ulCode;
}
- pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
+ // CrossPoint patch: guard against MCU_SKIP. See note on the
+ // matching SA write above.
+ if (iMCU >= 0)
+ pMCU[0] = (short)*iDCPredictor; // store in MCU[0]
}
// Now get the other 63 AC coefficients
pFast = &pJPEG->usHuffAC[pJPEG->ucACTable * HUFF11SIZE];
--
2.50.1 (Apple Git-155)
+59 -99
View File
@@ -1,125 +1,85 @@
"""
PlatformIO pre-build script: patch JPEGDEC for safe MCU_SKIP handling.
PlatformIO pre-build script: apply CrossPoint's JPEGDEC patches via `git apply`.
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.
The upstream JPEGDEC pin still has the wild-pointer + DC-write bugs in
JPEGDecodeMCU_P that surface when EIGHT_BIT_GRAYSCALE decodes a 3-component
progressive JPEG (each Y MCU drags two MCU_SKIP calls behind it for Cb/Cr).
The patches in `scripts/jpegdec_patches/` carry the fix; this script applies
each one against the libdep working tree.
Two patches, both required:
Each patch's idempotency is decided by git itself:
* `git apply --check --reverse` succeeds -> already applied, skip
* `git apply --check` succeeds -> apply
* neither succeeds -> abort the build
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.
Patches live in `scripts/jpegdec_patches/` as one-commit-per-fix files
(see the file headers for context). Applied in lexical order.
"""
Import("env")
import os
import subprocess
import sys
PATCH_DIR = os.path.join(env["PROJECT_DIR"], "scripts", "jpegdec_patches")
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:
patches = _patch_files()
if not patches:
return
for env_dir in os.listdir(libdeps_dir):
jpeg_dir = os.path.join(libdeps_dir, env_dir, "JPEGDEC")
if not os.path.isdir(os.path.join(jpeg_dir, ".git")):
continue
for patch in patches:
_apply_one(jpeg_dir, patch)
OLD = " signed short *pMCU = &pJPEG->sMCUs[iMCU & 0xffffff];"
NEW = (
" " + MARKER + "\n"
" signed short *pMCU = (iMCU < 0) ? pJPEG->sMCUs\n"
" : &pJPEG->sMCUs[iMCU & 0xffffff];"
def _patch_files():
if not os.path.isdir(PATCH_DIR):
return []
return sorted(
os.path.join(PATCH_DIR, name)
for name in os.listdir(PATCH_DIR)
if name.endswith(".patch")
)
if OLD not in content:
print(
"WARNING: JPEGDEC MCU_SKIP pointer patch target not found in %s "
"-- library may have been updated" % filepath
)
def _apply_one(jpeg_dir, patch_path):
name = os.path.basename(patch_path)
if _git_apply_succeeds(jpeg_dir, patch_path, reverse=True):
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
if not _git_apply_succeeds(jpeg_dir, patch_path, reverse=False):
# Not applied, not appliable -- the libdep source has diverged from
# what the patch expects. Don't write a half-patched file.
result = subprocess.run(
["git", "apply", "--check", patch_path],
cwd=jpeg_dir,
capture_output=True,
text=True,
)
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
sys.stderr.write(
"ERROR: JPEGDEC patch %s does not apply cleanly:\n%s%s\n"
% (name, result.stdout, result.stderr)
)
raise SystemExit(1)
subprocess.run(["git", "apply", patch_path], cwd=jpeg_dir, check=True)
print("Applied JPEGDEC patch: %s" % name)
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)
def _git_apply_succeeds(jpeg_dir, patch_path, *, reverse):
cmd = ["git", "apply", "--check"]
if reverse:
cmd.append("--reverse")
cmd.append(patch_path)
return subprocess.run(
cmd, cwd=jpeg_dir, capture_output=True, text=True
).returncode == 0
patch_jpegdec(env)