Skip to content

Commit ddc155b

Browse files
committed
New directives NoExecPaths= ExecPaths=
Implement directives `NoExecPaths=` and `ExecPaths=` to control `MS_NOEXEC` mount flag for the file system tree. This can be used to implement file system W^X policies, and for example with allow-listing mode (NoExecPaths=/) a compromised service would not be able to execute a shell, if that was not explicitly allowed. Example: [Service] NoExecPaths=/ ExecPaths=/usr/bin/daemon /usr/lib64 /usr/lib Closes: systemd#17942.
1 parent 78dff3f commit ddc155b

File tree

15 files changed

+240
-37
lines changed

15 files changed

+240
-37
lines changed

man/org.freedesktop.systemd1.xml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,6 +2643,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
26432643
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
26442644
readonly as InaccessiblePaths = ['...', ...];
26452645
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
2646+
readonly as ExecPaths = ['...', ...];
2647+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
2648+
readonly as NoExecPaths = ['...', ...];
2649+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
26462650
readonly t MountFlags = ...;
26472651
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
26482652
readonly b PrivateTmp = ...;
@@ -3154,6 +3158,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
31543158

31553159
<!--property InaccessiblePaths is not documented!-->
31563160

3161+
<!--property ExecPaths is not documented!-->
3162+
3163+
<!--property NoExecPaths is not documented!-->
3164+
31573165
<!--property PrivateTmp is not documented!-->
31583166

31593167
<!--property PrivateDevices is not documented!-->
@@ -3722,6 +3730,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
37223730

37233731
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
37243732

3733+
<variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
3734+
3735+
<variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
3736+
37253737
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
37263738

37273739
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@@ -4385,6 +4397,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
43854397
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
43864398
readonly as InaccessiblePaths = ['...', ...];
43874399
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
4400+
readonly as ExecPaths = ['...', ...];
4401+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
4402+
readonly as NoExecPaths = ['...', ...];
4403+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
43884404
readonly t MountFlags = ...;
43894405
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
43904406
readonly b PrivateTmp = ...;
@@ -4924,6 +4940,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
49244940

49254941
<!--property InaccessiblePaths is not documented!-->
49264942

4943+
<!--property ExecPaths is not documented!-->
4944+
4945+
<!--property NoExecPaths is not documented!-->
4946+
49274947
<!--property PrivateTmp is not documented!-->
49284948

49294949
<!--property PrivateDevices is not documented!-->
@@ -5490,6 +5510,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
54905510

54915511
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
54925512

5513+
<variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
5514+
5515+
<variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
5516+
54935517
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
54945518

54955519
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@@ -6066,6 +6090,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
60666090
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
60676091
readonly as InaccessiblePaths = ['...', ...];
60686092
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
6093+
readonly as ExecPaths = ['...', ...];
6094+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
6095+
readonly as NoExecPaths = ['...', ...];
6096+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
60696097
readonly t MountFlags = ...;
60706098
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
60716099
readonly b PrivateTmp = ...;
@@ -6533,6 +6561,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
65336561

65346562
<!--property InaccessiblePaths is not documented!-->
65356563

6564+
<!--property ExecPaths is not documented!-->
6565+
6566+
<!--property NoExecPaths is not documented!-->
6567+
65366568
<!--property PrivateTmp is not documented!-->
65376569

