From: Thomas Weißschuh Date: Wed, 6 Dec 2023 17:39:51 +0000 (+0100) Subject: setpriv: add landlock support X-Git-Tag: v2.40-rc1~112 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ae015d713895c4d72589a656a9ca359ede3d5073;p=thirdparty%2Futil-linux.git setpriv: add landlock support landlock [0] is a Linux stackable LSM that can be used by unprivileged processes to build a sandbox around them. With support for landlock in setpriv users can easily construct a sandbox on-the-fly when executing programs. [0] https://landlock.io/ Signed-off-by: Thomas Weißschuh --- diff --git a/bash-completion/setpriv b/bash-completion/setpriv index 69df34b39b..766bbcb79b 100644 --- a/bash-completion/setpriv +++ b/bash-completion/setpriv @@ -87,6 +87,16 @@ _setpriv_module() COMPREPLY=( $(compgen -W "profile" -- $cur) ) return 0 ;; + '--landlock-access') + # FIXME: how to list landlock accesses? + COMPREPLY=( $(compgen -W "access" -- $cur) ) + return 0 + ;; + '--landlock-rule') + # FIXME: how to list landlock rules? + COMPREPLY=( $(compgen -W "rule" -- $cur) ) + return 0 + ;; '-h'|'--help'|'-V'|'--version') return 0 ;; @@ -112,6 +122,8 @@ _setpriv_module() --reset-env --selinux-label --apparmor-profile + --landlock-access + --landlock-rule --help --version" COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) ) diff --git a/configure.ac b/configure.ac index 913e50449f..6ed1f78228 100644 --- a/configure.ac +++ b/configure.ac @@ -325,6 +325,7 @@ AC_CHECK_HEADERS([ \ linux/falloc.h \ linux/fd.h \ linux/fiemap.h \ + linux/landlock.h \ linux/kcmp.h \ linux/net_namespace.h \ linux/nsfs.h \ @@ -588,6 +589,9 @@ AC_CHECK_FUNCS([ \ getttynam \ inotify_init \ jrand48 \ + landlock_create_ruleset \ + landlock_add_rule \ + landlock_restrict_self \ lchown \ lgetxattr \ llistxattr \ @@ -646,6 +650,7 @@ AC_CHECK_FUNCS([reboot], [have_reboot=yes],[have_reboot=no]) AC_CHECK_FUNCS([updwtmpx updwtmpx], [have_gnu_utmpx=yes], [have_gnu_utmpx=no]) AM_CONDITIONAL([HAVE_OPENAT], [test "x$have_openat" = xyes]) +AM_CONDITIONAL([HAVE_LINUX_LANDLOCK_H], [test "x$ac_cv_header_linux_landlock_h" = xyes]) have_setns_syscall="yes" UL_CHECK_SYSCALL([setns]) diff --git a/meson.build b/meson.build index 331d758298..2d0f6e14f1 100644 --- a/meson.build +++ b/meson.build @@ -176,6 +176,7 @@ headers = ''' linux/fiemap.h linux/gsmmux.h linux/if_alg.h + linux/landlock.h linux/kcmp.h linux/net_namespace.h linux/nsfs.h @@ -539,6 +540,9 @@ funcs = ''' getsgnam inotify_init jrand48 + landlock_create_ruleset + landlock_add_rule + landlock_restrict_self lchown lgetxattr llistxattr diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index 4d2728c191..209b656b00 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -581,5 +581,9 @@ MANPAGES += sys-utils/setpriv.1 dist_noinst_DATA += sys-utils/setpriv.1.adoc setpriv_SOURCES = sys-utils/setpriv.c \ lib/caputils.c +dist_noinst_HEADERS += sys-utils/setpriv-landlock.h +if HAVE_LINUX_LANDLOCK_H +setpriv_SOURCES += sys-utils/setpriv-landlock.c +endif setpriv_LDADD = $(LDADD) -lcap-ng libcommon.la endif diff --git a/sys-utils/meson.build b/sys-utils/meson.build index 0e9857349e..e683253287 100644 --- a/sys-utils/meson.build +++ b/sys-utils/meson.build @@ -185,6 +185,9 @@ nsenter_sources = files( setpriv_sources = files( 'setpriv.c', ) +if LINUX and conf.get('HAVE_LINUX_LANDLOCK_H').to_string() == '1' + setpriv_sources += files('setpriv-landlock.c') +endif flock_sources = files( 'flock.c', diff --git a/sys-utils/setpriv-landlock.c b/sys-utils/setpriv-landlock.c new file mode 100644 index 0000000000..153e748f67 --- /dev/null +++ b/sys-utils/setpriv-landlock.c @@ -0,0 +1,211 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Copyright (C) 2023 Thomas Weißschuh + */ + +#include +#include +#include + +#include "setpriv-landlock.h" + +#include "strutils.h" +#include "xalloc.h" +#include "nls.h" +#include "c.h" + +#ifndef HAVE_LANDLOCK_CREATE_RULESET +static inline int landlock_create_ruleset( + const struct landlock_ruleset_attr *attr, + size_t size, uint32_t flags) +{ + return syscall(__NR_landlock_create_ruleset, attr, size, flags); +} +#endif + +#ifndef HAVE_LANDLOCK_ADD_RULE +static inline int landlock_add_rule( + int ruleset_fd, enum landlock_rule_type rule_type, + const void *rule_attr, uint32_t flags) +{ + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, + rule_attr, flags); +} +#endif + +#ifndef HAVE_LANDLOCK_RESTRICT_SELF +static inline int landlock_restrict_self(int ruleset_fd, uint32_t flags) +{ + return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); +} +#endif + +#define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */ + +struct landlock_rule_entry { + struct list_head head; + enum landlock_rule_type rule_type; + union { + struct landlock_path_beneath_attr path_beneath_attr; + }; +}; + +static const struct { + unsigned long long value; + const char *type; +} landlock_access_fs[] = { + { LANDLOCK_ACCESS_FS_EXECUTE, "execute" }, + { LANDLOCK_ACCESS_FS_WRITE_FILE, "write-file" }, + { LANDLOCK_ACCESS_FS_READ_FILE, "read-file" }, + { LANDLOCK_ACCESS_FS_READ_DIR, "read-dir" }, + { LANDLOCK_ACCESS_FS_REMOVE_DIR, "remove-dir" }, + { LANDLOCK_ACCESS_FS_REMOVE_FILE, "remove-file" }, + { LANDLOCK_ACCESS_FS_MAKE_CHAR, "make-char" }, + { LANDLOCK_ACCESS_FS_MAKE_DIR, "make-dir" }, + { LANDLOCK_ACCESS_FS_MAKE_REG, "make-reg" }, + { LANDLOCK_ACCESS_FS_MAKE_SOCK, "make-sock" }, + { LANDLOCK_ACCESS_FS_MAKE_FIFO, "make-fifo" }, + { LANDLOCK_ACCESS_FS_MAKE_BLOCK, "make-block" }, + { LANDLOCK_ACCESS_FS_MAKE_SYM, "make-sym" }, +#ifdef LANDLOCK_ACCESS_FS_REFER + { LANDLOCK_ACCESS_FS_REFER, "refer" }, +#endif +#ifdef LANDLOCK_ACCESS_FS_TRUNCATE + { LANDLOCK_ACCESS_FS_TRUNCATE, "truncate" }, +#endif +}; + +static long landlock_access_to_mask(const char *str, size_t len) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(landlock_access_fs); i++) + if (strncmp(landlock_access_fs[i].type, str, len) == 0) + return landlock_access_fs[i].value; + return -1; +} + +static uint64_t parse_landlock_fs_access(const char *list) +{ + unsigned long r = 0; + size_t i; + + /* without argument, match all */ + if (list[0] == '\0') { + for (i = 0; i < ARRAY_SIZE(landlock_access_fs); i++) + r |= landlock_access_fs[i].value; + } else { + if (string_to_bitmask(list, &r, landlock_access_to_mask)) + errx(EXIT_FAILURE, + _("could not parse landlock fs access: %s"), list); + } + + return r; +} + +void parse_landlock_access(struct setpriv_landlock_opts *opts, const char *str) +{ + const char *type; + size_t i; + + if (strcmp(str, "fs") == 0) { + for (i = 0; i < ARRAY_SIZE(landlock_access_fs); i++) + opts->access_fs |= landlock_access_fs[i].value; + return; + } + + type = startswith(str, "fs:"); + if (type) + opts->access_fs |= parse_landlock_fs_access(type); +} + +void parse_landlock_rule(struct setpriv_landlock_opts *opts, const char *str) +{ + struct landlock_rule_entry *rule = xmalloc(sizeof(*rule)); + const char *accesses, *path; + char *accesses_part; + int parent_fd; + + accesses = startswith(str, "path-beneath:"); + if (!accesses) + errx(EXIT_FAILURE, _("invalid landlock rule: %s"), str); + path = strchr(accesses, ':'); + if (!path) + errx(EXIT_FAILURE, _("invalid landlock rule: %s"), str); + rule->rule_type = LANDLOCK_RULE_PATH_BENEATH; + + accesses_part = xstrndup(accesses, path - accesses); + rule->path_beneath_attr.allowed_access = parse_landlock_fs_access(accesses_part); + free(accesses_part); + + path++; + + parent_fd = open(path, O_RDONLY | O_PATH | O_CLOEXEC); + if (parent_fd == -1) + err(EXIT_FAILURE, _("could not open file for landlock: %s"), path); + + rule->path_beneath_attr.parent_fd = parent_fd; + + list_add(&rule->head, &opts->rules); +} + +void init_landlock_opts(struct setpriv_landlock_opts *opts) +{ + INIT_LIST_HEAD(&opts->rules); +} + +void do_landlock(const struct setpriv_landlock_opts *opts) +{ + struct landlock_rule_entry *rule; + struct list_head *entry; + int fd, ret; + + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = opts->access_fs, + }; + + fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + if (fd == -1) + err(SETPRIV_EXIT_PRIVERR, _("landlock_create_ruleset failed")); + + list_for_each(entry, &opts->rules) { + rule = list_entry(entry, struct landlock_rule_entry, head); + + assert(rule->rule_type == LANDLOCK_RULE_PATH_BENEATH); + + ret = landlock_add_rule(fd, rule->rule_type, &rule->path_beneath_attr, 0); + if (ret == -1) + err(SETPRIV_EXIT_PRIVERR, _("adding landlock rule failed")); + } + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) + err(SETPRIV_EXIT_PRIVERR, _("disallow granting new privileges for landlock failed")); + + if (landlock_restrict_self(fd, 0) == -1) + err(SETPRIV_EXIT_PRIVERR, _("landlock_restrict_self faild")); +} + +void usage_setpriv(FILE *out) +{ + size_t i; + + fprintf(out, "\n"); + fprintf(out, _("Landlock accesses:\n")); + fprintf(out, " Access: fs\n"); + fprintf(out, " Rule types: path-beneath\n"); + + fprintf(out, " Rules: "); + for (i = 0; i < ARRAY_SIZE(landlock_access_fs); i++) { + fprintf(out, "%s", landlock_access_fs[i].type); + if (i == ARRAY_SIZE(landlock_access_fs) - 1) + fprintf(out, "\n"); + else + fprintf(out, ","); + } +} diff --git a/sys-utils/setpriv-landlock.h b/sys-utils/setpriv-landlock.h new file mode 100644 index 0000000000..d66f86d71a --- /dev/null +++ b/sys-utils/setpriv-landlock.h @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Copyright (C) 2023 Thomas Weißschuh + */ + +#ifndef UTIL_LINUX_SETPRIV_LANDLOCK +#define UTIL_LINUX_SETPRIV_LANDLOCK + +#ifdef HAVE_LINUX_LANDLOCK_H + +#include + +#include "list.h" + +struct setpriv_landlock_opts { + uint64_t access_fs; + struct list_head rules; +}; + +void do_landlock(const struct setpriv_landlock_opts *opts); +void parse_landlock_access(struct setpriv_landlock_opts *opts, const char *str); +void parse_landlock_rule(struct setpriv_landlock_opts *opts, const char *str); +void init_landlock_opts(struct setpriv_landlock_opts *opts); +void usage_setpriv(FILE *out); + +#else + +#include "c.h" +#include "nls.h" + +struct setpriv_landlock_opts {}; + +static inline void do_landlock(const void *opts __attribute__((unused))) {} +static inline void parse_landlock_access( + void *opts __attribute__((unused)), + const char *str __attribute__((unused))) +{ + errx(EXIT_FAILURE, _("no support for landlock")); +} +#define parse_landlock_rule parse_landlock_access +static inline void init_landlock_opts(void *opts __attribute__((unused))) {} +static inline void usage_setpriv(FILE *out __attribute__((unused))) {} + +#endif /* HAVE_LINUX_LANDLOCK_H */ + +#endif diff --git a/sys-utils/setpriv.1.adoc b/sys-utils/setpriv.1.adoc index a0ad6f8dfa..9029346225 100644 --- a/sys-utils/setpriv.1.adoc +++ b/sys-utils/setpriv.1.adoc @@ -84,6 +84,32 @@ Request a particular SELinux transition (using a transition on exec, not dyntran *--apparmor-profile* _profile_:: Request a particular AppArmor profile (using a transition on exec). This will fail and cause *setpriv* to abort if AppArmor is not in use, and the transition may be ignored or cause *execve*(2) to fail at AppArmor's whim. +*--landlock-access* _access_:: +Enable landlock restrictions for a specific set of system accesses. +To allow specific subgroups of accesses use *--landlock-rule*. ++ +Block all filesystem access: ++ +*setpriv --landlock-access fs* ++ +Block all file deletions and directory creations: ++ +*setpriv --landlock-access fs:remove-file,make-dir* ++ +For a complete set of supported access categories use *setpriv --help*. + +*--landlock-rule* _rule_:: + +Allow one specific access from the categories blocked by *--landlock-access*. ++ +The syntax is as follows: ++ +*--landlock-rule $ruletype:$access:$rulearg* ++ +For example grant file read access to everything under */boot*: ++ +*--landlock-rule path-beneath:read-file:/boot* + *--reset-env*:: Clears all the environment variables except *TERM*; initializes the environment variables *HOME*, *SHELL*, *USER*, *LOGNAME* according to the user's passwd entry; sets *PATH* to _/usr/local/bin:/bin:/usr/bin_ for a regular user and to _/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin_ for root. + @@ -117,6 +143,7 @@ mailto:luto@amacapital.net[Andy Lutomirski] *su*(1), *prctl*(2), *capabilities*(7) +*landlock*(7) include::man-common/bugreports.adoc[] diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c index 4099355106..74d3fbe789 100644 --- a/sys-utils/setpriv.c +++ b/sys-utils/setpriv.c @@ -41,6 +41,7 @@ #include "pathnames.h" #include "signames.h" #include "env.h" +#include "setpriv-landlock.h" #ifndef PR_SET_NO_NEW_PRIVS # define PR_SET_NO_NEW_PRIVS 38 @@ -110,6 +111,7 @@ struct privctx { /* LSMs */ const char *selinux_label; const char *apparmor_profile; + struct setpriv_landlock_opts landlock; }; static void __attribute__((__noreturn__)) usage(void) @@ -143,6 +145,8 @@ static void __attribute__((__noreturn__)) usage(void) " set or clear parent death signal\n"), out); fputs(_(" --selinux-label