From patchwork Mon Aug 21 00:09:28 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 9911529 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id EE460602A0 for ; Mon, 21 Aug 2017 00:19:42 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DE469286BA for ; Mon, 21 Aug 2017 00:19:42 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D1AF2286F2; Mon, 21 Aug 2017 00:19:42 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 31C29286BA for ; Mon, 21 Aug 2017 00:19:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753461AbdHUATk (ORCPT ); Sun, 20 Aug 2017 20:19:40 -0400 Received: from smtp-sh.infomaniak.ch ([128.65.195.4]:60219 "EHLO smtp-sh.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753408AbdHUATf (ORCPT ); Sun, 20 Aug 2017 20:19:35 -0400 X-Greylist: delayed 501 seconds by postgrey-1.27 at vger.kernel.org; Sun, 20 Aug 2017 20:19:16 EDT Received: from smtp6.infomaniak.ch (smtp6.infomaniak.ch [83.166.132.19]) by smtp-sh.infomaniak.ch (8.14.5/8.14.5) with ESMTP id v7L09nwP005103 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=FAIL); Mon, 21 Aug 2017 02:09:49 +0200 Received: from localhost (ns3096276.ip-94-23-54.eu [94.23.54.103]) (authenticated bits=0) by smtp6.infomaniak.ch (8.14.5/8.14.5) with ESMTP id v7L09mZr015873; Mon, 21 Aug 2017 02:09:48 +0200 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= To: linux-kernel@vger.kernel.org Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Alexei Starovoitov , Andy Lutomirski , Arnaldo Carvalho de Melo , Casey Schaufler , Daniel Borkmann , David Drysdale , "David S . Miller" , "Eric W . Biederman" , James Morris , Jann Horn , Jonathan Corbet , Matthew Garrett , Michael Kerrisk , Kees Cook , Paul Moore , Sargun Dhillon , "Serge E . Hallyn" , Shuah Khan , Tejun Heo , Thomas Graf , Will Drewry , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, netdev@vger.kernel.org Subject: [PATCH net-next v7 05/10] landlock: Add LSM hooks related to filesystem Date: Mon, 21 Aug 2017 02:09:28 +0200 Message-Id: <20170821000933.13024-6-mic@digikod.net> X-Mailer: git-send-email 2.14.1 In-Reply-To: <20170821000933.13024-1-mic@digikod.net> References: <20170821000933.13024-1-mic@digikod.net> MIME-Version: 1.0 X-Antivirus: Dr.Web (R) for Unix mail servers drweb plugin ver.6.0.2.8 X-Antivirus-Code: 0x100000 Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP Handle 33 filesystem-related LSM hooks for the Landlock filesystem event: LANDLOCK_SUBTYPE_EVENT_FS. A Landlock event wrap LSM hooks for similar kernel object types (e.g. struct file, struct path...). Multiple LSM hooks can trigger the same Landlock event. Landlock handle nine coarse-grained actions: read, write, execute, new, get, remove, ioctl, lock and fcntl. Each of them abstract LSM hook access control in a way that can be extended in the future. The Landlock LSM hook registration is done after other LSM to only run actions from user-space, via eBPF programs, if the access was granted by major (privileged) LSMs. Signed-off-by: Mickaël Salaün Cc: Alexei Starovoitov Cc: Andy Lutomirski Cc: Daniel Borkmann Cc: David S. Miller Cc: James Morris Cc: Kees Cook Cc: Serge E. Hallyn --- Changes since v6: * add 3 more sub-events: IOCTL, LOCK, FCNTL https://lkml.kernel.org/r/2fbc99a6-f190-f335-bd14-04bdeed35571@digikod.net * use the new security_add_hooks() * explain the -Werror=unused-function * constify pointers * cleanup headers Changes since v5: * split hooks.[ch] into hooks.[ch] and hooks_fs.[ch] * add more documentation * cosmetic fixes * rebase (SCALAR_VALUE) Changes since v4: * add LSM hook abstraction called Landlock event * use the compiler type checking to verify hooks use by an event * handle all filesystem related LSM hooks (e.g. file_permission, mmap_file, sb_mount...) * register BPF programs for Landlock just after LSM hooks registration * move hooks registration after other LSMs * add failsafes to check if a hook is not used by the kernel * allow partial raw value access form the context (needed for programs generated by LLVM) Changes since v3: * split commit * add hooks dealing with struct inode and struct path pointers: inode_permission and inode_getattr * add abstraction over eBPF helper arguments thanks to wrapping structs --- include/linux/lsm_hooks.h | 5 + security/landlock/Makefile | 7 +- security/landlock/common.h | 2 + security/landlock/hooks.c | 83 ++++++ security/landlock/hooks.h | 177 +++++++++++++ security/landlock/hooks_fs.c | 586 +++++++++++++++++++++++++++++++++++++++++++ security/landlock/hooks_fs.h | 19 ++ security/landlock/init.c | 10 + security/security.c | 12 +- 9 files changed, 899 insertions(+), 2 deletions(-) create mode 100644 security/landlock/hooks.c create mode 100644 security/landlock/hooks.h create mode 100644 security/landlock/hooks_fs.c create mode 100644 security/landlock/hooks_fs.h diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 3a90febadbe2..7614c3d66265 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1982,5 +1982,10 @@ void __init loadpin_add_hooks(void); #else static inline void loadpin_add_hooks(void) { }; #endif +#ifdef CONFIG_SECURITY_LANDLOCK +extern void __init landlock_add_hooks(void); +#else +static inline void __init landlock_add_hooks(void) { } +#endif /* CONFIG_SECURITY_LANDLOCK */ #endif /* ! __LINUX_LSM_HOOKS_H */ diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 7205f9a7a2ee..b382be409b3b 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -1,3 +1,8 @@ +# Catch defined but unused hooks, e.g. error out if a HOOK_NEW_FS(foo) is not +# used with a HOOK_INIT_FS(foo) in the struct security_hook_list +# landlock_hooks. +ccflags-$(CONFIG_SECURITY_LANDLOCK) += -Werror=unused-function + obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o -landlock-y := init.o +landlock-y := init.o hooks.o hooks_fs.o diff --git a/security/landlock/common.h b/security/landlock/common.h index c82cbd3fb640..a69c35231d35 100644 --- a/security/landlock/common.h +++ b/security/landlock/common.h @@ -18,4 +18,6 @@ */ #define LANDLOCK_ABI 1 +#define LANDLOCK_NAME "landlock" + #endif /* _SECURITY_LANDLOCK_COMMON_H */ diff --git a/security/landlock/hooks.c b/security/landlock/hooks.c new file mode 100644 index 000000000000..b48caeb0a49a --- /dev/null +++ b/security/landlock/hooks.c @@ -0,0 +1,83 @@ +/* + * Landlock LSM - hook helpers + * + * Copyright © 2016-2017 Mickaël Salaün + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include +#include /* enum bpf_access_type, struct landlock_context */ +#include +#include /* BPF_PROG_RUN() */ +#include /* list_add_tail_rcu */ +#include /* offsetof */ + +#include "hooks.h" /* CTX_ARG_NB */ + + +bool landlock_is_valid_access(int off, int size, enum bpf_access_type type, + enum bpf_reg_type *reg_type, + enum bpf_reg_type ctx_types[CTX_ARG_NB], + const union bpf_prog_subtype *prog_subtype) +{ + int max_size; + + if (type != BPF_READ) + return false; + if (off < 0 || off >= sizeof(struct landlock_context)) + return false; + if (size <= 0 || size > sizeof(__u64)) + return false; + + /* set max size */ + switch (off) { + case offsetof(struct landlock_context, status): + case offsetof(struct landlock_context, event): + case offsetof(struct landlock_context, arg1): + case offsetof(struct landlock_context, arg2): + max_size = sizeof(__u64); + break; + default: + return false; + } + + /* set register type */ + switch (off) { + case offsetof(struct landlock_context, arg1): + *reg_type = ctx_types[0]; + break; + case offsetof(struct landlock_context, arg2): + *reg_type = ctx_types[1]; + break; + default: + *reg_type = SCALAR_VALUE; + } + + /* check memory range access */ + switch (*reg_type) { + case NOT_INIT: + return false; + case SCALAR_VALUE: + /* allow partial raw value */ + if (size > max_size) + return false; + break; + default: + /* deny partial pointer */ + if (size != max_size) + return false; + } + + return true; +} + +int landlock_decide(enum landlock_subtype_event event, + __u64 ctx_values[CTX_ARG_NB], const char *hook) +{ + bool deny = false; + + return deny ? -EPERM : 0; +} diff --git a/security/landlock/hooks.h b/security/landlock/hooks.h new file mode 100644 index 000000000000..51957211b67d --- /dev/null +++ b/security/landlock/hooks.h @@ -0,0 +1,177 @@ +/* + * Landlock LSM - hooks helpers + * + * Copyright © 2016-2017 Mickaël Salaün + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include +#include /* enum bpf_access_type */ +#include +#include /* struct task_struct */ + +/* separators */ +#define SEP_COMMA() , +#define SEP_SPACE() +#define SEP_AND() && + +#define MAP2x1(s, m, x1, x2, ...) m(x1, x2) +#define MAP2x2(s, m, x1, x2, ...) m(x1, x2) s() MAP2x1(s, m, __VA_ARGS__) +#define MAP2x3(s, m, x1, x2, ...) m(x1, x2) s() MAP2x2(s, m, __VA_ARGS__) +#define MAP2x4(s, m, x1, x2, ...) m(x1, x2) s() MAP2x3(s, m, __VA_ARGS__) +#define MAP2x5(s, m, x1, x2, ...) m(x1, x2) s() MAP2x4(s, m, __VA_ARGS__) +#define MAP2x6(s, m, x1, x2, ...) m(x1, x2) s() MAP2x5(s, m, __VA_ARGS__) +#define MAP2x(n, ...) MAP2x##n(__VA_ARGS__) + +#define MAP1x1(s, m, x1, ...) m(x1) +#define MAP1x2(s, m, x1, ...) m(x1) s() MAP1x1(s, m, __VA_ARGS__) +#define MAP1x(n, ...) MAP1x##n(__VA_ARGS__) + +#define SKIP2x1(x1, x2, ...) __VA_ARGS__ +#define SKIP2x2(x1, x2, ...) SKIP2x1(__VA_ARGS__) +#define SKIP2x3(x1, x2, ...) SKIP2x2(__VA_ARGS__) +#define SKIP2x4(x1, x2, ...) SKIP2x3(__VA_ARGS__) +#define SKIP2x5(x1, x2, ...) SKIP2x4(__VA_ARGS__) +#define SKIP2x6(x1, x2, ...) SKIP2x5(__VA_ARGS__) +#define SKIP2x(n, ...) SKIP2x##n(__VA_ARGS__) + +/* LSM hook argument helpers */ +#define MAP_HOOK_COMMA(n, ...) MAP2x(n, SEP_COMMA, __VA_ARGS__) + +#define GET_HOOK_TA(t, a) t a + +/* Landlock event argument helpers */ +#define MAP_EVENT_COMMA(h, n, m, ...) MAP2x(n, SEP_COMMA, m, SKIP2x(h, __VA_ARGS__)) +#define MAP_EVENT_SPACE(h, n, m, ...) MAP2x(n, SEP_SPACE, m, SKIP2x(h, __VA_ARGS__)) +#define MAP_EVENT_AND(h, n, m, ...) MAP2x(n, SEP_AND, m, SKIP2x(h, __VA_ARGS__)) + +#define EXPAND_TYPE(d) d##_TYPE +#define EXPAND_BPF(d) d##_BPF +#define EXPAND_C(d) d##_C + +#define GET_TYPE_BPF(t) EXPAND_BPF(t) +#define GET_TYPE_C(t) EXPAND_C(t) * + +#define GET_EVENT_C(d, a) GET_TYPE_C(EXPAND_TYPE(d)) +#define GET_EVENT_U64(d, a) ((u64)(d##_VAL(a))) +#define GET_EVENT_DEC(d, a) d##_DEC(a) +#define GET_EVENT_OK(d, a) d##_OK(a) + +/** + * HOOK_ACCESS + * + * @EVENT: Landlock event name + * @NA: number of event arguments + * + * The __consistent_##EVENT() extern functions and __wrapcheck_* types are + * useful to catch inconsistencies in LSM hook definitions thanks to the + * compiler type checking. + */ +#define HOOK_ACCESS(EVENT, NA, ...) \ + inline bool landlock_is_valid_access_event_##EVENT( \ + int off, int size, enum bpf_access_type type, \ + enum bpf_reg_type *reg_type, \ + const union bpf_prog_subtype *prog_subtype) \ + { \ + enum bpf_reg_type _ctx_types[CTX_ARG_NB] = { \ + MAP1x(NA, SEP_COMMA, GET_TYPE_BPF, __VA_ARGS__) \ + }; \ + return landlock_is_valid_access(off, size, type, \ + reg_type, _ctx_types, prog_subtype); \ + } \ + extern void __consistent_##EVENT( \ + MAP1x(NA, SEP_COMMA, GET_TYPE_C, __VA_ARGS__)) + +/** + * HOOK_NEW + * + * @INST: event instance for this hook + * @EVENT: Landlock event name + * @NE: number of event arguments + * @HOOK: LSM hook name + * @NH: number of hook arguments + */ +#define HOOK_NEW(INST, EVENT, NE, HOOK, NH, ...) \ + static int landlock_hook_##EVENT##_##HOOK##_##INST( \ + MAP_HOOK_COMMA(NH, GET_HOOK_TA, __VA_ARGS__)) \ + { \ + if (!landlocked(current)) \ + return 0; \ + if (!(MAP_EVENT_AND(NH, NE, GET_EVENT_OK, \ + __VA_ARGS__))) \ + return 0; \ + { \ + MAP_EVENT_SPACE(NH, NE, GET_EVENT_DEC, __VA_ARGS__) \ + __u64 _ctx_values[CTX_ARG_NB] = { \ + MAP_EVENT_COMMA(NH, NE, GET_EVENT_U64, \ + __VA_ARGS__) \ + }; \ + return landlock_decide(LANDLOCK_SUBTYPE_EVENT_##EVENT, \ + _ctx_values, #HOOK); \ + } \ + } \ + extern void __consistent_##EVENT(MAP_EVENT_COMMA( \ + NH, NE, GET_EVENT_C, __VA_ARGS__)) + +#define HOOK_INIT(EVENT, HOOK, ID) \ + LSM_HOOK_INIT(HOOK, landlock_hook_##EVENT##_##HOOK##_##ID) + +/* + * The WRAP_TYPE_* definitions group the bpf_reg_type enum value and the C + * type. This C type may remains unused except to catch inconsistencies in LSM + * hook definitions thanks to the compiler type checking. + */ + +/* WRAP_TYPE_NONE */ +#define WRAP_TYPE_NONE_BPF NOT_INIT +#define WRAP_TYPE_NONE_C struct __wrapcheck_none +WRAP_TYPE_NONE_C; + +/* WRAP_TYPE_RAW */ +#define WRAP_TYPE_RAW_BPF SCALAR_VALUE +#define WRAP_TYPE_RAW_C struct __wrapcheck_raw +WRAP_TYPE_RAW_C; + +/* + * The WRAP_ARG_* definitions group the LSM hook argument type (C and BPF), the + * wrapping struct declaration (if any) and the value to copy to the BPF + * context. This definitions may be used thanks to the EXPAND_* helpers. + * + * WRAP_ARG_*_TYPE: type for BPF and C (cf. WRAP_TYPE_*) + * WRAP_ARG_*_DEC: declare a wrapper + * WRAP_ARG_*_VAL: get this wrapper's address + * WRAP_ARG_*_OK: check if the argument is usable + */ + +/* WRAP_ARG_NONE */ +#define WRAP_ARG_NONE_TYPE WRAP_TYPE_NONE +#define WRAP_ARG_NONE_DEC(arg) +#define WRAP_ARG_NONE_VAL(arg) 0 +#define WRAP_ARG_NONE_OK(arg) (!WARN_ON(true)) + +/* WRAP_ARG_RAW */ +#define WRAP_ARG_RAW_TYPE WRAP_TYPE_RAW +#define WRAP_ARG_RAW_DEC(arg) +#define WRAP_ARG_RAW_VAL(arg) arg +#define WRAP_ARG_RAW_OK(arg) (true) + + +#define CTX_ARG_NB 2 + +static inline bool landlocked(const struct task_struct *task) +{ + return false; +} + +__init void landlock_register_hooks(struct security_hook_list *hooks, int count); + +bool landlock_is_valid_access(int off, int size, enum bpf_access_type type, + enum bpf_reg_type *reg_type, + enum bpf_reg_type ctx_types[CTX_ARG_NB], + const union bpf_prog_subtype *prog_subtype); + +int landlock_decide(enum landlock_subtype_event event, + __u64 ctx_values[CTX_ARG_NB], const char *hook); diff --git a/security/landlock/hooks_fs.c b/security/landlock/hooks_fs.c new file mode 100644 index 000000000000..fa80b35a269d --- /dev/null +++ b/security/landlock/hooks_fs.c @@ -0,0 +1,586 @@ +/* + * Landlock LSM - filesystem hooks + * + * Copyright © 2016-2017 Mickaël Salaün + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include /* ARRAY_SIZE */ +#include +#include /* uintptr_t */ + +/* permissions translation */ +#include /* MAY_* */ +#include /* PROT_* */ + +/* hook arguments */ +#include +#include /* struct dentry */ +#include /* struct inode, struct iattr */ +#include /* struct vm_area_struct */ +#include /* struct vfsmount */ +#include /* struct path */ +#include /* struct task_struct */ +#include /* struct timespec */ + +#include "common.h" +#include "hooks.h" +#include "hooks_fs.h" + + +#define HOOK_NEW_FS(...) HOOK_NEW(1, FS, 2, __VA_ARGS__) +#define HOOK_NEW_FS2(...) HOOK_NEW(2, FS, 2, __VA_ARGS__) +#define HOOK_NEW_FS3(...) HOOK_NEW(3, FS, 2, __VA_ARGS__) +#define HOOK_NEW_FS4(...) HOOK_NEW(4, FS, 2, __VA_ARGS__) + +#define HOOK_INIT_FS(HOOK) HOOK_INIT(FS, HOOK, 1) +#define HOOK_INIT_FS2(HOOK) HOOK_INIT(FS, HOOK, 2) +#define HOOK_INIT_FS3(HOOK) HOOK_INIT(FS, HOOK, 3) +#define HOOK_INIT_FS4(HOOK) HOOK_INIT(FS, HOOK, 4) + +/* WRAP_TYPE_FS */ +#define WRAP_TYPE_FS_BPF CONST_PTR_TO_HANDLE_FS +#define WRAP_TYPE_FS_C const struct bpf_handle_fs + +/* WRAP_ARG_FILE */ +#define WRAP_ARG_FILE_TYPE WRAP_TYPE_FS +#define WRAP_ARG_FILE_DEC(arg) \ + EXPAND_C(WRAP_TYPE_FS) wrap_##arg = \ + { .type = BPF_HANDLE_FS_TYPE_FILE, .file = arg }; +#define WRAP_ARG_FILE_VAL(arg) ((uintptr_t)&wrap_##arg) +#define WRAP_ARG_FILE_OK(arg) (arg) + +/* WRAP_ARG_VMAF */ +#define WRAP_ARG_VMAF_TYPE WRAP_TYPE_FS +#define WRAP_ARG_VMAF_DEC(arg) \ + EXPAND_C(WRAP_TYPE_FS) wrap_##arg = \ + { .type = BPF_HANDLE_FS_TYPE_FILE, .file = arg->vm_file }; +#define WRAP_ARG_VMAF_VAL(arg) ((uintptr_t)&wrap_##arg) +#define WRAP_ARG_VMAF_OK(arg) (arg && arg->vm_file) + +/* WRAP_ARG_INODE */ +#define WRAP_ARG_INODE_TYPE WRAP_TYPE_FS +#define WRAP_ARG_INODE_DEC(arg) \ + EXPAND_C(WRAP_TYPE_FS) wrap_##arg = \ + { .type = BPF_HANDLE_FS_TYPE_INODE, .inode = arg }; +#define WRAP_ARG_INODE_VAL(arg) ((uintptr_t)&wrap_##arg) +#define WRAP_ARG_INODE_OK(arg) (arg) + +/* WRAP_ARG_PATH */ +#define WRAP_ARG_PATH_TYPE WRAP_TYPE_FS +#define WRAP_ARG_PATH_DEC(arg) \ + EXPAND_C(WRAP_TYPE_FS) wrap_##arg = \ + { .type = BPF_HANDLE_FS_TYPE_PATH, .path = arg }; +#define WRAP_ARG_PATH_VAL(arg) ((uintptr_t)&wrap_##arg) +#define WRAP_ARG_PATH_OK(arg) (arg) + +/* WRAP_ARG_DENTRY */ +#define WRAP_ARG_DENTRY_TYPE WRAP_TYPE_FS +#define WRAP_ARG_DENTRY_DEC(arg) \ + EXPAND_C(WRAP_TYPE_FS) wrap_##arg = \ + { .type = BPF_HANDLE_FS_TYPE_DENTRY, .dentry = arg }; +#define WRAP_ARG_DENTRY_VAL(arg) ((uintptr_t)&wrap_##arg) +#define WRAP_ARG_DENTRY_OK(arg) (arg) + +/* WRAP_ARG_SB */ +#define WRAP_ARG_SB_TYPE WRAP_TYPE_FS +#define WRAP_ARG_SB_DEC(arg) \ + EXPAND_C(WRAP_TYPE_FS) wrap_##arg = \ + { .type = BPF_HANDLE_FS_TYPE_DENTRY, .dentry = arg->s_root }; +#define WRAP_ARG_SB_VAL(arg) ((uintptr_t)&wrap_##arg) +#define WRAP_ARG_SB_OK(arg) (arg && arg->s_root) + +/* WRAP_ARG_MNTROOT */ +#define WRAP_ARG_MNTROOT_TYPE WRAP_TYPE_FS +#define WRAP_ARG_MNTROOT_DEC(arg) \ + EXPAND_C(WRAP_TYPE_FS) wrap_##arg = \ + { .type = BPF_HANDLE_FS_TYPE_DENTRY, .dentry = arg->mnt_root }; +#define WRAP_ARG_MNTROOT_VAL(arg) ((uintptr_t)&wrap_##arg) +#define WRAP_ARG_MNTROOT_OK(arg) (arg && arg->mnt_root) + + +static inline u64 fs_may_to_access(int fs_may) +{ + u64 ret = 0; + + if (fs_may & MAY_EXEC) + ret |= LANDLOCK_ACTION_FS_EXEC; + if (fs_may & MAY_READ) + ret |= LANDLOCK_ACTION_FS_READ; + if (fs_may & MAY_WRITE) + ret |= LANDLOCK_ACTION_FS_WRITE; + if (fs_may & MAY_APPEND) + ret |= LANDLOCK_ACTION_FS_WRITE; + if (fs_may & MAY_OPEN) + ret |= LANDLOCK_ACTION_FS_GET; + /* ignore MAY_CHDIR and MAY_ACCESS */ + + return ret; +} + +static u64 mem_prot_to_access(unsigned long prot, bool private) +{ + u64 ret = 0; + + /* private mapping do not write to files */ + if (!private && (prot & PROT_WRITE)) + ret |= LANDLOCK_ACTION_FS_WRITE; + if (prot & PROT_READ) + ret |= LANDLOCK_ACTION_FS_READ; + if (prot & PROT_EXEC) + ret |= LANDLOCK_ACTION_FS_EXEC; + + return ret; +} + +/* hook definitions */ + +HOOK_ACCESS(FS, 2, WRAP_TYPE_FS, WRAP_TYPE_RAW); + +/* binder_* hooks */ + +HOOK_NEW_FS(binder_transfer_file, 3, + struct task_struct *, from, + struct task_struct *, to, + struct file *, file, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +/* sb_* hooks */ + +HOOK_NEW_FS(sb_statfs, 1, + struct dentry *, dentry, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +/* + * Being able to mount on a path means being able to override the underlying + * filesystem view of this path, hence the need for a write access right. + */ +HOOK_NEW_FS(sb_mount, 5, + const char *, dev_name, + const struct path *, path, + const char *, type, + unsigned long, flags, + void *, data, + WRAP_ARG_PATH, path, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS(sb_remount, 2, + struct super_block *, sb, + void *, data, + WRAP_ARG_SB, sb, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS(sb_umount, 2, + struct vfsmount *, mnt, + int, flags, + WRAP_ARG_MNTROOT, mnt, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +/* + * The old_path is similar to a destination mount point. + */ +HOOK_NEW_FS(sb_pivotroot, 2, + const struct path *, old_path, + const struct path *, new_path, + WRAP_ARG_PATH, old_path, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +/* inode_* hooks */ + +/* a directory inode contains only one dentry */ +HOOK_NEW_FS(inode_create, 3, + struct inode *, dir, + struct dentry *, dentry, + umode_t, mode, + WRAP_ARG_INODE, dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS2(inode_create, 3, + struct inode *, dir, + struct dentry *, dentry, + umode_t, mode, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW +); + +HOOK_NEW_FS(inode_link, 3, + struct dentry *, old_dentry, + struct inode *, dir, + struct dentry *, new_dentry, + WRAP_ARG_DENTRY, old_dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +HOOK_NEW_FS2(inode_link, 3, + struct dentry *, old_dentry, + struct inode *, dir, + struct dentry *, new_dentry, + WRAP_ARG_INODE, dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS3(inode_link, 3, + struct dentry *, old_dentry, + struct inode *, dir, + struct dentry *, new_dentry, + WRAP_ARG_DENTRY, new_dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW +); + +HOOK_NEW_FS(inode_unlink, 2, + struct inode *, dir, + struct dentry *, dentry, + WRAP_ARG_INODE, dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS2(inode_unlink, 2, + struct inode *, dir, + struct dentry *, dentry, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_REMOVE +); + +HOOK_NEW_FS(inode_symlink, 3, + struct inode *, dir, + struct dentry *, dentry, + const char *, old_name, + WRAP_ARG_INODE, dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS2(inode_symlink, 3, + struct inode *, dir, + struct dentry *, dentry, + const char *, old_name, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW +); + +HOOK_NEW_FS(inode_mkdir, 3, + struct inode *, dir, + struct dentry *, dentry, + umode_t, mode, + WRAP_ARG_INODE, dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS2(inode_mkdir, 3, + struct inode *, dir, + struct dentry *, dentry, + umode_t, mode, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW +); + +HOOK_NEW_FS(inode_rmdir, 2, + struct inode *, dir, + struct dentry *, dentry, + WRAP_ARG_INODE, dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS2(inode_rmdir, 2, + struct inode *, dir, + struct dentry *, dentry, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_REMOVE +); + +HOOK_NEW_FS(inode_mknod, 4, + struct inode *, dir, + struct dentry *, dentry, + umode_t, mode, + dev_t, dev, + WRAP_ARG_INODE, dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS2(inode_mknod, 4, + struct inode *, dir, + struct dentry *, dentry, + umode_t, mode, + dev_t, dev, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW +); + +HOOK_NEW_FS(inode_rename, 4, + struct inode *, old_dir, + struct dentry *, old_dentry, + struct inode *, new_dir, + struct dentry *, new_dentry, + WRAP_ARG_INODE, old_dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS2(inode_rename, 4, + struct inode *, old_dir, + struct dentry *, old_dentry, + struct inode *, new_dir, + struct dentry *, new_dentry, + WRAP_ARG_DENTRY, old_dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_REMOVE +); + +HOOK_NEW_FS3(inode_rename, 4, + struct inode *, old_dir, + struct dentry *, old_dentry, + struct inode *, new_dir, + struct dentry *, new_dentry, + WRAP_ARG_INODE, new_dir, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS4(inode_rename, 4, + struct inode *, old_dir, + struct dentry *, old_dentry, + struct inode *, new_dir, + struct dentry *, new_dentry, + WRAP_ARG_DENTRY, new_dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_NEW +); + +HOOK_NEW_FS(inode_readlink, 1, + struct dentry *, dentry, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +// XXX: handle inode? +HOOK_NEW_FS(inode_follow_link, 3, + struct dentry *, dentry, + struct inode *, inode, + bool, rcu, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +HOOK_NEW_FS(inode_permission, 2, + struct inode *, inode, + int, mask, + WRAP_ARG_INODE, inode, + WRAP_ARG_RAW, fs_may_to_access(mask) +); + +HOOK_NEW_FS(inode_setattr, 2, + struct dentry *, dentry, + struct iattr *, attr, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS(inode_getattr, 1, + const struct path *, path, + WRAP_ARG_PATH, path, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +HOOK_NEW_FS(inode_setxattr, 5, + struct dentry *, dentry, + const char *, name, + const void *, value, + size_t, size, + int, flags, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS(inode_getxattr, 2, + struct dentry *, dentry, + const char *, name, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +HOOK_NEW_FS(inode_listxattr, 1, + struct dentry *, dentry, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +HOOK_NEW_FS(inode_removexattr, 2, + struct dentry *, dentry, + const char *, name, + WRAP_ARG_DENTRY, dentry, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +HOOK_NEW_FS(inode_getsecurity, 4, + struct inode *, inode, + const char *, name, + void **, buffer, + bool, alloc, + WRAP_ARG_INODE, inode, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_READ +); + +HOOK_NEW_FS(inode_setsecurity, 5, + struct inode *, inode, + const char *, name, + const void *, value, + size_t, size, + int, flag, + WRAP_ARG_INODE, inode, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_WRITE +); + +/* file_* hooks */ + +HOOK_NEW_FS(file_permission, 2, + struct file *, file, + int, mask, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, fs_may_to_access(mask) +); + +/* + * An ioctl command can be a read or a write. This can be checked with _IOC*() + * for some commands but a Landlock rule should check the ioctl command to + * whitelist them. + */ +HOOK_NEW_FS(file_ioctl, 3, + struct file *, file, + unsigned int, cmd, + unsigned long, arg, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_IOCTL +); + +HOOK_NEW(1, FS_IOCTL, 2, file_ioctl, 3, + struct file *, file, + unsigned int, cmd, + unsigned long, arg, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, cmd +); + +HOOK_NEW_FS(file_lock, 2, + struct file *, file, + unsigned int, cmd, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_LOCK +); + +HOOK_NEW(1, FS_LOCK, 2, file_lock, 2, + struct file *, file, + unsigned int, cmd, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, cmd +); + +HOOK_NEW_FS(file_fcntl, 3, + struct file *, file, + unsigned int, cmd, + unsigned long, arg, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_FCNTL +); + +HOOK_NEW(1, FS_FCNTL, 2, file_fcntl, 3, + struct file *, file, + unsigned int, cmd, + unsigned long, arg, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, cmd +); + +HOOK_NEW_FS(mmap_file, 4, + struct file *, file, + unsigned long, reqprot, + unsigned long, prot, + unsigned long, flags, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, mem_prot_to_access(prot, flags & MAP_PRIVATE) +); + +HOOK_NEW_FS(file_mprotect, 3, + struct vm_area_struct *, vma, + unsigned long, reqprot, + unsigned long, prot, + WRAP_ARG_VMAF, vma, + WRAP_ARG_RAW, mem_prot_to_access(prot, !(vma->vm_flags & VM_SHARED)) +); + +HOOK_NEW_FS(file_receive, 1, + struct file *, file, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_GET +); + +HOOK_NEW_FS(file_open, 2, + struct file *, file, + const struct cred *, cred, + WRAP_ARG_FILE, file, + WRAP_ARG_RAW, LANDLOCK_ACTION_FS_GET +); + +static struct security_hook_list landlock_hooks[] = { + HOOK_INIT_FS(binder_transfer_file), + + HOOK_INIT_FS(sb_statfs), + HOOK_INIT_FS(sb_mount), + HOOK_INIT_FS(sb_remount), + HOOK_INIT_FS(sb_umount), + HOOK_INIT_FS(sb_pivotroot), + + HOOK_INIT_FS(inode_create), + HOOK_INIT_FS2(inode_create), + HOOK_INIT_FS(inode_link), + HOOK_INIT_FS2(inode_link), + HOOK_INIT_FS3(inode_link), + HOOK_INIT_FS(inode_unlink), + HOOK_INIT_FS2(inode_unlink), + HOOK_INIT_FS(inode_symlink), + HOOK_INIT_FS2(inode_symlink), + HOOK_INIT_FS(inode_mkdir), + HOOK_INIT_FS2(inode_mkdir), + HOOK_INIT_FS(inode_rmdir), + HOOK_INIT_FS2(inode_rmdir), + HOOK_INIT_FS(inode_mknod), + HOOK_INIT_FS2(inode_mknod), + HOOK_INIT_FS(inode_rename), + HOOK_INIT_FS2(inode_rename), + HOOK_INIT_FS3(inode_rename), + HOOK_INIT_FS4(inode_rename), + HOOK_INIT_FS(inode_readlink), + HOOK_INIT_FS(inode_follow_link), + HOOK_INIT_FS(inode_permission), + HOOK_INIT_FS(inode_setattr), + HOOK_INIT_FS(inode_getattr), + HOOK_INIT_FS(inode_setxattr), + HOOK_INIT_FS(inode_getxattr), + HOOK_INIT_FS(inode_listxattr), + HOOK_INIT_FS(inode_removexattr), + HOOK_INIT_FS(inode_getsecurity), + HOOK_INIT_FS(inode_setsecurity), + + HOOK_INIT_FS(file_permission), + HOOK_INIT_FS(file_ioctl), + HOOK_INIT(FS_IOCTL, file_ioctl, 1), + HOOK_INIT_FS(file_lock), + HOOK_INIT(FS_LOCK, file_lock, 1), + HOOK_INIT_FS(file_fcntl), + HOOK_INIT(FS_FCNTL, file_fcntl, 1), + HOOK_INIT_FS(mmap_file), + HOOK_INIT_FS(file_mprotect), + HOOK_INIT_FS(file_receive), + HOOK_INIT_FS(file_open), +}; + +__init void landlock_add_hooks_fs(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), LANDLOCK_NAME); +} diff --git a/security/landlock/hooks_fs.h b/security/landlock/hooks_fs.h new file mode 100644 index 000000000000..aab07c018f38 --- /dev/null +++ b/security/landlock/hooks_fs.h @@ -0,0 +1,19 @@ +/* + * Landlock LSM - filesystem hooks + * + * Copyright © 2017 Mickaël Salaün + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + */ + +#include /* enum bpf_access_type */ + + +bool landlock_is_valid_access_event_FS( + int off, int size, enum bpf_access_type type, + enum bpf_reg_type *reg_type, + const union bpf_prog_subtype *prog_subtype); + +__init void landlock_add_hooks_fs(void); diff --git a/security/landlock/init.c b/security/landlock/init.c index 09acbc74abd6..1e6660fed697 100644 --- a/security/landlock/init.c +++ b/security/landlock/init.c @@ -10,8 +10,10 @@ #include /* enum bpf_access_type */ #include /* capable */ +#include #include "common.h" /* LANDLOCK_* */ +#include "hooks_fs.h" static inline bool bpf_landlock_is_valid_access(int off, int size, @@ -23,6 +25,8 @@ static inline bool bpf_landlock_is_valid_access(int off, int size, switch (prog_subtype->landlock_rule.event) { case LANDLOCK_SUBTYPE_EVENT_FS: + return landlock_is_valid_access_event_FS(off, size, type, + &info->reg_type, prog_subtype); case LANDLOCK_SUBTYPE_EVENT_UNSPEC: default: return false; @@ -113,3 +117,9 @@ const struct bpf_verifier_ops bpf_landlock_ops = { .is_valid_access = bpf_landlock_is_valid_access, .is_valid_subtype = bpf_landlock_is_valid_subtype, }; + +void __init landlock_add_hooks(void) +{ + pr_info("%s: ABI %u", LANDLOCK_NAME, LANDLOCK_ABI); + landlock_add_hooks_fs(); +} diff --git a/security/security.c b/security/security.c index 30132378d103..000d95d53902 100644 --- a/security/security.c +++ b/security/security.c @@ -75,10 +75,20 @@ int __init security_init(void) loadpin_add_hooks(); /* - * Load all the remaining security modules. + * Load all remaining privileged security modules. */ do_security_initcalls(); + /* + * Load potentially-unprivileged security modules at the end. + * + * For an unprivileged access-control, we don't want to give the + * ability to any process to do some checks (e.g. through an eBPF + * program) on kernel objects (e.g. files) if a privileged security + * policy forbid their access. + */ + landlock_add_hooks(); + return 0; }