From 76517a27d8f1be974d03a7d967631186dfc91fed Mon Sep 17 00:00:00 2001 From: Eugene Gvozdetsky Date: Tue, 13 Oct 2026 16:25:04 +0200 Subject: [PATCH 1/3] config-linux: add linux.ioUring Restrict the io_uring operations a container can submit, using the io_uring task restrictions added in Linux 7.0. io_uring operations are not system calls, so seccomp cannot filter them. ops is an allowlist that doesn't depend on submission flags, so runtimes behave the same whether they use BPF filters or the task allowlist. An empty socketFamilies is not allowed: Go's omitempty would turn it into "any family". To deny all sockets, leave IORING_OP_SOCKET out of ops. Closes #1323. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eugene Gvozdetsky --- config-linux.md | 46 +++++++++++++++++++ features-linux.md | 38 +++++++++++++++ features.md | 12 +++++ schema/config-linux.json | 21 +++++++++ schema/defs-linux.json | 8 ++++ schema/features-linux.json | 20 ++++++++ .../bad/linux-io-uring-empty-families.json | 14 ++++++ .../config/bad/linux-io-uring-invalid-op.json | 13 ++++++ schema/test/config/bad/linux-io-uring.json | 13 ++++++ schema/test/config/good/linux-io-uring.json | 19 ++++++++ specs-go/config.go | 13 ++++++ specs-go/features/features.go | 16 +++++++ 12 files changed, 233 insertions(+) create mode 100644 schema/test/config/bad/linux-io-uring-empty-families.json create mode 100644 schema/test/config/bad/linux-io-uring-invalid-op.json create mode 100644 schema/test/config/bad/linux-io-uring.json create mode 100644 schema/test/config/good/linux-io-uring.json diff --git a/config-linux.md b/config-linux.md index c71757494..2d8f2e49a 100644 --- a/config-linux.md +++ b/config-linux.md @@ -1040,6 +1040,51 @@ Example sending a single `seccompFd` file descriptor in the `SCM_RIGHTS` array: } ``` +## 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 ``, for example `IORING_OP_READ`. + The runtime MUST generate an error if it does not recognize a value. + The runtime MUST ignore a recognized value that the kernel does not support. +* **`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 ``, for example `AF_INET`. + The runtime MUST generate an error if it does not recognize a value. + 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. +Applying `ioUring` SHOULD NOT prevent the container process from registering further io_uring restrictions of its own. +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" + ] +} +``` + ## Rootfs Mount Propagation **`rootfsPropagation`** (string, OPTIONAL) sets the rootfs's mount propagation. @@ -1140,6 +1185,7 @@ subset of the available options. [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 diff --git a/features-linux.md b/features-linux.md index 3331266dd..9ec66ce75 100644 --- a/features-linux.md +++ b/features-linux.md @@ -165,6 +165,44 @@ Irrelevant to the kernel version of the host operating system. } ``` +## 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" + ] +} +``` + ## AppArmor **`apparmor`** (object, OPTIONAL) represents the runtime's implementation status of AppArmor. diff --git a/features.md b/features.md index 9c729591a..136feb940 100644 --- a/features.md +++ b/features.md @@ -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 }, diff --git a/schema/config-linux.json b/schema/config-linux.json index 778561d89..613b14a00 100644 --- a/schema/config-linux.json +++ b/schema/config-linux.json @@ -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" }, diff --git a/schema/defs-linux.json b/schema/defs-linux.json index edc0a523c..a0e7545ac 100644 --- a/schema/defs-linux.json +++ b/schema/defs-linux.json @@ -266,6 +266,14 @@ "allow" ] }, + "IoUringOp": { + "type": "string", + "pattern": "^IORING_OP_[A-Z0-9_]+$" + }, + "SocketFamily": { + "type": "string", + "pattern": "^AF_[A-Z0-9_]+$" + }, "MemoryPolicyMode": { "type": "string", "enum": [ diff --git a/schema/features-linux.json b/schema/features-linux.json index fcf3df7d6..43204ea62 100644 --- a/schema/features-linux.json +++ b/schema/features-linux.json @@ -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": { diff --git a/schema/test/config/bad/linux-io-uring-empty-families.json b/schema/test/config/bad/linux-io-uring-empty-families.json new file mode 100644 index 000000000..789638c36 --- /dev/null +++ b/schema/test/config/bad/linux-io-uring-empty-families.json @@ -0,0 +1,14 @@ +{ + "ociVersion": "1.0.0", + "root": { + "path": "rootfs" + }, + "linux": { + "ioUring": { + "ops": [ + "IORING_OP_SOCKET" + ], + "socketFamilies": [] + } + } +} diff --git a/schema/test/config/bad/linux-io-uring-invalid-op.json b/schema/test/config/bad/linux-io-uring-invalid-op.json new file mode 100644 index 000000000..c79455729 --- /dev/null +++ b/schema/test/config/bad/linux-io-uring-invalid-op.json @@ -0,0 +1,13 @@ +{ + "ociVersion": "1.0.0", + "root": { + "path": "rootfs" + }, + "linux": { + "ioUring": { + "ops": [ + "read" + ] + } + } +} diff --git a/schema/test/config/bad/linux-io-uring.json b/schema/test/config/bad/linux-io-uring.json new file mode 100644 index 000000000..2e63cfdd4 --- /dev/null +++ b/schema/test/config/bad/linux-io-uring.json @@ -0,0 +1,13 @@ +{ + "ociVersion": "1.0.0", + "root": { + "path": "rootfs" + }, + "linux": { + "ioUring": { + "socketFamilies": [ + "AF_INET" + ] + } + } +} diff --git a/schema/test/config/good/linux-io-uring.json b/schema/test/config/good/linux-io-uring.json new file mode 100644 index 000000000..18817ab4e --- /dev/null +++ b/schema/test/config/good/linux-io-uring.json @@ -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" + ] + } + } +} diff --git a/specs-go/config.go b/specs-go/config.go index 711eb9e17..18e04fbcf 100644 --- a/specs-go/config.go +++ b/specs-go/config.go @@ -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. @@ -892,6 +894,17 @@ 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 or empty means any address family; + // to deny all sockets, leave IORING_OP_SOCKET out of Ops. + SocketFamilies []string `json:"socketFamilies,omitempty"` +} + // LinuxMemoryPolicy represents input for the set_mempolicy syscall. type LinuxMemoryPolicy struct { // Mode for the set_mempolicy syscall. diff --git a/specs-go/features/features.go b/specs-go/features/features.go index 7b4c40640..d7bf626f4 100644 --- a/specs-go/features/features.go +++ b/specs-go/features/features.go @@ -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"` @@ -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. From 88292120e85223a57fa067760e70faa0167811e9 Mon Sep 17 00:00:00 2001 From: Eugene Gvozdetsky Date: Fri, 30 Oct 2026 10:00:12 +0100 Subject: [PATCH 2/3] fixup! config-linux: add linux.ioUring - Unrecognized ops and socketFamilies values are ignored, not an error: they stay denied, and a config for a newer runtime still works. - The runtime must fail if a process would lose the restrictions on execve(2) after using io_uring, as on Linux 7.0 and 7.1 without bc0e8faf90e7. - The container process can add BPF filters and restrict its own rings; registering task-level IORING_REGISTER_RESTRICTIONS is not possible once BPF filters are registered. - specs-go: SocketFamilies is a pointer, so that an empty list is not turned into "any family" by omitempty. - schema: allow lowercase in family names (AF_DECnet). Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eugene Gvozdetsky --- config-linux.md | 12 ++++++++---- schema/defs-linux.json | 2 +- specs-go/config.go | 7 ++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/config-linux.md b/config-linux.md index 2d8f2e49a..b6eedf9c6 100644 --- a/config-linux.md +++ b/config-linux.md @@ -1050,18 +1050,20 @@ Whether the io_uring system calls themselves are allowed is controlled by seccom 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 ``, for example `IORING_OP_READ`. - The runtime MUST generate an error if it does not recognize a value. - The runtime MUST ignore a recognized value that the kernel does not support. + 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 ``, for example `AF_INET`. - The runtime MUST generate an error if it does not recognize a value. + 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. -Applying `ioUring` SHOULD NOT prevent the container process from registering further io_uring restrictions of its own. +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. @@ -1176,6 +1178,7 @@ 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 @@ -1183,6 +1186,7 @@ subset of the available options. [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 diff --git a/schema/defs-linux.json b/schema/defs-linux.json index a0e7545ac..248ebd32a 100644 --- a/schema/defs-linux.json +++ b/schema/defs-linux.json @@ -272,7 +272,7 @@ }, "SocketFamily": { "type": "string", - "pattern": "^AF_[A-Z0-9_]+$" + "pattern": "^AF_[A-Za-z0-9_]+$" }, "MemoryPolicyMode": { "type": "string", diff --git a/specs-go/config.go b/specs-go/config.go index 18e04fbcf..806bb601b 100644 --- a/specs-go/config.go +++ b/specs-go/config.go @@ -900,9 +900,10 @@ type LinuxIOUring struct { // 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 or empty means any address family; - // to deny all sockets, leave IORING_OP_SOCKET out of Ops. - SocketFamilies []string `json:"socketFamilies,omitempty"` + // 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. From aef3a10250e69ca7ce47154faea8c63b5d1f8a41 Mon Sep 17 00:00:00 2001 From: Eugene Gvozdetsky Date: Fri, 30 Oct 2026 10:00:45 +0100 Subject: [PATCH 3/3] amend! config-linux: add linux.ioUring config-linux: add linux.ioUring Restrict the io_uring operations a container can submit, using the io_uring task restrictions added in Linux 7.0. io_uring operations are not system calls, so seccomp cannot filter them. ops is an allowlist that doesn't depend on submission flags, so runtimes behave the same whether they use BPF filters or the task allowlist. Unrecognized names are ignored: they can only stay denied, and a config written for a newer runtime or kernel still works. The runtime must fail if the restrictions would be lost on execve(2), as on Linux 7.0 and 7.1 without bc0e8faf90e7. An empty socketFamilies is not allowed; to deny all sockets, leave IORING_OP_SOCKET out of ops. In specs-go it is a pointer, so that an empty list is not omitted and turned into "any family". Closes #1323. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eugene Gvozdetsky