Received: by 2002:a05:6a10:f347:0:0:0:0 with SMTP id d7csp4373887pxu; Wed, 9 Dec 2020 15:48:46 -0800 (PST) X-Google-Smtp-Source: ABdhPJw4uiPPVmY7Cp1BB7HzOnkdckxrZ36N/h/bALiE9kboii8WenrdHJj7DCmOQ2l0jrmYYXtt X-Received: by 2002:aa7:c3cf:: with SMTP id l15mr4372849edr.282.1607557726004; Wed, 09 Dec 2020 15:48:46 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1607557725; cv=none; d=google.com; s=arc-20160816; b=Eq5uVthYTDr9ctCsxalE56FuOaKKF5AFhrYHlab0hFo/xRjNKfTMedHeWJlqcUL6Ni uxzZe9pOOvSDqcLcppB3RbFZdRrERezQDhsToVP16CmOb4iKfDAFmS67svbL6mTQckHu boveLmdVmdFvZtSAlHKiykemq/vWseuhoXIqEw2aFYvpfpfYAOWBG85eNRHI/fGV8OTe YKFrA2WYF/HfLZSlzNtKo8C6sOdFdzMJDYiNU5mmePA0YpsR1tjfGxHArmmBAR4bYFs6 ICKBG9sK6HmkJDdQzcJ6ccbWkMgyw3KznDKMwz1EX4PT5AW5rZvCyloIc+hC/yYHPwp1 JHMg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=Oq1f8iIOaO/uyc9X81X/dVWHwEE0IkSa+qO59BvYnGw=; b=Mem4WyzPc9YNvAnrxLelJaLtfMAcCfMTUA4fP0LWk63mVQZN20VJ6s6wfpaKYdWdd7 dUevOD66/ORLxdh7L3XmgnvxHM7Mm74twvo5LGH9hLVD0WFzkJ3YIQ52CzOIeLKDF93W MFvvGkTwXBfz70fi6txn2coBfID59wTRkcHWWTsfYGIH/CeuZOjbe+3FNIJfhiqqbsu6 kIc8oeYyqB5DPTc37e6RJGp3HMP5LluT+/lVlIO3gNvEUNAps655qpvjYbVIupglLRV/ hjzYt3Z+BrpBL6lw1wSdNnrdYAbfLKSvbX78zcF32uk8+A7R48Bjhb7dtmzoDSLSiIW3 m9qw== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id m17si1496702ejb.268.2020.12.09.15.48.23; Wed, 09 Dec 2020 15:48:45 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387780AbgLITbv (ORCPT + 99 others); Wed, 9 Dec 2020 14:31:51 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54924 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2387767AbgLITbh (ORCPT ); Wed, 9 Dec 2020 14:31:37 -0500 Received: from smtp-42aa.mail.infomaniak.ch (smtp-42aa.mail.infomaniak.ch [IPv6:2001:1600:4:17::42aa]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4A715C0619DE; Wed, 9 Dec 2020 11:29:06 -0800 (PST) Received: from smtp-3-0001.mail.infomaniak.ch (unknown [10.4.36.108]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4CrnD04WKHzlhPHX; Wed, 9 Dec 2020 20:29:04 +0100 (CET) Received: from localhost (unknown [23.97.221.149]) by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4CrnCz1k2xzlh8TG; Wed, 9 Dec 2020 20:29:03 +0100 (CET) From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= To: James Morris , Jann Horn , "Serge E . Hallyn" Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Al Viro , Andy Lutomirski , Anton Ivanov , Arnd Bergmann , Casey Schaufler , Jeff Dike , Jonathan Corbet , Kees Cook , Michael Kerrisk , Richard Weinberger , Shuah Khan , Vincent Dagonneau , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-arch@vger.kernel.org, linux-doc@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-security-module@vger.kernel.org, x86@kernel.org, =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Subject: [PATCH v26 11/12] samples/landlock: Add a sandbox manager example Date: Wed, 9 Dec 2020 20:28:38 +0100 Message-Id: <20201209192839.1396820-12-mic@digikod.net> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201209192839.1396820-1-mic@digikod.net> References: <20201209192839.1396820-1-mic@digikod.net> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Mickaël Salaün Add a basic sandbox tool to launch a command which can only access a whitelist of file hierarchies in a read-only or read-write way. Cc: James Morris Cc: Jann Horn Cc: Kees Cook Cc: Serge E. Hallyn Signed-off-by: Mickaël Salaün --- Changes since v25: * Remove useless errno set in the syscall wrappers. * Cosmetic variable renames. Changes since v23: * Re-add hints to help users understand the required kernel configuration. This was removed with the removal of landlock_get_features(2). Changes since v21: * Remove LANDLOCK_ACCESS_FS_CHROOT. * Clean up help. Changes since v20: * Update with new syscalls and type names. * Update errno check for EOPNOTSUPP. * Use the full syscall interfaces: explicitely set the "flags" field to zero. Changes since v19: * Update with the new Landlock syscalls. * Comply with commit 5f2fb52fac15 ("kbuild: rename hostprogs-y/always to hostprogs/always-y"). Changes since v16: * Switch syscall attribute pointer and size arguments. Changes since v15: * Update access right names. * Properly assign access right to files according to the new related syscall restriction. * Replace "select" with "depends on" HEADERS_INSTALL (suggested by Randy Dunlap). Changes since v14: * Fix Kconfig dependency. * Remove access rights that may be required for FD-only requests: mmap, truncate, getattr, lock, chmod, chown, chgrp, ioctl. * Fix useless hardcoded syscall number. * Use execvpe(). * Follow symlinks. * Extend help with common file paths. * Constify variables. * Clean up comments. * Improve error message. Changes since v11: * Add back the filesystem sandbox manager and update it to work with the new Landlock syscall. Previous changes: https://lore.kernel.org/lkml/20190721213116.23476-9-mic@digikod.net/ --- samples/Kconfig | 7 ++ samples/Makefile | 1 + samples/landlock/.gitignore | 1 + samples/landlock/Makefile | 15 +++ samples/landlock/sandboxer.c | 233 +++++++++++++++++++++++++++++++++++ 5 files changed, 257 insertions(+) create mode 100644 samples/landlock/.gitignore create mode 100644 samples/landlock/Makefile create mode 100644 samples/landlock/sandboxer.c diff --git a/samples/Kconfig b/samples/Kconfig index 0ed6e4d71d87..e6129496ced5 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -124,6 +124,13 @@ config SAMPLE_HIDRAW bool "hidraw sample" depends on CC_CAN_LINK && HEADERS_INSTALL +config SAMPLE_LANDLOCK + bool "Build Landlock sample code" + depends on HEADERS_INSTALL + help + Build a simple Landlock sandbox manager able to launch a process + restricted by a user-defined filesystem access control. + config SAMPLE_PIDFD bool "pidfd sample" depends on CC_CAN_LINK && HEADERS_INSTALL diff --git a/samples/Makefile b/samples/Makefile index c3392a595e4b..087e0988ccc5 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_SAMPLE_KDB) += kdb/ obj-$(CONFIG_SAMPLE_KFIFO) += kfifo/ obj-$(CONFIG_SAMPLE_KOBJECT) += kobject/ obj-$(CONFIG_SAMPLE_KPROBES) += kprobes/ +subdir-$(CONFIG_SAMPLE_LANDLOCK) += landlock obj-$(CONFIG_SAMPLE_LIVEPATCH) += livepatch/ subdir-$(CONFIG_SAMPLE_PIDFD) += pidfd obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi/ diff --git a/samples/landlock/.gitignore b/samples/landlock/.gitignore new file mode 100644 index 000000000000..f43668b2d318 --- /dev/null +++ b/samples/landlock/.gitignore @@ -0,0 +1 @@ +/sandboxer diff --git a/samples/landlock/Makefile b/samples/landlock/Makefile new file mode 100644 index 000000000000..21eda5774948 --- /dev/null +++ b/samples/landlock/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-3-Clause + +hostprogs := sandboxer + +always-y := $(hostprogs) + +KBUILD_HOSTCFLAGS += -I$(objtree)/usr/include + +.PHONY: all clean + +all: + $(MAKE) -C ../.. samples/landlock/ + +clean: + $(MAKE) -C ../.. M=samples/landlock/ clean diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c new file mode 100644 index 000000000000..82b2738b216c --- /dev/null +++ b/samples/landlock/sandboxer.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Simple Landlock sandbox manager able to launch a process restricted by a + * user-defined filesystem access control. + * + * Copyright © 2017-2020 Mickaël Salaün + * Copyright © 2020 ANSSI + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef landlock_create_ruleset +static inline int landlock_create_ruleset( + const struct landlock_ruleset_attr *const attr, + const size_t size, const __u32 flags) +{ + return syscall(__NR_landlock_create_ruleset, attr, size, flags); +} +#endif + +#ifndef landlock_add_rule +static inline int landlock_add_rule(const int ruleset_fd, + const enum landlock_rule_type rule_type, + const void *const rule_attr, const __u32 flags) +{ + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, + rule_attr, flags); +} +#endif + +#ifndef landlock_enforce_ruleset_current +static inline int landlock_enforce_ruleset_current(const int ruleset_fd, + const __u32 flags) +{ + return syscall(__NR_landlock_enforce_ruleset_current, ruleset_fd, + flags); +} +#endif + +#define ENV_FS_RO_NAME "LL_FS_RO" +#define ENV_FS_RW_NAME "LL_FS_RW" +#define ENV_PATH_TOKEN ":" + +static int parse_path(char *env_path, const char ***const path_list) +{ + int i, num_paths = 0; + + if (env_path) { + num_paths++; + for (i = 0; env_path[i]; i++) { + if (env_path[i] == ENV_PATH_TOKEN[0]) + num_paths++; + } + } + *path_list = malloc(num_paths * sizeof(**path_list)); + for (i = 0; i < num_paths; i++) + (*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN); + + return num_paths; +} + +#define ACCESS_FILE ( \ + LANDLOCK_ACCESS_FS_EXECUTE | \ + LANDLOCK_ACCESS_FS_WRITE_FILE | \ + LANDLOCK_ACCESS_FS_READ_FILE) + +static int populate_ruleset( + const char *const env_var, const int ruleset_fd, + const __u64 allowed_access) +{ + int num_paths, i; + char *env_path_name; + const char **path_list = NULL; + struct landlock_path_beneath_attr path_beneath = { + .parent_fd = -1, + }; + + env_path_name = getenv(env_var); + if (!env_path_name) { + fprintf(stderr, "Missing environment variable %s\n", env_var); + return 1; + } + env_path_name = strdup(env_path_name); + unsetenv(env_var); + num_paths = parse_path(env_path_name, &path_list); + if (num_paths == 1 && path_list[0][0] == '\0') { + fprintf(stderr, "Missing path in %s\n", env_var); + goto err_free_name; + } + + for (i = 0; i < num_paths; i++) { + struct stat statbuf; + + path_beneath.parent_fd = open(path_list[i], O_PATH | + O_CLOEXEC); + if (path_beneath.parent_fd < 0) { + fprintf(stderr, "Failed to open \"%s\": %s\n", + path_list[i], + strerror(errno)); + goto err_free_name; + } + if (fstat(path_beneath.parent_fd, &statbuf)) { + close(path_beneath.parent_fd); + goto err_free_name; + } + path_beneath.allowed_access = allowed_access; + if (!S_ISDIR(statbuf.st_mode)) + path_beneath.allowed_access &= ACCESS_FILE; + if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath, 0)) { + fprintf(stderr, "Failed to update the ruleset with \"%s\": %s\n", + path_list[i], strerror(errno)); + close(path_beneath.parent_fd); + goto err_free_name; + } + close(path_beneath.parent_fd); + } + free(env_path_name); + return 0; + +err_free_name: + free(env_path_name); + return 1; +} + +#define ACCESS_FS_ROUGHLY_READ ( \ + LANDLOCK_ACCESS_FS_EXECUTE | \ + LANDLOCK_ACCESS_FS_READ_FILE | \ + LANDLOCK_ACCESS_FS_READ_DIR) + +#define ACCESS_FS_ROUGHLY_WRITE ( \ + LANDLOCK_ACCESS_FS_WRITE_FILE | \ + LANDLOCK_ACCESS_FS_REMOVE_DIR | \ + LANDLOCK_ACCESS_FS_REMOVE_FILE | \ + LANDLOCK_ACCESS_FS_MAKE_CHAR | \ + LANDLOCK_ACCESS_FS_MAKE_DIR | \ + LANDLOCK_ACCESS_FS_MAKE_REG | \ + LANDLOCK_ACCESS_FS_MAKE_SOCK | \ + LANDLOCK_ACCESS_FS_MAKE_FIFO | \ + LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ + LANDLOCK_ACCESS_FS_MAKE_SYM) + +int main(const int argc, char *const argv[], char *const *const envp) +{ + const char *cmd_path; + char *const *cmd_argv; + int ruleset_fd; + struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = ACCESS_FS_ROUGHLY_READ | + ACCESS_FS_ROUGHLY_WRITE, + }; + + if (argc < 2) { + fprintf(stderr, "usage: %s=\"...\" %s=\"...\" %s [args]...\n\n", + ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); + fprintf(stderr, "Launch a command in a restricted environment.\n\n"); + fprintf(stderr, "Environment variables containing paths, " + "each separated by a colon:\n"); + fprintf(stderr, "* %s: list of paths allowed to be used in a read-only way.\n", + ENV_FS_RO_NAME); + fprintf(stderr, "* %s: list of paths allowed to be used in a read-write way.\n", + ENV_FS_RO_NAME); + fprintf(stderr, "\nexample:\n" + "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " + "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " + "%s bash -i\n", + ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); + return 1; + } + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + if (ruleset_fd < 0) { + perror("Failed to create a ruleset"); + switch (errno) { + case ENOSYS: + fprintf(stderr, "Hint: Landlock is not supported by the current kernel. " + "To support it, build the kernel with " + "CONFIG_SECURITY_LANDLOCK=y and prepend " + "\"landlock,\" to the content of CONFIG_LSM.\n"); + break; + case EOPNOTSUPP: + fprintf(stderr, "Hint: Landlock is currently disabled. " + "It can be enabled in the kernel configuration by " + "prepending \"landlock,\" to the content of CONFIG_LSM, " + "or at boot time by setting the same content to the " + "\"lsm\" kernel parameter.\n"); + break; + } + return 1; + } + if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, + ACCESS_FS_ROUGHLY_READ)) { + goto err_close_ruleset; + } + if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE)) { + goto err_close_ruleset; + } + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + perror("Failed to restrict privileges"); + goto err_close_ruleset; + } + if (landlock_enforce_ruleset_current(ruleset_fd, 0)) { + perror("Failed to enforce ruleset"); + goto err_close_ruleset; + } + close(ruleset_fd); + + cmd_path = argv[1]; + cmd_argv = argv + 1; + execvpe(cmd_path, cmd_argv, envp); + fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, + strerror(errno)); + fprintf(stderr, "Hint: access to the binary, the interpreter or " + "shared libraries may be denied.\n"); + return 1; + +err_close_ruleset: + close(ruleset_fd); + return 1; +} -- 2.29.2