# A09 — pathUtilities: `first[strlen(first)-1]` underflows when `first` is empty

Bug ref      : always.md A.9 ; pharo.md §4.3
Severity     : MEDIUM (out-of-bounds read of 1 byte; reachable from fallback path)
File         : src/pathUtilities.c
Lines (HEAD) : 157-169 (`vm_path_join_into`)

## Problem

```
EXPORT(VMErrorCode)
vm_path_join_into(char *target, size_t targetSize, const char *first, const char *second)
{
    strncpy(target, first, targetSize - 1);
    target[targetSize - 1] = 0;

    if(first[strlen(first)-1] != SEPARATOR_CHAR) {
        ...
```

When `first` is the empty string `""`, `strlen(first)` is `0` and
`first[-1]` reads one byte before the buffer. Reachable from the
`parameters/parameters.c:210-212` fallback that synthesises a join
between an empty base and a relative image path.

## Fix

Guard the dereference on a non-empty `first`. (The unconditional
append branch correctly handles the empty case via
`vm_string_append_into`.)

```diff
--- a/src/pathUtilities.c
+++ b/src/pathUtilities.c
@@ -160,7 +160,7 @@ vm_path_join_into(char *target, size_t targetSize, const char *first, const char
     strncpy(target, first, targetSize - 1);
     target[targetSize - 1] = 0;
 
-    if(first[strlen(first)-1] != SEPARATOR_CHAR) {
+    if(first[0] == 0 || first[strlen(first)-1] != SEPARATOR_CHAR) {
         vm_string_append_into(target, SEPARATOR_STRING, targetSize);
     }
     vm_string_append_into(target, second, targetSize);

```

## Test plan

- `vm_path_join_into(buf, sizeof buf, "", "image.image")` must
  produce `"/image.image"` on POSIX, `"\\image.image"` on Windows, and
  must not read out of bounds (verify with ASAN).
- `vm_path_join_into(buf, sizeof buf, "/foo", "bar")` must still
  produce `"/foo/bar"` (single separator) — i.e. the existing
  trailing-separator behavior is preserved.

## Risk

Adds one extra separator insertion when `first` is empty. On the
fallback path the alternative is undefined behavior (1-byte OOB
read), so this is strictly safer.
