runsc: bind mounts are invisible inside the sandbox when root.path is "/" (regression since 2023 releases; any other bind of / as root works)
- Dominant language
- Go
- Stars
- 19.3k
- Forks
- 2k
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 264
Description
### Description
With a bundle whose `root.path` is literally `/` (the host root, read-only) and a plain bind mount in `mounts`, the current release mounts the bind (the gofer logs `Mounting src: "/srv/src", dst: "/home"`) but inside the sandbox the destination shows the **host's real directory** at that path, not the bind source. Any other `root.path` — including a plain `mount --bind / /srv/rootfs` of the same host root — makes the same bundle work. A 2023 release (`0.0~20230807.0`, the version Debian/Ubuntu apt currently carry for arm64) works with `root.path: "/"` too.
### Steps to reproduce
```sh
mkdir -p /srv/src && echo hello-from-source > /srv/src/y.txt && chown -R 10001:10001 /srv/src
mkdir /srv/b && cd /srv/b && runsc spec -- /bin/sh -c 'ls -lan /home | head -3; cat /home/y.txt; mount | grep home'
python3 - <<'PY'
import json
c=json.load(open('config.json'))
c['root']={'path':'/','readonly':True}
c['mounts'].append({'destination':'/home','type':'bind','source':'/srv/src','options':['bind','rw']})
json.dump(c,open('config.json','w'),indent=1)
PY
runsc --platform=systrap --network=none --ignore-cgroups run t1
```
Observed with `release-20260817.0`:
```
total 13
drwxr-xr-x 3 0 0 4096 Aug 28 07:35 . <- the HOST's /home (its real contents were listed)
cat: /home/y.txt: No such file or directory
none on /home type 9p (rw,...,aname=/,...)
```
Now change only `root.path`:
```sh
mkdir -p /srv/rootfs2 && mount --bind / /srv/rootfs2 # non-recursive; --rbind behaves the same
# c['root']['path'] = '/srv/rootfs2'
runsc --platform=systrap --network=none --ignore-cgroups run t2
```
```
total 9
drwxr-xr-x 2 10001 10001 4096 Aug 28 07:36 .
hello-from-source
```
Writes through the working variant land on the host with the right ownership. The result is the same with `--overlay2=none` and with `--directfs=false`, and the same whether the destination exists on the host (`/home`) or not (`/state`). The gofer's debug log is identical in the failing and working cases apart from the `dst:` prefix.
| runsc | kernel / arch | `root.path` | bind mount inside |
| --- | --- | --- | --- |
| `0.0~20230807.0` | 6.8 arm64 | `/` | works |
| `release-20260817.0` | 6.8 arm64 | `/` | **empty / host dir** |
| `release-20260817.0` | 6.12 amd64 (Fly.io guest) | `/` | **empty / host dir** |
| `release-20260817.0` | 6.8 arm64 | `mount --rbind / /srv/rootfs` | works |
| `release-20260817.0` | 6.8 arm64 | `mount --bind / /srv/rootfs2` | works |
### Where it seems to be
`runsc/cmd/sandboxsetup/gofer_mount.go` `SetupRootFS`: the rootfs is self-bind-mounted recursively, each bind source is mounted at `Root.Path + Destination`, then `Root.Path` is bind-mounted recursively into `/proc/fs/root` before the pivot. When `Root.Path` is `/`, the self-bind is `mount --rbind / /` and the bind sources land on the host's own paths in the gofer's private namespace; the later recursive rebind into `/proc/fs/root` then appears not to carry those submounts, while a `Root.Path` that is any other mount point round-trips them. The 2023 gofer mounted binds straight into `/proc/root/` instead, which is why it is unaffected.
### runsc version
```
runsc version release-20260817.0
spec: 1.2.1
```
### docker version (if using docker)
Not using Docker: plain OCI bundle, `runsc run`.
### uname
`6.12.105-fly x86_64` (Fly.io Firecracker guest) and `6.8.0-117-generic aarch64` (Ubuntu 24.04 VM).
### repo
n/a
Contributor guide
Research direction
Run the provided OCI bundle reproduction with runsc, comparing root.path "/" against the bind-mounted root variant. Then inspect runsc/cmd/sandboxsetup/gofer_mount.go, especially SetupRootFS and its mount sequence. Done means a bind at /home exposes /srv/src inside the sandbox when root.path is "/", while the existing working variants remain unaffected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, linux
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100