Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions config-linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,53 @@ Example sending a single `seccompFd` file descriptor in the `SCM_RIGHTS` array:
}
```

## <a name="configLinuxIoUring" />io_uring

**`ioUring`** (object, OPTIONAL) restricts the [io_uring(7)][io_uring.7] operations that the container process can submit.
io_uring operations are not system calls, so [seccomp](#configLinuxSeccomp) cannot filter them.
Whether the io_uring system calls themselves are allowed is controlled by seccomp.

* **`ops`** *(array of strings, REQUIRED)* - the io_uring operations that are allowed.
All other operations are denied, and an empty array denies all operations.
Whether an operation is allowed does not depend on its submission flags (`IOSQE_*`).
Values are names of the `IORING_OP_*` constants from `<linux/io_uring.h>`, for example `IORING_OP_READ`.
The runtime MUST ignore a value that it does not recognize or that the kernel does not support: the operation stays denied.
* **`socketFamilies`** *(array of strings, OPTIONAL)* - the address families that `IORING_OP_SOCKET` can create.
If this property is not set, any address family is allowed.
If it is set, it MUST NOT be empty; to deny all sockets, leave `IORING_OP_SOCKET` out of `ops`.
Values are names of the `AF_*` constants from `<sys/socket.h>`, for example `AF_INET`.
The runtime MUST ignore a value that it does not recognize: the address family stays denied.
If the runtime recognizes none of the values, `IORING_OP_SOCKET` is denied.
The runtime MUST generate an error if this property is set and `ops` does not contain `IORING_OP_SOCKET`.

The runtime MUST apply the restrictions before the container process is executed, so that they apply to the container process and all of its descendants.
If the runtime cannot apply the restrictions, it MUST generate an error.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About the kernel bug mentioned in the description (restrictions lost on exec on 7.0/7.1 without bc0e8faf90e7, fixed in v7.2-rc6, Cc stable 7.1+): per the commit, if a task has used io_uring, the exec cancellation path frees its per-task restrictions, so a ring created after exec is unrestricted. The container init itself probably never creates a ring before exec, but any process inside the container that uses io_uring and then execs gets an unrestricted image, i.e. a trivial bypass of the policy.

On such a kernel, registering the filters succeeds, so with the current wording the runtime "applies" the restrictions and doesn't error out, yet the requirement that they apply "to the container process and all of its descendants" is silently violated. Leaving the detection to runtimes means in practice nobody does it (neither the crun nor the youki implementation checks for it, as far as I can see).

Maybe the spec should say explicitly that if the runtime cannot ensure the restrictions are preserved across exec, it MUST (or at least SHOULD) generate an error, so runtimes have to do a functional check (or a kernel version check) instead of only probing for IORING_REGISTER_BPF_FILTER.

This comment was written by an AI (Claude Code).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, added: "If the runtime cannot ensure that a process keeps the restrictions when it calls execve(2) after using io_uring, it MUST generate an error", with 7.0/7.1 without bc0e8faf90e7 as the example. crun and youki now check it on a child that restricts itself, creates a ring and re-executes the runtime. They refuse the policy on mainline 7.0, and accept it on 7.2.9 and on Ubuntu's 7.0.0-42, which has the backport.

If the runtime cannot ensure that a process keeps the restrictions when it calls [execve(2)][execve.2] after using io_uring, it MUST generate an error.
For example, Linux 7.0 and 7.1 without commit [bc0e8faf90e7][io-uring-exec-fix] drop them.
Applying `ioUring` SHOULD NOT prevent the container process from adding io_uring BPF filters of its own, or restrictions to the io_uring instances it creates.
The restrictions only apply to io_uring instances created after they are applied.
An io_uring instance passed into the container, for example with `SCM_RIGHTS`, is not restricted.

### Example

```json
"ioUring": {
"ops": [
"IORING_OP_READ",
"IORING_OP_WRITE",
"IORING_OP_SEND",
"IORING_OP_RECV",
"IORING_OP_ACCEPT",
"IORING_OP_SOCKET"
],
"socketFamilies": [
"AF_UNIX",
"AF_INET",
"AF_INET6"
]
}
```

## <a name="configLinuxRootfsMountPropagation" />Rootfs Mount Propagation

**`rootfsPropagation`** (string, OPTIONAL) sets the rootfs's mount propagation.
Expand Down Expand Up @@ -1131,15 +1178,18 @@ subset of the available options.
[devices]: https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
[devpts]: https://www.kernel.org/doc/Documentation/filesystems/devpts.txt
[file]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_164
[io-uring-exec-fix]: https://git.kernel.org/torvalds/c/bc0e8faf90e7
[libseccomp]: https://github.com/seccomp/libseccomp
[proc]: https://www.kernel.org/doc/Documentation/filesystems/proc.txt
[seccomp]: https://www.kernel.org/doc/Documentation/prctl/seccomp_filter.txt
[sharedsubtree]: https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
[sysfs]: https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
[tmpfs]: https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt

[execve.2]: https://man7.org/linux/man-pages/man2/execve.2.html
[full.4]: https://man7.org/linux/man-pages/man4/full.4.html
[set_mempolicy.2]: https://man7.org/linux/man-pages/man2/set_mempolicy.2.html
[io_uring.7]: https://man7.org/linux/man-pages/man7/io_uring.7.html
[mknod.1]: https://man7.org/linux/man-pages/man1/mknod.1.html
[mknod.2]: https://man7.org/linux/man-pages/man2/mknod.2.html
[namespaces.7_2]: https://man7.org/linux/man-pages/man7/namespaces.7.html
Expand Down
38 changes: 38 additions & 0 deletions features-linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,44 @@ Irrelevant to the kernel version of the host operating system.
}
```

