diff options
Diffstat (limited to 'sys-utils/setpriv-landlock.c')
-rw-r--r-- | sys-utils/setpriv-landlock.c | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/sys-utils/setpriv-landlock.c b/sys-utils/setpriv-landlock.c new file mode 100644 index 0000000..00ad38c --- /dev/null +++ b/sys-utils/setpriv-landlock.c @@ -0,0 +1,214 @@ +/* + * 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; + + if (!opts->access_fs) + return; + + 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, ","); + } +} |