65386570
<!--property PrivateDevices is not documented!-->
@@ -7017,6 +7049,10 @@ node /org/freedesktop/systemd1/unit/home_2emount {
70177049

70187050
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
70197051

7052+
<variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
7053+
7054+
<variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
7055+
70207056
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
70217057

70227058
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>
@@ -7714,6 +7750,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
77147750
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
77157751
readonly as InaccessiblePaths = ['...', ...];
77167752
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
7753+
readonly as ExecPaths = ['...', ...];
7754+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
7755+
readonly as NoExecPaths = ['...', ...];
7756+
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
77177757
readonly t MountFlags = ...;
77187758
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
77197759
readonly b PrivateTmp = ...;
@@ -8167,6 +8207,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
81678207

81688208
<!--property InaccessiblePaths is not documented!-->
81698209

8210+
<!--property ExecPaths is not documented!-->
8211+
8212+
<!--property NoExecPaths is not documented!-->
8213+
81708214
<!--property PrivateTmp is not documented!-->
81718215

81728216
<!--property PrivateDevices is not documented!-->
@@ -8637,6 +8681,10 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
86378681

86388682
<variablelist class="dbus-property" generated="True" extra-ref="InaccessiblePaths"/>
86398683

8684+
<variablelist class="dbus-property" generated="True" extra-ref="ExecPaths"/>
8685+
8686+
<variablelist class="dbus-property" generated="True" extra-ref="NoExecPaths"/>
8687+
86408688
<variablelist class="dbus-property" generated="True" extra-ref="MountFlags"/>
86418689

86428690
<variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/>

man/systemd.exec.xml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,8 @@ StateDirectory=aaa/bbb ccc</programlisting>
13591359
<term><varname>ReadWritePaths=</varname></term>
13601360
<term><varname>ReadOnlyPaths=</varname></term>
13611361
<term><varname>InaccessiblePaths=</varname></term>
1362+
<term><varname>ExecPaths=</varname></term>
1363+
<term><varname>NoExecPaths=</varname></term>
13621364

13631365
<listitem><para>Sets up a new file system namespace for executed processes. These options may be used
13641366
to limit access a process has to the file system. Each setting takes a space-separated list of paths
@@ -1380,12 +1382,18 @@ StateDirectory=aaa/bbb ccc</programlisting>
13801382
<varname>BindPaths=</varname>, or <varname>BindReadOnlyPaths=</varname> inside it. For a more flexible option,
13811383
see <varname>TemporaryFileSystem=</varname>.</para>
13821384

1385+
<para>Content in paths listed in <varname>NoExecPaths=</varname> are not executable even if the usual
1386+
file access controls would permit this. Nest <varname>ExecPaths=</varname> inside of
1387+
<varname>NoExecPaths=</varname> in order to provide executable content within non-executable
1388+
directories.</para>
1389+
13831390
<para>Non-directory paths may be specified as well. These options may be specified more than once,
13841391
in which case all paths listed will have limited access from within the namespace. If the empty string is
13851392
assigned to this option, the specific list is reset, and all prior assignments have no effect.</para>
13861393

1387-
<para>Paths in <varname>ReadWritePaths=</varname>, <varname>ReadOnlyPaths=</varname> and
1388-
<varname>InaccessiblePaths=</varname> may be prefixed with <literal>-</literal>, in which case they will be
1394+
<para>Paths in <varname>ReadWritePaths=</varname>, <varname>ReadOnlyPaths=</varname>,
1395+
<varname>InaccessiblePaths=</varname>, <varname>ExecPaths=</varname> and
1396+
<varname>NoExecPaths=</varname> may be prefixed with <literal>-</literal>, in which case they will be
13891397
ignored when they do not exist. If prefixed with <literal>+</literal> the paths are taken relative to the root
13901398
directory of the unit, as configured with <varname>RootDirectory=</varname>/<varname>RootImage=</varname>,
13911399
instead of relative to the root directory of the host (see above). When combining <literal>-</literal> and
@@ -1408,6 +1416,15 @@ StateDirectory=aaa/bbb ccc</programlisting>
14081416
<varname>CapabilityBoundingSet=~CAP_SYS_ADMIN</varname> or
14091417
<varname>SystemCallFilter=~@mount</varname>.</para>
14101418

1419+
<para>Simple allow-list example using these directives:
1420+
<programlisting>[Service]
1421+
ReadOnlyPaths=/
1422+
ReadWritePaths=/var /run
1423+
InaccessiblePaths=-/lost+found
1424+
NoExecPaths=/
1425+
ExecPaths=/usr/sbin/my_daemon /usr/lib /usr/lib64
1426+
</programlisting></para>
1427+
14111428
<xi:include href="system-only.xml" xpointer="plural"/></listitem>
14121429
</varlistentry>
14131430

src/core/dbus-execute.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
10941094
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
10951095
SD_BUS_PROPERTY("ReadOnlyPaths", "as", NULL, offsetof(ExecContext, read_only_paths), SD_BUS_VTABLE_PROPERTY_CONST),
10961096
SD_BUS_PROPERTY("InaccessiblePaths", "as", NULL, offsetof(ExecContext, inaccessible_paths), SD_BUS_VTABLE_PROPERTY_CONST),
1097+
SD_BUS_PROPERTY("ExecPaths", "as", NULL, offsetof(ExecContext, exec_paths), SD_BUS_VTABLE_PROPERTY_CONST),
1098+
SD_BUS_PROPERTY("NoExecPaths", "as", NULL, offsetof(ExecContext, no_exec_paths), SD_BUS_VTABLE_PROPERTY_CONST),
10971099
SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_flags), SD_BUS_VTABLE_PROPERTY_CONST),
10981100
SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_bool, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST),
10991101
SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -2981,7 +2983,7 @@ int bus_exec_context_set_transient_property(
29812983
return 1;
29822984

29832985
} else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories",
2984-
"ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths")) {
2986+
"ReadWritePaths", "ReadOnlyPaths", "InaccessiblePaths", "ExecPaths", "NoExecPaths")) {
29852987
_cleanup_strv_free_ char **l = NULL;
29862988
char ***dirs;
29872989
char **p;
@@ -3007,6 +3009,10 @@ int bus_exec_context_set_transient_property(
30073009
dirs = &c->read_write_paths;
30083010
else if (STR_IN_SET(name, "ReadOnlyDirectories", "ReadOnlyPaths"))
30093011
dirs = &c->read_only_paths;
3012+
else if (streq(name, "ExecPaths"))
3013+
dirs = &c->exec_paths;
3014+
else if (streq(name, "NoExecPaths"))
3015+
dirs = &c->no_exec_paths;
30103016
else /* "InaccessiblePaths" */
30113017
dirs = &c->inaccessible_paths;
30123018

src/core/execute.c

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,7 +1999,9 @@ bool exec_needs_mount_namespace(
19991999

20002000
if (!strv_isempty(context->read_write_paths) ||
20012001
!strv_isempty(context->read_only_paths) ||
2002-
!strv_isempty(context->inaccessible_paths))
2002+
!strv_isempty(context->inaccessible_paths) ||
2003+
!strv_isempty(context->exec_paths) ||
2004+
!strv_isempty(context->no_exec_paths))
20032005
return true;
20042006

20052007
if (context->n_bind_mounts > 0)
@@ -3206,6 +3208,8 @@ static int apply_mount_namespace(
32063208
&ns_info, context->read_write_paths,
32073209
needs_sandboxing ? context->read_only_paths : NULL,
32083210
needs_sandboxing ? context->inaccessible_paths : NULL,
3211+
needs_sandboxing ? context->exec_paths : NULL,
3212+
needs_sandboxing ? context->no_exec_paths : NULL,
32093213
empty_directories,
32103214
bind_mounts,
32113215
n_bind_mounts,
@@ -4815,6 +4819,8 @@ void exec_context_done(ExecContext *c) {
48154819
c->read_only_paths = strv_free(c->read_only_paths);
48164820
c->read_write_paths = strv_free(c->read_write_paths);
48174821
c->inaccessible_paths = strv_free(c->inaccessible_paths);
4822+
c->exec_paths = strv_free(c->exec_paths);
4823+
c->no_exec_paths = strv_free(c->no_exec_paths);
48184824

48194825
bind_mount_free_many(c->bind_mounts, c->n_bind_mounts);
48204826
c->bind_mounts = NULL;
@@ -5162,6 +5168,18 @@ static void strv_fprintf(FILE *f, char **l) {
51625168
fprintf(f, " %s", *g);
51635169
}
51645170

5171+
static void strv_dump(FILE* f, const char *prefix, const char *name, char **strv) {
5172+
assert(f);
5173+
assert(prefix);
5174+
assert(name);
5175+
5176+
if (!strv_isempty(strv)) {
5177+
fprintf(f, "%s%s:", name, prefix);
5178+
strv_fprintf(f, strv);
5179+
fputs("\n", f);
5180+
}
5181+
}
5182+
51655183
void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
51665184
char **e, **d, buf_clean[FORMAT_TIMESPAN_MAX];
51675185
int r;
@@ -5474,32 +5492,16 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
54745492

54755493
fprintf(f, "%sDynamicUser: %s\n", prefix, yes_no(c->dynamic_user));
54765494

5477-
if (!strv_isempty(c->supplementary_groups)) {
5478-
fprintf(f, "%sSupplementaryGroups:", prefix);
5479-
strv_fprintf(f, c->supplementary_groups);
5480-
fputs("\n", f);
5481-
}
5495+
strv_dump(f, prefix, "SupplementaryGroups", c->supplementary_groups);
54825496

54835497
if (c->pam_name)
54845498
fprintf(f, "%sPAMName: %s\n", prefix, c->pam_name);
54855499

5486-
if (!strv_isempty(c->read_write_paths)) {
5487-
fprintf(f, "%sReadWritePaths:", prefix);
5488-
strv_fprintf(f, c->read_write_paths);
5489-
fputs("\n", f);
5490-
}
5491-
5492-
if (!strv_isempty(c->read_only_paths)) {
5493-
fprintf(f, "%sReadOnlyPaths:", prefix);
5494-
strv_fprintf(f, c->read_only_paths);
5495-
fputs("\n", f);
5496-
}
5497-
5498-
if (!strv_isempty(c->inaccessible_paths)) {
5499-
fprintf(f, "%sInaccessiblePaths:", prefix);
5500-
strv_fprintf(f, c->inaccessible_paths);
5501-
fputs("\n", f);
5502-
}
5500+
strv_dump(f, prefix, "ReadWritePaths", c->read_write_paths);
5501+
strv_dump(f, prefix, "ReadOnlyPaths", c->read_only_paths);
5502+
strv_dump(f, prefix, "InaccessiblePaths", c->inaccessible_paths);
5503+
strv_dump(f, prefix, "ExecPaths", c->exec_paths);
5504+
strv_dump(f, prefix, "NoExecPaths", c->no_exec_paths);
55035505

55045506
for (size_t i = 0; i < c->n_bind_mounts; i++)
55055507
fprintf(f, "%s%s: %s%s:%s:%s\n", prefix,

src/core/execute.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ struct ExecContext {
243243
char *apparmor_profile;
244244
char *smack_process_label;
245245

246-
char **read_write_paths, **read_only_paths, **inaccessible_paths;
246+
char **read_write_paths, **read_only_paths, **inaccessible_paths, **exec_paths, **no_exec_paths;
247247
unsigned long mount_flags;
248248
BindMount *bind_mounts;
249249
size_t n_bind_mounts;

src/core/load-fragment-gperf.gperf.m4

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ $1.InaccessibleDirectories, config_parse_namespace_path_strv,
119119
$1.ReadWritePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths)
120120
$1.ReadOnlyPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths)
121121
$1.InaccessiblePaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths)
122+
$1.ExecPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.exec_paths)
123+
$1.NoExecPaths, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.no_exec_paths)
122124
$1.BindPaths, config_parse_bind_paths, 0, offsetof($1, exec_context)
123125
$1.BindReadOnlyPaths, config_parse_bind_paths, 0, offsetof($1, exec_context)
124126
$1.TemporaryFileSystem, config_parse_temporary_filesystems, 0, offsetof($1, exec_context)

0 commit comments

Comments
 (0)