Description
It seems that gVisor doesn't check ownership of SysV segments before destroying them. An unprivileged process with no capabilities can destroy a segment created by root.
I hit this while writing cleanup code that walks the SysV id space and removes the segments belonging to one uid. Under runc I could let the kernel do the filtering, i.e., try to remove every ID, and the ones that aren't mine get refused. Under runsc that took out the segments belonging to root as well.
Impact is small, as far as I can tell. It's confined to one IPC namespace, so a process can only reach segments in its own sandbox, and there's no escape here. It matters if you run something untrusted alongside something trusted in the same sandbox and the trusted side uses SysV shared memory. In that scenario the untrusted side can destroy those segments. We put untrusted code in its own IPC namespace, which sidesteps this issue, still wanted to raise it here.
Steps to reproduce
import ctypes, os
libc = ctypes.CDLL(None, use_errno=True)
IPC_PRIVATE, IPC_CREAT, IPC_RMID = 0, 0o1000, 0
shmid = libc.shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0o600)
print("root created segment", shmid, "mode 0600")
if os.fork() == 0:
os.setgroups([])
os.setgid(1000)
os.setuid(1000)
caps = [l.split()[1] for l in open("/proc/self/status") if l.startswith("CapEff")]
ctypes.set_errno(0)
rc = libc.shmctl(shmid, IPC_RMID, None)
print("uid", os.geteuid(), "CapEff", caps[0], "-> IPC_RMID returned", rc, "errno", ctypes.get_errno())
os._exit(0)
os.wait()
Run it as root in a container, once under each runtime:
docker run --rm --runtime=runc -v /tmp/repro.py:/repro.py:ro <image> python3 /repro.py
docker run --rm --runtime=runsc -v /tmp/repro.py:/repro.py:ro <image> python3 /repro.py
runc:
root created segment 0 mode 0600
uid 1000 CapEff 0000000000000000 -> IPC_RMID returned -1 errno 1
runsc:
root created segment 1 mode 0600
uid 1000 CapEff 0000000000000000 -> IPC_RMID returned 0 errno 0
runsc version
runsc version release-20260810.0
spec: 1.2.1
docker version (if using docker)
Client:
Version: 29.1.3
API version: 1.52
Go version: go1.24.13
Git commit: 29.1.3-0ubuntu4.1
Built: Wed Apr 29 16:40:20 2026
OS/Arch: linux/amd64
Context: default
Server:
Engine:
Version: 29.1.3
API version: 1.52 (minimum version 1.44)
Go version: go1.24.13
Git commit: 29.1.3-0ubuntu4.1
Built: Wed Apr 29 16:40:20 2026
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 2.2.2
GitCommit:
runc:
Version: 1.4.0-0ubuntu1
GitCommit:
docker-init:
Version: 0.19.0
GitCommit:
uname
Linux 7.0.0-1007-gcp #7-Ubuntu SMP PREEMPT x86_64
Description
It seems that gVisor doesn't check ownership of SysV segments before destroying them. An unprivileged process with no capabilities can destroy a segment created by root.
I hit this while writing cleanup code that walks the SysV id space and removes the segments belonging to one uid. Under runc I could let the kernel do the filtering, i.e., try to remove every ID, and the ones that aren't mine get refused. Under runsc that took out the segments belonging to root as well.
Impact is small, as far as I can tell. It's confined to one IPC namespace, so a process can only reach segments in its own sandbox, and there's no escape here. It matters if you run something untrusted alongside something trusted in the same sandbox and the trusted side uses SysV shared memory. In that scenario the untrusted side can destroy those segments. We put untrusted code in its own IPC namespace, which sidesteps this issue, still wanted to raise it here.
Steps to reproduce
Run it as root in a container, once under each runtime:
runc:
runsc:
runsc version
docker version (if using docker)
Client: Version: 29.1.3 API version: 1.52 Go version: go1.24.13 Git commit: 29.1.3-0ubuntu4.1 Built: Wed Apr 29 16:40:20 2026 OS/Arch: linux/amd64 Context: default Server: Engine: Version: 29.1.3 API version: 1.52 (minimum version 1.44) Go version: go1.24.13 Git commit: 29.1.3-0ubuntu4.1 Built: Wed Apr 29 16:40:20 2026 OS/Arch: linux/amd64 Experimental: false containerd: Version: 2.2.2 GitCommit: runc: Version: 1.4.0-0ubuntu1 GitCommit: docker-init: Version: 0.19.0 GitCommit:uname
Linux 7.0.0-1007-gcp #7-Ubuntu SMP PREEMPT x86_64