# Extended 4.9 — sqExternalSemaphores: realloc-NULL deref + leak + int overflow

Bug ref      : pharo.md §4.9
Severity     : MEDIUM-HIGH (NULL deref on realloc fail; leak of old buffer; multiplication overflow)
File         : extracted/vm/src/common/sqExternalSemaphores.c
Lines (HEAD) : 106-109 (`ioSetMaxExtSemTableSize`)

## Problem

```c
signalRequests = realloc(signalRequests, sz * sizeof(SignalRequest));
memset(signalRequests + numSignalRequests, 0, ...);
```

Three issues:

  1. `realloc` failure aliases NULL into `signalRequests`, leaking
     the original buffer.
  2. The subsequent `memset(signalRequests + numSignalRequests, …)`
     dereferences NULL + offset → SEGV.
  3. `sz * sizeof(SignalRequest)` is `int * size_t`. If the image
     header reports a huge external-semaphore count, the multiply
     wraps and we allocate too small, then memset writes past the
     end.

## Fix

```diff
diff --git a/extracted/vm/src/common/sqExternalSemaphores.c b/extracted/vm/src/common/sqExternalSemaphores.c
index 5abef48a0..ae2d2a968 100644
--- a/extracted/vm/src/common/sqExternalSemaphores.c
+++ b/extracted/vm/src/common/sqExternalSemaphores.c
@@ -103,10 +103,24 @@ ioSetMaxExtSemTableSize(int n)
 	if (numSignalRequests < n) {
 		int sz = 1 << highBit(n-1);
 		assert(sz >= n);
-		signalRequests = realloc(signalRequests, sz * sizeof(SignalRequest));
+		{
+			size_t newSize;
+			void *resized;
+			if (sz < 0 || (size_t)sz > SIZE_MAX / sizeof(SignalRequest)) {
+				logError("ioSetMaxExtSemTableSize: requested size %d overflows", sz);
+				return;
+			}
+			newSize = (size_t)sz * sizeof(SignalRequest);
+			resized = realloc(signalRequests, newSize);
+			if (resized == NULL) {
+				logError("ioSetMaxExtSemTableSize: realloc(%zu) failed", newSize);
+				return;
+			}
+			signalRequests = (SignalRequest *)resized;
+		}
 		memset(signalRequests + numSignalRequests,
 				0,
-				(sz - numSignalRequests) * sizeof(SignalRequest));
+				(size_t)(sz - numSignalRequests) * sizeof(SignalRequest));
 		numSignalRequests = sz;
 	}
 }
```

## Test plan

- Under malloc-failure injection on the realloc path, function
  returns cleanly without leaking and without zeroing through NULL.
- Image header with a huge external-semaphore count: function
  refuses to allocate instead of wrapping the multiply.
- Normal expansion: unchanged behaviour.

## Risk notes

- On realloc failure the table is not grown, but the existing
  `numSignalRequests` semaphores continue to work. Callers should
  see no semaphores added past the existing capacity (consistent
  behaviour with an out-of-resources scenario).
- The SIZE_MAX overflow check is portable C99.
