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: #17942.
This commit is contained in:
committed by
Topi Miettinen
parent
78dff3f3d7
commit
ddc155b2fd
+91
-3
@@ -54,6 +54,8 @@ typedef enum MountMode {
|
||||
RUN,
|
||||
READONLY,
|
||||
READWRITE,
|
||||
NOEXEC,
|
||||
EXEC,
|
||||
TMPFS,
|
||||
READWRITE_IMPLICIT, /* Should have the lowest priority. */
|
||||
_MOUNT_MODE_MAX,
|
||||
@@ -66,6 +68,8 @@ typedef struct MountEntry {
|
||||
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 */
|
||||
@@ -212,6 +216,8 @@ static const char * const mount_mode_table[_MOUNT_MODE_MAX] = {
|
||||
[TMPFS] = "tmpfs",
|
||||
[MOUNT_IMAGES] = "mount-images",
|
||||
[READWRITE_IMPLICIT] = "rw-implicit",
|
||||
[EXEC] = "exec",
|
||||
[NOEXEC] = "noexec",
|
||||
};
|
||||
|
||||
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(mount_mode, MountMode);
|
||||
@@ -231,6 +237,18 @@ static bool mount_entry_read_only(const MountEntry *p) {
|
||||
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);
|
||||
|
||||
@@ -497,7 +515,10 @@ static void drop_duplicates(MountEntry *m, size_t *n) {
|
||||
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;
|
||||
}
|
||||
@@ -1057,6 +1078,8 @@ static int apply_mount(
|
||||
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;
|
||||
@@ -1064,7 +1087,7 @@ static int apply_mount(
|
||||
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);
|
||||
@@ -1195,7 +1218,7 @@ static int make_read_only(const MountEntry *m, char **deny_list, FILE *proc_self
|
||||
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. */
|
||||
|
||||
@@ -1207,6 +1230,40 @@ static int make_read_only(const MountEntry *m, char **deny_list, FILE *proc_self
|
||||
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);
|
||||
|
||||
@@ -1228,6 +1285,8 @@ static size_t namespace_calculate_mounts(
|
||||
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,
|
||||
@@ -1260,6 +1319,8 @@ static size_t namespace_calculate_mounts(
|
||||
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 +
|
||||
@@ -1406,6 +1467,8 @@ int setup_namespace(
|
||||
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,
|
||||
@@ -1523,6 +1586,8 @@ int setup_namespace(
|
||||
read_write_paths,
|
||||
read_only_paths,
|
||||
inaccessible_paths,
|
||||
exec_paths,
|
||||
no_exec_paths,
|
||||
empty_directories,
|
||||
n_bind_mounts,
|
||||
n_temporary_filesystems,
|
||||
@@ -1550,6 +1615,14 @@ int setup_namespace(
|
||||
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;
|
||||
@@ -1869,6 +1942,21 @@ int setup_namespace(
|
||||
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 */
|
||||
|
||||
Reference in New Issue
Block a user