## <a name="linuxFeaturesIoUring" />io_uring

**`ioUring`** (object, OPTIONAL) represents the runtime's implementation status of io_uring restrictions.
Irrelevant to the kernel version of the host operating system.

* **`enabled`** (bool, OPTIONAL) represents whether the runtime supports io_uring restrictions.
* **`ops`** (array of strings, OPTIONAL) The recognized names of the io_uring operations.
The runtime MUST recognize the elements in this array in the [`ops` property of the `linux.ioUring` object in `config.json`](config-linux.md#configLinuxIoUring).
* **`socketFamilies`** (array of strings, OPTIONAL) The recognized names of the address families.
The runtime MUST recognize the elements in this array in the [`socketFamilies` property of the `linux.ioUring` object in `config.json`](config-linux.md#configLinuxIoUring).

### Example

```json
"ioUring": {
"enabled": true,
"ops": [
"IORING_OP_NOP",
"IORING_OP_READV",
"IORING_OP_WRITEV",
"IORING_OP_READ",
"IORING_OP_WRITE",
"IORING_OP_SEND",
"IORING_OP_RECV",
"IORING_OP_ACCEPT",
"IORING_OP_SOCKET"
],
"socketFamilies": [
"AF_UNIX",
"AF_INET",
"AF_INET6",
"AF_NETLINK",
"AF_PACKET",
"AF_VSOCK"
]
}
```

## <a name="linuxFeaturesApparmor" />AppArmor

**`apparmor`** (object, OPTIONAL) represents the runtime's implementation status of AppArmor.
Expand Down
12 changes: 12 additions & 0 deletions features.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,18 @@ Here is a full example for reference.
"SECCOMP_FILTER_FLAG_LOG"
]
},
"ioUring": {
"enabled": true,
"ops": [
"IORING_OP_READ",
"IORING_OP_WRITE"
],
"socketFamilies": [
"AF_UNIX",
"AF_INET",
"AF_INET6"
]
},
"apparmor": {
"enabled": true
},
Expand Down
21 changes: 21 additions & 0 deletions schema/config-linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,27 @@
"defaultAction"
]
},
"ioUring": {
"type": "object",
"properties": {
"ops": {
"type": "array",
"items": {
"$ref": "defs-linux.json#/definitions/IoUringOp"
}
},
"socketFamilies": {
"type": "array",
"items": {
"$ref": "defs-linux.json#/definitions/SocketFamily"
},
"minItems": 1
}
},
"required": [
"ops"
]
},
"sysctl": {
"$ref": "defs.json#/definitions/mapStringString"
},
Expand Down
8 changes: 8 additions & 0 deletions schema/defs-linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,14 @@
"allow"
]
},
"IoUringOp": {
"type": "string",
"pattern": "^IORING_OP_[A-Z0-9_]+$"
},
"SocketFamily": {
"type": "string",
"pattern": "^AF_[A-Za-z0-9_]+$"
},
"MemoryPolicyMode": {
"type": "string",
"enum": [
Expand Down
20 changes: 20 additions & 0 deletions schema/features-linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,26 @@
}
}
},
"ioUring": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"ops": {
"type": "array",
"items": {
"$ref": "defs-linux.json#/definitions/IoUringOp"
}
},
"socketFamilies": {
"type": "array",
"items": {
"$ref": "defs-linux.json#/definitions/SocketFamily"
}
}
}
},
"apparmor": {
"type": "object",
"properties": {
Expand Down
14 changes: 14 additions & 0 deletions schema/test/config/bad/linux-io-uring-empty-families.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"ociVersion": "1.0.0",
"root": {
"path": "rootfs"
},
"linux": {
"ioUring": {
"ops": [
"IORING_OP_SOCKET"
],
"socketFamilies": []
}
}
}
13 changes: 13 additions & 0 deletions schema/test/config/bad/linux-io-uring-invalid-op.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"ociVersion": "1.0.0",
"root": {
"path": "rootfs"
},
"linux": {
"ioUring": {
"ops": [
"read"
]
}
}
}
13 changes: 13 additions & 0 deletions schema/test/config/bad/linux-io-uring.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"ociVersion": "1.0.0",
"root": {
"path": "rootfs"
},
"linux": {
"ioUring": {
"socketFamilies": [
"AF_INET"
]
}
}
}
19 changes: 19 additions & 0 deletions schema/test/config/good/linux-io-uring.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"ociVersion": "1.0.0",
"root": {
"path": "rootfs"
},
"linux": {
"ioUring": {
"ops": [
"IORING_OP_READ",
"IORING_OP_WRITE",
"IORING_OP_SOCKET"
],
"socketFamilies": [
"AF_UNIX",
"AF_INET6"
]
}
}
}
14 changes: 14 additions & 0 deletions specs-go/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ type Linux struct {
NetDevices map[string]LinuxNetDevice `json:"netDevices,omitempty"`
// Seccomp specifies the seccomp security settings for the container.
Seccomp *LinuxSeccomp `json:"seccomp,omitempty"`
// IOUring restricts the io_uring operations available to the container.
IOUring *LinuxIOUring `json:"ioUring,omitempty"`
// RootfsPropagation is the rootfs mount propagation mode for the container.
RootfsPropagation string `json:"rootfsPropagation,omitempty"`
// MaskedPaths masks over the provided paths inside the container.
Expand Down Expand Up @@ -892,6 +894,18 @@ type LinuxIntelRdt struct {
EnableMonitoring bool `json:"enableMonitoring,omitempty"`
}

// LinuxIOUring restricts the io_uring operations available to the container.
type LinuxIOUring struct {
// Ops is the list of allowed io_uring operations, e.g., "IORING_OP_READ".
// All other operations are denied.
Ops []string `json:"ops"`
// SocketFamilies is the list of address families that IORING_OP_SOCKET
// can create, e.g., "AF_INET". Nil means any address family. If set, it
// must not be empty: to deny all sockets, leave IORING_OP_SOCKET out of
// Ops. A pointer, so that an empty list is not omitted.
SocketFamilies *[]string `json:"socketFamilies,omitempty"`
}

// LinuxMemoryPolicy represents input for the set_mempolicy syscall.
type LinuxMemoryPolicy struct {
// Mode for the set_mempolicy syscall.
Expand Down
16 changes: 16 additions & 0 deletions specs-go/features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Linux struct {

Cgroup *Cgroup `json:"cgroup,omitempty"`
Seccomp *Seccomp `json:"seccomp,omitempty"`
IOUring *IOUring `json:"ioUring,omitempty"`
Apparmor *Apparmor `json:"apparmor,omitempty"`
Selinux *Selinux `json:"selinux,omitempty"`
IntelRdt *IntelRdt `json:"intelRdt,omitempty"`
Expand Down Expand Up @@ -109,6 +110,21 @@ type Seccomp struct {
SupportedFlags []string `json:"supportedFlags,omitempty"`
}

// IOUring represents the "ioUring" field.
type IOUring struct {
// Enabled is true if io_uring restrictions support is compiled in.
// Nil value means "unknown", not "false".
Enabled *bool `json:"enabled,omitempty"`

// Ops is the list of the recognized io_uring operations, e.g., "IORING_OP_READ".
// Nil value means "unknown", not "no support for any operation".
Ops []string `json:"ops,omitempty"`

// SocketFamilies is the list of the recognized address families, e.g., "AF_INET".
// Nil value means "unknown", not "no support for any address family".
SocketFamilies []string `json:"socketFamilies,omitempty"`
}

// Apparmor represents the "apparmor" field.
type Apparmor struct {
// Enabled is true if AppArmor support is compiled in.
Expand Down