core: add IOUringFilter=

SystemCallFilter= can allow or deny io_uring as a whole, but not
individual io_uring operations. It only denies an operation together with
the system calls that the operation can do the work of. Some operations,
such as IORING_OP_URING_CMD, which passes commands to drivers, or the
zero-copy network operations, can thus only be denied together with
ioctl() or the socket calls.

Let's add IOUringFilter=, which takes an allow or deny list of io_uring
operations, like SystemCallFilter= does for system calls. On kernels
without io_uring BPF filters, we can't enforce IOUringFilter=, so we deny
io_uring_setup() with ENOSYS instead. Programs fall back to regular
system calls when io_uring_setup() fails with ENOSYS.
This commit is contained in:
Daan De Meyer committed 2026-11-04 10:54:29 +01:00
1 parent f80d935027
commit 9dcd9ffd89
23 files changed
+628 -5

No files matched your search

+1
View File
@@ -212,6 +212,7 @@ All execution-related settings are available for transient units.
✓ RestrictRealtime=
✓ RestrictSUIDSGID=
✓ RestrictAddressFamilies=
✓ IOUringFilter=
✓ RootHash=
✓ RootHashSignature=
✓ RootVerity=
+28
View File
@@ -3537,6 +3537,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) IOUringFilter = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sst) RuntimeDirectorySymlink = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@@ -4188,6 +4190,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property RestrictAddressFamilies is not documented!-->
<!--property IOUringFilter is not documented!-->
<!--property RuntimeDirectoryPreserve is not documented!-->
<!--property RuntimeDirectoryMode is not documented!-->
@@ -4930,6 +4934,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
<variablelist class="dbus-property" generated="True" extra-ref="IOUringFilter"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
@@ -5858,6 +5864,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) IOUringFilter = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sst) RuntimeDirectorySymlink = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@@ -6521,6 +6529,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<!--property RestrictAddressFamilies is not documented!-->
<!--property IOUringFilter is not documented!-->
<!--property RuntimeDirectoryPreserve is not documented!-->
<!--property RuntimeDirectoryMode is not documented!-->
@@ -7239,6 +7249,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
<variablelist class="dbus-property" generated="True" extra-ref="IOUringFilter"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
@@ -7986,6 +7998,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) IOUringFilter = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sst) RuntimeDirectorySymlink = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@@ -8573,6 +8587,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<!--property RestrictAddressFamilies is not documented!-->
<!--property IOUringFilter is not documented!-->
<!--property RuntimeDirectoryPreserve is not documented!-->
<!--property RuntimeDirectoryMode is not documented!-->
@@ -9193,6 +9209,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
<variablelist class="dbus-property" generated="True" extra-ref="IOUringFilter"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
@@ -10062,6 +10080,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) IOUringFilter = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly a(sst) RuntimeDirectorySymlink = [...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@@ -10631,6 +10651,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<!--property RestrictAddressFamilies is not documented!-->
<!--property IOUringFilter is not documented!-->
<!--property RuntimeDirectoryPreserve is not documented!-->
<!--property RuntimeDirectoryMode is not documented!-->
@@ -11233,6 +11255,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
<variablelist class="dbus-property" generated="True" extra-ref="IOUringFilter"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
@@ -13032,6 +13056,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<para><varname>LUOSession</varname> was added in version 262.</para>
<para><varname>RestartRandomizedDelayUSec</varname> was added in version 262.</para>
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
<para><varname>IOUringFilter</varname> was added in version 263.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>
@@ -13112,6 +13137,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>XAttrListen</varname>, and
<varname>XAttrAccept</varname> were added in version 262.</para>
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
<para><varname>IOUringFilter</varname> was added in version 263.</para>
</refsect2>
<refsect2>
<title>Mount Unit Objects</title>
@@ -13184,6 +13210,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>CPUSetPartition</varname>, and
<varname>OOMRules</varname> were added in version 261.</para>
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
<para><varname>IOUringFilter</varname> was added in version 263.</para>
</refsect2>
<refsect2>
<title>Swap Unit Objects</title>
@@ -13254,6 +13281,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>CPUSetPartition</varname>, and
<varname>OOMRules</varname> were added in version 261.</para>
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
<para><varname>IOUringFilter</varname> was added in version 263.</para>
</refsect2>
<refsect2>
<title>Slice Unit Objects</title>
+44 -1
View File
@@ -2526,6 +2526,48 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting>
<xi:include href="version-info.xml" xpointer="v211"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>IOUringFilter=</varname></term>
<listitem><para>Restricts the
<citerefentry project='man-pages'><refentrytitle>io_uring</refentrytitle><manvolnum>7</manvolnum></citerefentry>
operations that the processes of this unit may submit. Takes a comma- or space-separated list of
operation names. The name of an operation is its <constant>IORING_OP_</constant> constant in lower
case and without the prefix, for example <literal>read</literal> for
<constant>IORING_OP_READ</constant>, or <literal>uring_cmd</literal> for
<constant>IORING_OP_URING_CMD</constant>. If the list is prefixed with <literal>~</literal>, the
listed operations are denied (deny-listing). Otherwise, only the listed operations are allowed
(allow-listing). An allow list also denies the operations of newer kernels that systemd doesn't
know, even though the kernel reports them as supported. Operation names that systemd doesn't know
are ignored.</para>
<para>This option may be specified more than once, in which case the lists are merged. If the first
assignment starts with <literal>~</literal>, the list is a deny list, otherwise it is an allow list.
Later assignments of the other type remove the listed operations from the list. If the empty string
is assigned, the list is reset and all prior assignments have no effect. This setting does not
affect commands prefixed with <literal>+</literal>.</para>
<para>Requests for denied operations fail with <constant>EACCES</constant>. The restriction applies
to all io_uring instances that the processes of the unit create, but not to io_uring instances that
they receive from other processes. <constant>IORING_REGISTER_RESTRICTIONS</constant> without an
io_uring instance fails with <constant>EPERM</constant> for the processes of the unit.</para>
<para>This setting requires a kernel with io_uring BPF filters (Linux 7.0 or newer). On older
kernels, <function>io_uring_setup()</function> fails with <constant>ENOSYS</constant> for the
processes of the unit instead, so that programs fall back to regular system calls. If seccomp is not
available either, this setting has no effect.</para>
<para>Example: to deny passthrough commands to drivers and zero-copy network operations, use:
<programlisting>IOUringFilter=~uring_cmd uring_cmd128 send_zc sendmsg_zc recv_zc</programlisting>
</para>
<para><varname>SystemCallFilter=</varname>, <varname>RestrictAddressFamilies=</varname>, and
<varname>RestrictSUIDSGID=</varname> apply to the equivalent io_uring operations as well. To deny
io_uring entirely, use <varname>SystemCallFilter=~io_uring_setup</varname>.</para>
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>RestrictFileSystems=</varname></term>
@@ -3008,7 +3050,8 @@ RestrictNamespaces=~cgroup net</programlisting>
can't pretend to succeed. The restriction applies to all io_uring instances that the processes of
the unit create, but not to io_uring instances that they receive from other processes. On such
kernels, <constant>IORING_REGISTER_RESTRICTIONS</constant> without an io_uring instance fails with
<constant>EPERM</constant> for the processes of the unit.</para>
<constant>EPERM</constant> for the processes of the unit. To filter individual io_uring operations,
use <varname>IOUringFilter=</varname>.</para>
<para>Note that on systems supporting multiple ABIs (such as x86/x86-64) it is recommended to turn
off alternative ABIs for services, so that they cannot be used to circumvent the restrictions of this
+103
View File
@@ -23,6 +23,7 @@
#include "hexdecoct.h"
#include "hostname-util.h"
#include "image-policy.h"
#include "io-uring-filter.h"
#include "ioprio-util.h"
#include "iovec-util.h"
#include "journal-file.h"
@@ -329,6 +330,41 @@ static int property_get_smack_process_label(
return sd_bus_message_append(reply, "(bs)", c->smack_process_label_ignore, c->smack_process_label);
}
static int property_get_io_uring_filter(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *reterr_error) {
ExecContext *c = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **l = NULL;
int r;
assert(bus);
assert(reply);
r = sd_bus_message_open_container(reply, 'r', "bas");
if (r < 0)
return r;
r = sd_bus_message_append(reply, "b", c->io_uring_filter_allow_list);
if (r < 0)
return r;
l = exec_context_get_io_uring_filter(c);
if (!l)
return -ENOMEM;
r = sd_bus_message_append_strv(reply, l);
if (r < 0)
return r;
return sd_bus_message_close_container(reply);
}
static int property_get_address_families(
sd_bus *bus,
const char *path,
@@ -1356,6 +1392,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("Personality", "s", property_get_personality, offsetof(ExecContext, personality), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IOUringFilter", "(bas)", property_get_io_uring_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", bus_property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -2446,6 +2483,72 @@ int bus_exec_context_set_transient_property(
return 1;
}
if (streq(name, "IOUringFilter")) {
_cleanup_strv_free_ char **l = NULL;
int allow_list;
r = sd_bus_message_enter_container(message, 'r', "bas");
if (r < 0)
return r;
r = sd_bus_message_read(message, "b", &allow_list);
if (r < 0)
return r;
r = sd_bus_message_read_strv(message, &l);
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
STRV_FOREACH(s, l)
if (io_uring_op_from_string(*s) < 0)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid io_uring operation: %s", *s);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
_cleanup_free_ char *joined = NULL;
if (strv_isempty(l)) {
c->io_uring_filter_allow_list = false;
c->io_uring_filter = set_free(c->io_uring_filter);
unit_write_setting(u, flags, name, "IOUringFilter=");
return 1;
}
if (!c->io_uring_filter) {
c->io_uring_filter = set_new(/* hash_ops= */ NULL);
if (!c->io_uring_filter)
return -ENOMEM;
c->io_uring_filter_allow_list = allow_list;
}
STRV_FOREACH(s, l) {
int op = io_uring_op_from_string(*s);
assert(op >= 0);
if (allow_list == c->io_uring_filter_allow_list) {
r = set_put(c->io_uring_filter, INT_TO_PTR(op + 1));
if (r < 0)
return r;
} else
set_remove(c->io_uring_filter, INT_TO_PTR(op + 1));
}
joined = strv_join(l, " ");
if (!joined)
return -ENOMEM;
unit_write_settingf(u, flags, name, "IOUringFilter=%s%s", allow_list ? "" : "~", joined);
}
return 1;
}
if (streq(name, "MountFlags"))
return bus_set_transient_mount_propagation_flag(u, name, &c->mount_propagation_flag, message, flags, reterr_error);
+48 -2
View File
@@ -1756,7 +1756,7 @@ static bool context_has_syscall_logs(const ExecContext *c) {
static bool context_has_seccomp(const ExecContext *c) {
assert(c);
/* We need NNP if we have any form of seccomp and are unprivileged */
/* We need NNP if we have any form of seccomp or io_uring filters and are unprivileged */
return c->lock_personality ||
c->memory_deny_write_execute ||
c->private_devices ||
@@ -1771,7 +1771,8 @@ static bool context_has_seccomp(const ExecContext *c) {
c->restrict_suid_sgid ||
!set_isempty(c->syscall_archs) ||
exec_context_has_syscall_filters(c) ||
context_has_syscall_logs(c);
context_has_syscall_logs(c) ||
exec_context_io_uring_filter_set(c);
}
static bool context_has_no_new_privileges(const ExecContext *c) {
@@ -1868,6 +1869,45 @@ static int apply_io_uring_suid_sgid(const ExecContext *c, const ExecParameters *
return io_uring_restrict_suid_sgid();
}
static int apply_io_uring_filter(const ExecContext *c, const ExecParameters *p) {
assert(c);
assert(p);
if (!exec_context_io_uring_filter_set(c))
return 0;
if (check_io_uring_bpf_filters(c, p, "IOUringFilter="))
return io_uring_restrict_ops(c->io_uring_filter, c->io_uring_filter_allow_list);
if (syscall_filter_denies_io_uring(c))
return 0;
#if HAVE_SECCOMP
/* The kernel can't filter io_uring operations, so we deny io_uring_setup() to enforce
* IOUringFilter=. Programs fall back to regular system calls when io_uring_setup() fails with
* ENOSYS. */
if (is_seccomp_available()) {
_cleanup_hashmap_free_ Hashmap *filter = NULL;
int r;
log_debug("Denying io_uring_setup() to enforce IOUringFilter=.");
filter = hashmap_new(/* hash_ops= */ NULL);
if (!filter)
return -ENOMEM;
r = seccomp_filter_set_add_by_name(filter, /* add= */ true, "io_uring_setup");
if (r < 0)
return r;
return seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, filter, SCMP_ACT_ERRNO(ENOSYS), /* log_missing= */ false);
}
#endif
log_debug("Seccomp is not available, not applying IOUringFilter=.");
return 0;
}
#if HAVE_SECCOMP
static bool seccomp_allows_drop_privileges(const ExecContext *c) {
@@ -7083,6 +7123,12 @@ int exec_invoke(
return log_error_errno(r, "Failed to apply SUID/SGID restrictions to io_uring: %m");
}
r = apply_io_uring_filter(context, params);
if (r < 0) {
*exit_status = EXIT_SECCOMP;
return log_error_errno(r, "Failed to apply io_uring filters: %m");
}
#if HAVE_SECCOMP
r = apply_io_uring_syscall_filter(context, params);
if (r < 0) {
+28
View File
@@ -2442,6 +2442,17 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) {
if (r < 0)
return r;
void *op;
SET_FOREACH(op, c->io_uring_filter) {
r = serialize_item_format(f, "exec-context-io-uring-filter", "%d", PTR_TO_INT(op) - 1);
if (r < 0)
return r;
}
r = serialize_bool_elide(f, "exec-context-io-uring-filter-allow-list", c->io_uring_filter_allow_list);
if (r < 0)
return r;
r = serialize_item(f, "exec-context-user-namespace-path", c->user_namespace_path);
if (r < 0)
return r;
@@ -3496,6 +3507,23 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) {
if (r < 0)
return r;
c->address_families_allow_list = r;
} else if ((val = startswith(l, "exec-context-io-uring-filter="))) {
int op;
r = safe_atoi(val, &op);
if (r < 0)
return r;
if (op < 0)
return -EINVAL;
r = set_ensure_put(&c->io_uring_filter, /* hash_ops= */ NULL, INT_TO_PTR(op + 1));
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-context-io-uring-filter-allow-list="))) {
r = parse_boolean(val);
if (r < 0)
return r;
c->io_uring_filter_allow_list = r;
} else if ((val = startswith(l, "exec-context-network-namespace-path="))) {
r = free_and_strdup(&c->network_namespace_path, val);
if (r < 0)
+40 -1
View File
@@ -508,7 +508,8 @@ int exec_spawn(
if (FLAGS_SET(params->flags, EXEC_APPLY_SANDBOXING) &&
!FLAGS_SET(command->flags, EXEC_COMMAND_FULLY_PRIVILEGED) &&
(exec_context_has_address_families(context) ||
context->restrict_suid_sgid || exec_context_has_syscall_filters(context))) {
context->restrict_suid_sgid || exec_context_has_syscall_filters(context) ||
exec_context_io_uring_filter_set(context))) {
r = io_uring_bpf_filter_supported();
if (r < 0)
return log_unit_error_errno(unit, r, "Failed to check whether io_uring BPF filters are supported: %m");
@@ -749,6 +750,7 @@ void exec_context_done(ExecContext *c) {
c->syscall_archs = set_free(c->syscall_archs);
c->syscall_log = hashmap_free(c->syscall_log);
c->address_families = set_free(c->address_families);
c->io_uring_filter = set_free(c->io_uring_filter);
FOREACH_ARRAY(d, c->directories, _EXEC_DIRECTORY_TYPE_MAX)
exec_directory_done(d);
@@ -1666,6 +1668,14 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
}
#endif
if (exec_context_io_uring_filter_set(c)) {
_cleanup_strv_free_ char **l = exec_context_get_io_uring_filter(c);
_cleanup_free_ char *joined = strv_join(l, " ");
fprintf(f, "%sIOUringFilter: %s%s\n",
prefix, c->io_uring_filter_allow_list ? "" : "~", strnull(joined));
}
if (c->user_namespace_path)
fprintf(f,
"%sUserNamespacePath: %s\n",
@@ -2117,6 +2127,28 @@ char** exec_context_get_syscall_log(const ExecContext *c) {
return l ? TAKE_PTR(l) : strv_new(NULL);
}
char** exec_context_get_io_uring_filter(const ExecContext *c) {
_cleanup_strv_free_ char **l = NULL;
void *op;
assert(c);
SET_FOREACH(op, c->io_uring_filter) {
const char *name;
name = io_uring_op_to_string(PTR_TO_INT(op) - 1);
if (!name)
continue;
if (strv_extend(&l, name) < 0)
return NULL;
}
strv_sort(l);
return l ? TAKE_PTR(l) : strv_new(NULL);
}
char** exec_context_get_address_families(const ExecContext *c) {
_cleanup_strv_free_ char **l = NULL;
void *af;
@@ -2166,6 +2198,13 @@ bool exec_context_restrict_filesystems_set(const ExecContext *c) {
!set_isempty(c->restrict_filesystems);
}
bool exec_context_io_uring_filter_set(const ExecContext *c) {
assert(c);
return c->io_uring_filter_allow_list ||
!set_isempty(c->io_uring_filter);
}
bool exec_context_with_rootfs(const ExecContext *c) {
assert(c);
+5
View File
@@ -383,6 +383,9 @@ typedef struct ExecContext {
bool address_families_allow_list:1;
Set *address_families;
bool io_uring_filter_allow_list:1;
Set *io_uring_filter; /* io_uring opcodes plus one */
char *user_namespace_path;
char *network_namespace_path;
char *ipc_namespace_path;
@@ -620,11 +623,13 @@ char** exec_context_get_syscall_filter(const ExecContext *c);
char** exec_context_get_syscall_archs(const ExecContext *c);
char** exec_context_get_syscall_log(const ExecContext *c);
char** exec_context_get_address_families(const ExecContext *c);
char** exec_context_get_io_uring_filter(const ExecContext *c);
char** exec_context_get_restrict_filesystems(const ExecContext *c);
bool exec_context_has_address_families(const ExecContext *c);
bool exec_context_has_syscall_filters(const ExecContext *c);
bool exec_context_restrict_namespaces_set(const ExecContext *c);
bool exec_context_restrict_filesystems_set(const ExecContext *c);
bool exec_context_io_uring_filter_set(const ExecContext *c);
bool exec_context_with_rootfs(const ExecContext *c);
bool exec_context_with_rootfs_strict(const ExecContext *c);
+1
View File
@@ -98,6 +98,7 @@
{% endif %}
{{type}}.DelegateNamespaces, config_parse_namespace_flags, 0, offsetof({{type}}, exec_context.delegate_namespaces)
{{type}}.RestrictFileSystems, config_parse_restrict_filesystems, 0, offsetof({{type}}, exec_context)
{{type}}.IOUringFilter, config_parse_io_uring_filter, 0, offsetof({{type}}, exec_context)
{{type}}.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof({{type}}, exec_context.rlimit)
{{type}}.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof({{type}}, exec_context.rlimit)
{{type}}.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof({{type}}, exec_context.rlimit)
+72
View File
@@ -35,6 +35,7 @@
#include "hashmap.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "io-uring-filter.h"
#include "ioprio-util.h"
#include "iovec-util.h"
#include "ip-protocol-list.h"
@@ -3576,6 +3577,76 @@ int config_parse_address_families(
}
#endif
int config_parse_io_uring_filter(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
ExecContext *c = ASSERT_PTR(data);
bool invert = false;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
c->io_uring_filter = set_free(c->io_uring_filter);
c->io_uring_filter_allow_list = false;
return 0;
}
if (rvalue[0] == '~') {
invert = true;
rvalue++;
}
if (!c->io_uring_filter) {
c->io_uring_filter = set_new(/* hash_ops= */ NULL);
if (!c->io_uring_filter)
return log_oom();
c->io_uring_filter_allow_list = !invert;
}
for (const char *p = rvalue;;) {
_cleanup_free_ char *word = NULL;
int op;
r = extract_first_word(&p, &word, /* separators= */ WHITESPACE ",", EXTRACT_UNQUOTE);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
return 0;
}
if (r == 0)
return 0;
op = io_uring_op_from_string(word);
if (op < 0) {
log_syntax(unit, LOG_WARNING, filename, line, op,
"Unknown io_uring operation, ignoring: %s", word);
continue;
}
/* An assignment of the other list type removes the listed operations from the list. */
if (invert != c->io_uring_filter_allow_list) {
r = set_put(c->io_uring_filter, INT_TO_PTR(op + 1));
if (r < 0)
return log_oom();
} else
set_remove(c->io_uring_filter, INT_TO_PTR(op + 1));
}
}
int config_parse_namespace_flags(
const char *unit,
const char *filename,
@@ -6420,6 +6491,7 @@ void unit_dump_config_items(FILE *f) {
{ config_parse_syscall_log, "SYSCALLS" },
{ config_parse_address_families, "FAMILIES" },
#endif
{ config_parse_io_uring_filter, "OPERATIONS" },
{ config_parse_namespace_flags, "NAMESPACES" },
{ config_parse_restrict_filesystems, "FILESYSTEMS" },
{ config_parse_cg_weight, "WEIGHT" },
+1
View File
@@ -99,6 +99,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_selinux_context);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_apparmor_profile);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label);
CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
CONFIG_PARSER_PROTOTYPE(config_parse_io_uring_filter);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_preserve_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_quota);
+97
View File
@@ -11,6 +11,7 @@
#include "exec-credential.h"
#include "execute.h"
#include "image-policy.h"
#include "io-uring-filter.h"
#include "ioprio-util.h"
#include "iovec-util.h"
#include "json-util.h"
@@ -437,6 +438,28 @@ static int address_families_build_json(sd_json_variant **ret, const char *name,
SD_JSON_BUILD_PAIR_STRV("addressFamilies", l));
}
static int io_uring_filter_build_json(sd_json_variant **ret, const char *name, void *userdata) {
_cleanup_strv_free_ char **l = NULL;
ExecContext *c = ASSERT_PTR(userdata);
assert(ret);
assert(name);
if (!exec_context_io_uring_filter_set(c)) {
*ret = NULL;
return 0;
}
l = exec_context_get_io_uring_filter(c);
if (!l)
return -ENOMEM;
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_BOOLEAN("isAllowList", c->io_uring_filter_allow_list),
SD_JSON_BUILD_PAIR_STRV("operations", l));
}
static int restrict_filesystems_build_json(sd_json_variant **ret, const char *name, void *userdata) {
ExecContext *c = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **l = NULL;
@@ -927,6 +950,7 @@ int unit_exec_context_build_json(sd_json_variant **ret, const char *name, void *
JSON_BUILD_PAIR_ENUM("ProtectControlGroups", protect_control_groups_to_string(c->protect_control_groups)),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictAddressFamilies", address_families_build_json, c),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictFileSystems", restrict_filesystems_build_json, c),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("IOUringFilter", io_uring_filter_build_json, c),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestrictNamespaces", namespace_flags_build_json, ULONG_TO_PTR(c->restrict_namespaces)),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("DelegateNamespaces", namespace_flags_build_json, ULONG_TO_PTR(c->delegate_namespaces)),
JSON_BUILD_PAIR_ENUM("PrivatePBF", private_bpf_to_string(c->private_bpf)),
@@ -1020,6 +1044,9 @@ struct TransientExecContextParameters {
const char *user;
const char *group;
char **supplementary_groups;
bool io_uring_filter_set;
bool io_uring_filter_allow_list;
char **io_uring_filter;
const char *pam_name;
/* Absolute-path strings: must validate via path_is_absolute(). */
const char *ipc_namespace_path;
@@ -1096,6 +1123,7 @@ TransientExecContextParameters* transient_exec_context_parameters_free(Transient
strv_free(p->environment);
strv_free(p->supplementary_groups);
strv_free(p->io_uring_filter);
transient_set_credential_array_free(p->set_credentials, p->n_set_credentials);
transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted);
free(p->bad_field);
@@ -1104,6 +1132,35 @@ TransientExecContextParameters* transient_exec_context_parameters_free(Transient
return mfree(p);
}
static int dispatch_transient_io_uring_filter(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch[] = {
{ "isAllowList", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientExecContextParameters, io_uring_filter_allow_list), SD_JSON_MANDATORY },
{ "operations", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecContextParameters, io_uring_filter), SD_JSON_MANDATORY },
{}
};
TransientExecContextParameters *p = ASSERT_PTR(userdata);
const char *bad_field = NULL;
int r;
r = sd_json_dispatch_full(variant, dispatch, /* bad= */ NULL, flags, p, &bad_field);
if (r < 0) {
if (bad_field)
varlink_propagate_bad_field(bad_field, /* child_bad_field= */ NULL, &p->bad_field);
return r;
}
STRV_FOREACH(s, p->io_uring_filter)
if (io_uring_op_from_string(*s) < 0) {
varlink_propagate_bad_field("operations", /* child_bad_field= */ NULL, &p->bad_field);
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"IOUringFilter: invalid io_uring operation: %s", *s);
}
p->io_uring_filter_set = true;
return 0;
}
static int dispatch_transient_working_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch[] = {
{ "path", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(TransientWorkingDirectory, path), 0 },
@@ -1337,6 +1394,45 @@ static int apply_exec_umask(Unit *u, ExecContext *c, TransientExecContextParamet
return 0;
}
static int apply_exec_io_uring_filter(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
_cleanup_free_ char *joined = NULL;
int r;
assert(c);
assert(p);
if (!p->io_uring_filter_set)
return 0;
c->io_uring_filter = set_free(c->io_uring_filter);
c->io_uring_filter_allow_list = false;
/* Like in unit files and on D-Bus, an empty list resets the setting. */
if (strv_isempty(p->io_uring_filter)) {
unit_write_setting(u, UNIT_RUNTIME|UNIT_PRIVATE, "IOUringFilter", "IOUringFilter=");
return 0;
}
c->io_uring_filter_allow_list = p->io_uring_filter_allow_list;
STRV_FOREACH(s, p->io_uring_filter) {
int op = io_uring_op_from_string(*s);
assert(op >= 0);
r = set_ensure_put(&c->io_uring_filter, /* hash_ops= */ NULL, INT_TO_PTR(op + 1));
if (r < 0)
return r;
}
joined = strv_join(p->io_uring_filter, " ");
if (!joined)
return -ENOMEM;
unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "IOUringFilter", "IOUringFilter=\nIOUringFilter=%s%s",
p->io_uring_filter_allow_list ? "" : "~", joined);
return 0;
}
static int apply_exec_working_directory(Unit *u, ExecContext *c, TransientExecContextParameters *p) {
assert(p);
@@ -1553,6 +1649,7 @@ static const TransientExecProperty exec_properties[] = {
EXEC_PROPERTY_TRISTATE_BOOL("RestrictRealtime", restrict_realtime),
EXEC_PROPERTY_TRISTATE_BOOL("RestrictSUIDSGID", restrict_suid_sgid),
EXEC_PROPERTY_TRISTATE_BOOL("RemoveIPC", remove_ipc),
EXEC_PROPERTY ("IOUringFilter", io_uring_filter, SD_JSON_VARIANT_OBJECT),
EXEC_PROPERTY_SETTABLE ("Environment", environment, SD_JSON_VARIANT_ARRAY),
EXEC_PROPERTY_ENUM ("StandardInput", std_input, dispatch_exec_input),
EXEC_PROPERTY_ENUM ("StandardOutput", std_output, dispatch_exec_output),
+1
View File
@@ -2705,6 +2705,7 @@ static const BusProperty execute_properties[] = {
{ "SystemCallFilter", bus_append_filter_list },
{ "SystemCallLog", bus_append_filter_list },
{ "RestrictNetworkInterfaces", bus_append_filter_list },
{ "IOUringFilter", bus_append_filter_list },
{ "RestrictNamespaces", bus_append_namespace_list },
{ "DelegateNamespaces", bus_append_namespace_list },
{ "BindPaths", bus_append_bind_paths },
+70
View File
@@ -21,6 +21,76 @@
#include "signal-util.h"
#include "string-table.h"
static const char* const io_uring_op_table[IORING_OP_LAST] = {
[IORING_OP_NOP] = "nop",
[IORING_OP_READV] = "readv",
[IORING_OP_WRITEV] = "writev",
[IORING_OP_FSYNC] = "fsync",
[IORING_OP_READ_FIXED] = "read_fixed",
[IORING_OP_WRITE_FIXED] = "write_fixed",
[IORING_OP_POLL_ADD] = "poll_add",
[IORING_OP_POLL_REMOVE] = "poll_remove",
[IORING_OP_SYNC_FILE_RANGE] = "sync_file_range",
[IORING_OP_SENDMSG] = "sendmsg",
[IORING_OP_RECVMSG] = "recvmsg",
[IORING_OP_TIMEOUT] = "timeout",
[IORING_OP_TIMEOUT_REMOVE] = "timeout_remove",
[IORING_OP_ACCEPT] = "accept",
[IORING_OP_ASYNC_CANCEL] = "async_cancel",
[IORING_OP_LINK_TIMEOUT] = "link_timeout",
[IORING_OP_CONNECT] = "connect",
[IORING_OP_FALLOCATE] = "fallocate",
[IORING_OP_OPENAT] = "openat",
[IORING_OP_CLOSE] = "close",
[IORING_OP_FILES_UPDATE] = "files_update",
[IORING_OP_STATX] = "statx",
[IORING_OP_READ] = "read",
[IORING_OP_WRITE] = "write",
[IORING_OP_FADVISE] = "fadvise",
[IORING_OP_MADVISE] = "madvise",
[IORING_OP_SEND] = "send",
[IORING_OP_RECV] = "recv",
[IORING_OP_OPENAT2] = "openat2",
[IORING_OP_EPOLL_CTL] = "epoll_ctl",
[IORING_OP_SPLICE] = "splice",
[IORING_OP_PROVIDE_BUFFERS] = "provide_buffers",
[IORING_OP_REMOVE_BUFFERS] = "remove_buffers",
[IORING_OP_TEE] = "tee",
[IORING_OP_SHUTDOWN] = "shutdown",
[IORING_OP_RENAMEAT] = "renameat",
[IORING_OP_UNLINKAT] = "unlinkat",
[IORING_OP_MKDIRAT] = "mkdirat",
[IORING_OP_SYMLINKAT] = "symlinkat",
[IORING_OP_LINKAT] = "linkat",
[IORING_OP_MSG_RING] = "msg_ring",
[IORING_OP_FSETXATTR] = "fsetxattr",
[IORING_OP_SETXATTR] = "setxattr",
[IORING_OP_FGETXATTR] = "fgetxattr",
[IORING_OP_GETXATTR] = "getxattr",
[IORING_OP_SOCKET] = "socket",
[IORING_OP_URING_CMD] = "uring_cmd",
[IORING_OP_SEND_ZC] = "send_zc",
[IORING_OP_SENDMSG_ZC] = "sendmsg_zc",
[IORING_OP_READ_MULTISHOT] = "read_multishot",
[IORING_OP_WAITID] = "waitid",
[IORING_OP_FUTEX_WAIT] = "futex_wait",
[IORING_OP_FUTEX_WAKE] = "futex_wake",
[IORING_OP_FUTEX_WAITV] = "futex_waitv",
[IORING_OP_FIXED_FD_INSTALL] = "fixed_fd_install",
[IORING_OP_FTRUNCATE] = "ftruncate",
[IORING_OP_BIND] = "bind",
[IORING_OP_LISTEN] = "listen",
[IORING_OP_RECV_ZC] = "recv_zc",
[IORING_OP_EPOLL_WAIT] = "epoll_wait",
[IORING_OP_READV_FIXED] = "readv_fixed",
[IORING_OP_WRITEV_FIXED] = "writev_fixed",
[IORING_OP_PIPE] = "pipe",
[IORING_OP_NOP128] = "nop128",
[IORING_OP_URING_CMD128] = "uring_cmd128",
};
DEFINE_STRING_TABLE_LOOKUP(io_uring_op, int);
/* Each opcode maps to the system calls that it can do the work of. An opcode that is missing here has no
* system call equivalent, because it only manages the ring itself. */
static const char* const io_uring_op_syscalls_table[IORING_OP_LAST] = {
+3
View File
@@ -3,6 +3,9 @@
#include "forward.h"
const char* io_uring_op_to_string(int i) _const_;
int io_uring_op_from_string(const char *s) _pure_;
const char* io_uring_op_syscalls_to_string(int i) _const_;
int io_uring_bpf_filter_supported(void);
+10
View File
@@ -571,6 +571,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_FIELD_COMMENT("The list of filesystems"),
SD_VARLINK_DEFINE_FIELD(filesystems, SD_VARLINK_STRING, SD_VARLINK_ARRAY));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
IOUringOperationList,
SD_VARLINK_FIELD_COMMENT("Whether the list of io_uring operations is an allow list"),
SD_VARLINK_DEFINE_FIELD(isAllowList, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("The list of io_uring operations"),
SD_VARLINK_DEFINE_FIELD(operations, SD_VARLINK_STRING, SD_VARLINK_ARRAY));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
SystemCallList,
SD_VARLINK_FIELD_COMMENT("Whether the list of system calls is an allow list"),
@@ -816,6 +823,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestrictAddressFamilies, AddressFamilyList, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictFileSystems="),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestrictFilesystems, FilesystemList, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IOUringFilter="),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(IOUringFilter, IOUringOperationList, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictNamespaces="),
SD_VARLINK_DEFINE_FIELD(RestrictNamespaces, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#DelegateNamespaces="),
@@ -2120,6 +2129,7 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_type_TemporaryFilesystem,
&vl_type_AddressFamilyList,
&vl_type_FilesystemList,
&vl_type_IOUringOperationList,
&vl_type_SystemCallList,
&vl_type_EnvironmentFile,
&vl_type_LogFilterPattern,
+1 -1
View File
@@ -1276,7 +1276,7 @@ static int print_property(
return 1;
} else if (STR_IN_SET(name, "SystemCallFilter", "SystemCallLog", "RestrictAddressFamilies", "RestrictNetworkInterfaces", "RestrictFileSystems")) {
} else if (STR_IN_SET(name, "SystemCallFilter", "SystemCallLog", "RestrictAddressFamilies", "RestrictNetworkInterfaces", "RestrictFileSystems", "IOUringFilter")) {
_cleanup_strv_free_ char **l = NULL;
int allow_list;
+6
View File
@@ -667,6 +667,8 @@ TEST(execute_properties) {
"RestrictFileSystems=ext4",
"RestrictFileSystems=ext4 xfs",
"RestrictFileSystems=~tmpfs",
"IOUringFilter=read write",
"IOUringFilter=~uring_cmd,socket",
"SystemCallFilter=@system-service",
"SystemCallFilter=read write open close",
"SystemCallFilter=read,write,open,close",
@@ -1456,6 +1458,10 @@ TEST(values_filter_lists) {
"SystemCallLog", "(bas)", "[true,['@system-service']]");
test_setting(UNIT_SERVICE, "RestrictFileSystems=~tmpfs,ext4",
"RestrictFileSystems", "(bas)", "[false,['tmpfs','ext4']]");
test_setting(UNIT_SERVICE, "IOUringFilter=read,write",
"IOUringFilter", "(bas)", "[true,['read','write']]");
test_setting(UNIT_SERVICE, "IOUringFilter=~uring_cmd socket",
"IOUringFilter", "(bas)", "[false,['uring_cmd','socket']]");
test_setting(UNIT_SERVICE, "RestrictNetworkInterfaces=~eth0 lo",
"RestrictNetworkInterfaces", "(bas)", "[false,['eth0','lo']]");
test_setting(UNIT_SERVICE, "RestrictNetworkInterfaces=~iface,wo,comma 'iface,w,comma'",
+10
View File
@@ -103,6 +103,16 @@ static bool have_io_uring_bpf_filters(void) {
if (!have_io_uring_bpf_filters()) \
return (void) log_tests_skipped("io_uring BPF filters are not available")
TEST(io_uring_op_names) {
for (int op = 0; op < IORING_OP_LAST; op++) {
const char *name = ASSERT_NOT_NULL(io_uring_op_to_string(op));
ASSERT_EQ(io_uring_op_from_string(name), op);
}
ASSERT_NULL(io_uring_op_to_string(IORING_OP_LAST));
ASSERT_ERROR(io_uring_op_from_string("foobar"), EINVAL);
}
TEST(restrict_ops_deny_list) {
int r;
+31
View File
@@ -11,6 +11,7 @@
#include "capability-util.h"
#include "cgroup.h"
#include "conf-parser.h"
#include "execute.h"
#include "fileio.h"
#include "format-util.h"
#include "hashmap.h"
@@ -720,6 +721,36 @@ TEST(config_parse_capability_set) {
ASSERT_EQ(capability_bounding_set, make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN));
}
static void check_io_uring_filter(ExecContext *c, const char *rvalue, bool allow_list, const char *expected) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *joined = NULL;
ASSERT_OK(config_parse_io_uring_filter(/* unit= */ NULL, "fake", 1, "section", 1,
"IOUringFilter", /* ltype= */ 0, rvalue,
c, /* userdata= */ NULL));
ASSERT_EQ((bool) c->io_uring_filter_allow_list, allow_list);
l = ASSERT_NOT_NULL(exec_context_get_io_uring_filter(c));
joined = ASSERT_NOT_NULL(strv_join(l, " "));
ASSERT_STREQ(joined, expected);
}
TEST(config_parse_io_uring_filter) {
ExecContext c = {};
check_io_uring_filter(&c, "read write", /* allow_list= */ true, "read write");
check_io_uring_filter(&c, "socket,nop", /* allow_list= */ true, "nop read socket write");
check_io_uring_filter(&c, "~write", /* allow_list= */ true, "nop read socket");
check_io_uring_filter(&c, "foobar openat", /* allow_list= */ true, "nop openat read socket");
check_io_uring_filter(&c, "", /* allow_list= */ false, "");
check_io_uring_filter(&c, "~uring_cmd socket", /* allow_list= */ false, "socket uring_cmd");
check_io_uring_filter(&c, "socket", /* allow_list= */ false, "uring_cmd");
check_io_uring_filter(&c, "~send_zc", /* allow_list= */ false, "send_zc uring_cmd");
c.io_uring_filter = set_free(c.io_uring_filter);
}
TEST(config_parse_rlimit) {
struct rlimit * rl[_RLIMIT_MAX] = {};
@@ -119,6 +119,7 @@ IOAccounting=
IODeviceWeight=
IOReadBandwidthMax=
IOReadIOPSMax=
IOUringFilter=
IOWeight=
IOWriteBandwidthMax=
IOWriteIOPSMax=
+16
View File
@@ -139,6 +139,8 @@ run -p DynamicUser=yes -p User=io-uring-limits -p LimitNPROC=1 -p RestrictAddres
run -p TasksMax=1 -p RestrictAddressFamilies=AF_UNIX true
if [[ "$(run python3 "$HELPER" probe)" != "supported" ]]; then
# Without io_uring BPF filters, IOUringFilter= denies io_uring_setup() with ENOSYS.
[[ "$(run -p IOUringFilter=~nop python3 "$HELPER" nop)" == "setup -38" ]]
echo "kernel does not support io_uring BPF filters, skipping" >&2
exit 77
fi
@@ -180,5 +182,19 @@ done
[[ "$(run -p SystemCallFilter=~fsync:0 python3 "$HELPER" fsync "$HELPER")" == 0 ]]
[[ "$(run -p SystemCallFilter=~io_uring_setup -p SystemCallErrorNumber=ENOSYS python3 "$HELPER" nop)" == "setup -38" ]]
# IOUringFilter= works as a deny list and as an allow list.
[[ "$(run -p IOUringFilter=~nop python3 "$HELPER" nop)" == "$EACCES" ]]
(( $(run -p IOUringFilter=~nop python3 "$HELPER" socket unix) >= 0 ))
[[ "$(run -p IOUringFilter=nop python3 "$HELPER" nop)" == 0 ]]
[[ "$(run -p IOUringFilter=nop python3 "$HELPER" socket unix)" == "$EACCES" ]]
[[ "$(run -p IOUringFilter=nop -p IOUringFilter=~nop python3 "$HELPER" nop)" == "$EACCES" ]]
[[ "$(run -p IOUringFilter=~nop -p IOUringFilter= python3 "$HELPER" nop)" == 0 ]]
# Services that run as an unprivileged user get the filters as well.
[[ "$(run -p User=testuser -p IOUringFilter=~nop python3 "$HELPER" nop)" == "$EACCES" ]]
[[ "$(run -p User=testuser -p RestrictAddressFamilies=AF_UNIX python3 "$HELPER" socket inet)" == "$EACCES" ]]
# The property is visible over D-Bus.
systemd-run --unit=io-uring-filter-test --remain-after-exit -p IOUringFilter="~uring_cmd socket" true
[[ "$(systemctl show -P IOUringFilter io-uring-filter-test.service)" == "~socket uring_cmd" ]]
systemctl stop io-uring-filter-test.service
@@ -130,6 +130,9 @@ testcase_dispatch_validation() {
expect_invalid_parameter \
'{"context":{"ID":"varlink-transient-bad-dispatch.service","Exec":{"WorkingDirectory":{"path":"","missingOK":true}}}}' \
"context.Exec.WorkingDirectory.path"
expect_invalid_parameter \
'{"context":{"ID":"varlink-transient-bad-dispatch.service","Exec":{"IOUringFilter":{"isAllowList":true,"operations":["read","foobar"]}}}}' \
"context.Exec.IOUringFilter.operations"
}
testcase_authorization() {
@@ -241,6 +244,14 @@ testcase_exec_properties() {
fragment=$(systemctl show -P FragmentPath varlink-transient-stdio.service)
grep '^StandardInput=null$' "$fragment" >/dev/null
# StartTransient accepts Exec.IOUringFilter.
defer_transient_cleanup varlink-transient-io-uring.service
result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \
'{"context":{"ID":"varlink-transient-io-uring.service","Exec":{"IOUringFilter":{"isAllowList":false,"operations":["uring_cmd","socket"]}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}')
echo "$result" | jq -e '.context.Exec.IOUringFilter == {"isAllowList":false,"operations":["socket","uring_cmd"]}'
timeout 30 bash -c 'until systemctl is-active varlink-transient-io-uring.service; do sleep 0.5; done'
[[ "$(systemctl show -P IOUringFilter varlink-transient-io-uring.service)" == "~socket uring_cmd" ]]
# The combined output types are spelled with a "+" in unit files, but with a "_" in the Varlink IDL (and
# hence in our own replies). The latter spelling must be accepted, so that replies can be fed back in.
defer_transient_cleanup varlink-transient-stdio-combined.service