# A15 — aioWin: transient heap-interior alias plus unchecked malloc in sliceWaitForMultipleObjects

Bug ref      : always.md A.15 ; pharo.md §1 (partial), §4.7
Severity     : MEDIUM (latent alias hazard + crash on OOM and on CreateThread failure)
File         : extracted/vm/src/win/aioWin.c
Lines (HEAD) : 451-479 (`sliceWaitForMultipleObjects`)

## Problem

```c
static HANDLE sliceWaitForMultipleObjects(HANDLE* allHandles, int initialIndex, int sizeToProcess, long microSeconds){
	HANDLE r;
	HANDLE* handles;
	struct sliceData* sliceData = malloc(sizeof(struct sliceData));

	sliceData->handles = &(allHandles[initialIndex]);     // (1) heap-interior alias
	sliceData->size = sizeToProcess;
	sliceData->microSeconds = microSeconds;
	
	handles = malloc(sizeof(HANDLE) * sizeToProcess);     // (2) unchecked
	for(int i = 0; i < sizeToProcess; i++){
		handles[i] = sliceData->handles[i];               // (3) reads through alias
	}
	sliceData->handles = handles;                         // (4) re-binds to owned copy

	r = CreateThread(NULL, 0, ..., sliceData, 0, NULL);

	if(r)
		return r;
	// CreateThread failed: sliceData and handles are leaked.
```

Three independent defects:

  1. Between (1) and (4) the field `sliceData->handles` aliases the
     `allHandles` array passed in. Any future early-return inserted
     between those two lines leaves a long-lived structure pointing
     into someone else's buffer.
  2. The `malloc` at (2) is unchecked. On OOM, line (3) dereferences
     NULL.
  3. The first `malloc` (`sliceData`) is also unchecked; same NULL
     deref on line (1).
  4. On `CreateThread` failure both allocations are leaked.

## Fix

Allocate and check first, populate the structure once with the
owned buffer, never alias. Free on failure paths.

```diff
--- a/extracted/vm/src/win/aioWin.c
+++ b/extracted/vm/src/win/aioWin.c
@@ -449,28 +449,38 @@
 }
 
 static HANDLE sliceWaitForMultipleObjects(HANDLE* allHandles, int initialIndex, int sizeToProcess, long microSeconds){
 
 	HANDLE r;
-	HANDLE* handles;
-	struct sliceData* sliceData = malloc(sizeof(struct sliceData));
+	HANDLE* handles = malloc(sizeof(HANDLE) * sizeToProcess);
+	if (!handles) {
+		logError("sliceWaitForMultipleObjects: out of memory (handles)");
+		return NULL;
+	}
+
+	struct sliceData* sliceData = malloc(sizeof(struct sliceData));
+	if (!sliceData) {
+		free(handles);
+		logError("sliceWaitForMultipleObjects: out of memory (sliceData)");
+		return NULL;
+	}
 
-	sliceData->handles = &(allHandles[initialIndex]);
+	for(int i = 0; i < sizeToProcess; i++){
+		handles[i] = allHandles[initialIndex + i];
+	}
+
+	sliceData->handles = handles;
 	sliceData->size = sizeToProcess;
 	sliceData->microSeconds = microSeconds;
-	
-	handles = malloc(sizeof(HANDLE) * sizeToProcess);
-	for(int i = 0; i < sizeToProcess; i++){
-		handles[i] = sliceData->handles[i];
-	}
-	sliceData->handles = handles;
 
 	r = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) waitHandlesThreadFunction, sliceData, 0, NULL);
 
 	if(r)
 		return r;
 
 	int lastError = GetLastError();
+	free(handles);
+	free(sliceData);
 
 	char* msg = formatMessageFromErrorCode(lastError);
 
 	logError("Error CreateThread: %d - %s", lastError,msg);
```

## Test plan

- Inject `CreateThread` failure (e.g. by exhausting handles) and
  verify with a leak checker that no allocation survives.
- Force `malloc` failure (Windows: `_set_new_handler` or use a
  shim); verify clean `NULL` return.
- Verify `waitHandlesThreadFunction` still frees the buffer on
  normal exit (it does — line 446-447 free `handles` and `sliceData`
  in that order).

## Risk notes

- The thread function's freeing pattern is preserved
  (`waitHandlesThreadFunction` frees `sliceData->handles` then
  `sliceData`). Both pointers it sees are now always the owned copy.
- Removes the transient heap-interior alias entirely — there is no
  longer any window in which `sliceData->handles` points into
  `allHandles`.
- Caller (`aioPoll` at line 481+) is unchanged: still receives a
  HANDLE or NULL.
