Repository navigation
Open
Labels
area/gvisorarea/nodekind/bugSomething isn't working / bugfixesSomething isn't working / bugfixesprio/P2real, but not urgentreal, but not urgent
Description
Summary
Inside a gVisor actor, the root directory / is root:root 0700. A workload that drops to a non-root user cannot resolve any path at all, not even /bin/sh, because the first path component is not traversable. Docker and containerd keep the image root's mode (normally 0755). As shipped, any non-root workload fails on Substrate.
Impact
- Who hits it: every integrator whose workload runs as, or switches to, a non-root user. Agent runtimes and most hardened images do this.
- What breaks:
su,runuserand any exec as a non-root uid fail withPermission denied. The workload cannot start. - Cost: each integrator has to add a root-privileged fixup at actor start.
- Severity: High.
- Proposed priority: P1. It blocks non-root workloads out of the box. A workaround exists but needs root at startup, which depends on the uid-0 start (see Workload has no
userfield or no-new-privileges setting…).
Environment
- Substrate
d277088b(v1alpha). - GKE 1.36.x.
- gVisor sandbox class, unprivileged WorkerPool workers.
Steps to reproduce / evidence
- Create the actor (verified live). Use an ActorTemplate whose image is a Debian-based image with a non-root user (for example
useradd -u 1000 agent). Create an actor from it. - Check the root mode inside the actor, as root (verified live):
$ stat -c '%a:%U:%G %n' / 700:root:root / - Switch to the non-root user (verified live):
$ su agent -s /bin/sh -c id su: failed to execute /bin/sh: Permission denied/bin/shis itself 0755. The failure is at the traversal of/. - Show that a 0755 root is sufficient (verified live). After
chmod 755 /(as root),suandrunuserto the non-root uid both succeed. The same test also chowned the user's home directory; the exec itself (su … -c id) depends only on/being traversable. - Cause (code-read at
d277088b):internal/imagecache/bundle_linux.go:61-66creates<bundle>/rootfs,upperandworkwithos.MkdirAll(d, 0o700).- The overlay is then mounted at
rootfswith that upperdir (bundle_linux.go:81). - The merged overlay root takes its attributes from the upperdir root.
applyDirFixupsininternal/imagecache/implicitdirs.gorepairs implicit parent directories. It does not appear to cover/itself.
Expected vs actual
- Expected: the actor's
/has the image root's mode (0755 for standard base images), as under Docker, containerd and Kubernetes. - Actual:
/is 0700 and owned by root. No non-root uid can traverse it.
Workaround
At actor start, while still uid 0, an integrator can:
chmod 755 /if/is more restrictive than that.- Check that the workload user can traverse the rootfs (for example that
/bin/shresolves). - Fail closed if it cannot.
This only works because the workload starts as root.
Additional detail
- Suggested fix: create the bundle rootfs/upperdir root with 0755, or copy the image root's mode onto it after the overlay is mounted.
- Related issues (same class, image metadata lost in the actor rootfs):
- Image-layer file ownership appears as uid 0 inside the actor (
image-layer-file-ownership-lost.md). - Actor rootfs drops setuid, setgid and sticky bits from image layers (
/tmpis 0777) (actor-rootfs-drops-special-mode-bits.md).
- Image-layer file ownership appears as uid 0 inside the actor (
Metadata
Metadata
Assignees
Labels
area/gvisorarea/nodekind/bugSomething isn't working / bugfixesSomething isn't working / bugfixesprio/P2real, but not urgentreal, but not urgent
Activity
fvoznika commentedon Oct 13, 2026
gVisor and other runtimes expose the filesystem unchanged. For Docker and K8s, containerd changes the root directory to 0755 when the merged rootfs mount is created. This is being addressed by #2430.
fw, ax: the tool user gets a home of its own, and the ax boot opens t…