mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
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:
1 parent
f80d935027
commit
9dcd9ffd89
23 files changed
+628
-5
No files matched your search
@@ -212,6 +212,7 @@ All execution-related settings are available for transient units.
|
||||
✓ RestrictRealtime=
|
||||
✓ RestrictSUIDSGID=
|
||||
✓ RestrictAddressFamilies=
|
||||
✓ IOUringFilter=
|
||||
✓ RootHash=
|
||||
✓ RootHashSignature=
|
||||
✓ RootVerity=
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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'",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user