Skip to content

chroot grate: virtual-CWD tracking inconsistency breaks the Perl test harness #179

Description

@vidyalakshmir

Summary

Under the chroot grate (chroot-grate.cwasm --chroot-dir /), ~38 Perl-driven coreutils tests
fail. The harness's per-subtest rename → open → unlink sequence ends with unlink failing ENOENT
even though rename and open on the same path string just succeeded, and teardown then fails with
Directory not empty. The same tests pass on the bare wasm runtime.

Reproduction

  ./run_default_tests.sh --grate chroot misc/dirname
  dirname: cannot unlink fail-1.E.orig: No such file or directory
  dirname: cannot unlink fail-2.E.orig: No such file or directory
  ... (one per subtest) ...
  cannot remove directory for /tmp/dirname.tmp-MZ1F: Directory not empty at
  //lib/perl5/5.40.4/File/Temp.pm line 921.

Affected tests (~38)

dd/skip-seek, dd/unblock, misc/base64, misc/basename, misc/comm, misc/date-next-dow,
misc/dircolors, misc/dirname, misc/expand, misc/expr, misc/factor, misc/fmt, misc/fold,
misc/head, misc/head-elide-tail, misc/md5sum, misc/md5sum-newline, misc/mktemp, misc/od,
misc/paste, misc/pr, misc/ptx, misc/sha1sum, misc/sha1sum-vec, misc/sha224sum, misc/sha256sum,
misc/sha384sum, misc/sha512sum, misc/stat-printf, misc/sum, misc/tac, misc/tail, misc/tr,
misc/tsort, misc/wc, misc/xstrtol, mv/i-1, pr/pr-tests, rm/unreadable.

Details

For each subtest, tests/Coreutils.pm (around line 529) does:
rename $out, $orig # $orig = "$out.orig"
or (warn "...cannot rename..."), next;
open IN, $orig
or (warn "...cannot open..."), (unlink $orig), next;
unlink $orig # unlink-while-open idiom; IN still open
or (warn "...cannot unlink $orig: $!\n"), $fail = 1;
Under the chroot grate: rename succeeds, open IN succeeds, but unlink fails ENOENT. The .orig
files physically exist (teardown reports the tmpdir as non-empty), so the file is at some path P
while unlink resolves the same string to a different path Q ≠ P.

Root cause

The chroot grate uses a purely virtual CWD model:

  • chdir_handler (chroot-grate/src/main.rs:1454) deliberately does not forward chdir to the host
    — it only updates the grate's in-process CAGE_CWDS table.
  • Every path-taking handler is then responsible for absolutizing relative paths itself, via
    chroot_path() → normalize_path(path, get_cage_cwd(cageid)) (chroot-grate/src/paths.rs).

This model is only correct if (a) every path syscall is intercepted, and (b) the per-cage
virtual cwd is always accurate. The rename/open-vs-unlink divergence means a relative path is
being resolved against a stale virtual cwd for at least one of those syscalls.

Prime suspect: chdir_handler only commits the cwd update if check_dir(chroot_path(&new_cwd,
cageid)) returns true (main.rs:1481):
match check_dir(chroot_path(&new_cwd, cageid)) {
false => return -20, // ENOTDIR ← silently drops the cwd update
true => { set_cage_cwd(cageid, new_cwd); return 0; }
}
If that probe fails for the freshly-mkdir'd CuTmpdir directory (e.g. a path-translation or
timing issue in check_dir), chdir silently returns ENOTDIR and the cage's virtual cwd stays
stale. Subsequent relative-path syscalls from Perl then resolve against the wrong directory.
Secondary suspect: virtual-cwd inheritance across fork/execve for the Perl cage (fork_handler →
register_cage copies the parent cwd; execve_handler leaves it untouched — both look correct on
inspection but haven't been verified at runtime).

Note: unlike the imfs grate, the chroot grate does register all the relevant handlers (rename,
renameat, renameat2, unlink, unlinkat, open, openat, chdir, getcwd) and they all call
chroot_path — so this is a cwd-tracking bug, not a missing-handler bug.

Suggested fix

  1. Don't silently drop the cwd update. In chdir_handler, when check_dir returns false, log the
    path and the chroot_path result. Verify check_dir/chroot_path actually succeed on a just-created
    directory.
  2. Add cwd tracing. Print get_cage_cwd(cageid) at the top of the rename, open, and unlink
    handlers and run one failing test (misc/dirname) to find exactly where the virtual cwd diverges
    between the three calls.
  3. Belt-and-suspenders: have chdir_handler also forward chdir to the host so that any
    un-intercepted relative path still resolves correctly, instead of relying solely on the
    virtual-cwd model.

Impact

~38 of the 44 chroot-specific regressions. Because the failure happens in the harness's
setup/compare scaffolding, the affected tests never run their real assertions — fixing the
virtual-cwd tracking should recover the large majority of them.

Note

Confirming the exact divergence point needs a syscall-level trace of one failing test (the
current run logs don't include one). Steps 1–2 above add the needed trace points.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions