Any uid can destroy any SysV shared memory segment
- Dominant language
- Go
- Stars
- 19.3k
- Forks
- 2k
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 264
Description
### 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
```python
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 python3 /repro.py
docker run --rm --runtime=runsc -v /tmp/repro.py:/repro.py:ro 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
```shell
runsc version release-20260810.0
spec: 1.2.1
```
### docker version (if using docker)
```shell
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
Contributor guide
Research direction
Start with the supplied Python reproduction and the shmctl IPC_RMID call, then compare runsc's behavior with runc/Linux for a segment owned by another uid. Done means an unprivileged process without capabilities is refused when removing a segment it does not own, while permitted removals still work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, linux, python
- Domain
- operating-systems, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100