# A10 — externalPrimitives: file-static moduleNameBuffer race on concurrent loads

Bug ref      : always.md A.10 ; pharo.md §5.3
Severity     : MEDIUM (torn path passed to dlopen/LoadLibrary on concurrent load)
File         : src/externalPrimitives.c
Lines (HEAD) : 57 (declaration), 59-75 (`tryToLoadModuleInPath`)

## Problem

```c
char moduleNameBuffer[FILENAME_MAX];

void * tryToLoadModuleInPath(char *path, const char *moduleName)
{
    ...
    for(i = 0; moduleNamePatterns[i] != NULL; i++)
    {
        snprintf(moduleNameBuffer, FILENAME_MAX, moduleNamePatterns[i], path, moduleName);
        moduleNameBuffer[FILENAME_MAX - 1] = 0;
        moduleHandle = loadModuleHandle(moduleNameBuffer);
        ...
```

`moduleNameBuffer` is a single file-static buffer. Two threads
calling `ioLoadModule` concurrently (the FFI worker thread and the
VM thread, for example) interleave their `snprintf` writes; one
thread's `loadModuleHandle(moduleNameBuffer)` then sees a torn path
that may correspond to neither plugin (and on Windows can match a
DLL the loader was not supposed to find).

## Fix

Move the buffer onto the local stack. `FILENAME_MAX` is 4 KiB on
Linux, 260 on Windows; either fits comfortably on the C stack of
the loader.

```diff
diff --git a/src/externalPrimitives.c b/src/externalPrimitives.c
index 3fd40549f..ab14130eb 100644
--- a/src/externalPrimitives.c
+++ b/src/externalPrimitives.c
@@ -54,12 +54,11 @@ const char *moduleNamePatterns[] = {
     NULL
 };
 
-char moduleNameBuffer[FILENAME_MAX];
-
 void * tryToLoadModuleInPath(char *path, const char *moduleName)
 {
     void *moduleHandle;
     int i;
+    char moduleNameBuffer[FILENAME_MAX];
 
     for(i = 0; moduleNamePatterns[i] != NULL; i++)
     {
```

## Test plan

- Concurrent test: spawn two threads, each call `ioLoadModule`
  with different plugin names in a tight loop for 10⁴ iterations.
  Before: occasional `dlopen` failures or mis-matched plugins;
  after: clean.
- Single-threaded call sites unchanged in behaviour; the buffer is
  re-used per call as before.

## Risk notes

- The buffer was extern-visible (no `static`) but is not referenced
  outside `externalPrimitives.c` — verified with `git grep
  moduleNameBuffer`. Removing the file-static breaks no external
  callers.
- Adds `FILENAME_MAX` bytes per call frame; cheap.
