Skip to content

open(O_PATH|O_NOFOLLOW) of a symlink opens the link: tar -p no longer fails - #32

Merged
chandarith-chea merged 1 commit into
mainfrom
opath-symlink
Nov 14, 2026
Merged

chandarith-chea merged 1 commit into
mainfrom
opath-symlink

Conversation

@chandarith-chea

Copy link
Copy Markdown
Contributor

Problem

Extracting a tar archive as root in a container (for example node_modules/.bin links in node:24) printed Cannot change mode to rwxrwxrwx: Too many levels of symbolic links for every symlink, and tar exited 2.

tar chmods each link it makes with fchmodat(AT_SYMLINK_NOFOLLOW). glibc before 2.39 implements that call in three steps:

  1. Open the path with O_PATH|O_NOFOLLOW.
  2. fstat the descriptor.
  3. Return EOPNOTSUPP for a link, which tar ignores.

The engine handled O_PATH as a plain read-only open, so step 1 failed with ELOOP.

Fix

native/fs.h fs_open: an O_PATH|O_NOFOLLOW open of a symlink on disk now opens the link itself.

  • It uses macOS O_SYMLINK, still together with O_NOFOLLOW_ANY, so no link before the leaf is followed.
  • fstat on the descriptor reports S_IFLNK.
  • O_NOFOLLOW without O_PATH still returns ELOOP.
  • The in-memory layer already handled this case.
  • The Linux build defines O_SYMLINK as O_PATH's value.

Tests

  • tests/native/opath_link_guest.c was added to verify_fork_sockets.py. It covers a dangling link and a link to a file:
    • the O_PATH|O_NOFOLLOW open, fstat and fstatat(AT_EMPTY_PATH) give S_IFLNK;
    • O_NOFOLLOW alone gives ELOOP;
    • fchmodat2 with AT_SYMLINK_NOFOLLOW gives EOPNOTSUPP;
    • O_PATH without O_NOFOLLOW reaches the file.
  • verify_fork_sockets.py --cpus 1 and --cpus 4: 100 PASS, 0 FAIL each (interpreter and hvf).
  • go test -race ./...: ok.
  • verify_dockivad.py --new-home on the bundle: 31/31.
  • End to end in node:24 on a test engine:
    • with the fix, extracting the archive with a ../../b/t link gives rc 0, and open(O_PATH|O_NOFOLLOW) works;
    • on Dockiva 0.18.0 the same steps give rc 2 and ELOOP.
  • The Linux CI build passed a syntax check in gcc:14.

… fails

tar extracting as root (node_modules/.bin links, in a node:24 container) printed
"Cannot change mode to rwxrwxrwx: Too many levels of symbolic links" for each
symlink and exited 2. tar chmods every link it makes with
fchmodat(AT_SYMLINK_NOFOLLOW); glibc before 2.39 does that by opening the path
with O_PATH|O_NOFOLLOW, fstat'ing the descriptor and answering EOPNOTSUPP for a
link, which tar ignores. The engine opened O_PATH as a plain read-only open, so
O_NOFOLLOW on a symlink failed with ELOOP and tar reported it.

An O_PATH|O_NOFOLLOW open of a symlink on disk now opens the link itself
(macOS O_SYMLINK, still with O_NOFOLLOW_ANY so no link before the leaf is
followed); fstat on it reports S_IFLNK. Without O_PATH, O_NOFOLLOW on a link
is ELOOP as before. Links in the in-memory layer already worked.

tests/native/opath_link_guest.c (fork suite) checks a dangling link and a link
to a file: the O_PATH open, fstat and fstatat(AT_EMPTY_PATH) give S_IFLNK,
O_NOFOLLOW alone is ELOOP, fchmodat2 NOFOLLOW is EOPNOTSUPP, and O_PATH
without O_NOFOLLOW reaches the file.
@chandarith-chea
chandarith-chea merged commit af13b2b into main Nov 14, 2026
2 checks passed
@chandarith-chea
chandarith-chea deleted the opath-symlink branch November 14, 2026 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant