Skip to content

Feature: handle-relative filesystem ops (openat/renameat) for TOCTOU-safe atomic replace #493

Description

@bobzhang

Problem

moonbitlang/async/fs exposes path-based create, rename, and opendir → Directory, but Directory only supports listing (next/read_all/close). There is no way to create or rename a file relative to a pinned directory handle.

This makes it impossible to implement a fully TOCTOU-safe atomic file replace when the target path contains intermediate directory components an adversary might swap. The standard POSIX-safe pattern is:

dirfd = open("parent", O_DIRECTORY)          // pin the directory inode once
openat(dirfd, "file.tmp", O_CREAT|O_EXCL, …) // create relative to the pinned fd
renameat(dirfd, "file.tmp", dirfd, "file")   // rename relative to the pinned fd

Because dirfd is resolved once, later swaps of the parent name are inert. With path-based create/rename, each call re-resolves the path, so an intermediate component swapped between the create and the rename can redirect where the rename lands.

Concrete case

A spreadsheet CLI (office.mbt) writes a rendered HTML document via temp-file + rename to an --out path. The temp+rename correctly prevents truncation and is safe against a symlink/hard-link at the final path component, but an intermediate-directory-component TOCTOU cannot be closed with the current API — there is no openat/renameat to pin the output directory.

Requested API (sketch)

Handle-relative operations on Directory (or a new DirHandle):

  • Directory::create_at(self, name, allow_existing?, permission?) -> File
  • Directory::rename_at(self, old_name, new_dir, new_name, replace?) -> Unit
  • (optionally) Directory::open_at, Directory::remove_at, Directory::stat_at

These map directly to openat(2) / renameat(2) and would let downstream code implement the pin-the-directory atomic-replace pattern.

Workaround today

Path-based temp+rename with a best-effort equality guard, documented residual limitation for the intermediate-component race.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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