Expand description
Symlink-safe filesystem tree operations.
Recursive chmod/chown/delete walkers and tar extraction routinely escape
a container rootfs through an absolute symlink. The standard one is
var/run -> /run: a naive walker that decides “is this a directory?” with
std::path::Path::is_dir (which dereferences symlinks) recurses through
the link and mutates the host’s /run — most painfully chmod/chowning
the host’s /run/sshd, which makes sshd reject every new connection
(fatal: /run/sshd must be owned by root and not group or world-writable.).
The TCP handshake still completes, so it looks exactly like a firewall drop
while being a filesystem-permissions bug, and only a reboot (tmpfs /run
recreated clean) recovers it.
Every operation here uses std::fs::symlink_metadata (lstat, which does
NOT follow symlinks) to classify a node before touching it, and never
recurses into a symlink. The two helpers used by the OCI unpacker
(materialize_real_parent and lremove_if_symlink) additionally make
sure a write never lands through an on-disk symlinked parent that points
outside the destination tree.
Prefer these over hand-rolled read_dir + is_dir() walks. The footguns
they avoid: std::fs::{set_permissions, metadata, canonicalize},
nix::unistd::chown, and Path::{is_dir, is_file} all dereference symlinks;
only symlink_metadata/lstat and remove_file (on the link itself) do not.
Functions§
- chgrp_
setgid_ tree chgrpevery real entry underpathtogidand set every real directory todir_mode(e.g.0o2775for setgid + group write), skipping symlinks.- chmod_
tree_ files - Apply
modeto every real file underroot(directories are left untouched — applying a file mode such as0o644to a directory would clear its execute bit and make it non-traversable), skipping symlinks. Used for buildCOPY/ADD --chmod. - chmod_
tree_ writable - Make every real directory under
rootwritable+executable by the owner (0o700) so a subsequentstd::fs::remove_dir_allcan delete a tree that contains read-only directories (e.g. Fedora’s0o555ca-trust), skipping symlinks. Best-effort. - chown_
tree chownevery real file and directory underroottouid/gid(either may beNoneto leave unchanged), skipping symlinks. Used for buildCOPY/ADD --chown. A no-op when both areNone.- lremove_
if_ symlink - If
pathcurrently exists as a symlink, unlink it (so a followingFile::create/set_permissions/create_dirlands on a fresh real entry instead of writing through the link). No-op ifpathis absent or is not a symlink. - materialize_
real_ parent - Ensure every parent component of
full_path, fromrootfsdown tofull_path.parent(), is a real directory insiderootfs— replacing any component that is currently a symlink with a real directory — so a subsequent write tofull_pathcannot be redirected outsiderootfs. - relativize_
abs_ symlink - Compute a rootfs-confined relative target for an absolute symlink.
- remove_
path_ no_ follow - Remove
pathwithout ever following a symlink: a symlink (or any non-dir) is unlinked viastd::fs::remove_file(which removes the link itself, not its target); a real directory is removed withstd::fs::remove_dir_all(whose own top-level entry islstat’d, so it will not traverse a symlink either). A missing path is not an error. - walk_
no_ follow - Walk
rootdepth-first without ever following a symlink, invokingvisit(path, metadata)for every real file and directory (themetadatais thelstatresult, sometadata.is_dir()is the true on-disk type).