Summary
do_masked_or_readonly_path() (src/libcrun/linux.c) skips a configured maskedPaths /
readonlyPaths entry when the O_PATH open of its target fails with EACCES (in addition
to ENOENT). So if a configured mask/readonly target cannot be opened due to a permission
error, crun proceeds without applying that protection, and does not fail the start. runc,
for the same operation, fails closed: it skips only the not-exist case and errors on any other
open failure, including EACCES.
This is a code-consistency / defense-in-depth question, not a reported vulnerability — I
have no exploit and, as noted below, the obvious trigger is not reachable. Filing to ask
whether the EACCES-skip is intentional and, if not, to align the fail-closed discipline with
runc.
Where
src/libcrun/linux.c, do_masked_or_readonly_path():
pathfd = safe_openat (get_private_data (container)->rootfsfd, rootfs, rel_path,
O_PATH | O_CLOEXEC, 0, err);
if (UNLIKELY (pathfd < 0))
{
if (errno != ENOENT && errno != EACCES) // <-- EACCES is treated like ENOENT
return pathfd;
crun_error_release (err);
return 0; // <-- skip the path, keep going
}
Why this is likely not a security issue (context checked)
At the point do_masked_or_readonly_path runs, inside container_init_setup
(src/libcrun/container.c), the order is:
libcrun_set_mounts -> do_masked_and_readonly_paths (the skip)
libcrun_finalize_mounts
libcrun_set_selinux_label / libcrun_set_apparmor_profile (process LSM label set AFTER)
apply_security_settings -> libcrun_set_caps (capabilities dropped AFTER)
exec
So crun is still fully privileged during masking (it holds CAP_SYS_ADMIN for the mounts
and CAP_DAC_OVERRIDE / CAP_DAC_READ_SEARCH). A root holder of CAP_DAC_OVERRIDE does not
get EACCES from filesystem DAC on an O_PATH open, so the obvious "crafted rootfs makes the
target unopenable" path is not reachable via DAC. I could not construct a case (LSM denial, or
a rootless unmapped-uid path) where a mask target ends up both unmasked and
container-reachable. Hence this is raised as a consistency question, not an exposure report.
Comparison with runc
runc's maskPaths (libcontainer/rootfs_linux.go) skips only the not-exist case and fails
closed otherwise:
dstFh, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC, 0)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
continue // skip only if it doesn't exist
}
return fmt.Errorf("can't mask path %q: %w", path, err) // fail-closed on EACCES etc.
}
Question / suggested change
Is the EACCES skip intentional (e.g. a known class of targets expected to be
permission-denied yet safe)? If so, a short comment documenting it would prevent it reading as
an accidental weakening. If not, treating EACCES as fail-closed like every other non-ENOENT
error would match runc:
if (errno != ENOENT)
return pathfd;
Version
- crun commit
2f1be57b318734d3c29b50006f3a830a20969e9c; compared against runc fc89fbd.
- Please confirm against latest
main and check for existing/duplicate reports.
Summary
do_masked_or_readonly_path()(src/libcrun/linux.c) skips a configuredmaskedPaths/readonlyPathsentry when theO_PATHopen of its target fails withEACCES(in additionto
ENOENT). So if a configured mask/readonly target cannot be opened due to a permissionerror, crun proceeds without applying that protection, and does not fail the start. runc,
for the same operation, fails closed: it skips only the not-exist case and errors on any other
open failure, including
EACCES.This is a code-consistency / defense-in-depth question, not a reported vulnerability — I
have no exploit and, as noted below, the obvious trigger is not reachable. Filing to ask
whether the
EACCES-skip is intentional and, if not, to align the fail-closed discipline withrunc.
Where
src/libcrun/linux.c,do_masked_or_readonly_path():Why this is likely not a security issue (context checked)
At the point
do_masked_or_readonly_pathruns, insidecontainer_init_setup(
src/libcrun/container.c), the order is:So crun is still fully privileged during masking (it holds
CAP_SYS_ADMINfor the mountsand
CAP_DAC_OVERRIDE/CAP_DAC_READ_SEARCH). A root holder ofCAP_DAC_OVERRIDEdoes notget
EACCESfrom filesystem DAC on anO_PATHopen, so the obvious "crafted rootfs makes thetarget unopenable" path is not reachable via DAC. I could not construct a case (LSM denial, or
a rootless unmapped-uid path) where a mask target ends up both unmasked and
container-reachable. Hence this is raised as a consistency question, not an exposure report.
Comparison with runc
runc's
maskPaths(libcontainer/rootfs_linux.go) skips only the not-exist case and failsclosed otherwise:
Question / suggested change
Is the
EACCESskip intentional (e.g. a known class of targets expected to bepermission-denied yet safe)? If so, a short comment documenting it would prevent it reading as
an accidental weakening. If not, treating
EACCESas fail-closed like every other non-ENOENTerror would match runc:
Version
2f1be57b318734d3c29b50006f3a830a20969e9c; compared against runcfc89fbd.mainand check for existing/duplicate reports.