-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Description
We noticed that if the cwd in an OCI image spec is a symlink to another folder in the rootfs, starting a container does not work. It appears to work in runc though.
{
"ociVersion": "1.0.2-dev",
// ...
"cwd": "/this-folder-is-symlinked-to-another"
}running container: starting container: starting root container: starting sandbox: failed to create process working directory "/eagle-data": not a directory
Context
I know this is bug report sounds strange(!) and so I just wanted to give some context as to why we ran into this edge case.
We support mounting "Volumes" in containers at Modal and creating sandboxes.
import modal
app = modal.App(name="eric-test-sandbox-workdir")
@app.local_entrypoint()
def main():
sb = modal.Sandbox.create(
"ls",
"-a",
app=app,
volumes={
"/eagle-data": modal.Volume.from_name("eagle-data"),
},
workdir="/eagle-data",
)
print(sb.stdout.read())
sb.wait()
print("Exit code:", sb.returncode)We mount the volume in a path like /__modal/volumes/vo-123/..., and then symlink the user-provided path /eagle-data to that mount point. This is done so that we can commit / reload the volume dynamically when changes are made over the network, without interfering with user workloads.
But if we create a container with workdir set to /eagle-data, it fails to start up with this error message:
running container: starting container: starting root container: starting sandbox: failed to create process working directory "/eagle-data": not a directory
Error location
It appears to be triggered in this block of code.
Lines 205 to 211 in d839885
| if err := mntr.k.VFS().MkdirAllAt( | |
| ctx, procArgs.WorkingDirectory, mnsRoot, rootCreds, | |
| &vfs.MkdirOptions{Mode: 0755}, true, /* mustBeDir */ | |
| ); err != nil { | |
| return fmt.Errorf("failed to create process working directory %q: %w", | |
| procArgs.WorkingDirectory, err) | |
| } |
And I think it could be fixed by adding FollowFinalSymlink: true to the flags of this PathOperation in vfs.go:
Lines 875 to 881 in d839885
| func (vfs *VirtualFilesystem) MkdirAllAt(ctx context.Context, currentPath string, root VirtualDentry, creds *auth.Credentials, mkdirOpts *MkdirOptions, mustBeDir bool) error { | |
| pop := &PathOperation{ | |
| Root: root, | |
| Start: root, | |
| Path: fspath.Parse(currentPath), | |
| } | |
| stat, err := vfs.StatAt(ctx, creds, pop, &StatOptions{Mask: linux.STATX_TYPE}) |
Docs of FollowFinalSymlink:
Lines 233 to 236 in d839885
| // If FollowFinalSymlink is true, and the Dentry traversed by the final | |
| // path component represents a symbolic link, the symbolic link should be | |
| // followed. | |
| FollowFinalSymlink bool |
Expected behavior
If we run the same container with runc, it works fine and the container starts up as usual. We have an internal ability to run this Modal reproduction with runc, and I verified with that.
Let us know if you need any more information or if we can help with a fix. Thanks!
Steps to reproduce
I've only reproduced it within Modal so far (sorry!) but I believe this should reproduce the issue in gVisor:
- Create a rootfs for a container.
- Create a symlink inside the rootfs by running
sudo mkdir /fooandsudo ln -s /foo /bar, this points/bar -> /foo. - Start gVisor with
"cwd": "/bar".
runsc version
runsc version bb08e9604675
spec: 1.2.0docker version (if using docker)
Not using dockeruname
Linux ip-10-110-33-234.sa-east-1.compute.internal 5.15.0-309.180.4.el9uek.x86_64 #2 SMP Wed May 21 06:56:22 PDT 2025 x86_64 x86_64 x86_64 GNU/Linux
kubectl (if using Kubernetes)
Not using Kubernetesrepo state (if built from source)
Clean state on the commit sha