# Extended 3.25 — LargeIntegers digitLshift: shiftCount overflow → undersized alloc

Bug ref      : pharo.md §3.25
Severity     : HIGH (heap write past undersized newOop, attacker-chosen shiftCount)
File         : extracted/plugins/LargeIntegers/src/common/LargeIntegers.c
Lines (HEAD) : ~640-660 (`digitLshift`)

## Problem

```c
newByteLen = ((highBit + shiftCount) + 7) / 8;
...
newOop = instantiateClassindexableSize(fetchClassOf(anOop), newByteLen);
```

`shiftCount` is image-supplied. On a 32-bit `sqInt`, a large
`shiftCount` overflows the `highBit + shiftCount` sum to a small
positive value; `newByteLen` is then much smaller than
`cDigitLshift` will write into. The shift loop then walks past
`newOop`.

## Fix

Validate `shiftCount` upfront (must be non-negative, and
`shiftCount <= 0x10000` or some reasonable cap), and use a 64-bit
intermediate for the sum.

```diff
diff --git a/extracted/plugins/LargeIntegers/src/common/LargeIntegers.c b/extracted/plugins/LargeIntegers/src/common/LargeIntegers.c
index 5bac5ba9a..3fcbc39a7 100644
--- a/extracted/plugins/LargeIntegers/src/common/LargeIntegers.c
+++ b/extracted/plugins/LargeIntegers/src/common/LargeIntegers.c
@@ -636,10 +636,25 @@ digitLshift(sqInt anOop, sqInt shiftCount)
 
 	/* begin digitSizeOfLargeInt: */
 	oldDigitLen = ((slotSizeOf(anOop)) + 3) / 4;
+	if (shiftCount < 0 || shiftCount > 0x100000) {
+		/* shiftCount is image-supplied; large values wrap the sum below
+		 * on 32-bit sqInt and produce an undersized allocation. */
+		primitiveFailFor(PrimErrBadArgument);
+		return null;
+	}
 	if (((highBit = cDigitHighBitlen(((unsigned int *) (firstIndexableField(anOop))), oldDigitLen))) == 0) {
 		return instantiateClassindexableSize(fetchClassOf(anOop), 1);
 	}
-	newByteLen = ((highBit + shiftCount) + 7) / 8;
+	{
+		/* uint64 to avoid signed overflow on the sum. */
+		uint64_t sum = (uint64_t)highBit + (uint64_t)shiftCount;
+		uint64_t bytes = (sum + 7) / 8;
+		if (bytes > 0x40000000ULL) {  /* >1 GiB cap */
+			primitiveFailFor(PrimErrUnsupported);
+			return null;
+		}
+		newByteLen = (sqInt)bytes;
+	}
 	
 #if SPURVM
 	newOop = instantiateClassindexableSize(fetchClassOf(anOop), newByteLen);
```

## Test plan

- 32-bit build: call `primDigitBitShiftMagnitude` with shift large
  enough that `highBit + shiftCount` wraps. Before: ASAN reports
  heap write past `newOop`. After: primitive fails with
  `PrimErrBadArgument` (or `PrimErrUnsupported` for the 1 GiB cap).
- Normal shifts (e.g. shift by 64 bits on a 256-byte LPI): unchanged.

## Risk notes

- The 1 GiB cap is conservative; tighten if any legitimate caller
  needs less. Pharo images rarely shift LPIs by more than a few
  thousand bits.
- Matches the same overflow pattern as 3.4, 3.5, 3.8.
