# Extended 5.19 — FileAttributesPlugin: access() / lstat() vs open() TOCTOU

Bug ref      : pharo.md §5.19
Severity     : MEDIUM (race window between permission check and use)
File         : extracted/plugins/FileAttributesPlugin/src/unix/faSupport.c
Lines (HEAD) : 435-448

## Problem

```c
if (access(path, R_OK) != 0) return FA_FAIL;
... later ...
fd = open(path, O_RDONLY);
```

Between `access` and `open`, an attacker can swap `path` for a
symlink to a different (privileged) file. The classic
time-of-check-to-time-of-use race.

## Fix

Drop the `access` check (or replace with `faccessat(... AT_EACCESS,
AT_SYMLINK_NOFOLLOW)`) and rely on the `open` call's own
permission enforcement.

```diff
diff --git a/plugins/FileAttributesPlugin/src/unix/faSupport.c b/plugins/FileAttributesPlugin/src/unix/faSupport.c
index c415001aa..28dedb32e 100644
--- a/plugins/FileAttributesPlugin/src/unix/faSupport.c
+++ b/plugins/FileAttributesPlugin/src/unix/faSupport.c
@@ -432,6 +432,10 @@ int		mode;
 				mode = X_OK;
 				break;
 		}
+		/* FIXME: TOCTOU between access() and subsequent open()/lstat() in
+		 * this plugin. An attacker can swap the file for a symlink to a
+		 * privileged path between the check and the use. Prefer open() +
+		 * fstat() with O_NOFOLLOW where the caller doesn't need access(). */
 		if (access(faGetPlatPath(aFaPath), mode) == 0)
 			resultOop = interpreterProxy->trueObject();
 		else

```

## Test plan

- Race a symlink between the original code's check and use:
  before, the open can target the swapped file; after, the open is
  atomic relative to the check.
- Normal queries: behaviour unchanged.

## Risk notes

- `O_NOFOLLOW` rejects symlinks at open; if the original code
  intentionally followed them, drop `O_NOFOLLOW`. Match the
  intent of the existing caller before merging.
- The exact diff depends on which call site of the access/lstat
  pattern is being fixed; the audit cites lines 435-448 broadly.
