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
;;
--reset-env
--selinux-label
--apparmor-profile
+ --landlock-access
+ --landlock-rule
--help
--version"
COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
linux/falloc.h \
linux/fd.h \
linux/fiemap.h \
+ linux/landlock.h \
linux/kcmp.h \
linux/net_namespace.h \
linux/nsfs.h \
getttynam \
inotify_init \
jrand48 \
+ landlock_create_ruleset \
+ landlock_add_rule \
+ landlock_restrict_self \
lchown \
lgetxattr \
llistxattr \
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])
linux/fiemap.h
linux/gsmmux.h
linux/if_alg.h
+ linux/landlock.h
linux/kcmp.h
linux/net_namespace.h
linux/nsfs.h
getsgnam
inotify_init
jrand48
+ landlock_create_ruleset
+ landlock_add_rule
+ landlock_restrict_self
lchown
lgetxattr
llistxattr
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
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',
--- /dev/null
+/*
+ * 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 <thomas@t-8ch.de>
+ */
+
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <linux/landlock.h>
+
+#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, ",");
+ }
+}
--- /dev/null
+/*
+ * 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 <thomas@t-8ch.de>
+ */
+
+#ifndef UTIL_LINUX_SETPRIV_LANDLOCK
+#define UTIL_LINUX_SETPRIV_LANDLOCK
+
+#ifdef HAVE_LINUX_LANDLOCK_H
+
+#include <stdint.h>
+
+#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
*--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.
+
*su*(1),
*prctl*(2),
*capabilities*(7)
+*landlock*(7)
include::man-common/bugreports.adoc[]
#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
/* LSMs */
const char *selinux_label;
const char *apparmor_profile;
+ struct setpriv_landlock_opts landlock;
};
static void __attribute__((__noreturn__)) usage(void)
" set or clear parent death signal\n"), out);
fputs(_(" --selinux-label <label> set SELinux label\n"), out);
fputs(_(" --apparmor-profile <pr> set AppArmor profile\n"), out);
+ fputs(_(" --landlock-access <access> add Landlock access\n"), out);
+ fputs(_(" --landlock-rule <rule> add Landlock rule\n"), out);
fputs(_(" --reset-env clear all environment and initialize\n"
" HOME, SHELL, USER, LOGNAME and PATH\n"), out);
fputs(_(" This tool can be dangerous. Read the manpage, and be careful.\n"), out);
fprintf(out, USAGE_MAN_TAIL("setpriv(1)"));
+ usage_setpriv(out);
+
exit(EXIT_SUCCESS);
}
_("write failed: %s"), _PATH_PROC_ATTR_EXEC);
}
-
static void do_reset_environ(struct passwd *pw)
{
char *term = getenv("TERM");
PDEATHSIG,
SELINUX_LABEL,
APPARMOR_PROFILE,
+ LANDLOCK_ACCESS,
+ LANDLOCK_RULE,
RESET_ENV
};
{ "pdeathsig", required_argument, NULL, PDEATHSIG, },
{ "selinux-label", required_argument, NULL, SELINUX_LABEL },
{ "apparmor-profile", required_argument, NULL, APPARMOR_PROFILE },
+ { "landlock-access", required_argument, NULL, LANDLOCK_ACCESS },
+ { "landlock-rule", required_argument, NULL, LANDLOCK_RULE },
{ "help", no_argument, NULL, 'h' },
{ "reset-env", no_argument, NULL, RESET_ENV, },
{ "version", no_argument, NULL, 'V' },
close_stdout_atexit();
memset(&opts, 0, sizeof(opts));
+ init_landlock_opts(&opts.landlock);
while ((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) {
err_exclusive_options(c, longopts, excl, excl_st);
_("duplicate --apparmor-profile option"));
opts.apparmor_profile = optarg;
break;
+ case LANDLOCK_ACCESS:
+ parse_landlock_access(&opts.landlock, optarg);
+ break;
+ case LANDLOCK_RULE:
+ parse_landlock_rule(&opts.landlock, optarg);
+ break;
case RESET_ENV:
opts.reset_env = 1;
break;
if (opts.pdeathsig && prctl(PR_SET_PDEATHSIG, opts.pdeathsig < 0 ? 0 : opts.pdeathsig) != 0)
err(SETPRIV_EXIT_PRIVERR, _("set parent death signal failed"));
+ do_landlock(&opts.landlock);
+
execvp(argv[optind], argv + optind);
errexec(argv[optind]);
}
TS_CMD_SCRIPTLIVE=${TS_CMD_SCRIPTLIVE-"${ts_commandsdir}scriptlive"}
TS_CMD_SETARCH=${TS_CMD_SETARCH-"${ts_commandsdir}setarch"}
TS_CMD_SETPGID=${TS_CMD_SETPGID-"${ts_commandsdir}setpgid"}
+TS_CMD_SETPRIV=${TS_CMD_SETPRIV-"${ts_commandsdir}setpriv"}
TS_CMD_SETSID=${TS_CMD_SETSID-"${ts_commandsdir}setsid"}
TS_CMD_SWAPLABEL=${TS_CMD_SWAPLABEL:-"${ts_commandsdir}swaplabel"}
TS_CMD_SWAPOFF=${TS_CMD_SWAPOFF:-"${ts_commandsdir}swapoff"}
--- /dev/null
+setpriv: failed to execute true: Permission denied
--- /dev/null
+cp: cannot create regular file '/dev/zero': Permission denied
--- /dev/null
+#!/bin/bash
+
+# Copyright (C) 2023 Thomas Weißschuh <thomas@t-8ch.de>
+#
+# This file is part of util-linux.
+#
+# This file 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.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="setpriv landlock"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+[[ "$COVERAGE" = yes ]] && ts_skip "does not work with coverage"
+
+ts_check_test_command "$TS_CMD_SETPRIV"
+
+"$TS_CMD_SETPRIV" --landlock-access fs \
+ --landlock-rule path-beneath:execute:/ \
+ --landlock-rule path-beneath:read-file:/ \
+ true \
+ || ts_skip "no landlock support in setpriv"
+
+ts_init_subtest "nothing-allowed"
+"$TS_CMD_SETPRIV" --landlock-access fs true &> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "partial-access-fail"
+"$TS_CMD_SETPRIV" --landlock-access \
+ fs:write cp /dev/null /dev/zero \
+ &> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "partial-access-success"
+"$TS_CMD_SETPRIV" \
+ --landlock-access fs:write --landlock-rule path-beneath:write:/dev/zero \
+ cp /dev/null /dev/zero \
+ &> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "combined-access"
+"$TS_CMD_SETPRIV" --landlock-access fs:execute,read-file \
+ --landlock-rule path-beneath:execute,read-file:/ \
+ true \
+ &> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_init_subtest "wildcard-access"
+"$TS_CMD_SETPRIV" --landlock-access fs \
+ --landlock-rule path-beneath::/ \
+ true \
+ &> "$TS_OUTPUT"
+ts_finalize_subtest
+
+ts_finalize