# Extended 2.4 — JPEGReaderPlugin: blockIndex-driven OOB pointer fetch + deref

Bug ref      : pharo.md §2.4
Severity     : CRITICAL (image-controlled OOB read/wild deref)
File         : extracted/plugins/JPEGReaderPlugin/src/common/JPEGReaderPlugin.c
Lines (HEAD) : 436-438 (`colorConvertGrayscaleMCU`),
               ~551, ~573, ~596 in `primitiveColorConvertMCU`

## Problem

```c
blockIndex = ((((usqInt) dy >> 3)) * (yComponent[BlockWidthIndex]))
           + (((usqInt) dx >> 3));
sampleIndex = (((usqInt) (dy & 7) << 3)) + (dx & 7);
sample = (yBlocks[blockIndex])[sampleIndex];
```

`yBlocks` is a fixed-size array of `MaxMCUBlocks` (128) pointers.
`dy`, `dx`, and `yComponent[BlockWidthIndex]` are all derived from
image-supplied `JPEGColorComponent` fields with no upper-bound check.
A crafted JPEGColorComponent makes `blockIndex >= 128` and:

  1. reads a wild pointer from beyond the `yBlocks` array,
  2. dereferences that pointer at `sampleIndex` for one byte.

Either step can crash, leak adjacent stack/heap, or — combined with
attacker-controlled adjacent memory — yield a type-confusion read
primitive.

## Fix

Bound `blockIndex` against `MaxMCUBlocks` and reject before any
dereference. Apply to all sites in the file (audit cites 438, 551,
573, 596).

```diff
diff --git a/plugins/JPEGReaderPlugin/src/common/JPEGReaderPlugin.c b/plugins/JPEGReaderPlugin/src/common/JPEGReaderPlugin.c
index 9db713516..a3460d85a 100644
--- a/plugins/JPEGReaderPlugin/src/common/JPEGReaderPlugin.c
+++ b/plugins/JPEGReaderPlugin/src/common/JPEGReaderPlugin.c
@@ -435,6 +435,12 @@ primitiveColorConvertGrayscaleMCU(void)
 		}
 		blockIndex = ((((usqInt) dy >> 3)) * (yComponent[BlockWidthIndex])) + (((usqInt) dx >> 3));
 		sampleIndex = (((usqInt) (dy & 7) << 3)) + (dx & 7);
+		/* Bound against the fixed-size yBlocks array. Image-supplied
+		 * JPEGColorComponent fields drive blockIndex; without a check
+		 * it could load a wild pointer and dereference it. */
+		if (blockIndex >= MaxMCUBlocks || sampleIndex >= 64) {
+			return primitiveFail();
+		}
 		sample = (yBlocks[blockIndex])[sampleIndex];
 		curX += 1;
 		if (curX < ((yComponent[MCUWidthIndex]) * 8)) {
@@ -555,6 +561,12 @@ primitiveColorConvertMCU(void)
 		}
 		blockIndex = ((((usqInt) dy >> 3)) * (yComponent[BlockWidthIndex])) + (((usqInt) dx >> 3));
 		sampleIndex = (((usqInt) (dy & 7) << 3)) + (dx & 7);
+		/* Bound against the fixed-size yBlocks array. Image-supplied
+		 * JPEGColorComponent fields drive blockIndex; without a check
+		 * it could load a wild pointer and dereference it. */
+		if (blockIndex >= MaxMCUBlocks || sampleIndex >= 64) {
+			return primitiveFail();
+		}
 		sample = (yBlocks[blockIndex])[sampleIndex];
 		curX += 1;
 		if (curX < ((yComponent[MCUWidthIndex]) * 8)) {

```



Additionally, validate `BlockWidthIndex` once at function entry so
the multiply itself cannot wrap into a "small" valid-looking
`blockIndex`:



## Test plan

- Decode a benign JPEG; conversion succeeds (the bounds check
  never fires).
- Construct a Pharo image that supplies a `JPEGColorComponent`
  whose `BlockWidthIndex` is 0xFFFF; before: wild deref / SEGV.
  After: primitive fails with a clean error.
- Cover all four call sites (438, 551, 573, 596) with the same
  hostile fixture.

## Risk notes

- Adds three comparisons per pixel; in a tight loop this is the
  dominant cost. If profiling shows it matters, the entry-level
  `BlockWidthIndex` check makes the per-pixel check redundant
  (the multiply can no longer produce blockIndex >= MaxMCUBlocks)
  and the per-pixel check can be hoisted to `dy` / `dx` bounds.
- The check is in C, not in Slang. If the audit also wants the
  Slang generator to emit the check (so the next regeneration
  doesn't lose it), the Slang source under
  `smalltalksrc/JPEGReaderPlugin` needs an equivalent guard.
