Skip to content

Audit: 6 grates have fd-translation bugs (same root cause as the IPC grate postgres bug) #114

@rennergade

Description

@rennergade

While debugging the IPC grate's PostgreSQL failures (fixed in the fix-ipc-grate-fd-translation branch), we identified an architectural pitfall that affects every Rust grate that allocates its own fdtables entries.

Root cause

The grate's fdtables instance lives in WASM linear memory; RawPOSIX's lives on the host. They are independent globals. When a grate calls fdtables::get_unused_virtual_fd to allocate its own entries (custom fdkind, e.g. IPC_PIPE, DEVNULL_KIND, IMFS_FDKIND), the grate-vfd does not match the runtime-vfd. From that point on, any fd-taking syscall the grate forwards to RawPOSIX must translate the user-supplied grate-vfd to its underfd (the runtime-vfd) — otherwise RawPOSIX gets a bogus fd and returns EBADF (or panics).

Identity mapping with the runtime works as long as the two counters stay in lockstep. As soon as the grate allocates one of its own entries before the user opens a file, the counters diverge and every subsequent forward sends the wrong fd. PostgreSQL hit this because it creates postmaster_alive_fds (pipe at fds 3,4) before opening config files.

The full pitfall list lives in the fix-ipc-grate-fd-translation branch commit message. Highlights: mmap has fd in arg5; epoll_ctl has fd in arg3 too; *at syscalls take AT_FDCWD which must NOT be translated; dup2 can't forward grate-vfd as newfd to the runtime; poll/select need fd translation inside their pollfd buffers / fd_set bitmaps.

Audit results

Six grates allocate their own fdtable entries. All six have the same class of bug.

🔴 Broken — same root cause as the postgres IPC bug

devnull-grate (rust-grates/devnull-grate/src/)

  • open_handler (handlers.rs:67) forces `grate-vfd == runtime-vfd` via `get_specific_virtual_fd(..., ret, 0, ret, ...)`. Works only until the counters diverge, then DEVNULL entries get clobbered by future opens.
  • `read` / `write` / `close` / `dup` / `dup2` (handlers.rs:86–265) forward `args[0]` raw without translation.
  • `dup2_handler` forwards user grate-vfd as `newfd` to the runtime — runtime can't honor it.
  • Registration gap: missing `lseek`, `ioctl`, `fcntl`, `fstat`, `mmap`, `openat`, `dup3`, `*v` variants, all sockets/poll/epoll.
  • No `register_close_handlers(DEVNULL_KIND, …)`, no `SYS_EXIT`/`SYS_EXIT_GROUP`, fork doesn't re-overlay.

`mtls-grate` (`rust-grates/mtls-grate/src/handlers.rs`)

Half-translated. `read`/`write` plaintext branches correctly use `entry.underfd` (lines 400, 512). Everything else doesn't:

  • `connect` (178, 187), `accept` (280, 287), `close` (861, 868) forward user grate-vfd directly.
  • `dup`/`dup2` (730–833) both broken — neither input translated nor output `register_kernel_fd`'d.
  • Registration gap: `bind`, `listen`, `shutdown`, all `sockopt`/`sockname`/`peername`, `sendto`/`recvfrom`/`sendmsg`/`recvmsg`, `accept4`, `fcntl`, `ioctl`, `poll`/`select`/`epoll_`, plus most file ops.

`write-filter-grate` (`rust-grates/write-filter-grate/src/handlers.rs`)

  • Same pattern as devnull: identity mapping in `open_handler` (78), no translation on `write`/`writev`/`pwrite`/`dup`/`dup2`/`close` forwards.
  • `dup2` broken in the same way.
  • Registration gap (`read` missing, plus the usual long tail).
  • Uses `check_cage_exists` as a guard (anti-pattern).

`fdtables-test-grate` (`rust-grates/fdtables-test-grate/src/main.rs`)

  • Uses `FDT_KIND=1` (non-kernel) but stores `underfd = ret` (runtime-vfd) — identity by coincidence.
  • All forwards (`close`, `dup`, `dup2`, `read`, `write` at 111/128/150/215/246) discard `entry.underfd` and pass the grate-vfd. The `translate_virtual_fd` calls just before each forward fetch the entry and throw it away.
  • Test grate, but still broken; would mislead anyone using it as a reference.

`imfs-grate` (`rust-grates/imfs-grate/src/`)

  • Pure in-memory allocator (`IMFS_FDKIND=1`), so I/O on IMFS fds doesn't forward — that part is fine.
  • BUT: registration gap means `fstat`, `dup`, `mmap`, `getdents`, `*at`, etc. on an IMFS fd go straight to RawPOSIX with a grate-vfd → EBADF. Most real programs hit this.
  • `write_handler`/`pwrite_handler` (198-204, 413-419) call `libc::write(fd, ...)` directly when `fd < 3`. This bypasses Lind entirely and writes to the grate process's own stdio, not the cage's. Real bug.
  • `imfs::fork` (`imfs/mod.rs:405-411`) clones `FDInfo` Arc — parent/child share the same offset, so concurrent reads/writes corrupt each other's position. Should deep-clone.
  • Multiple `unwrap()` panic sites on missing `fd_info` entries (`mod.rs:240, 246, 382, 639`).
  • No close handlers, no `SYS_EXIT` cleanup.

🟡 Works by accident — latent

`resource-grate` (`rust-grates/resource-grate/src/handlers.rs`)

  • Pins every entry with `get_specific_virtual_fd(cage, ret, FD_FILE/SOCKET, ``0``, …)` — so grate-vfd matches runtime-vfd by construction. Forwarding raw `args[0]` is currently safe.
  • BUT `underfd` is stored as literal `0`, not the runtime-vfd. The moment anyone reads `entry.underfd` for forwarding, every syscall goes to fd=0. Latent bug at handlers.rs:223, 447, 562.
  • No fork re-overlay, no close handlers, no `SYS_EXIT_GROUP`, missing `fcntl`/`dup`/`dup2`/`dup3`/`openat`/`accept4`/`socketpair`/`pipe*` — all of which create new fds that the grate's resource counters miss entirely.

Suggested fix order

These are independent. Roughly by user-impact:

  1. `devnull-grate` and `write-filter-grate` — small (~280 LOC each), same fix shape as the IPC grate. Straightforward.
  2. `mtls-grate` — half-translated, targeted patches on the broken syscalls.
  3. `imfs-grate` — host-libc stdio bypass and fork offset-sharing bugs bite even without translation issues; most user-visible.
  4. `resource-grate` — small fix (store correct `underfd`) plus the missing-handlers cleanup.
  5. `fdtables-test-grate` — it's a test, but actively misleading; fix or delete.

Reference

The fix shape lives in the IPC grate now: see `forward_with_fd1`, `translate_to_underfd`, `register_kernel_fd` helpers, plus the `translate_fd1_handler!` macro. Branch: `fix-ipc-grate-fd-translation`.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions