diff --git a/man/rules/meson.build b/man/rules/meson.build
index a3d8500cc6..b37eb5f19f 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -1196,6 +1196,11 @@ manpages = [
['systemd-report-sign-plain',
'systemd-report-sign-plain.socket'],
'HAVE_OPENSSL'],
+ ['systemd-report-sign-tsm@.service',
+ '8',
+ ['systemd-report-sign-tsm',
+ 'systemd-report-sign-tsm.socket'],
+ ''],
['systemd-resolved.service', '8', ['systemd-resolved'], 'ENABLE_RESOLVE'],
['systemd-rfkill.service',
'8',
diff --git a/man/systemd-report-sign-tsm@.service.xml b/man/systemd-report-sign-tsm@.service.xml
new file mode 100644
index 0000000000..61d67b24a9
--- /dev/null
+++ b/man/systemd-report-sign-tsm@.service.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+ systemd-report-sign-tsm@.service
+ systemd
+
+
+
+ systemd-report-sign-tsm@.service
+ 8
+
+
+
+ systemd-report-sign-tsm@.service
+ systemd-report-sign-tsm.socket
+ systemd-report-sign-tsm
+ Sign system reports with a confidential-computing attestation report
+
+
+
+ systemd-report-sign-tsm@.service
+ systemd-report-sign-tsm.socket
+ /usr/lib/systemd/systemd-report-sign-tsm
+
+
+
+ Description
+
+ systemd-report-sign-tsm@.service is a system service that signs system reports
+ generated by
+ systemd-report1. It is
+ a signing backend for the logic of that tool: it implements the
+ io.systemd.Report.Signer.Sign() Varlink method and is reached via a socket linked into
+ the /run/systemd/report.sign/ directory, named tsm.
+
+ The service is socket-activated (one instance per connection) via
+ systemd-report-sign-tsm.socket. Rather than signing with a local key, it obtains a
+ hardware-backed attestation report from the platform's Trusted Security Module (TSM) through the kernel's
+ configfs-TSM interface at /sys/kernel/config/tsm/report/. The digest passed to it is
+ embedded into the report as its run-time provided data (inblob), cryptographically
+ binding the attestation report to the system report being signed.
+
+ The returned signature carries the binary attestation report (the outblob), the
+ name of the TSM provider that generated it (for example sev_guest for AMD SEV-SNP or
+ tdx_guest for Intel TDX), and, where the provider supplies them, supplemental
+ certificate (auxblob) and manifest (manifestblob) data. A verifier
+ selects the appropriate validation logic based on the provider field.
+
+ This backend is only functional inside a confidential virtual machine whose kernel exposes the
+ configfs-TSM interface (such as AMD SEV-SNP or Intel TDX guests). On systems where the interface is
+ unavailable the signing operation is reported as unsupported and the mechanism is skipped. The service
+ keeps no persistent state.
+
+
+
+
+
+ See Also
+
+ systemd1
+ systemd-report1
+
+
+
+
diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset
index a00643f684..005280cd94 100644
--- a/presets/90-systemd.preset
+++ b/presets/90-systemd.preset
@@ -35,6 +35,7 @@ enable systemd-report-basic.socket
enable systemd-report-cgroup.socket
enable systemd-report-files.socket
enable systemd-report-sign-plain.socket
+enable systemd-report-sign-tsm.socket
enable systemd-report.socket
enable systemd-resolved.service
enable systemd-sysext.service
diff --git a/src/report/meson.build b/src/report/meson.build
index c3e7cf959e..d0a316c689 100644
--- a/src/report/meson.build
+++ b/src/report/meson.build
@@ -44,4 +44,10 @@ executables += [
),
'dependencies' : libopenssl_cflags,
},
+ libexec_template + {
+ 'name' : 'systemd-report-sign-tsm',
+ 'sources' : files(
+ 'report-sign-tsm.c',
+ ),
+ },
]
diff --git a/src/report/report-sign-tsm.c b/src/report/report-sign-tsm.c
new file mode 100644
index 0000000000..0929fbbc0b
--- /dev/null
+++ b/src/report/report-sign-tsm.c
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-json.h"
+#include "sd-varlink.h"
+
+#include "build.h"
+#include "format-table.h"
+#include "help-util.h"
+#include "iovec-util.h"
+#include "json-util.h"
+#include "log.h"
+#include "main-func.h"
+#include "options.h"
+#include "sha256.h"
+#include "string-util.h"
+#include "tsm-report.h"
+#include "varlink-io.systemd.Report.Signer.h"
+#include "varlink-util.h"
+
+typedef struct SignParameters {
+ struct iovec digest;
+ const char *algorithm;
+} SignParameters;
+
+static void sign_parameters_done(SignParameters *p) {
+ iovec_done(&p->digest);
+}
+
+static int vl_method_sign(
+ sd_varlink *link,
+ sd_json_variant *parameters,
+ sd_varlink_method_flags_t flags,
+ void *userdata) {
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "digest", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(SignParameters, digest), SD_JSON_MANDATORY },
+ { "algorithm", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(SignParameters, algorithm), SD_JSON_MANDATORY },
+ {}
+ };
+
+ _cleanup_(sign_parameters_done) SignParameters sp = {};
+ _cleanup_(tsm_report_freep) TsmReport *report = NULL;
+ int r;
+
+ assert(link);
+ assert(parameters);
+
+ r = sd_varlink_dispatch(link, parameters, dispatch_table, &sp);
+ if (r != 0)
+ return r;
+
+ if (!streq(sp.algorithm, "SHA256"))
+ return sd_varlink_error_invalid_parameter_name(link, "algorithm");
+ if (sp.digest.iov_len != SHA256_DIGEST_SIZE)
+ return sd_varlink_error_invalid_parameter_name(link, "digest");
+
+ uint8_t report_data[TSM_REPORT_DATA_SIZE] = {};
+ memcpy(report_data, sp.digest.iov_base, sp.digest.iov_len);
+
+ r = tsm_report_acquire(&IOVEC_MAKE(report_data, sizeof report_data), /* options= */ NULL, &report);
+ if (IN_SET(r, -EOPNOTSUPP, -ENXIO))
+ /* Returning an error will fail the whole report signing, so we return
+ * no signature instead which will be ignored by the aggregator. */
+ return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_EMPTY_ARRAY("data"));
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire TSM report: %m");
+
+ return sd_varlink_replybo(
+ link,
+ SD_JSON_BUILD_PAIR("data",
+ SD_JSON_BUILD_ARRAY(
+ SD_JSON_BUILD_OBJECT(
+ SD_JSON_BUILD_PAIR_STRING("provider", report->provider),
+ JSON_BUILD_PAIR_IOVEC_BASE64("outblob", &report->outblob),
+ SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(&report->auxblob),
+ "auxblob", JSON_BUILD_IOVEC_BASE64(&report->auxblob)),
+ SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(&report->manifestblob),
+ "manifestblob", JSON_BUILD_IOVEC_BASE64(&report->manifestblob))))));
+}
+
+static int vl_server(void) {
+ _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL;
+ int r;
+
+ r = varlink_server_new(&vs, SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_MYSELF_ONLY, /* userdata= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+ r = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Report_Signer);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+ r = sd_varlink_server_bind_method(vs, "io.systemd.Report.Signer.Sign", vl_method_sign);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+ r = sd_varlink_server_loop_auto(vs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
+ return 0;
+}
+
+static int help(void) {
+ _cleanup_(table_unrefp) Table *options = NULL;
+ int r;
+
+ r = option_parser_get_help_table(&options);
+ if (r < 0)
+ return r;
+
+ help_cmdline("[OPTIONS...]");
+ help_abstract("Get an attestation report via configfs Trusted Security Module (TSM) "
+ "that includes the hash of the system report.");
+ help_section("Options");
+
+ r = table_print_or_warn(options);
+ if (r < 0)
+ return r;
+
+ help_man_page_reference("systemd-report-sign-tsm@.service", "8");
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ int r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ OptionParser opts = { argc, argv };
+
+ FOREACH_OPTION_OR_RETURN(c, &opts)
+ switch (c) {
+ OPTION_COMMON_HELP:
+ return help();
+ OPTION_COMMON_VERSION:
+ return version();
+ }
+
+ if (option_parser_get_n_args(&opts) > 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "This program takes no arguments.");
+
+ r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "This program can only run as a Varlink service.");
+
+ return 1;
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ return vl_server();
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/shared/meson.build b/src/shared/meson.build
index a3684ade1e..8e874cb99d 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -208,6 +208,7 @@ shared_sources = files(
'tomoyo-util.c',
'tpm2-util.c',
'tpm2-event-log.c',
+ 'tsm-report.c',
'udev-util.c',
'unit-file.c',
'user-record-nss.c',
diff --git a/src/shared/tsm-report.c b/src/shared/tsm-report.c
new file mode 100644
index 0000000000..d10eb7fdbf
--- /dev/null
+++ b/src/shared/tsm-report.c
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+#include
+
+#include "sd-id128.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "io-util.h"
+#include "iovec-util.h"
+#include "log.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "stdio-util.h"
+#include "tsm-report.h"
+
+#define TSM_REPORT_PATH "/sys/kernel/config/tsm/report"
+
+TsmReport *tsm_report_free(TsmReport *report) {
+ if (!report)
+ return NULL;
+
+ free(report->provider);
+ iovec_done(&report->outblob);
+ iovec_done(&report->auxblob);
+ iovec_done(&report->manifestblob);
+
+ return mfree(report);
+}
+
+static int read_generation(int entry_fd, uint64_t *ret) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ assert(entry_fd >= 0);
+ assert(ret);
+
+ /* The kernel bumps this counter on every option write to the entry. We snapshot it before writing
+ * and re-check it after reading the report to detect a writer racing us on this entry. */
+
+ r = read_one_line_file_at(entry_fd, "generation", &s);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read 'generation' attribute: %m");
+
+ r = safe_atou64(s, ret);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse 'generation' attribute: %m");
+
+ return 0;
+}
+
+static int tsm_report_fill(
+ int entry_fd,
+ const struct iovec *report_data,
+ const TsmReportOptions *options,
+ TsmReport **ret) {
+
+ _cleanup_close_ int inblob_fd = -EBADF;
+ _cleanup_(tsm_report_freep) TsmReport *report = NULL;
+ _cleanup_free_ char *floor = NULL;
+ bool has_privlevel = false, has_floor = false;
+ unsigned privlevel = 0, privlevel_floor = 0;
+ int r;
+
+ assert(entry_fd >= 0);
+ assert(report_data);
+ assert(ret);
+
+ r = read_one_line_file_at(entry_fd, "privlevel_floor", &floor);
+ if (r >= 0) {
+ r = safe_atou(floor, &privlevel_floor);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse 'privlevel_floor' attribute: %m");
+ has_floor = true;
+ } else if (r != -ENOENT)
+ return log_debug_errno(r, "Failed to read 'privlevel_floor' attribute: %m");
+ /* -ENOENT: provider has no privlevel concept, leave it unset. */
+
+ if (options && options->privlevel_set) {
+ if (!has_floor)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "TSM provider does not support 'privlevel'.");
+
+ if (options->privlevel < privlevel_floor)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Requested privlevel %u is below the provider's floor %u.",
+ options->privlevel, privlevel_floor);
+
+ privlevel = options->privlevel;
+ has_privlevel = true;
+ } else if (has_floor) {
+ privlevel = privlevel_floor;
+ has_privlevel = true;
+ }
+
+ /* Snapshot the write-generation counter before touching any input. Note that tracking generations
+ * is defense in-depth, as we are already operating on a private directory per report request. */
+ uint64_t generation;
+ r = read_generation(entry_fd, &generation);
+ if (r < 0)
+ return r;
+
+ /* Write inputs. Each successful option write bumps the kernel's generation by one. */
+
+ if (has_privlevel) {
+ char privlvl_buf[DECIMAL_STR_MAX(unsigned)];
+ xsprintf(privlvl_buf, "%u", privlevel);
+
+ r = write_string_file_at(entry_fd, "privlevel",
+ privlvl_buf,
+ WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to write 'privlevel' attribute: %m");
+ generation++;
+ }
+
+ inblob_fd = openat(entry_fd, "inblob", O_WRONLY|O_CLOEXEC);
+ if (inblob_fd < 0)
+ return log_debug_errno(errno, "Failed to open 'inblob' attribute: %m");
+ r = loop_write(inblob_fd, report_data->iov_base, report_data->iov_len);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to write 'inblob' attribute: %m");
+ inblob_fd = safe_close(inblob_fd); /* configfs commits the buffered write only on close. */
+ generation++;
+
+ /* Read output. */
+
+ report = new0(TsmReport, 1);
+ if (!report)
+ return log_oom_debug();
+
+ r = read_full_file_at(entry_fd, "outblob",
+ (char**) &report->outblob.iov_base, &report->outblob.iov_len);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read 'outblob' attribute: %m");
+
+ r = read_one_line_file_at(entry_fd, "provider", &report->provider);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to read 'provider' attribute: %m");
+
+ r = read_full_file_at(entry_fd, "auxblob",
+ (char**) &report->auxblob.iov_base, &report->auxblob.iov_len);
+ if (r < 0 && r != -ENOENT) /* auxblob is optional */
+ return log_debug_errno(r, "Failed to read 'auxblob' attribute: %m");
+
+ r = read_full_file_at(entry_fd, "manifestblob",
+ (char**) &report->manifestblob.iov_base, &report->manifestblob.iov_len);
+ if (r < 0 && r != -ENOENT) /* manifestblob is optional */
+ return log_debug_errno(r, "Failed to read 'manifestblob' attribute: %m");
+
+ uint64_t generation_now;
+ r = read_generation(entry_fd, &generation_now);
+ if (r < 0)
+ return r;
+ if (generation_now != generation)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBUSY),
+ "TSM report generation changed during acquisition (%" PRIu64 " != %" PRIu64 "), concurrent access?",
+ generation_now, generation);
+
+ *ret = TAKE_PTR(report);
+ return 0;
+}
+
+int tsm_report_acquire(
+ const struct iovec *report_data,
+ const TsmReportOptions *options,
+ TsmReport **ret) {
+
+ _cleanup_close_ int report_fd = -EBADF, entry_fd = -EBADF;
+ _cleanup_free_ char *name = NULL;
+ sd_id128_t rnd;
+ int r;
+
+ assert(ret);
+
+ if (!report_data || !iovec_is_set(report_data))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Report data can't be empty");
+ if (report_data->iov_len != TSM_REPORT_DATA_SIZE)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Report data must be %u bytes.",
+ TSM_REPORT_DATA_SIZE);
+
+ report_fd = open(TSM_REPORT_PATH, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
+ if (report_fd < 0) {
+ if (errno == ENOENT)
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "configfs-tsm interface not available at " TSM_REPORT_PATH ".");
+ return log_debug_errno(errno, "Failed to open " TSM_REPORT_PATH ": %m");
+ }
+
+ /* Private, unique entry name so we don't race with other callers.
+ * PID is included for attribution. */
+ r = sd_id128_randomize(&rnd);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate report entry name: %m");
+ r = asprintf(&name, "systemd-report-" PID_FMT "-%s", getpid_cached(), SD_ID128_TO_STRING(rnd));
+ if (r < 0)
+ return log_oom_debug();
+
+ entry_fd = open_mkdir_at(report_fd, name, O_EXCL|O_RDONLY|O_CLOEXEC, 0700);
+ if (entry_fd < 0)
+ return log_debug_errno(entry_fd, "Failed to create TSM report entry: %m");
+
+ r = tsm_report_fill(entry_fd, report_data, options, ret);
+
+ /* Remove the entry regardless of success/failure. */
+ if (unlinkat(report_fd, name, AT_REMOVEDIR) < 0)
+ log_debug_errno(errno, "Failed to remove TSM report entry '%s', ignoring: %m", name);
+
+ return r;
+}
diff --git a/src/shared/tsm-report.h b/src/shared/tsm-report.h
new file mode 100644
index 0000000000..6b7d63ac60
--- /dev/null
+++ b/src/shared/tsm-report.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+#include "shared-forward.h"
+
+#define TSM_REPORT_DATA_SIZE 64U
+
+/* Optional knobs. Mirrors the write-only attributes other than inblob.
+ * Zero-initialize ({}) and set only what you need. NULL options == all defaults. */
+typedef struct TsmReportOptions {
+ unsigned privlevel; /* e.g. SEV-SNP VMPL */
+ bool privlevel_set; /* privlevel_floor is used when unset */
+ /* service_provider currently not supported. */
+} TsmReportOptions;
+
+/* Result. Mirrors the read-only attributes. */
+typedef struct TsmReport {
+ char *provider; /* e.g. "sev_guest", "tdx_guest" */
+ struct iovec outblob; /* the attestation report */
+ struct iovec auxblob; /* optional, unset if empty (e.g. SEV cert_table) */
+ struct iovec manifestblob; /* optional, unset if empty */
+} TsmReport;
+
+TsmReport *tsm_report_free(TsmReport *report);
+DEFINE_TRIVIAL_CLEANUP_FUNC(TsmReport*, tsm_report_free);
+
+/* Acquire an attestation report via configfs-tsm.
+ * report_data: mandatory inblob to include in the report, TSM_REPORT_DATA_SIZE bytes
+ * options: optional, NULL for defaults
+ * ret: result, freed with tsm_report_free()
+ * Returns -EOPNOTSUPP if the configfs-tsm interface is absent, -ENXIO if it is present
+ * but no provider is registered. Other negative errnos are real failures. */
+int tsm_report_acquire(
+ const struct iovec *report_data,
+ const TsmReportOptions *options,
+ TsmReport **ret);
diff --git a/units/meson.build b/units/meson.build
index 47c48bcabc..ea59dac424 100644
--- a/units/meson.build
+++ b/units/meson.build
@@ -747,6 +747,8 @@ units = [
'file' : 'systemd-report-sign-plain@.service.in',
'conditions' : ['HAVE_OPENSSL'],
},
+ { 'file' : 'systemd-report-sign-tsm.socket' },
+ { 'file' : 'systemd-report-sign-tsm@.service.in' },
{
'file' : 'systemd-resolved.service.in',
'conditions' : ['ENABLE_RESOLVE'],
diff --git a/units/systemd-report-sign-tsm.socket b/units/systemd-report-sign-tsm.socket
new file mode 100644
index 0000000000..1756dde8cf
--- /dev/null
+++ b/units/systemd-report-sign-tsm.socket
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Report Signing Socket (configfs-tsm)
+Documentation=man:systemd-report-sign-tsm@.service(8)
+DefaultDependencies=no
+Before=sockets.target shutdown.target
+Conflicts=shutdown.target
+ConditionSecurity=cvm
+
+[Socket]
+ListenStream=/run/systemd/report.sign/tsm
+FileDescriptorName=varlink
+SocketMode=0600
+Accept=yes
+MaxConnectionsPerSource=16
+RemoveOnStop=yes
+
+[Install]
+WantedBy=sockets.target
diff --git a/units/systemd-report-sign-tsm@.service.in b/units/systemd-report-sign-tsm@.service.in
new file mode 100644
index 0000000000..6e2f4805dc
--- /dev/null
+++ b/units/systemd-report-sign-tsm@.service.in
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Report Signing Service (configfs-tsm)
+Documentation=man:systemd-report-sign-tsm@.service(8)
+DefaultDependencies=no
+Before=shutdown.target
+Conflicts=shutdown.target
+Wants=sys-kernel-config.mount
+After=sys-kernel-config.mount
+
+[Service]
+ExecStart={{LIBEXECDIR}}/systemd-report-sign-tsm
+CapabilityBoundingSet=
+DeviceAllow=
+IPAddressDeny=any
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+PrivateDevices=yes
+PrivateIPC=yes
+PrivateNetwork=yes
+PrivateTmp=disconnected
+ProtectControlGroups=yes
+ProtectHome=yes
+ProtectHostname=yes
+ProtectKernelLogs=yes
+ProtectKernelModules=yes
+ProtectKernelTunables=yes
+ProtectSystem=strict
+ReadWritePaths=-/sys/kernel/config/tsm/report
+RestrictAddressFamilies=AF_UNIX
+RestrictNamespaces=yes
+RestrictRealtime=yes
+RestrictSUIDSGID=yes
+RuntimeMaxSec=5min
+SystemCallArchitectures=native
+SystemCallErrorNumber=EPERM
+SystemCallFilter=@system-service