runsc Doesn't Skip Empty BlockIO
- Dominant language
- Go
- Stars
- 19.3k
- Forks
- 2k
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 264
Description
### Description
#### Symptom
**runsc** fails on a kernel with
- cgroup v1, and
- `CONFIG_BLK_CGROUP` disabled
#### Root Cause
[992a840889](https://github.com/google/gvisor/commit/992a840889ae0eff8ebed811c54020be8487621d) intends to make cgroup blkio optional. But it skips iff the `BlockIO` struct is nil.
Docker always populates that struct, even when no I/O limit was requested.
- [v24.0.2](https://github.com/moby/moby/blob/v24.0.2/daemon/oci_linux.go#L959-L973) effectively emits `"blockIO":{"weight":0}`
- [newer version](https://github.com/moby/moby/blob/235d2876f80fff1ee13378646a4d41a4c670a052/daemon/oci_linux.go#L958-L968) effectively emits `"blockIO":{}`
##### Inspect Container Config
```bash
docker run --rm -v /run:/rc:ro alpine sh -c 'grep -o "\"blockIO\":{[^}]*}" $(find /rc -path "*/io.containerd.runtime.v2.task/moby/$(hostname)*/config.json")'
```
Output
```
// Docker v24.0.2
"blockIO":{"weight":0}
// Newer Docker
"blockIO":{}
```
#### Proposal
In addition to nil check, [blockIO.skip()](https://github.com/google/gvisor/blob/f737bb92f276d3339e391891e92e46f3d26c2e3d/runsc/cgroup/cgroup.go#L932) checks whether all fields of [LinuxBlockIO](https://pkg.go.dev/github.com/opencontainers/runtime-spec@v1.2.1/specs-go#LinuxBlockIO) are default.
```go
func (*blockIO) skip(spec *specs.LinuxResources) error {
if spec == nil || spec.BlockIO == nil {
return nil
}
b := spec.BlockIO
if (b.Weight != nil && *b.Weight != 0) ||
(b.LeafWeight != nil && *b.LeafWeight != 0) ||
len(b.WeightDevice) > 0 ||
len(b.ThrottleReadBpsDevice) > 0 ||
len(b.ThrottleWriteBpsDevice) > 0 ||
len(b.ThrottleReadIOPSDevice) > 0 ||
len(b.ThrottleWriteIOPSDevice) > 0 {
return fmt.Errorf("blkio controller is missing but limits are set in OCI spec")
}
return nil
}
```
### Steps to reproduce
#### Docker Runtime Failure
Requirements (if infeasible, try `Unit Test Failure` instead)
- cgroup v1
- `CONFIG_BLK_CGROUP` disabled. i.e. build the kernel with `CONFIG_BLK_CGROUP=n`
Command
```bash
docker run --rm --runtime runsc alpine true
```
Error
```
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: creating container: cannot set up cgroup for root: configuring cgroup: stat /sys/fs/cgroup/blkio: no such file or directory: unknown.
```
#### Unit Test Failure
Add `extraValid` to [TestOptional](https://github.com/google/gvisor/blob/2eeb2f0065a0e8c7b40eede3c54bcff9cd2e7346/runsc/cgroup/cgroup_test.go#L936)
```
{
name: "blkio",
ctrlr: &blockIO{},
extraValid: []*specs.LinuxResources{
{BlockIO: &specs.LinuxBlockIO{}}, // Docker 29
{BlockIO: &specs.LinuxBlockIO{Weight: uint16Ptr(0)}}, // Docker ≤ 24
},
invalid: []struct {
name string
spec *specs.LinuxResources
err string
}{
{
name: "weight",
spec: &specs.LinuxResources{BlockIO: &specs.LinuxBlockIO{Weight: uint16Ptr(1)}},
err: "blkio controller is missing but limits are set in OCI spec",
},
},
},
```
The test fails with the current [skip()](https://github.com/google/gvisor/blob/2eeb2f0065a0e8c7b40eede3c54bcff9cd2e7346/runsc/cgroup/cgroup.go#L932).
The proposal fixes it.
Combined with `Inspect Container Config`, this shows that the current implementation does not skip `BlockIO` as expected.
### runsc version
```shell
runsc version VERSION_MISSING
spec: 1.2.1
Built from source at release-20260817.0-43-g80336ad54
```
### docker version (if using docker)
```shell
Client:
Version: 24.0.2
API version: 1.43
Go version: go1.20.4
Git commit: 610b8d0
Built: Thu Sep 25 10:08:01 2025
OS/Arch: linux/amd64
Context: default
Server:
Engine:
Version: 24.0.2
API version: 1.43 (minimum version 1.12)
Go version: go1.20.4
Git commit: bd0a34d
Built: Thu Sep 25 10:09:08 2025
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v1.7.1
GitCommit: fea6458b8abe502f6228eab2e5a6678fcb5c3fb0
runc:
Version: v1.1.7
GitCommit: 0320c58
docker-init:
Version: 0.19.0
GitCommit: ed96d00
```
### uname
Linux 5.10.55+ SMP x86_64 GNU/Linux
### kubectl (if using Kubernetes)
```shell
```
### repo state (if built from source)
fatal: No names found, cannot describe anything.
### runsc debug logs (if available)
```shell
Not applicable: the failure happens in runsc create before the sandbox starts
```
Contributor guide
Research direction
Start with runsc/cgroup/cgroup.go at blockIO.skip() and runsc/cgroup/cgroup_test.go at TestOptional. Add the reported empty and zero-valued BlockIO cases, then run the cgroup unit tests; done means empty/default BlockIO is skipped while non-default limits still report the missing controller.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, go, linux
- Domain
- operating-systems
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100