Description
On a host where unprivileged overlay mounts are permitted but metacopy=on is not, the
overlay graphdriver refuses to initialise at all, even though it would work perfectly with
metacopy disabled.
This is the normal state of affairs inside an unprivileged user namespace: metacopy records
copy-up metadata in trusted.overlay.* extended attributes, and an unprivileged mount cannot
set trusted.* xattrs, so the kernel rejects metacopy=on. A rootless container engine
therefore can never use metacopy — which the driver already knows how to cope with, since
metacopy is optional and doesMetacopy() returning false is a perfectly good answer.
The bug is that a probe failure is treated as fatal rather than as "unsupported".
Steps to reproduce
Any environment where an unprivileged overlay mount succeeds but metacopy=on is denied. Ours
is a Kubernetes/OpenShift pod under a restricted SCC (SELinux type container_t, all
capabilities dropped at the container level, no subuid/subgid ranges so buildah's own user
namespace maps a single ID):
# storage root must be on a non-overlayfs filesystem (the kernel refuses an overlay
# upperdir on overlay); in our pods only the /builds emptyDir (xfs) qualifies.
buildah --root /builds/store/root --runroot /builds/store/run \
--storage-driver overlay --storage-opt overlay.ignore_chown_errors=true \
info
Actual result
Error: failed to mount overlay for metacopy check with "" options: permission denied
The driver is unusable. Every subsequent build fails.
Crucially, the runroot shows that the basic overlay mount had already succeeded:
$ ls /builds/store/run/overlay/
overlay-true
checkAndRecordOverlaySupport() runs before the metacopy check, performs a real overlay
mount, and records overlay-true. Reaching the metacopy error therefore proves that
unprivileged overlay works in this environment — only the metacopy=on variant is refused.
Expected result
doesMetacopy() returning EPERM should be recorded as metacopy unsupported and the
driver should initialise with metacopy off, exactly as it does on hosts where the probe
returns a clean negative.
Analysis
In drivers/overlay/overlay.go, Init():
supportsDType, err = checkAndRecordOverlaySupport(home, runhome) // real overlay mount
if err != nil {
return nil, err // different error text
}
feature := fmt.Sprintf("metacopy(%s)", opts.mountOptions)
metacopyCacheResult, _, err := cachedFeatureCheck(runhome, feature)
The first check is fatal-on-failure and produces a distinct message
(kernel does not support overlay fs / 'overlay' is not supported over %s). Because we see
the metacopy message instead, the basic mount succeeded. The metacopy probe's error is then
propagated out of Init() rather than being interpreted.
Evidence that this is a probe-classification problem and not an environment problem: on an
ordinary developer workstation (no SELinux, rootless podman), the same probe yields a clean
negative and the cache file metacopy()-false is written — overlay then works normally, with
metacopy off. The pod differs only in that the refusal surfaces as EPERM rather than as an
unsupported result.
Suggested fix
Treat a permission error from the metacopy probe as false rather than as a hard error —
i.e. record the negative result and continue, in the same way a clean negative is handled.
EPERM from metacopy=on is expected and unremarkable for any unprivileged mount, so it
carries no information beyond "metacopy is not available here".
Workaround (in use)
The probe result is cached in the runroot as a zero-length file named
<feature>-<result>, and cachedFeatureCheck() short-circuits when it is present. Seeding it
before the first buildah call skips the failing probe entirely:
mkdir -p "$RUNROOT/overlay"
: > "$RUNROOT/overlay/metacopy()-false"
The parentheses are literal and the argument is empty because mountOptions is empty; setting
--storage-opt overlay.mountopt=<opts> changes the key to metacopy(<opts>) and the probe
runs again, so that is not an alternative workaround.
This is an undocumented internal and we would rather not depend on it — hence this report.
Version
buildah v1.43.1 (vendoring go.podman.io/storage v1.62.0), compiled from the upstream
release tarball. Node kernel 5.14.0-570.117.1.el9_6 (RHCOS), UBI9 runner image.
Environment
- GitLab Runner Kubernetes executor on OpenShift, restricted SCC.
- Pod:
container_t SELinux type (unchanged inside buildah's own user namespace), container
CapEff 0000000000000000, buildah's namespace single-ID (uid_map: 0 1000 1) with
/proc/self/setgroups = deny.
--isolation chroot, --format docker, overlay.ignore_chown_errors=true.
- Storage root on an xfs
emptyDir; the container rootfs, /tmp and /var/tmp are all
overlayfs, so the store must be relocated for overlay to be legal at all.
/dev/fuse absent, so fuse-overlayfs is not an alternative.
Description
On a host where unprivileged overlay mounts are permitted but
metacopy=onis not, theoverlay graphdriver refuses to initialise at all, even though it would work perfectly with
metacopy disabled.
This is the normal state of affairs inside an unprivileged user namespace:
metacopyrecordscopy-up metadata in
trusted.overlay.*extended attributes, and an unprivileged mount cannotset
trusted.*xattrs, so the kernel rejectsmetacopy=on. A rootless container enginetherefore can never use metacopy — which the driver already knows how to cope with, since
metacopyis optional anddoesMetacopy()returningfalseis a perfectly good answer.The bug is that a probe failure is treated as fatal rather than as "unsupported".
Steps to reproduce
Any environment where an unprivileged overlay mount succeeds but
metacopy=onis denied. Oursis a Kubernetes/OpenShift pod under a restricted SCC (SELinux type
container_t, allcapabilities dropped at the container level, no subuid/subgid ranges so buildah's own user
namespace maps a single ID):
Actual result
The driver is unusable. Every subsequent build fails.
Crucially, the runroot shows that the basic overlay mount had already succeeded:
checkAndRecordOverlaySupport()runs before the metacopy check, performs a real overlaymount, and records
overlay-true. Reaching the metacopy error therefore proves thatunprivileged overlay works in this environment — only the
metacopy=onvariant is refused.Expected result
doesMetacopy()returningEPERMshould be recorded as metacopy unsupported and thedriver should initialise with metacopy off, exactly as it does on hosts where the probe
returns a clean negative.
Analysis
In
drivers/overlay/overlay.go,Init():The first check is fatal-on-failure and produces a distinct message
(
kernel does not support overlay fs/'overlay' is not supported over %s). Because we seethe metacopy message instead, the basic mount succeeded. The metacopy probe's error is then
propagated out of
Init()rather than being interpreted.Evidence that this is a probe-classification problem and not an environment problem: on an
ordinary developer workstation (no SELinux, rootless podman), the same probe yields a clean
negative and the cache file
metacopy()-falseis written — overlay then works normally, withmetacopy off. The pod differs only in that the refusal surfaces as
EPERMrather than as anunsupported result.
Suggested fix
Treat a permission error from the metacopy probe as
falserather than as a hard error —i.e. record the negative result and continue, in the same way a clean negative is handled.
EPERMfrommetacopy=onis expected and unremarkable for any unprivileged mount, so itcarries no information beyond "metacopy is not available here".
Workaround (in use)
The probe result is cached in the runroot as a zero-length file named
<feature>-<result>, andcachedFeatureCheck()short-circuits when it is present. Seeding itbefore the first buildah call skips the failing probe entirely:
The parentheses are literal and the argument is empty because
mountOptionsis empty; setting--storage-opt overlay.mountopt=<opts>changes the key tometacopy(<opts>)and the proberuns again, so that is not an alternative workaround.
This is an undocumented internal and we would rather not depend on it — hence this report.
Version
buildah v1.43.1 (vendoring
go.podman.io/storage v1.62.0), compiled from the upstreamrelease tarball. Node kernel
5.14.0-570.117.1.el9_6(RHCOS), UBI9 runner image.Environment
container_tSELinux type (unchanged inside buildah's own user namespace), containerCapEff0000000000000000, buildah's namespace single-ID (uid_map: 0 1000 1) with/proc/self/setgroups=deny.--isolation chroot,--format docker,overlay.ignore_chown_errors=true.emptyDir; the container rootfs,/tmpand/var/tmpare alloverlayfs, so the store must be relocated for overlay to be legal at all./dev/fuseabsent, sofuse-overlayfsis not an alternative.