google / google/gvisor

Any uid can destroy any SysV shared memory segment

Open
#14,191 1 comment 0 reactions 0 assignees View on GitHub
type: bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.