Skip to main content

Module safe_fs

Module safe_fs 

Source
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
chgrp every real entry under path to gid and set every real directory to dir_mode (e.g. 0o2775 for setgid + group write), skipping symlinks.
chmod_tree_files
Apply mode to every real file under root (directories are left untouched — applying a file mode such as 0o644 to a directory would clear its execute bit and make it non-traversable), skipping symlinks. Used for build COPY/ADD --chmod.
chmod_tree_writable
Make every real directory under root writable+executable by the owner (0o700) so a subsequent std::fs::remove_dir_all can delete a tree that contains read-only directories (e.g. Fedora’s 0o555 ca-trust), skipping symlinks. Best-effort.
chown_tree
chown every real file and directory under root to uid/gid (either may be None to leave unchanged), skipping symlinks. Used for build COPY/ADD --chown. A no-op when both are None.
lremove_if_symlink
If path currently exists as a symlink, unlink it (so a following File::create/set_permissions/create_dir lands on a fresh real entry instead of writing through the link). No-op if path is absent or is not a symlink.
materialize_real_parent
Ensure every parent component of full_path, from rootfs down to full_path.parent(), is a real directory inside rootfs — replacing any component that is currently a symlink with a real directory — so a subsequent write to full_path cannot be redirected outside rootfs.
relativize_abs_symlink
Compute a rootfs-confined relative target for an absolute symlink.
remove_path_no_follow
Remove path without ever following a symlink: a symlink (or any non-dir) is unlinked via std::fs::remove_file (which removes the link itself, not its target); a real directory is removed with std::fs::remove_dir_all (whose own top-level entry is lstat’d, so it will not traverse a symlink either). A missing path is not an error.
walk_no_follow
Walk root depth-first without ever following a symlink, invoking visit(path, metadata) for every real file and directory (the metadata is the lstat result, so metadata.is_dir() is the true on-disk type).