@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as InaccessiblePaths = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as ExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as NoExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t MountFlags = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
<!--property InaccessiblePaths is not documented!-->
+ <!--property ExecPaths is not documented!-->
+
+ <!--property NoExecPaths is not documented!-->
+
<!--property PrivateTmp is not documented!-->
<!--property PrivateDevices is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as InaccessiblePaths = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as ExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as NoExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t MountFlags = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
<!--property InaccessiblePaths is not documented!-->
+ <!--property ExecPaths is not documented!-->
+
+ <!--property NoExecPaths is not documented!-->
+
<!--property PrivateTmp is not documented!-->
<!--property PrivateDevices is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as InaccessiblePaths = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as ExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as NoExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t MountFlags = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
<!--property InaccessiblePaths is not documented!-->
+ <!--property ExecPaths is not documented!-->
+
+ <!--property NoExecPaths is not documented!-->
+
<!--property PrivateTmp is not documented!-->
<!--property PrivateDevices is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as InaccessiblePaths = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as ExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly as NoExecPaths = ['...', ...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t MountFlags = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly b PrivateTmp = ...;
<!--property InaccessiblePaths is not documented!-->
+ <!--property ExecPaths is not documented!-->
+
+ <!--property NoExecPaths is not documented!-->
+
<!--property PrivateTmp is not documented!-->
<!--property PrivateDevices is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
+
+ <variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
<term><varname>ReadWritePaths=</varname></term>
<term><varname>ReadOnlyPaths=</varname></term>
<term><varname>InaccessiblePaths=</varname></term>
+ <term><varname>ExecPaths=</varname></term>
+ <term><varname>NoExecPaths=</varname></term>
<listitem><para>Sets up a new file system namespace for executed processes. These options may be used
to limit access a process has to the file system. Each setting takes a space-separated list of paths
<varname>BindPaths=</varname>, or <varname>BindReadOnlyPaths=</varname> inside it. For a more flexible option,
see <varname>TemporaryFileSystem=</varname>.</para>
+ <para>Content in paths listed in <varname>NoExecPaths=</varname> are not executable even if the usual
+ file access controls would permit this. Nest <varname>ExecPaths=</varname> inside of
+ <varname>NoExecPaths=</varname> in order to provide executable content within non-executable
+ directories.</para>
+
<para>Non-directory paths may be specified as well. These options may be specified more than once,
in which case all paths listed will have limited access from within the namespace. If the empty string is
assigned to this option, the specific list is reset, and all prior assignments have no effect.</para>
- <para>Paths in <varname>ReadWritePaths=</varname>, <varname>ReadOnlyPaths=</varname> and
- <varname>InaccessiblePaths=</varname> may be prefixed with <literal>-</literal>, in which case they will be
+ <para>Paths in <varname>ReadWritePaths=</varname>, <varname>ReadOnlyPaths=</varname>,
+ <varname>InaccessiblePaths=</varname>, <varname>ExecPaths=</varname> and
+ <varname>NoExecPaths=</varname> may be prefixed with <literal>-</literal>, in which case they will be
ignored when they do not exist. If prefixed with <literal>+</literal> the paths are taken relative to the root
directory of the unit, as configured with <varname>RootDirectory=</varname>/<varname>RootImage=</varname>,
instead of relative to the root directory of the host (see above). When combining <literal>-</literal> and
<varname>CapabilityBoundingSet=~CAP_SYS_ADMIN</varname> or
<varname>SystemCallFilter=~@mount</varname>.</para>
+ <para>Simple allow-list example using these directives:
+ <programlisting>[Service]
+ReadOnlyPaths=/
+ReadWritePaths=/var /run
+InaccessiblePaths=-/lost+found
+NoExecPaths=/
+ExecPaths=/usr/sbin/my_daemon /usr/lib /usr/lib64
+</programlisting></para>
+
<xi:include href="system-only.xml" xpointer="plural"/></listitem>
</varlistentry>
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReadOnlyPaths", "as", NULL, offsetof(ExecContext, read_only_paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("InaccessiblePaths", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ExecPaths", "as", NULL, offsetof(ExecContext, exec_paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("NoExecPaths", "as", NULL, offsetof(ExecContext, no_exec_paths), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST),
return 1;
} else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
- "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
+ "ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths", "ExecPaths", "NoExecPaths")) {
_cleanup_strv_free_ char **l = NULL;
char ***dirs;
char **p;
dirs = &c->read_write_paths;
else if (STR_IN_SET(name, "ReadOnlyDirectories", "ReadOnlyPaths"))
dirs = &c->read_only_paths;
+ else if (streq(name, "ExecPaths"))
+ dirs = &c->exec_paths;
+ else if (streq(name, "NoExecPaths"))
+ dirs = &c->no_exec_paths;
else /* "InaccessiblePaths" */
dirs = &c->inaccessible_paths;
if (!strv_isempty(context->read_write_paths) ||
!strv_isempty(context->read_only_paths) ||
- !strv_isempty(context->inaccessible_paths))
+ !strv_isempty(context->inaccessible_paths) ||
+ !strv_isempty(context->exec_paths) ||
+ !strv_isempty(context->no_exec_paths))
return true;
if (context->n_bind_mounts > 0)
&ns_info, context->read_write_paths,
needs_sandboxing ? context->read_only_paths : NULL,
needs_sandboxing ? context->inaccessible_paths : NULL,
+ needs_sandboxing ? context->exec_paths : NULL,
+ needs_sandboxing ? context->no_exec_paths : NULL,
empty_directories,
bind_mounts,
n_bind_mounts,
c->read_only_paths = strv_free(c->read_only_paths);
c->read_write_paths = strv_free(c->read_write_paths);
c->inaccessible_paths = strv_free(c->inaccessible_paths);
+ c->exec_paths = strv_free(c->exec_paths);
+ c->no_exec_paths = strv_free(c->no_exec_paths);
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
c->bind_mounts = NULL;
fprintf(f, " %s", *g);
}
+static void strv_dump(FILE* f, const char *prefix, const char *name, char **strv) {
+ assert(f);
+ assert(prefix);
+ assert(name);
+
+ if (!strv_isempty(strv)) {
+ fprintf(f, "%s%s:", name, prefix);
+ strv_fprintf(f, strv);
+ fputs("\n", f);
+ }
+}
+
void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
char **e, **d, buf_clean[FORMAT_TIMESPAN_MAX];
int r;
fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user));
- if (!strv_isempty(c->supplementary_groups)) {
- fprintf(f, "%sSupplementaryGroups:", prefix);
- strv_fprintf(f, c->supplementary_groups);
- fputs("\n", f);
- }
+ strv_dump(f, prefix, "SupplementaryGroups", c->supplementary_groups);
if (c->pam_name)
fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name);
- if (!strv_isempty(c->read_write_paths)) {
- fprintf(f, "%sReadWritePaths:", prefix);
- strv_fprintf(f, c->read_write_paths);
- fputs("\n", f);
- }
-
- if (!strv_isempty(c->read_only_paths)) {
- fprintf(f, "%sReadOnlyPaths:", prefix);
- strv_fprintf(f, c->read_only_paths);
- fputs("\n", f);
- }
-
- if (!strv_isempty(c->inaccessible_paths)) {
- fprintf(f, "%sInaccessiblePaths:", prefix);
- strv_fprintf(f, c->inaccessible_paths);
- fputs("\n", f);
- }
+ strv_dump(f, prefix, "ReadWritePaths", c->read_write_paths);
+ strv_dump(f, prefix, "ReadOnlyPaths", c->read_only_paths);
+ strv_dump(f, prefix, "InaccessiblePaths", c->inaccessible_paths);
+ strv_dump(f, prefix, "ExecPaths", c->exec_paths);
+ strv_dump(f, prefix, "NoExecPaths", c->no_exec_paths);
for (size_t i = 0; i < c->n_bind_mounts; i++)
fprintf(f, "%s%s: %s%s:%s:%s\n", prefix,
char *apparmor_profile;
char *smack_process_label;
- char **read_write_paths, **read_only_paths, **inaccessible_paths;
+ char **read_write_paths, **read_only_paths, **inaccessible_paths, **exec_paths, **no_exec_paths;
unsigned long mount_flags;
BindMount *bind_mounts;
size_t n_bind_mounts;
$1.ReadWritePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths)
$1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths)
$1.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths)
+$1.ExecPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.exec_paths)
+$1.NoExecPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.no_exec_paths)
$1.BindPaths, config_parse_bind_paths, 0, offsetof($1, exec_context)
$1.BindReadOnlyPaths, config_parse_bind_paths, 0, offsetof($1, exec_context)
$1.TemporaryFileSystem, config_parse_temporary_filesystems, 0, offsetof($1, exec_context)
RUN,
READONLY,
READWRITE,
+ NOEXEC,
+ EXEC,
TMPFS,
READWRITE_IMPLICIT, /* Should have the lowest priority. */
_MOUNT_MODE_MAX,
bool has_prefix:1; /* Already is prefixed by the root dir? */
bool read_only:1; /* Shall this mount point be read-only? */
bool nosuid:1; /* Shall set MS_NOSUID on the mount itself */
+ bool noexec:1; /* Shall set MS_NOEXEC on the mount itself */
+ bool exec:1; /* Shall clear MS_NOEXEC on the mount itself */
bool applied:1; /* Already applied */
char *path_malloc; /* Use this instead of 'path_const' if we had to allocate memory */
const char *source_const; /* The source path, for bind mounts or images */
[TMPFS] = "tmpfs",
[MOUNT_IMAGES] = "mount-images",
[READWRITE_IMPLICIT] = "rw-implicit",
+ [EXEC] = "exec",
+ [NOEXEC] = "noexec",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(mount_mode, MountMode);
return p->read_only || IN_SET(p->mode, READONLY, INACCESSIBLE, PRIVATE_TMP_READONLY);
}
+static bool mount_entry_noexec(const MountEntry *p) {
+ assert(p);
+
+ return p->noexec || IN_SET(p->mode, NOEXEC, INACCESSIBLE, SYSFS, PROCFS);
+}
+
+static bool mount_entry_exec(const MountEntry *p) {
+ assert(p);
+
+ return p->exec || p->mode == EXEC;
+}
+
static const char *mount_entry_source(const MountEntry *p) {
assert(p);
path_equal(mount_entry_path(f), mount_entry_path(previous)) &&
!f->applied && !previous->applied) {
log_debug("%s (%s) is duplicate.", mount_entry_path(f), mount_mode_to_string(f->mode));
- previous->read_only = previous->read_only || mount_entry_read_only(f); /* Propagate the read-only flag to the remaining entry */
+ /* Propagate the flags to the remaining entry */
+ previous->read_only = previous->read_only || mount_entry_read_only(f);
+ previous->noexec = previous->noexec || mount_entry_noexec(f);
+ previous->exec = previous->exec || mount_entry_exec(f);
mount_entry_done(f);
continue;
}
case READONLY:
case READWRITE:
case READWRITE_IMPLICIT:
+ case EXEC:
+ case NOEXEC:
r = path_is_mount_point(mount_entry_path(m), root_directory, 0);
if (r == -ENOENT && m->ignore)
return 0;
return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m",
mount_entry_path(m));
if (r > 0) /* Nothing to do here, it is already a mount. We just later toggle the MS_RDONLY
- * bit for the mount point if needed. */
+ * and MS_NOEXEC bits for the mount point if needed. */
return 0;
/* This isn't a mount point yet, let's make it one. */
what = mount_entry_path(m);
else
r = bind_remount_one_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, proc_self_mountinfo);
- /* Not that we only turn on the MS_RDONLY flag here, we never turn it off. Something that was marked
+ /* Note that we only turn on the MS_RDONLY flag here, we never turn it off. Something that was marked
* read-only already stays this way. This improves compatibility with container managers, where we
* won't attempt to undo read-only mounts already applied. */
return 0;
}
+static int make_noexec(const MountEntry *m, char **deny_list, FILE *proc_self_mountinfo) {
+ unsigned long new_flags = 0, flags_mask = 0;
+ bool submounts = false;
+ int r = 0;
+
+ assert(m);
+ assert(proc_self_mountinfo);
+
+ if (mount_entry_noexec(m)) {
+ new_flags |= MS_NOEXEC;
+ flags_mask |= MS_NOEXEC;
+ } else if (mount_entry_exec(m)) {
+ new_flags &= ~MS_NOEXEC;
+ flags_mask |= MS_NOEXEC;
+ }
+
+ if (flags_mask == 0) /* No Change? */
+ return 0;
+
+ submounts = !IN_SET(m->mode, EMPTY_DIR, TMPFS);
+
+ if (submounts)
+ r = bind_remount_recursive_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, deny_list, proc_self_mountinfo);
+ else
+ r = bind_remount_one_with_mountinfo(mount_entry_path(m), new_flags, flags_mask, proc_self_mountinfo);
+
+ if (r == -ENOENT && m->ignore)
+ return 0;
+ if (r < 0)
+ return log_debug_errno(r, "Failed to re-mount '%s'%s: %m", mount_entry_path(m),
+ submounts ? " and its submounts" : "");
+ return 0;
+}
+
static bool namespace_info_mount_apivfs(const NamespaceInfo *ns_info) {
assert(ns_info);
char** read_write_paths,
char** read_only_paths,
char** inaccessible_paths,
+ char** exec_paths,
+ char** no_exec_paths,
char** empty_directories,
size_t n_bind_mounts,
size_t n_temporary_filesystems,
strv_length(read_write_paths) +
strv_length(read_only_paths) +
strv_length(inaccessible_paths) +
+ strv_length(exec_paths) +
+ strv_length(no_exec_paths) +
strv_length(empty_directories) +
n_bind_mounts +
n_mount_images +
char** read_write_paths,
char** read_only_paths,
char** inaccessible_paths,
+ char** exec_paths,
+ char** no_exec_paths,
char** empty_directories,
const BindMount *bind_mounts,
size_t n_bind_mounts,
read_write_paths,
read_only_paths,
inaccessible_paths,
+ exec_paths,
+ no_exec_paths,
empty_directories,
n_bind_mounts,
n_temporary_filesystems,
if (r < 0)
goto finish;
+ r = append_access_mounts(&m, exec_paths, EXEC, require_prefix);
+ if (r < 0)
+ goto finish;
+
+ r = append_access_mounts(&m, no_exec_paths, NOEXEC, require_prefix);
+ if (r < 0)
+ goto finish;
+
r = append_empty_dir_mounts(&m, empty_directories);
if (r < 0)
goto finish;
goto finish;
}
}
+
+ /* Third round, flip the noexec bits with a simplified deny list. */
+ for (m = mounts, j = 0; m < mounts + n_mounts; ++m)
+ if (IN_SET(m->mode, EXEC, NOEXEC))
+ deny_list[j++] = (char*) mount_entry_path(m);
+ deny_list[j] = NULL;
+
+ for (m = mounts; m < mounts + n_mounts; ++m) {
+ r = make_noexec(m, deny_list, proc_self_mountinfo);
+ if (r < 0) {
+ if (error_path && mount_entry_path(m))
+ *error_path = strdup(mount_entry_path(m));
+ goto finish;
+ }
+ }
}
/* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */
char **read_write_paths,
char **read_only_paths,
char **inaccessible_paths,
+ char **exec_paths,
+ char **no_exec_paths,
char **empty_directories,
const BindMount *bind_mounts,
size_t n_bind_mounts,
"ReadWritePaths",
"ReadOnlyPaths",
"InaccessiblePaths",
+ "ExecPaths",
+ "NoExecPaths",
"RuntimeDirectory",
"StateDirectory",
"CacheDirectory",
assert(prefix);
assert(proc_self_mountinfo);
- /* Recursively remount a directory (and all its submounts) read-only or read-write. If the directory is already
- * mounted, we reuse the mount and simply mark it MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write
- * operation). If it isn't we first make it one. Afterwards we apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to
- * all submounts we can access, too. When mounts are stacked on the same mount point we only care for each
- * individual "top-level" mount on each point, as we cannot influence/access the underlying mounts anyway. We
- * do not have any effect on future submounts that might get propagated, they might be writable. This includes
- * future submounts that have been triggered via autofs.
+ /* Recursively remount a directory (and all its submounts) with desired flags (MS_READONLY,
+ * MS_NOSUID, MS_NOEXEC). If the directory is already mounted, we reuse the mount and simply mark it
+ * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write operation), ditto for other flags. If it
+ * isn't we first make it one. Afterwards we apply (or remove) the flags to all submounts we can
+ * access, too. When mounts are stacked on the same mount point we only care for each individual
+ * "top-level" mount on each point, as we cannot influence/access the underlying mounts anyway. We do
+ * not have any effect on future submounts that might get propagated, they might be writable
+ * etc. This includes future submounts that have been triggered via autofs.
*
* If the "deny_list" parameter is specified it may contain a list of subtrees to exclude from the
* remount operation. Note that we'll ignore the deny list for the top-level path. */
test(m, "exec-inaccessiblepaths-mount-propagation.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
}
+static void test_exec_noexecpaths(Manager *m) {
+
+ test(m, "exec-noexecpaths-simple.service", can_unshare ? 0 : EXIT_FAILURE, CLD_EXITED);
+}
+
static void test_exec_temporaryfilesystem(Manager *m) {
test(m, "exec-temporaryfilesystem-options.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED);
entry(test_exec_ignoresigpipe),
entry(test_exec_inaccessiblepaths),
entry(test_exec_ioschedulingclass),
+ entry(test_exec_noexecpaths),
entry(test_exec_oomscoreadjust),
entry(test_exec_passenvironment),
entry(test_exec_personality),
NULL,
NULL,
NULL,
+ NULL,
+ NULL,
NULL, 0,
NULL, 0,
NULL, 0,
NULL
};
+ const char * const exec[] = {
+ "/lib",
+ "/usr",
+ "-/lib64",
+ "-/usr/lib64",
+ NULL
+ };
+
+ const char * const no_exec[] = {
+ "/var",
+ NULL
+ };
+
const char *inaccessible[] = {
"/home/lennart/projects",
NULL
(char **) writable,
(char **) readonly,
(char **) inaccessible,
+ (char **) exec,
+ (char **) no_exec,
NULL,
&(BindMount) { .source = (char*) "/usr/bin", .destination = (char*) "/etc/systemd", .read_only = true }, 1,
&(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1,
DirectoryNotEmpty=
Documentation=
DynamicUser=
+ExecPaths=
ExecReload=
ExecCondition=
ExecStart=
MessageQueueMessageSize=
MountAPIVFS=
NoDelay=
+NoExecPaths=
NoNewPrivileges=
NonBlocking=
NotifyAccess=
--- /dev/null
+[Unit]
+Description=Test for NoExecPaths=
+
+[Service]
+Type=oneshot
+# This should work, as we explicitly disable the effect of NoExecPaths=
+ExecStart=+/bin/sh -c '/bin/cat /dev/null'
+# This should also work, as we do not disable the effect of NoExecPaths= but invert the exit code
+ExecStart=/bin/sh -x -c '! /bin/cat /dev/null'
+NoExecPaths=/bin/cat