diff options
Diffstat (limited to 'security')
203 files changed, 11917 insertions, 5069 deletions
diff --git a/security/Kconfig b/security/Kconfig index f10dbf15c294..f7bf6cdc6229 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -21,16 +21,14 @@ config SECURITY_DMESG_RESTRICT choice prompt "Allow /proc/pid/mem access override" - default PROC_MEM_ALWAYS_FORCE + default PROC_MEM_FORCE_PTRACE help Traditionally /proc/pid/mem allows users to override memory permissions for users like ptrace, assuming they have ptrace capability. This allows people to limit that - either never override, or - require actual active ptrace attachment. - - Defaults to the traditional behavior (for now) + require actual active ptrace attachment (default). config PROC_MEM_ALWAYS_FORCE bool "Traditional /proc/pid/mem behavior" @@ -51,6 +49,27 @@ config PROC_MEM_NO_FORCE endchoice +config MSEAL_SYSTEM_MAPPINGS + bool "mseal system mappings" + depends on 64BIT + depends on ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS + depends on !CHECKPOINT_RESTORE + help + Apply mseal on system mappings. + The system mappings includes vdso, vvar, vvar_vclock, + vectors (arm compat-mode), sigpage (arm compat-mode), uprobes. + + A 64-bit kernel is required for the memory sealing feature. + No specific hardware features from the CPU are needed. + + WARNING: This feature breaks programs which rely on relocating + or unmapping system mappings. Known broken software at the time + of writing includes CHECKPOINT_RESTORE, UML, gVisor, rr. Therefore + this config can't be enabled universally. + + For complete descriptions of memory sealing, please see + Documentation/userspace-api/mseal.rst + config SECURITY bool "Enable different security models" depends on SYSFS @@ -164,27 +183,6 @@ config LSM_MMAP_MIN_ADDR this low address space will need the permission specific to the systems running LSM. -config HARDENED_USERCOPY - bool "Harden memory copies between kernel and userspace" - imply STRICT_DEVMEM - help - This option checks for obviously wrong memory regions when - copying memory to/from the kernel (via copy_to_user() and - copy_from_user() functions) by rejecting memory ranges that - are larger than the specified heap object, span multiple - separately allocated pages, are not on the process stack, - or are part of the kernel text. This prevents entire classes - of heap overflow exploits and similar kernel memory exposures. - -config FORTIFY_SOURCE - bool "Harden common str/mem functions against buffer overflows" - depends on ARCH_HAS_FORTIFY_SOURCE - # https://github.com/llvm/llvm-project/issues/53645 - depends on !CC_IS_CLANG || !X86_32 - help - Detect overflows of buffers in common string and memory functions - where the compiler can determine and validate the buffer sizes. - config STATIC_USERMODEHELPER bool "Force all usermode helper calls through a single binary" help @@ -269,6 +267,7 @@ endchoice config LSM string "Ordered list of enabled LSMs" + depends on SECURITY default "landlock,lockdown,yama,loadpin,safesetid,smack,selinux,tomoyo,apparmor,ipe,bpf" if DEFAULT_SECURITY_SMACK default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO @@ -283,6 +282,23 @@ config LSM If unsure, leave this as the default. +config SECURITY_COMMONCAP_KUNIT_TEST + bool "Build KUnit tests for commoncap" if !KUNIT_ALL_TESTS + depends on KUNIT=y && USER_NS + default KUNIT_ALL_TESTS + help + This builds the commoncap KUnit tests. + + KUnit tests run during boot and output the results to the debug log + in TAP format (https://testanything.org/). Only useful for kernel devs + running KUnit test harness and are not for inclusion into a + production build. + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. + source "security/Kconfig.hardening" endmenu diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening index b56e001e0c6a..86f8768c63d4 100644 --- a/security/Kconfig.hardening +++ b/security/Kconfig.hardening @@ -1,22 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only menu "Kernel hardening options" -config GCC_PLUGIN_STRUCTLEAK - bool - help - While the kernel is built with warnings enabled for any missed - stack variable initializations, this warning is silenced for - anything passed by reference to another function, under the - occasionally misguided assumption that the function will do - the initialization. As this regularly leads to exploitable - flaws, this plugin is available to identify and zero-initialize - such variables, depending on the chosen level of coverage. - - This plugin was originally ported from grsecurity/PaX. More - information at: - * https://grsecurity.net/ - * https://pax.grsecurity.net/ - menu "Memory initialization" config CC_HAS_AUTO_VAR_INIT_PATTERN @@ -36,7 +20,6 @@ config CC_HAS_AUTO_VAR_INIT_ZERO choice prompt "Initialize kernel stack variables at function entry" - default GCC_PLUGIN_STRUCTLEAK_BYREF_ALL if COMPILE_TEST && GCC_PLUGINS default INIT_STACK_ALL_PATTERN if COMPILE_TEST && CC_HAS_AUTO_VAR_INIT_PATTERN default INIT_STACK_ALL_ZERO if CC_HAS_AUTO_VAR_INIT_ZERO default INIT_STACK_NONE @@ -60,55 +43,6 @@ choice classes of uninitialized stack variable exploits and information exposures. - config GCC_PLUGIN_STRUCTLEAK_USER - bool "zero-init structs marked for userspace (weak)" - # Plugin can be removed once the kernel only supports GCC 12+ - depends on GCC_PLUGINS && !CC_HAS_AUTO_VAR_INIT_ZERO - select GCC_PLUGIN_STRUCTLEAK - help - Zero-initialize any structures on the stack containing - a __user attribute. This can prevent some classes of - uninitialized stack variable exploits and information - exposures, like CVE-2013-2141: - https://git.kernel.org/linus/b9e146d8eb3b9eca - - config GCC_PLUGIN_STRUCTLEAK_BYREF - bool "zero-init structs passed by reference (strong)" - # Plugin can be removed once the kernel only supports GCC 12+ - depends on GCC_PLUGINS && !CC_HAS_AUTO_VAR_INIT_ZERO - depends on !(KASAN && KASAN_STACK) - select GCC_PLUGIN_STRUCTLEAK - help - Zero-initialize any structures on the stack that may - be passed by reference and had not already been - explicitly initialized. This can prevent most classes - of uninitialized stack variable exploits and information - exposures, like CVE-2017-1000410: - https://git.kernel.org/linus/06e7e776ca4d3654 - - As a side-effect, this keeps a lot of variables on the - stack that can otherwise be optimized out, so combining - this with CONFIG_KASAN_STACK can lead to a stack overflow - and is disallowed. - - config GCC_PLUGIN_STRUCTLEAK_BYREF_ALL - bool "zero-init everything passed by reference (very strong)" - # Plugin can be removed once the kernel only supports GCC 12+ - depends on GCC_PLUGINS && !CC_HAS_AUTO_VAR_INIT_ZERO - depends on !(KASAN && KASAN_STACK) - select GCC_PLUGIN_STRUCTLEAK - help - Zero-initialize any stack variables that may be passed - by reference and had not already been explicitly - initialized. This is intended to eliminate all classes - of uninitialized stack variable exploits and information - exposures. - - As a side-effect, this keeps a lot of variables on the - stack that can otherwise be optimized out, so combining - this with CONFIG_KASAN_STACK can lead to a stack overflow - and is disallowed. - config INIT_STACK_ALL_PATTERN bool "pattern-init everything (strongest)" depends on CC_HAS_AUTO_VAR_INIT_PATTERN @@ -148,20 +82,13 @@ choice endchoice -config GCC_PLUGIN_STRUCTLEAK_VERBOSE - bool "Report forcefully initialized variables" - depends on GCC_PLUGIN_STRUCTLEAK - depends on !COMPILE_TEST # too noisy - help - This option will cause a warning to be printed each time the - structleak plugin finds a variable it thinks needs to be - initialized. Since not all existing initializers are detected - by the plugin, this can produce false positive warnings. +config CC_HAS_SANCOV_STACK_DEPTH_CALLBACK + def_bool $(cc-option,-fsanitize-coverage-stack-depth-callback-min=1) -config GCC_PLUGIN_STACKLEAK +config KSTACK_ERASE bool "Poison kernel stack before returning from syscalls" - depends on GCC_PLUGINS - depends on HAVE_ARCH_STACKLEAK + depends on HAVE_ARCH_KSTACK_ERASE + depends on GCC_PLUGINS || CC_HAS_SANCOV_STACK_DEPTH_CALLBACK help This option makes the kernel erase the kernel stack before returning from system calls. This has the effect of leaving @@ -179,6 +106,10 @@ config GCC_PLUGIN_STACKLEAK are advised to test this feature on your expected workload before deploying it. +config GCC_PLUGIN_STACKLEAK + def_bool KSTACK_ERASE + depends on GCC_PLUGINS + help This plugin was ported from grsecurity/PaX. More information at: * https://grsecurity.net/ * https://pax.grsecurity.net/ @@ -193,37 +124,37 @@ config GCC_PLUGIN_STACKLEAK_VERBOSE instrumented. This is useful for comparing coverage between builds. -config STACKLEAK_TRACK_MIN_SIZE - int "Minimum stack frame size of functions tracked by STACKLEAK" +config KSTACK_ERASE_TRACK_MIN_SIZE + int "Minimum stack frame size of functions tracked by KSTACK_ERASE" default 100 range 0 4096 - depends on GCC_PLUGIN_STACKLEAK + depends on KSTACK_ERASE help - The STACKLEAK gcc plugin instruments the kernel code for tracking + The KSTACK_ERASE option instruments the kernel code for tracking the lowest border of the kernel stack (and for some other purposes). - It inserts the stackleak_track_stack() call for the functions with - a stack frame size greater than or equal to this parameter. + It inserts the __sanitizer_cov_stack_depth() call for the functions + with a stack frame size greater than or equal to this parameter. If unsure, leave the default value 100. -config STACKLEAK_METRICS - bool "Show STACKLEAK metrics in the /proc file system" - depends on GCC_PLUGIN_STACKLEAK +config KSTACK_ERASE_METRICS + bool "Show KSTACK_ERASE metrics in the /proc file system" + depends on KSTACK_ERASE depends on PROC_FS help - If this is set, STACKLEAK metrics for every task are available in - the /proc file system. In particular, /proc/<pid>/stack_depth + If this is set, KSTACK_ERASE metrics for every task are available + in the /proc file system. In particular, /proc/<pid>/stack_depth shows the maximum kernel stack consumption for the current and previous syscalls. Although this information is not precise, it - can be useful for estimating the STACKLEAK performance impact for - your workloads. + can be useful for estimating the KSTACK_ERASE performance impact + for your workloads. -config STACKLEAK_RUNTIME_DISABLE +config KSTACK_ERASE_RUNTIME_DISABLE bool "Allow runtime disabling of kernel stack erasing" - depends on GCC_PLUGIN_STACKLEAK + depends on KSTACK_ERASE help This option provides 'stack_erasing' sysctl, which can be used in runtime to control kernel stack erasing for kernels built with - CONFIG_GCC_PLUGIN_STACKLEAK. + CONFIG_KSTACK_ERASE. config INIT_ON_ALLOC_DEFAULT_ON bool "Enable heap memory zeroing on allocation by default" @@ -280,6 +211,39 @@ config ZERO_CALL_USED_REGS endmenu +menu "Bounds checking" + +config FORTIFY_SOURCE + bool "Harden common str/mem functions against buffer overflows" + depends on ARCH_HAS_FORTIFY_SOURCE + # https://github.com/llvm/llvm-project/issues/53645 + depends on !X86_32 || !CC_IS_CLANG || CLANG_VERSION >= 160000 + help + Detect overflows of buffers in common string and memory functions + where the compiler can determine and validate the buffer sizes. + +config HARDENED_USERCOPY + bool "Harden memory copies between kernel and userspace" + imply STRICT_DEVMEM + help + This option checks for obviously wrong memory regions when + copying memory to/from the kernel (via copy_to_user() and + copy_from_user() functions) by rejecting memory ranges that + are larger than the specified heap object, span multiple + separately allocated pages, are not on the process stack, + or are part of the kernel text. This prevents entire classes + of heap overflow exploits and similar kernel memory exposures. + +config HARDENED_USERCOPY_DEFAULT_ON + bool "Harden memory copies by default" + depends on HARDENED_USERCOPY + default HARDENED_USERCOPY + help + This has the effect of setting "hardened_usercopy=on" on the kernel + command line. This can be disabled with "hardened_usercopy=off". + +endmenu + menu "Hardening of kernel data structures" config LIST_HARDENED @@ -291,6 +255,16 @@ config LIST_HARDENED If unsure, say N. +config RUST_BITMAP_HARDENED + bool "Check integrity of bitmap Rust API" + depends on RUST + help + Enables additional assertions in the Rust Bitmap API to catch + arguments that are not guaranteed to result in an immediate access + fault. + + If unsure, say N. + config BUG_ON_DATA_CORRUPTION bool "Trigger a BUG when data corruption is detected" select LIST_HARDENED diff --git a/security/Makefile b/security/Makefile index 22ff4c8bd8ce..4601230ba442 100644 --- a/security/Makefile +++ b/security/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_SECURITY) += lsm_syscalls.o obj-$(CONFIG_MMU) += min_addr.o # Object file lists -obj-$(CONFIG_SECURITY) += security.o +obj-$(CONFIG_SECURITY) += security.o lsm_notifier.o lsm_init.o obj-$(CONFIG_SECURITYFS) += inode.o obj-$(CONFIG_SECURITY_SELINUX) += selinux/ obj-$(CONFIG_SECURITY_SMACK) += smack/ diff --git a/security/apparmor/.kunitconfig b/security/apparmor/.kunitconfig new file mode 100644 index 000000000000..aa842a0266e9 --- /dev/null +++ b/security/apparmor/.kunitconfig @@ -0,0 +1,5 @@ +CONFIG_KUNIT=y +CONFIG_NET=y +CONFIG_SECURITY=y +CONFIG_SECURITY_APPARMOR=y +CONFIG_SECURITY_APPARMOR_KUNIT_TEST=y diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 64cc3044a42c..1e3bd44643da 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -59,8 +59,7 @@ config SECURITY_APPARMOR_INTROSPECT_POLICY config SECURITY_APPARMOR_HASH bool "Enable introspection of sha256 hashes for loaded profiles" depends on SECURITY_APPARMOR_INTROSPECT_POLICY - select CRYPTO - select CRYPTO_SHA256 + select CRYPTO_LIB_SHA256 default y help This option selects whether introspection of loaded policy diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index b9c5879dd599..12fb419714c0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o secid.o file.o policy_ns.o label.o mount.o net.o \ - policy_compat.o + policy_compat.o af_unix.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o @@ -28,7 +28,7 @@ clean-files := capability_names.h rlim_names.h net_names.h # to # #define AA_SFS_AF_MASK "local inet" quiet_cmd_make-af = GEN $@ -cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ +cmd_make-af = echo "static const char *const address_family_names[] = {" > $@ ;\ sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ echo "};" >> $@ ;\ @@ -43,7 +43,7 @@ cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ # to # [1] = "stream", quiet_cmd_make-sock = GEN $@ -cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ +cmd_make-sock = echo "static const char *const sock_type_names[] = {" >> $@ ;\ sed $^ >>$@ -r -n \ -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ echo "};" >> $@ diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c new file mode 100644 index 000000000000..fdb4a9f212c3 --- /dev/null +++ b/security/apparmor/af_unix.c @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor af_unix fine grained mediation + * + * Copyright 2023 Canonical Ltd. + * + * 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, version 2 of the + * License. + */ + +#include <linux/fs.h> +#include <net/tcp_states.h> + +#include "include/audit.h" +#include "include/af_unix.h" +#include "include/apparmor.h" +#include "include/file.h" +#include "include/label.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/cred.h" + + +static inline struct sock *aa_unix_sk(struct unix_sock *u) +{ + return &u->sk; +} + +static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, + struct aa_label *label, const struct path *path) +{ + AA_BUG(!label); + AA_BUG(!path); + + if (unconfined(label) || !label_mediates(label, AA_CLASS_FILE)) + return 0; + + mask &= NET_FS_PERMS; + /* if !u->path.dentry socket is being shutdown - implicit delegation + * until obj delegation is supported + */ + if (path->dentry) { + /* the sunpath may not be valid for this ns so use the path */ + struct inode *inode = path->dentry->d_inode; + vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(path->mnt), inode); + struct path_cond cond = { + .uid = vfsuid_into_kuid(vfsuid), + .mode = inode->i_mode, + }; + + return aa_path_perm(op, subj_cred, label, path, + PATH_SOCK_COND, mask, &cond); + } /* else implicitly delegated */ + + return 0; +} + +/* match_addr special constants */ +#define ABSTRACT_ADDR "\x00" /* abstract socket addr */ +#define ANONYMOUS_ADDR "\x01" /* anonymous endpoint, no addr */ +#define DISCONNECTED_ADDR "\x02" /* addr is another namespace */ +#define SHUTDOWN_ADDR "\x03" /* path addr is shutdown and cleared */ +#define FS_ADDR "/" /* path addr in fs */ + +static aa_state_t match_addr(struct aa_dfa *dfa, aa_state_t state, + struct sockaddr_un *addr, int addrlen) +{ + if (addr) + /* include leading \0 */ + state = aa_dfa_match_len(dfa, state, addr->sun_path, + unix_addr_len(addrlen)); + else + state = aa_dfa_match_len(dfa, state, ANONYMOUS_ADDR, 1); + /* todo: could change to out of band for cleaner separation */ + state = aa_dfa_null_transition(dfa, state); + + return state; +} + +static aa_state_t match_to_local(struct aa_policydb *policy, + aa_state_t state, u32 request, + int type, int protocol, + struct sockaddr_un *addr, int addrlen, + struct aa_perms **p, + const char **info) +{ + state = aa_match_to_prot(policy, state, request, PF_UNIX, type, + protocol, NULL, info); + if (state) { + state = match_addr(policy->dfa, state, addr, addrlen); + if (state) { + /* todo: local label matching */ + state = aa_dfa_null_transition(policy->dfa, state); + if (!state) + *info = "failed local label match"; + } else { + *info = "failed local address match"; + } + } + + return state; +} + +struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen) +{ + struct unix_address *addr; + + /* memory barrier is sufficient see note in net/unix/af_unix.c */ + addr = smp_load_acquire(&u->addr); + if (addr) { + *addrlen = addr->len; + return addr->name; + } + *addrlen = 0; + return NULL; +} + +static aa_state_t match_to_sk(struct aa_policydb *policy, + aa_state_t state, u32 request, + struct unix_sock *u, struct aa_perms **p, + const char **info) +{ + int addrlen; + struct sockaddr_un *addr = aa_sunaddr(u, &addrlen); + + return match_to_local(policy, state, request, u->sk.sk_type, + u->sk.sk_protocol, addr, addrlen, p, info); +} + +#define CMD_ADDR 1 +#define CMD_LISTEN 2 +#define CMD_OPT 4 + +static aa_state_t match_to_cmd(struct aa_policydb *policy, aa_state_t state, + u32 request, struct unix_sock *u, + char cmd, struct aa_perms **p, + const char **info) +{ + AA_BUG(!p); + + state = match_to_sk(policy, state, request, u, p, info); + if (state && !*p) { + state = aa_dfa_match_len(policy->dfa, state, &cmd, 1); + if (!state) + *info = "failed cmd selection match"; + } + + return state; +} + +static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t state, + u32 request, struct unix_sock *u, + struct sockaddr_un *peer_addr, int peer_addrlen, + struct aa_perms **p, const char **info) +{ + AA_BUG(!p); + + state = match_to_cmd(policy, state, request, u, CMD_ADDR, p, info); + if (state && !*p) { + state = match_addr(policy->dfa, state, peer_addr, peer_addrlen); + if (!state) + *info = "failed peer address match"; + } + + return state; +} + +static aa_state_t match_label(struct aa_profile *profile, + struct aa_ruleset *rule, aa_state_t state, + u32 request, struct aa_profile *peer, + struct aa_perms *p, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!peer); + + ad->peer = &peer->label; + + if (state && !p) { + state = aa_dfa_match(rule->policy->dfa, state, + peer->base.hname); + if (!state) + ad->info = "failed peer label match"; + + } + + return aa_do_perms(profile, rule->policy, state, request, p, ad); +} + + +/* unix sock creation comes before we know if the socket will be an fs + * socket + * v6 - semantics are handled by mapping in profile load + * v7 - semantics require sock create for tasks creating an fs socket. + * v8 - same as v7 + */ +static int profile_create_perm(struct aa_profile *profile, int family, + int type, int protocol, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE, + PF_UNIX, type, protocol, NULL, + &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_CREATE, + NULL, ad); + } + + return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type, + protocol); +} + +static int profile_sk_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request, struct sock *sk, const struct path *path) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, + &unix_sk(sk)->path); + + state = match_to_sk(rules->policy, state, request, unix_sk(sk), + &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + if (is_unix_addr_fs(ad->net.addr, ad->net.addrlen)) + /* under v7-9 fs hook handles bind */ + return 0; + /* bind for abstract socket */ + state = match_to_local(rules->policy, state, AA_MAY_BIND, + sk->sk_type, sk->sk_protocol, + unix_addr(ad->net.addr), + ad->net.addrlen, + &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_BIND, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk); +} + +static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, + int backlog, struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + __be16 b = cpu_to_be16(backlog); + + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, AA_MAY_LISTEN, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + + state = match_to_cmd(rules->policy, state, AA_MAY_LISTEN, + unix_sk(sk), CMD_LISTEN, &p, &ad->info); + if (state && !p) { + state = aa_dfa_match_len(rules->policy->dfa, state, + (char *) &b, 2); + if (!state) + ad->info = "failed listen backlog match"; + } + return aa_do_perms(profile, rules->policy, state, AA_MAY_LISTEN, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk); +} + +static int profile_accept_perm(struct aa_profile *profile, + struct sock *sk, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, AA_MAY_ACCEPT, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + + state = match_to_sk(rules->policy, state, AA_MAY_ACCEPT, + unix_sk(sk), &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_ACCEPT, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk); +} + +static int profile_opt_perm(struct aa_profile *profile, u32 request, + struct sock *sk, int optname, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + __be16 b = cpu_to_be16(optname); + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, request, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + + state = match_to_cmd(rules->policy, state, request, unix_sk(sk), + CMD_OPT, &p, &ad->info); + if (state && !p) { + state = aa_dfa_match_len(rules->policy->dfa, state, + (char *) &b, 2); + if (!state) + ad->info = "failed sockopt match"; + } + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* null peer_label is allowed, in which case the peer_sk label is used */ +static int profile_peer_perm(struct aa_profile *profile, u32 request, + struct sock *sk, const struct path *path, + struct sockaddr_un *peer_addr, + int peer_addrlen, const struct path *peer_path, + struct aa_label *peer_label, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(profile_unconfined(profile)); + AA_BUG(!sk); + AA_BUG(!peer_label); + AA_BUG(!ad); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + struct aa_profile *peerp; + + if (peer_path) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, peer_path); + else if (path) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, path); + state = match_to_peer(rules->policy, state, request, + unix_sk(sk), + peer_addr, peer_addrlen, &p, &ad->info); + + return fn_for_each_in_scope(peer_label, peerp, + match_label(profile, rules, state, request, + peerp, p, ad)); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* -------------------------------- */ + +int aa_unix_create_perm(struct aa_label *label, int family, int type, + int protocol) +{ + if (!unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family, + type, protocol); + + return fn_for_each_confined(label, profile, + profile_create_perm(profile, family, type, + protocol, &ad)); + } + + return 0; +} + +static int aa_unix_label_sk_perm(const struct cred *subj_cred, + struct aa_label *label, + const char *op, u32 request, struct sock *sk, + const struct path *path) +{ + if (!unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + return fn_for_each_confined(label, profile, + profile_sk_perm(profile, &ad, request, sk, + path)); + } + return 0; +} + +/* revalidation, get/set attr, shutdown */ +int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) +{ + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = aa_unix_label_sk_perm(current_cred(), label, op, + request, sock->sk, + is_unix_fs(sock->sk) ? &unix_sk(sock->sk)->path : NULL); + end_current_label_crit_section(label); + + return error; +} + +static int valid_addr(struct sockaddr *addr, int addr_len) +{ + struct sockaddr_un *sunaddr = unix_addr(addr); + + /* addr_len == offsetof(struct sockaddr_un, sun_path) is autobind */ + if (addr_len < offsetof(struct sockaddr_un, sun_path) || + addr_len > sizeof(*sunaddr)) + return -EINVAL; + return 0; +} + +int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, + int addrlen) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + error = valid_addr(addr, addrlen); + if (error) + return error; + + label = begin_current_label_crit_section(); + /* fs bind is handled by mknod */ + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk); + + ad.net.addr = unix_addr(addr); + ad.net.addrlen = addrlen; + + error = fn_for_each_confined(label, profile, + profile_bind_perm(profile, sock->sk, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + +/* + * unix connections are covered by the + * - unix_stream_connect (stream) and unix_may_send hooks (dgram) + * - fs connect is handled by open + * This is just here to document this is not needed for af_unix + * +int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return 0; +} +*/ + +int aa_unix_listen_perm(struct socket *sock, int backlog) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_listen_perm(profile, sock->sk, + backlog, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + + +/* ability of sock to connect, not peer address binding */ +int aa_unix_accept_perm(struct socket *sock, struct socket *newsock) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, OP_ACCEPT, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_accept_perm(profile, sock->sk, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + + +/* + * dgram handled by unix_may_sendmsg, right to send on stream done at connect + * could do per msg unix_stream here, but connect + socket transfer is + * sufficient. This is just here to document this is not needed for af_unix + * + * sendmsg, recvmsg +int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size) +{ + return 0; +} +*/ + +int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, + int level, int optname) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_opt_perm(profile, request, sock->sk, + optname, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + +static int unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, const struct path *path, + struct sockaddr_un *peer_addr, int peer_addrlen, + const struct path *peer_path, struct aa_label *peer_label) +{ + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + ad.net.peer.addr = peer_addr; + ad.net.peer.addrlen = peer_addrlen; + + return fn_for_each_confined(label, profile, + profile_peer_perm(profile, request, sk, path, + peer_addr, peer_addrlen, peer_path, + peer_label, &ad)); +} + +/** + * + * Requires: lock held on both @sk and @peer_sk + * called by unix_stream_connect, unix_may_send + */ +int aa_unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, struct sock *peer_sk, + struct aa_label *peer_label) +{ + struct unix_sock *peeru = unix_sk(peer_sk); + struct unix_sock *u = unix_sk(sk); + int plen; + struct sockaddr_un *paddr = aa_sunaddr(unix_sk(peer_sk), &plen); + + AA_BUG(!label); + AA_BUG(!sk); + AA_BUG(!peer_sk); + AA_BUG(!peer_label); + + return unix_peer_perm(subj_cred, label, op, request, sk, + is_unix_fs(sk) ? &u->path : NULL, + paddr, plen, + is_unix_fs(peer_sk) ? &peeru->path : NULL, + peer_label); +} + +/* sk_plabel for comparison only */ +static void update_sk_ctx(struct sock *sk, struct aa_label *label, + struct aa_label *plabel) +{ + struct aa_label *l, *old; + struct aa_sk_ctx *ctx = aa_sock(sk); + bool update_sk; + + rcu_read_lock(); + update_sk = (plabel && + (plabel != rcu_access_pointer(ctx->peer_lastupdate) || + !aa_label_is_subset(plabel, rcu_dereference(ctx->peer)))) || + !__aa_subj_label_is_cached(label, rcu_dereference(ctx->label)); + rcu_read_unlock(); + if (!update_sk) + return; + + spin_lock(&unix_sk(sk)->lock); + old = rcu_dereference_protected(ctx->label, + lockdep_is_held(&unix_sk(sk)->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(ctx->label, l); + aa_put_label(old); + } else + aa_put_label(l); + } + if (plabel && rcu_access_pointer(ctx->peer_lastupdate) != plabel) { + old = rcu_dereference_protected(ctx->peer, lockdep_is_held(&unix_sk(sk)->lock)); + + if (old == plabel) { + rcu_assign_pointer(ctx->peer_lastupdate, plabel); + } else if (aa_label_is_subset(plabel, old)) { + rcu_assign_pointer(ctx->peer_lastupdate, plabel); + rcu_assign_pointer(ctx->peer, aa_get_label(plabel)); + aa_put_label(old); + } /* else race or a subset - don't update */ + } + spin_unlock(&unix_sk(sk)->lock); +} + +static void update_peer_ctx(struct sock *sk, struct aa_sk_ctx *ctx, + struct aa_label *label) +{ + struct aa_label *l, *old; + + spin_lock(&unix_sk(sk)->lock); + old = rcu_dereference_protected(ctx->peer, + lockdep_is_held(&unix_sk(sk)->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(ctx->peer, l); + aa_put_label(old); + } else + aa_put_label(l); + } + spin_unlock(&unix_sk(sk)->lock); +} + +/* This fn is only checked if something has changed in the security + * boundaries. Otherwise cached info off file is sufficient + */ +int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct file *file) +{ + struct socket *sock = (struct socket *) file->private_data; + struct sockaddr_un *addr, *peer_addr; + int addrlen, peer_addrlen; + struct aa_label *plabel = NULL; + struct sock *peer_sk = NULL; + u32 sk_req = request & ~NET_PEER_MASK; + struct path path; + bool is_sk_fs; + int error = 0; + + AA_BUG(!label); + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(sock->sk->sk_family != PF_UNIX); + + /* investigate only using lock via unix_peer_get() + * addr only needs the memory barrier, but need to investigate + * path + */ + unix_state_lock(sock->sk); + peer_sk = unix_peer(sock->sk); + if (peer_sk) + sock_hold(peer_sk); + + is_sk_fs = is_unix_fs(sock->sk); + addr = aa_sunaddr(unix_sk(sock->sk), &addrlen); + path = unix_sk(sock->sk)->path; + unix_state_unlock(sock->sk); + + if (is_sk_fs && peer_sk) + sk_req = request; + if (sk_req) { + error = aa_unix_label_sk_perm(subj_cred, label, op, + sk_req, sock->sk, + is_sk_fs ? &path : NULL); + } + if (!peer_sk) + goto out; + + peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen); + + struct path peer_path; + + peer_path = unix_sk(peer_sk)->path; + if (!is_sk_fs && is_unix_fs(peer_sk)) { + last_error(error, + unix_fs_perm(op, request, subj_cred, label, + is_unix_fs(peer_sk) ? &peer_path : NULL)); + } else if (!is_sk_fs) { + struct aa_label *plabel; + struct aa_sk_ctx *pctx = aa_sock(peer_sk); + + rcu_read_lock(); + plabel = aa_get_label_rcu(&pctx->label); + rcu_read_unlock(); + /* no fs check of aa_unix_peer_perm because conditions above + * ensure they will never be done + */ + last_error(error, + xcheck(unix_peer_perm(subj_cred, label, op, + MAY_READ | MAY_WRITE, sock->sk, + is_sk_fs ? &path : NULL, + peer_addr, peer_addrlen, + is_unix_fs(peer_sk) ? + &peer_path : NULL, + plabel), + unix_peer_perm(file->f_cred, plabel, op, + MAY_READ | MAY_WRITE, peer_sk, + is_unix_fs(peer_sk) ? + &peer_path : NULL, + addr, addrlen, + is_sk_fs ? &path : NULL, + label))); + if (!error && !__aa_subj_label_is_cached(plabel, label)) + update_peer_ctx(peer_sk, pctx, label); + } + sock_put(peer_sk); + +out: + + /* update peer cache to latest successful perm check */ + if (error == 0) + update_sk_ctx(sock->sk, label, plabel); + aa_put_label(plabel); + + return error; +} + diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index c07d150685d7..ededaf46f3ca 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -32,6 +32,7 @@ #include "include/crypto.h" #include "include/ipc.h" #include "include/label.h" +#include "include/lib.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/resource.h" @@ -43,7 +44,7 @@ * The interface is split into two main components based on their function * a securityfs component: * used for static files that are always available, and which allows - * userspace to specificy the location of the security filesystem. + * userspace to specify the location of the security filesystem. * * fns and data are prefixed with * aa_sfs_ @@ -62,6 +63,7 @@ * securityfs and apparmorfs filesystems. */ +#define IREF_POISON 101 /* * support fns @@ -79,7 +81,7 @@ static void rawdata_f_data_free(struct rawdata_f_data *private) if (!private) return; - aa_put_loaddata(private->loaddata); + aa_put_i_loaddata(private->loaddata); kvfree(private); } @@ -149,10 +151,75 @@ static int aafs_count; static int aafs_show_path(struct seq_file *seq, struct dentry *dentry) { - seq_printf(seq, "%s:[%lu]", AAFS_NAME, d_inode(dentry)->i_ino); + seq_printf(seq, "%s:[%llu]", AAFS_NAME, d_inode(dentry)->i_ino); return 0; } +static struct aa_ns *get_ns_common_ref(struct aa_common_ref *ref) +{ + if (ref) { + struct aa_label *reflabel = container_of(ref, struct aa_label, + count); + return aa_get_ns(labels_ns(reflabel)); + } + + return NULL; +} + +static struct aa_proxy *get_proxy_common_ref(struct aa_common_ref *ref) +{ + if (ref) + return aa_get_proxy(container_of(ref, struct aa_proxy, count)); + + return NULL; +} + +static struct aa_loaddata *get_loaddata_common_ref(struct aa_common_ref *ref) +{ + if (ref) + return aa_get_i_loaddata(container_of(ref, struct aa_loaddata, + count)); + return NULL; +} + +static void aa_put_common_ref(struct aa_common_ref *ref) +{ + if (!ref) + return; + + switch (ref->reftype) { + case REF_RAWDATA: + aa_put_i_loaddata(container_of(ref, struct aa_loaddata, + count)); + break; + case REF_PROXY: + aa_put_proxy(container_of(ref, struct aa_proxy, + count)); + break; + case REF_NS: + /* ns count is held on its unconfined label */ + aa_put_ns(labels_ns(container_of(ref, struct aa_label, count))); + break; + default: + AA_BUG(true, "unknown refcount type"); + break; + } +} + +static void aa_get_common_ref(struct aa_common_ref *ref) +{ + kref_get(&ref->count); +} + +static void aafs_evict(struct inode *inode) +{ + struct aa_common_ref *ref = inode->i_private; + + clear_inode(inode); + aa_put_common_ref(ref); + inode->i_private = (void *) IREF_POISON; +} + static void aafs_free_inode(struct inode *inode) { if (S_ISLNK(inode->i_mode)) @@ -162,6 +229,7 @@ static void aafs_free_inode(struct inode *inode) static const struct super_operations aafs_super_ops = { .statfs = simple_statfs, + .evict_inode = aafs_evict, .free_inode = aafs_free_inode, .show_path = aafs_show_path, }; @@ -204,7 +272,7 @@ static struct file_system_type aafs_ops = { /** * __aafs_setup_d_inode - basic inode setup for apparmorfs * @dir: parent directory for the dentry - * @dentry: dentry we are seting the inode up for + * @dentry: dentry we are setting the inode up for * @mode: permissions the file should have * @data: data to store on inode.i_private, available in open() * @link: if symlink, symlink target string @@ -262,7 +330,8 @@ static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, * aafs_remove(). Will return ERR_PTR on failure. */ static struct dentry *aafs_create(const char *name, umode_t mode, - struct dentry *parent, void *data, void *link, + struct dentry *parent, + struct aa_common_ref *data, void *link, const struct file_operations *fops, const struct inode_operations *iops) { @@ -282,32 +351,24 @@ static struct dentry *aafs_create(const char *name, umode_t mode, dir = d_inode(parent); - inode_lock(dir); - dentry = lookup_one_len(name, parent, strlen(name)); + dentry = simple_start_creating(parent, name); if (IS_ERR(dentry)) { error = PTR_ERR(dentry); - goto fail_lock; - } - - if (d_really_is_positive(dentry)) { - error = -EEXIST; - goto fail_dentry; + goto fail; } error = __aafs_setup_d_inode(dir, dentry, mode, data, link, fops, iops); + simple_done_creating(dentry); if (error) - goto fail_dentry; - inode_unlock(dir); + goto fail; - return dentry; + if (data) + aa_get_common_ref(data); -fail_dentry: - dput(dentry); + return dentry; -fail_lock: - inode_unlock(dir); +fail: simple_release_fs(&aafs_mnt, &aafs_count); - return ERR_PTR(error); } @@ -323,7 +384,8 @@ fail_lock: * see aafs_create */ static struct dentry *aafs_create_file(const char *name, umode_t mode, - struct dentry *parent, void *data, + struct dentry *parent, + struct aa_common_ref *data, const struct file_operations *fops) { return aafs_create(name, mode, parent, data, NULL, fops, NULL); @@ -355,17 +417,22 @@ static void aafs_remove(struct dentry *dentry) if (!dentry || IS_ERR(dentry)) return; + /* ->d_parent is stable as rename is not supported */ dir = d_inode(dentry->d_parent); - inode_lock(dir); - if (simple_positive(dentry)) { - if (d_is_dir(dentry)) - simple_rmdir(dir, dentry); - else - simple_unlink(dir, dentry); + dentry = start_removing_dentry(dentry->d_parent, dentry); + if (!IS_ERR(dentry) && simple_positive(dentry)) { + if (d_is_dir(dentry)) { + if (!WARN_ON(!simple_empty(dentry))) { + __simple_rmdir(dir, dentry); + dput(dentry); + } + } else { + __simple_unlink(dir, dentry); + dput(dentry); + } d_delete(dentry); - dput(dentry); } - inode_unlock(dir); + end_removing(dentry); simple_release_fs(&aafs_mnt, &aafs_count); } @@ -404,7 +471,8 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, data->size = copy_size; if (copy_from_user(data->data, userbuf, copy_size)) { - aa_put_loaddata(data); + /* trigger free - don't need to put pcount */ + aa_put_i_loaddata(data); return ERR_PTR(-EFAULT); } @@ -412,7 +480,8 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, } static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, - loff_t *pos, struct aa_ns *ns) + loff_t *pos, struct aa_ns *ns, + const struct cred *ocred) { struct aa_loaddata *data; struct aa_label *label; @@ -423,7 +492,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(current_cred(), label, ns, mask); + error = aa_may_manage_policy(current_cred(), label, ns, ocred, mask); if (error) goto end_section; @@ -431,7 +500,10 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, error = PTR_ERR(data); if (!IS_ERR(data)) { error = aa_replace_profiles(ns, label, mask, data); - aa_put_loaddata(data); + /* put pcount, which will put count and free if no + * profiles referencing it. + */ + aa_put_profile_loaddata(data); } end_section: end_current_label_crit_section(label); @@ -443,8 +515,9 @@ end_section: static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns); + struct aa_ns *ns = get_ns_common_ref(f->f_inode->i_private); + int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns, + f->f_cred); aa_put_ns(ns); @@ -460,9 +533,9 @@ static const struct file_operations aa_fs_profile_load = { static ssize_t profile_replace(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + struct aa_ns *ns = get_ns_common_ref(f->f_inode->i_private); int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, - buf, size, pos, ns); + buf, size, pos, ns, f->f_cred); aa_put_ns(ns); return error; @@ -480,14 +553,14 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, struct aa_loaddata *data; struct aa_label *label; ssize_t error; - struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + struct aa_ns *ns = get_ns_common_ref(f->f_inode->i_private); label = begin_current_label_crit_section(); /* high level check about policy management - fine grained in * below after unpack */ error = aa_may_manage_policy(current_cred(), label, ns, - AA_MAY_REMOVE_POLICY); + f->f_cred, AA_MAY_REMOVE_POLICY); if (error) goto out; @@ -501,7 +574,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, if (!IS_ERR(data)) { data->data[size] = 0; error = aa_remove_profiles(ns, label, data->data, size); - aa_put_loaddata(data); + aa_put_profile_loaddata(data); } out: end_current_label_crit_section(label); @@ -565,12 +638,12 @@ static ssize_t ns_revision_read(struct file *file, char __user *buf, static int ns_revision_open(struct inode *inode, struct file *file) { - struct aa_revision *rev = kzalloc(sizeof(*rev), GFP_KERNEL); + struct aa_revision *rev = kzalloc_obj(*rev); if (!rev) return -ENOMEM; - rev->ns = aa_get_ns(inode->i_private); + rev->ns = get_ns_common_ref(inode->i_private); if (!rev->ns) rev->ns = aa_get_current_ns(); file->private_data = rev; @@ -612,8 +685,7 @@ static const struct file_operations aa_fs_ns_revision_fops = { static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, const char *match_str, size_t match_len) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms tmp = { }; aa_state_t state = DFA_NOMATCH; @@ -626,11 +698,20 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, if (state) { struct path_cond cond = { }; - tmp = *(aa_lookup_fperms(rules->file, state, &cond)); + tmp = *(aa_lookup_condperms(current_fsuid(), + rules->file, state, &cond)); } } else if (rules->policy->dfa) { if (!RULE_MEDIATES(rules, *match_str)) return; /* no change to current perms */ + /* old user space does not correctly detect dbus mediation + * support so we may get dbus policy and requests when + * the abi doesn't support it. This can cause mediation + * regressions, so explicitly test for this situation. + */ + if (*match_str == AA_CLASS_DBUS && + !RULE_MEDIATES_v9NET(rules)) + return; /* no change to current perms */ state = aa_dfa_match_len(rules->policy->dfa, rules->policy->start[0], match_str, match_len); @@ -788,7 +869,7 @@ static ssize_t query_label(char *buf, size_t buf_len, perms = allperms; if (view_only) { - label_for_each_in_ns(i, labels_ns(label), label, profile) { + label_for_each_in_scope(i, labels_ns(label), label, profile) { profile_query_cb(profile, &perms, match_str, match_len); } } else { @@ -997,7 +1078,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v) switch (fs_file->v_type) { case AA_SFS_TYPE_BOOLEAN: - seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(fs_file->v.boolean)); break; case AA_SFS_TYPE_STRING: seq_printf(seq, "%s\n", fs_file->v.string); @@ -1006,7 +1087,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v) seq_printf(seq, "%#08lx\n", fs_file->v.u64); break; default: - /* Ignore unpritable entry types. */ + /* Ignore unprintable entry types. */ break; } @@ -1048,7 +1129,7 @@ static const struct file_operations seq_profile_ ##NAME ##_fops = { \ static int seq_profile_open(struct inode *inode, struct file *file, int (*show)(struct seq_file *, void *)) { - struct aa_proxy *proxy = aa_get_proxy(inode->i_private); + struct aa_proxy *proxy = get_proxy_common_ref(inode->i_private); int error = single_open(file, show, proxy); if (error) { @@ -1152,7 +1233,7 @@ static int seq_ns_stacked_show(struct seq_file *seq, void *v) struct aa_label *label; label = begin_current_label_crit_section(); - seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(label->size > 1)); end_current_label_crit_section(label); return 0; @@ -1175,7 +1256,7 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) } } - seq_printf(seq, "%s\n", count > 1 ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(count > 1)); end_current_label_crit_section(label); return 0; @@ -1240,18 +1321,17 @@ static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \ static int seq_rawdata_open(struct inode *inode, struct file *file, int (*show)(struct seq_file *, void *)) { - struct aa_loaddata *data = __aa_get_loaddata(inode->i_private); + struct aa_loaddata *data = get_loaddata_common_ref(inode->i_private); int error; if (!data) - /* lost race this ent is being reaped */ return -ENOENT; error = single_open(file, show, data); if (error) { AA_BUG(file->private_data && ((struct seq_file *)file->private_data)->private); - aa_put_loaddata(data); + aa_put_i_loaddata(data); } return error; @@ -1262,7 +1342,7 @@ static int seq_rawdata_release(struct inode *inode, struct file *file) struct seq_file *seq = (struct seq_file *) file->private_data; if (seq) - aa_put_loaddata(seq->private); + aa_put_i_loaddata(seq->private); return single_release(inode, file); } @@ -1374,9 +1454,8 @@ static int rawdata_open(struct inode *inode, struct file *file) if (!aa_current_policy_view_capable(NULL)) return -EACCES; - loaddata = __aa_get_loaddata(inode->i_private); + loaddata = get_loaddata_common_ref(inode->i_private); if (!loaddata) - /* lost race: this entry is being reaped */ return -ENOENT; private = rawdata_f_data_alloc(loaddata->size); @@ -1401,7 +1480,7 @@ fail_decompress: return error; fail_private_alloc: - aa_put_loaddata(loaddata); + aa_put_i_loaddata(loaddata); return error; } @@ -1418,7 +1497,6 @@ static void remove_rawdata_dents(struct aa_loaddata *rawdata) for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) { if (!IS_ERR_OR_NULL(rawdata->dents[i])) { - /* no refcounts on i_private */ aafs_remove(rawdata->dents[i]); rawdata->dents[i] = NULL; } @@ -1461,35 +1539,37 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata) return PTR_ERR(dir); rawdata->dents[AAFS_LOADDATA_DIR] = dir; - dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata, + dent = aafs_create_file("abi", S_IFREG | 0444, dir, &rawdata->count, &seq_rawdata_abi_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_ABI] = dent; - dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata, - &seq_rawdata_revision_fops); + dent = aafs_create_file("revision", S_IFREG | 0444, dir, + &rawdata->count, + &seq_rawdata_revision_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_REVISION] = dent; if (aa_g_hash_policy) { dent = aafs_create_file("sha256", S_IFREG | 0444, dir, - rawdata, &seq_rawdata_hash_fops); + &rawdata->count, + &seq_rawdata_hash_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_HASH] = dent; } dent = aafs_create_file("compressed_size", S_IFREG | 0444, dir, - rawdata, + &rawdata->count, &seq_rawdata_compressed_size_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent; - dent = aafs_create_file("raw_data", S_IFREG | 0444, - dir, rawdata, &rawdata_fops); + dent = aafs_create_file("raw_data", S_IFREG | 0444, dir, + &rawdata->count, &rawdata_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_DATA] = dent; @@ -1497,13 +1577,11 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata) rawdata->ns = aa_get_ns(ns); list_add(&rawdata->list, &ns->rawdata_list); - /* no refcount on inode rawdata */ return 0; fail: remove_rawdata_dents(rawdata); - return PTR_ERR(dent); } #endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ @@ -1527,13 +1605,10 @@ void __aafs_profile_rmdir(struct aa_profile *profile) __aafs_profile_rmdir(child); for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { - struct aa_proxy *proxy; if (!profile->dents[i]) continue; - proxy = d_inode(profile->dents[i])->i_private; aafs_remove(profile->dents[i]); - aa_put_proxy(proxy); profile->dents[i] = NULL; } } @@ -1567,14 +1642,7 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, struct aa_profile *profile, const struct file_operations *fops) { - struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy); - struct dentry *dent; - - dent = aafs_create_file(name, S_IFREG | 0444, dir, proxy, fops); - if (IS_ERR(dent)) - aa_put_proxy(proxy); - - return dent; + return aafs_create_file(name, S_IFREG | 0444, dir, &profile->label.proxy->count, fops); } #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY @@ -1594,16 +1662,20 @@ static char *gen_symlink_name(int depth, const char *dirname, const char *fname) { char *buffer, *s; int error; - int size = depth * 6 + strlen(dirname) + strlen(fname) + 11; + const char *path = "../../"; + size_t path_len = strlen(path); + int size; + /* Extra 11 bytes: "raw_data" (9) + two slashes "//" (2) */ + size = depth * path_len + strlen(dirname) + strlen(fname) + 11; s = buffer = kmalloc(size, GFP_KERNEL); if (!buffer) return ERR_PTR(-ENOMEM); for (; depth > 0; depth--) { - strcpy(s, "../../"); - s += 6; - size -= 6; + memcpy(s, path, path_len); + s += path_len; + size -= path_len; } error = snprintf(s, size, "raw_data/%s/%s", dirname, fname); @@ -1620,7 +1692,8 @@ static const char *rawdata_get_link_base(struct dentry *dentry, struct delayed_call *done, const char *name) { - struct aa_proxy *proxy = inode->i_private; + struct aa_common_ref *ref = inode->i_private; + struct aa_proxy *proxy = container_of(ref, struct aa_proxy, count); struct aa_label *label; struct aa_profile *profile; char *target; @@ -1631,6 +1704,15 @@ static const char *rawdata_get_link_base(struct dentry *dentry, label = aa_get_label_rcu(&proxy->label); profile = labels_profile(label); + + /* rawdata can be null when aa_g_export_binary is unset during + * runtime and a profile is replaced + */ + if (!profile->rawdata) { + aa_put_label(label); + return ERR_PTR(-ENOENT); + } + depth = profile_depth(profile); target = gen_symlink_name(depth, profile->rawdata->name, name); aa_put_label(label); @@ -1753,27 +1835,24 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) if (profile->rawdata) { if (aa_g_hash_policy) { dent = aafs_create("raw_sha256", S_IFLNK | 0444, dir, - profile->label.proxy, NULL, NULL, - &rawdata_link_sha256_iops); + &profile->label.proxy->count, NULL, + NULL, &rawdata_link_sha256_iops); if (IS_ERR(dent)) goto fail; - aa_get_proxy(profile->label.proxy); profile->dents[AAFS_PROF_RAW_HASH] = dent; } dent = aafs_create("raw_abi", S_IFLNK | 0444, dir, - profile->label.proxy, NULL, NULL, + &profile->label.proxy->count, NULL, NULL, &rawdata_link_abi_iops); if (IS_ERR(dent)) goto fail; - aa_get_proxy(profile->label.proxy); profile->dents[AAFS_PROF_RAW_ABI] = dent; dent = aafs_create("raw_data", S_IFLNK | 0444, dir, - profile->label.proxy, NULL, NULL, + &profile->label.proxy->count, NULL, NULL, &rawdata_link_data_iops); if (IS_ERR(dent)) goto fail; - aa_get_proxy(profile->label.proxy); profile->dents[AAFS_PROF_RAW_DATA] = dent; } #endif /*CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ @@ -1795,8 +1874,8 @@ fail2: return error; } -static int ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode) +static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { struct aa_ns *ns, *parent; /* TODO: improve permission check */ @@ -1804,13 +1883,13 @@ static int ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, int error; label = begin_current_label_crit_section(); - error = aa_may_manage_policy(current_cred(), label, NULL, + error = aa_may_manage_policy(current_cred(), label, NULL, NULL, AA_MAY_LOAD_POLICY); end_current_label_crit_section(label); if (error) - return error; + return ERR_PTR(error); - parent = aa_get_ns(dir->i_private); + parent = get_ns_common_ref(dir->i_private); AA_BUG(d_inode(ns_subns_dir(parent)) != dir); /* we have to unlock and then relock to get locking order right @@ -1843,7 +1922,7 @@ out: mutex_unlock(&parent->lock); aa_put_ns(parent); - return error; + return ERR_PTR(error); } static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) @@ -1854,13 +1933,13 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) int error; label = begin_current_label_crit_section(); - error = aa_may_manage_policy(current_cred(), label, NULL, + error = aa_may_manage_policy(current_cred(), label, NULL, NULL, AA_MAY_LOAD_POLICY); end_current_label_crit_section(label); if (error) return error; - parent = aa_get_ns(dir->i_private); + parent = get_ns_common_ref(dir->i_private); /* rmdir calls the generic securityfs functions to remove files * from the apparmor dir. It is up to the apparmor ns locking * to avoid races. @@ -1930,27 +2009,6 @@ void __aafs_ns_rmdir(struct aa_ns *ns) __aa_fs_list_remove_rawdata(ns); - if (ns_subns_dir(ns)) { - sub = d_inode(ns_subns_dir(ns))->i_private; - aa_put_ns(sub); - } - if (ns_subload(ns)) { - sub = d_inode(ns_subload(ns))->i_private; - aa_put_ns(sub); - } - if (ns_subreplace(ns)) { - sub = d_inode(ns_subreplace(ns))->i_private; - aa_put_ns(sub); - } - if (ns_subremove(ns)) { - sub = d_inode(ns_subremove(ns))->i_private; - aa_put_ns(sub); - } - if (ns_subrevision(ns)) { - sub = d_inode(ns_subrevision(ns))->i_private; - aa_put_ns(sub); - } - for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { aafs_remove(ns->dents[i]); ns->dents[i] = NULL; @@ -1975,40 +2033,40 @@ static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) return PTR_ERR(dent); ns_subdata_dir(ns) = dent; - dent = aafs_create_file("revision", 0444, dir, ns, + dent = aafs_create_file("revision", 0444, dir, + &ns->unconfined->label.count, &aa_fs_ns_revision_fops); if (IS_ERR(dent)) return PTR_ERR(dent); - aa_get_ns(ns); ns_subrevision(ns) = dent; - dent = aafs_create_file(".load", 0640, dir, ns, - &aa_fs_profile_load); + dent = aafs_create_file(".load", 0640, dir, + &ns->unconfined->label.count, + &aa_fs_profile_load); if (IS_ERR(dent)) return PTR_ERR(dent); - aa_get_ns(ns); ns_subload(ns) = dent; - dent = aafs_create_file(".replace", 0640, dir, ns, - &aa_fs_profile_replace); + dent = aafs_create_file(".replace", 0640, dir, + &ns->unconfined->label.count, + &aa_fs_profile_replace); if (IS_ERR(dent)) return PTR_ERR(dent); - aa_get_ns(ns); ns_subreplace(ns) = dent; - dent = aafs_create_file(".remove", 0640, dir, ns, - &aa_fs_profile_remove); + dent = aafs_create_file(".remove", 0640, dir, + &ns->unconfined->label.count, + &aa_fs_profile_remove); if (IS_ERR(dent)) return PTR_ERR(dent); - aa_get_ns(ns); ns_subremove(ns) = dent; /* use create_dentry so we can supply private data */ - dent = aafs_create("namespaces", S_IFDIR | 0755, dir, ns, NULL, NULL, - &ns_dir_inode_operations); + dent = aafs_create("namespaces", S_IFDIR | 0755, dir, + &ns->unconfined->label.count, + NULL, NULL, &ns_dir_inode_operations); if (IS_ERR(dent)) return PTR_ERR(dent); - aa_get_ns(ns); ns_subns_dir(ns) = dent; return 0; @@ -2244,7 +2302,7 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos) /** * p_stop - stop depth first traversal * @f: seq_file we are filling - * @p: the last profile writen + * @p: the last profile written * * Release all locking done by p_start/p_next on namespace tree */ @@ -2332,6 +2390,7 @@ static struct aa_sfs_entry aa_sfs_entry_attach[] = { static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("change_hat", 1), AA_SFS_FILE_BOOLEAN("change_hatv", 1), + AA_SFS_FILE_BOOLEAN("unconfined_allowed_children", 1), AA_SFS_FILE_BOOLEAN("change_onexec", 1), AA_SFS_FILE_BOOLEAN("change_profile", 1), AA_SFS_FILE_BOOLEAN("stack", 1), @@ -2340,6 +2399,7 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("computed_longest_left", 1), AA_SFS_DIR("attach_conditions", aa_sfs_entry_attach), AA_SFS_FILE_BOOLEAN("disconnected.path", 1), + AA_SFS_FILE_BOOLEAN("kill.signal", 1), AA_SFS_FILE_STRING("version", "1.2"), { } }; @@ -2364,7 +2424,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_FILE_BOOLEAN("set_load", 1), /* number of out of band transitions supported */ AA_SFS_FILE_U64("outofband", MAX_OOB_SUPPORTED), - AA_SFS_FILE_U64("permstable32_version", 1), + AA_SFS_FILE_U64("permstable32_version", 3), AA_SFS_FILE_STRING("permstable32", PERMS32STR), AA_SFS_FILE_U64("state32", 1), AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), @@ -2384,6 +2444,11 @@ static struct aa_sfs_entry aa_sfs_entry_ns[] = { { } }; +static struct aa_sfs_entry aa_sfs_entry_dbus[] = { + AA_SFS_FILE_STRING("mask", "acquire send receive"), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_query_label[] = { AA_SFS_FILE_STRING("perms", "allow deny audit quiet"), AA_SFS_FILE_BOOLEAN("data", 1), @@ -2406,6 +2471,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("domain", aa_sfs_entry_domain), AA_SFS_DIR("file", aa_sfs_entry_file), AA_SFS_DIR("network_v8", aa_sfs_entry_network), + AA_SFS_DIR("network_v9", aa_sfs_entry_networkv9), AA_SFS_DIR("mount", aa_sfs_entry_mount), AA_SFS_DIR("namespaces", aa_sfs_entry_ns), AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), @@ -2413,6 +2479,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("caps", aa_sfs_entry_caps), AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace), AA_SFS_DIR("signal", aa_sfs_entry_signal), + AA_SFS_DIR("dbus", aa_sfs_entry_dbus), AA_SFS_DIR("query", aa_sfs_entry_query), AA_SFS_DIR("io_uring", aa_sfs_entry_io_uring), { } @@ -2550,8 +2617,7 @@ static int aa_mk_null_file(struct dentry *parent) if (error) return error; - inode_lock(d_inode(parent)); - dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME)); + dentry = simple_start_creating(parent, NULL_FILE_NAME); if (IS_ERR(dentry)) { error = PTR_ERR(dentry); goto out; @@ -2559,7 +2625,7 @@ static int aa_mk_null_file(struct dentry *parent) inode = new_inode(parent->d_inode->i_sb); if (!inode) { error = -ENOMEM; - goto out1; + goto out; } inode->i_ino = get_next_ino(); @@ -2571,18 +2637,12 @@ static int aa_mk_null_file(struct dentry *parent) aa_null.dentry = dget(dentry); aa_null.mnt = mntget(mount); - error = 0; - -out1: - dput(dentry); out: - inode_unlock(d_inode(parent)); + simple_done_creating(dentry); simple_release_fs(&mount, &count); return error; } - - static const char *policy_get_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done) @@ -2609,7 +2669,7 @@ static int policy_readlink(struct dentry *dentry, char __user *buffer, char name[32]; int res; - res = snprintf(name, sizeof(name), "%s:[%lu]", AAFS_NAME, + res = snprintf(name, sizeof(name), "%s:[%llu]", AAFS_NAME, d_inode(dentry)->i_ino); if (res > 0 && res < sizeof(name)) res = readlink_copy(buffer, buflen, name, strlen(name)); @@ -2632,7 +2692,7 @@ static const struct inode_operations policy_link_iops = { * * Returns: error on failure */ -static int __init aa_create_aafs(void) +int __init aa_create_aafs(void) { struct dentry *dent; int error; @@ -2711,5 +2771,3 @@ error: AA_ERROR("Error creating AppArmor securityfs\n"); return error; } - -fs_initcall(aa_create_aafs); diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 73087d76f649..4a60b6fda75f 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -192,7 +192,7 @@ int aa_audit(int type, struct aa_profile *profile, aa_audit_msg(type, ad, cb); if (ad->type == AUDIT_APPARMOR_KILL) - (void)send_sig_info(SIGKILL, NULL, + (void)send_sig_info(profile->signal, NULL, ad->common.type == LSM_AUDIT_DATA_TASK && ad->common.u.tsk ? ad->common.u.tsk : current); @@ -230,7 +230,7 @@ int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, gfp_t gfp return -EINVAL; } - rule = kzalloc(sizeof(struct aa_audit_rule), gfp); + rule = kzalloc_obj(struct aa_audit_rule, gfp); if (!rule) return -ENOMEM; diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 7ca489ee1054..b9ea6bc45c1a 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -27,6 +27,7 @@ struct aa_sfs_entry aa_sfs_entry_caps[] = { AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK), + AA_SFS_FILE_BOOLEAN("extended", 1), { } }; @@ -68,8 +69,7 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile { const u64 AUDIT_CACHE_TIMEOUT_NS = 1000*1000*1000; /* 1 second */ - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; @@ -121,10 +121,32 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile static int profile_capable(struct aa_profile *profile, int cap, unsigned int opts, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; int error; + state = RULE_MEDIATES(rules, ad->class); + if (state) { + struct aa_perms perms = { }; + u32 request; + + /* caps broken into 256 x 32 bit permission chunks */ + state = aa_dfa_next(rules->policy->dfa, state, cap >> 5); + request = 1 << (cap & 0x1f); + perms = *aa_lookup_perms(rules->policy, state); + aa_apply_modes_to_perms(profile, &perms); + + if (opts & CAP_OPT_NOAUDIT) { + if (perms.complain & request) + ad->info = "optional: no audit"; + else + ad = NULL; + } + return aa_check_perms(profile, &perms, request, ad, + audit_cb); + } + + /* fallback to old caps mediation that doesn't support conditionals */ if (cap_raised(rules->caps.allow, cap) && !cap_raised(rules->caps.denied, cap)) error = 0; @@ -168,3 +190,34 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label, return error; } + +kernel_cap_t aa_profile_capget(struct aa_profile *profile) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; + + state = RULE_MEDIATES(rules, AA_CLASS_CAP); + if (state) { + kernel_cap_t caps = CAP_EMPTY_SET; + int i; + + /* caps broken into up to 256, 32 bit permission chunks */ + for (i = 0; i < (CAP_LAST_CAP >> 5); i++) { + struct aa_perms perms = { }; + aa_state_t tmp; + + tmp = aa_dfa_next(rules->policy->dfa, state, i); + perms = *aa_lookup_perms(rules->policy, tmp); + aa_apply_modes_to_perms(profile, &perms); + caps.val |= ((u64)(perms.allow)) << (i * 5); + caps.val |= ((u64)(perms.complain)) << (i * 5); + } + return caps; + } + + /* fallback to old caps */ + if (COMPLAIN_MODE(profile)) + return CAP_FULL_SET; + + return rules->caps.allow; +} diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c index aad486b2fca6..d8a7bde94d79 100644 --- a/security/apparmor/crypto.c +++ b/security/apparmor/crypto.c @@ -11,113 +11,51 @@ * it should be. */ -#include <crypto/hash.h> +#include <crypto/sha2.h> #include "include/apparmor.h" #include "include/crypto.h" -static unsigned int apparmor_hash_size; - -static struct crypto_shash *apparmor_tfm; - unsigned int aa_hash_size(void) { - return apparmor_hash_size; + return SHA256_DIGEST_SIZE; } char *aa_calc_hash(void *data, size_t len) { - SHASH_DESC_ON_STACK(desc, apparmor_tfm); char *hash; - int error; - - if (!apparmor_tfm) - return NULL; - hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); if (!hash) return ERR_PTR(-ENOMEM); - desc->tfm = apparmor_tfm; - - error = crypto_shash_init(desc); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) data, len); - if (error) - goto fail; - error = crypto_shash_final(desc, hash); - if (error) - goto fail; - + sha256(data, len, hash); return hash; - -fail: - kfree(hash); - - return ERR_PTR(error); } int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, size_t len) { - SHASH_DESC_ON_STACK(desc, apparmor_tfm); - int error; + struct sha256_ctx sctx; __le32 le32_version = cpu_to_le32(version); if (!aa_g_hash_policy) return 0; - if (!apparmor_tfm) - return 0; - - profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + profile->hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); if (!profile->hash) return -ENOMEM; - desc->tfm = apparmor_tfm; - - error = crypto_shash_init(desc); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) &le32_version, 4); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) start, len); - if (error) - goto fail; - error = crypto_shash_final(desc, profile->hash); - if (error) - goto fail; - + sha256_init(&sctx); + sha256_update(&sctx, (u8 *)&le32_version, 4); + sha256_update(&sctx, (u8 *)start, len); + sha256_final(&sctx, profile->hash); return 0; - -fail: - kfree(profile->hash); - profile->hash = NULL; - - return error; } -static int __init init_profile_hash(void) +int __init init_profile_hash(void) { - struct crypto_shash *tfm; - - if (!apparmor_initialized) - return 0; - - tfm = crypto_alloc_shash("sha256", 0, 0); - if (IS_ERR(tfm)) { - int error = PTR_ERR(tfm); - AA_ERROR("failed to setup profile sha256 hashing: %d\n", error); - return error; - } - apparmor_tfm = tfm; - apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm); - - aa_info_message("AppArmor sha256 policy hashing enabled"); - + if (apparmor_initialized) + aa_info_message("AppArmor sha256 policy hashing enabled"); return 0; } - -late_initcall(init_profile_hash); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 5939bd9a9b9b..f02bf770f638 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -28,6 +28,12 @@ #include "include/policy.h" #include "include/policy_ns.h" +static const char * const CONFLICTING_ATTACH_STR = "conflicting profile attachments"; +static const char * const CONFLICTING_ATTACH_STR_IX = + "conflicting profile attachments - ix fallback"; +static const char * const CONFLICTING_ATTACH_STR_UX = + "conflicting profile attachments - ux fallback"; + /** * may_change_ptraced_domain - check if can change profile on ptraced task * @to_cred: cred of task changing domain @@ -87,8 +93,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, struct aa_profile *tp, bool stack, aa_state_t state) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *ns_name; if (stack) @@ -110,7 +115,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, * @label: label to check access permissions for * @stack: whether this is a stacking request * @state: state to start match in - * @subns: whether to do permission checks on components in a subns + * @inview: whether to match labels in view or only in scope * @request: permissions to request * @perms: perms struct to set * @@ -122,18 +127,17 @@ static inline aa_state_t match_component(struct aa_profile *profile, */ static int label_compound_match(struct aa_profile *profile, struct aa_label *label, bool stack, - aa_state_t state, bool subns, u32 request, + aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_profile *tp; struct label_it i; struct path_cond cond = { }; - /* find first subcomponent that is visible */ + /* find first subcomponent that is in view and going to be interated with */ label_for_each(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = match_component(profile, tp, stack, state); if (!state) @@ -147,14 +151,15 @@ static int label_compound_match(struct aa_profile *profile, next: label_for_each_cont(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = aa_dfa_match(rules->file->dfa, state, "//&"); state = match_component(profile, tp, false, state); if (!state) goto fail; } - *perms = *(aa_lookup_fperms(rules->file, state, &cond)); + *perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -172,7 +177,7 @@ fail: * @label: label to check access permissions for * @stack: whether this is a stacking request * @start: state to start match in - * @subns: whether to do permission checks on components in a subns + * @inview: whether to match labels in view or only in scope * @request: permissions to request * @perms: an initialized perms struct to add accumulation to * @@ -184,11 +189,10 @@ fail: */ static int label_components_match(struct aa_profile *profile, struct aa_label *label, bool stack, - aa_state_t start, bool subns, u32 request, + aa_state_t start, bool inview, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_profile *tp; struct label_it i; struct aa_perms tmp; @@ -197,7 +201,7 @@ static int label_components_match(struct aa_profile *profile, /* find first subcomponent to test */ label_for_each(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = match_component(profile, tp, stack, start); if (!state) @@ -209,16 +213,18 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = *(aa_lookup_fperms(rules->file, state, &cond)); + tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = match_component(profile, tp, stack, start); if (!state) goto fail; - tmp = *(aa_lookup_fperms(rules->file, state, &cond)); + tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -239,26 +245,26 @@ fail: * @label: label to match (NOT NULL) * @stack: whether this is a stacking request * @state: state to start in - * @subns: whether to match subns components + * @inview: whether to match labels in view or only in scope * @request: permission request * @perms: Returns computed perms (NOT NULL) * * Returns: the state the match finished in, may be the none matching state */ static int label_match(struct aa_profile *profile, struct aa_label *label, - bool stack, aa_state_t state, bool subns, u32 request, + bool stack, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { int error; *perms = nullperms; - error = label_compound_match(profile, label, stack, state, subns, + error = label_compound_match(profile, label, stack, state, inview, request, perms); if (!error) return error; *perms = allperms; - return label_components_match(profile, label, stack, state, subns, + return label_components_match(profile, label, stack, state, inview, request, perms); } @@ -323,7 +329,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i], &value, value_size, GFP_KERNEL); if (size >= 0) { - u32 index, perm; + struct aa_perms *perms; /* * Check the xattr presence before value. This ensure @@ -335,9 +341,8 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, /* Check xattr value */ state = aa_dfa_match_len(attach->xmatch->dfa, state, value, size); - index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; - perm = attach->xmatch->perms[index].allow; - if (!(perm & MAY_EXEC)) { + perms = aa_lookup_perms(attach->xmatch, state); + if (!(perms->allow & MAY_EXEC)) { ret = -EINVAL; goto out; } @@ -415,15 +420,14 @@ restart: if (attach->xmatch->dfa) { unsigned int count; aa_state_t state; - u32 index, perm; + struct aa_perms *perms; state = aa_dfa_leftmatch(attach->xmatch->dfa, attach->xmatch->start[AA_CLASS_XMATCH], name, &count); - index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; - perm = attach->xmatch->perms[index].allow; + perms = aa_lookup_perms(attach->xmatch, state); /* any accepting state means a valid match. */ - if (perm & MAY_EXEC) { + if (perms->allow & MAY_EXEC) { int ret = 0; if (count < candidate_len) @@ -484,7 +488,7 @@ restart: if (!candidate || conflict) { if (conflict) - *info = "conflicting profile attachments"; + *info = CONFLICTING_ATTACH_STR; rcu_read_unlock(); return NULL; } @@ -508,15 +512,16 @@ static const char *next_name(int xtype, const char *name) * @name: returns: name tested to find label (NOT NULL) * * Returns: refcounted label, or NULL on failure (MAYBE NULL) + * @name will always be set with the last name tried */ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, const char **name) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; + const char *next; AA_BUG(!name); @@ -524,25 +529,27 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, /* TODO: move lookup parsing to unpack time so this is a straight * index into the resultant label */ - for (*name = rules->file->trans.table[index]; !label && *name; - *name = next_name(xtype, *name)) { + for (next = rules->file->trans.table[index].strs; next; + next = next_name(xtype, next)) { + const char *lookup = (*next == '&') ? next + 1 : next; + *name = next; if (xindex & AA_X_CHILD) { - struct aa_profile *new_profile; - /* release by caller */ - new_profile = aa_find_child(profile, *name); - if (new_profile) - label = &new_profile->label; + /* TODO: switich to parse to get stack of child */ + struct aa_profile *new = aa_find_child(profile, lookup); + + if (new) + /* release by caller */ + return &new->label; continue; } - label = aa_label_parse(&profile->label, *name, GFP_KERNEL, + label = aa_label_parse(&profile->label, lookup, GFP_KERNEL, true, false); - if (IS_ERR(label)) - label = NULL; + if (!IS_ERR_OR_NULL(label)) + /* release by caller */ + return label; } - /* released by caller */ - - return label; + return NULL; } /** @@ -564,12 +571,12 @@ static struct aa_label *x_to_label(struct aa_profile *profile, const char **lookupname, const char **info) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); struct aa_label *new = NULL; + struct aa_label *stack = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; - const char *stack = NULL; + /* Used for info checks during fallback handling */ + const char *old_info = NULL; switch (xtype) { case AA_X_NONE: @@ -578,13 +585,14 @@ static struct aa_label *x_to_label(struct aa_profile *profile, break; case AA_X_TABLE: /* TODO: fix when perm mapping done at unload */ - stack = rules->file->trans.table[xindex & AA_X_INDEX_MASK]; - if (*stack != '&') { - /* released by caller */ - new = x_table_lookup(profile, xindex, lookupname); - stack = NULL; + /* released by caller + * if null for both stack and direct want to try fallback + */ + new = x_table_lookup(profile, xindex, lookupname); + if (!new || **lookupname != '&') break; - } + stack = new; + new = NULL; fallthrough; /* to X_NAME */ case AA_X_NAME: if (xindex & AA_X_CHILD) @@ -599,17 +607,38 @@ static struct aa_label *x_to_label(struct aa_profile *profile, break; } + /* fallback transition check */ if (!new) { if (xindex & AA_X_INHERIT) { /* (p|c|n)ix - don't change profile but do * use the newest version */ - *info = "ix fallback"; + if (*info == CONFLICTING_ATTACH_STR) { + *info = CONFLICTING_ATTACH_STR_IX; + } else { + old_info = *info; + *info = "ix fallback"; + } /* no profile && no error */ new = aa_get_newest_label(&profile->label); } else if (xindex & AA_X_UNCONFINED) { new = aa_get_newest_label(ns_unconfined(profile->ns)); - *info = "ux fallback"; + if (*info == CONFLICTING_ATTACH_STR) { + *info = CONFLICTING_ATTACH_STR_UX; + } else { + old_info = *info; + *info = "ux fallback"; + } + } + /* We set old_info on the code paths above where overwriting + * could have happened, so now check if info was set by + * find_attach as well (i.e. whether we actually overwrote) + * and warn accordingly. + */ + if (old_info && old_info != CONFLICTING_ATTACH_STR) { + pr_warn_ratelimited( + "AppArmor: find_attach (from profile %s) audit info \"%s\" dropped", + profile->base.hname, old_info); } } @@ -617,12 +646,12 @@ static struct aa_label *x_to_label(struct aa_profile *profile, /* base the stack on post domain transition */ struct aa_label *base = new; - new = aa_label_parse(base, stack, GFP_KERNEL, true, false); - if (IS_ERR(new)) - new = NULL; + new = aa_label_merge(base, stack, GFP_KERNEL); + /* null on error */ aa_put_label(base); } + aa_put_label(stack); /* released by caller */ return new; } @@ -633,8 +662,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, char *buffer, struct path_cond *cond, bool *secure_exec) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_label *new = NULL; struct aa_profile *new_profile = NULL; const char *info = NULL, *name = NULL, *target = NULL; @@ -652,7 +680,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, if (error) { if (profile_unconfined(profile) || (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { - AA_DEBUG("name lookup ix on error"); + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); error = 0; new = aa_get_newest_label(&profile->label); } @@ -663,11 +691,27 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, if (profile_unconfined(profile)) { new = find_attach(bprm, profile->ns, &profile->ns->base.profiles, name, &info); + /* info set -> something unusual that we should report + * Currently this is only conflicting attachments, but other + * infos added in the future should also be logged by default + * and only excluded on a case-by-case basis + */ + if (info) { + /* Because perms is never used again after this audit + * we don't need to care about clobbering it + */ + perms.audit |= MAY_EXEC; + perms.allow |= MAY_EXEC; + /* Don't cause error if auditing fails */ + (void) aa_audit_file(subj_cred, profile, &perms, + OP_EXEC, MAY_EXEC, name, target, new, cond->uid, + info, error); + } if (new) { - AA_DEBUG("unconfined attached to new label"); + AA_DEBUG(DEBUG_DOMAIN, "unconfined attached to new label"); return new; } - AA_DEBUG("unconfined exec no attachment"); + AA_DEBUG(DEBUG_DOMAIN, "unconfined exec no attachment"); return aa_get_newest_label(&profile->label); } @@ -678,9 +722,21 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, new = x_to_label(profile, bprm, name, perms.xindex, &target, &info); if (new && new->proxy == profile->label.proxy && info) { + /* Force audit on conflicting attachment fallback + * Because perms is never used again after this audit + * we don't need to care about clobbering it + */ + if (info == CONFLICTING_ATTACH_STR_IX + || info == CONFLICTING_ATTACH_STR_UX) + perms.audit |= MAY_EXEC; /* hack ix fallback - improve how this is detected */ goto audit; } else if (!new) { + if (info) { + pr_warn_ratelimited( + "AppArmor: %s (from profile %s) audit info \"%s\" dropped on missing transition", + __func__, profile->base.hname, info); + } info = "profile transition not found"; /* remove MAY_EXEC to audit as failure or complaint */ perms.allow &= ~MAY_EXEC; @@ -739,8 +795,7 @@ static int profile_onexec(const struct cred *subj_cred, char *buffer, struct path_cond *cond, bool *secure_exec) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state = rules->file->start[AA_CLASS_FILE]; struct aa_perms perms = {}; const char *xname = NULL, *info = "change_profile onexec"; @@ -755,7 +810,7 @@ static int profile_onexec(const struct cred *subj_cred, /* change_profile on exec already granted */ /* * NOTE: Domain transitions from unconfined are allowed - * even when no_new_privs is set because this aways results + * even when no_new_privs is set because this always results * in a further reduction of permissions. */ return 0; @@ -766,7 +821,7 @@ static int profile_onexec(const struct cred *subj_cred, if (error) { if (profile_unconfined(profile) || (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { - AA_DEBUG("name lookup ix on error"); + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); error = 0; } xname = bprm->filename; @@ -825,14 +880,16 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred, AA_BUG(!bprm); AA_BUG(!buffer); - /* TODO: determine how much we want to loosen this */ - error = fn_for_each_in_ns(label, profile, + /* TODO: determine how much we want to loosen this + * only check profiles in scope for permission to change at exec + */ + error = fn_for_each_in_scope(label, profile, profile_onexec(subj_cred, profile, onexec, stack, bprm, buffer, cond, unsafe)); if (error) return ERR_PTR(error); - new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + new = fn_label_build_in_scope(label, profile, GFP_KERNEL, stack ? aa_label_merge(&profile->label, onexec, GFP_KERNEL) : aa_get_newest_label(onexec), @@ -842,7 +899,7 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred, return new; /* TODO: get rid of GLOBAL_ROOT_UID */ - error = fn_for_each_in_ns(label, profile, + error = fn_for_each_in_scope(label, profile, aa_audit_file(subj_cred, profile, &nullperms, OP_CHANGE_ONEXEC, AA_MAY_ONEXEC, bprm->filename, NULL, @@ -926,7 +983,7 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) * * NOTE: Domain transitions from unconfined and to stacked * subsets are allowed even when no_new_privs is set because this - * aways results in a further reduction of permissions. + * always results in a further reduction of permissions. */ if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) && @@ -1068,7 +1125,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred, /*find first matching hat */ for (i = 0; i < count && !hat; i++) { name = hats[i]; - label_for_each_in_ns(it, labels_ns(label), label, profile) { + label_for_each_in_scope(it, labels_ns(label), label, profile) { if (sibling && PROFILE_IS_HAT(profile)) { root = aa_get_profile_rcu(&profile->parent); } else if (!sibling && !PROFILE_IS_HAT(profile)) { @@ -1104,7 +1161,7 @@ outer_continue: * change_hat. */ name = NULL; - label_for_each_in_ns(it, labels_ns(label), label, profile) { + label_for_each_in_scope(it, labels_ns(label), label, profile) { if (!list_empty(&profile->base.profiles)) { info = "hat not found"; error = -ENOENT; @@ -1115,7 +1172,7 @@ outer_continue: error = -ECHILD; fail: - label_for_each_in_ns(it, labels_ns(label), label, profile) { + label_for_each_in_scope(it, labels_ns(label), label, profile) { /* * no target as it has failed to be found or built * @@ -1133,7 +1190,7 @@ fail: return ERR_PTR(error); build: - new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + new = fn_label_build_in_scope(label, profile, GFP_KERNEL, build_change_hat(subj_cred, profile, name, sibling), aa_get_label(&profile->label)); @@ -1188,10 +1245,24 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) ctx->nnp = aa_get_label(label); + /* return -EPERM when unconfined doesn't have children to avoid + * changing the traditional error code for unconfined. + */ if (unconfined(label)) { - info = "unconfined can not change_hat"; - error = -EPERM; - goto fail; + struct label_it i; + bool empty = true; + + rcu_read_lock(); + label_for_each_in_scope(i, labels_ns(label), label, profile) { + empty &= list_empty(&profile->base.profiles); + } + rcu_read_unlock(); + + if (empty) { + info = "unconfined can not change_hat"; + error = -EPERM; + goto fail; + } } if (count) { @@ -1216,7 +1287,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) if (task_no_new_privs(current) && !unconfined(label) && !aa_label_is_unconfined_subset(new, ctx->nnp)) { /* not an apparmor denial per se, so don't log it */ - AA_DEBUG("no_new_privs - change_hat denied"); + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); error = -EPERM; goto out; } @@ -1237,7 +1309,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) if (task_no_new_privs(current) && !unconfined(label) && !aa_label_is_unconfined_subset(previous, ctx->nnp)) { /* not an apparmor denial per se, so don't log it */ - AA_DEBUG("no_new_privs - change_hat denied"); + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); error = -EPERM; goto out; } @@ -1267,7 +1340,7 @@ kill: perms.kill = AA_MAY_CHANGEHAT; fail: - fn_for_each_in_ns(label, profile, + fn_for_each_in_scope(label, profile, aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, NULL, target, GLOBAL_ROOT_UID, info, error)); @@ -1282,8 +1355,7 @@ static int change_profile_perms_wrapper(const char *op, const char *name, struct aa_label *target, bool stack, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *info = NULL; int error = 0; @@ -1343,7 +1415,7 @@ int aa_change_profile(const char *fqname, int flags) if (!fqname || !*fqname) { aa_put_label(label); - AA_DEBUG("no profile name"); + AA_DEBUG(DEBUG_DOMAIN, "no profile name"); return -EINVAL; } @@ -1376,7 +1448,7 @@ int aa_change_profile(const char *fqname, int flags) */ stack = true; perms.audit = request; - (void) fn_for_each_in_ns(label, profile, + (void) fn_for_each_in_scope(label, profile, aa_audit_file(subj_cred, profile, &perms, op, request, auditname, NULL, target, GLOBAL_ROOT_UID, stack_msg, 0)); @@ -1422,7 +1494,7 @@ int aa_change_profile(const char *fqname, int flags) * * if (!stack) { */ - error = fn_for_each_in_ns(label, profile, + error = fn_for_each_in_scope(label, profile, change_profile_perms_wrapper(op, auditname, subj_cred, profile, target, stack, @@ -1436,7 +1508,7 @@ int aa_change_profile(const char *fqname, int flags) check: /* check if tracing task is allowed to trace target domain */ error = may_change_ptraced_domain(subj_cred, target, &info); - if (error && !fn_for_each_in_ns(label, profile, + if (error && !fn_for_each_in_scope(label, profile, COMPLAIN_MODE(profile))) goto audit; @@ -1452,7 +1524,7 @@ check: /* stacking is always a subset, so only check the nonstack case */ if (!stack) { - new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + new = fn_label_build_in_scope(label, profile, GFP_KERNEL, aa_get_label(target), aa_get_label(&profile->label)); /* @@ -1462,7 +1534,8 @@ check: if (task_no_new_privs(current) && !unconfined(label) && !aa_label_is_unconfined_subset(new, ctx->nnp)) { /* not an apparmor denial per se, so don't log it */ - AA_DEBUG("no_new_privs - change_hat denied"); + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); error = -EPERM; goto out; } @@ -1494,7 +1567,7 @@ check: } audit: - error = fn_for_each_in_ns(label, profile, + error = fn_for_each_in_scope(label, profile, aa_audit_file(subj_cred, profile, &perms, op, request, auditname, NULL, new ? new : target, diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d52a5b14dad4..694e157149e8 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -14,6 +14,7 @@ #include <linux/fs.h> #include <linux/mount.h> +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/audit.h" #include "include/cred.h" @@ -102,6 +103,7 @@ int aa_audit_file(const struct cred *subj_cred, ad.subj_cred = subj_cred; ad.request = request; + ad.tags = perms->tag; ad.name = name; ad.fs.target = target; ad.peer = tlabel; @@ -153,8 +155,12 @@ static int path_name(const char *op, const struct cred *subj_cred, const char *info = NULL; int error; - error = aa_path_name(path, flags, buffer, name, &info, - labels_profile(label)->disconnected); + /* don't reaudit files closed during inheritance */ + if (unlikely(path->dentry == aa_null.dentry)) + error = -EACCES; + else + error = aa_path_name(path, flags, buffer, name, &info, + labels_profile(label)->disconnected); if (error) { fn_for_each_confined(label, profile, aa_audit_file(subj_cred, @@ -168,8 +174,9 @@ static int path_name(const char *op, const struct cred *subj_cred, struct aa_perms default_perms = {}; /** - * aa_lookup_fperms - convert dfa compressed perms to internal perms - * @file_rules: the aa_policydb to lookup perms for (NOT NULL) + * aa_lookup_condperms - convert dfa compressed perms to internal perms + * @subj_uid: uid to use for subject owner test + * @rules: the aa_policydb to lookup perms for (NOT NULL) * @state: state in dfa * @cond: conditions to consider (NOT NULL) * @@ -177,18 +184,21 @@ struct aa_perms default_perms = {}; * * Returns: a pointer to a file permission set */ -struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, - aa_state_t state, struct path_cond *cond) +struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, struct aa_policydb *rules, + aa_state_t state, struct path_cond *cond) { - unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state]; + unsigned int index = ACCEPT_TABLE(rules->dfa)[state]; - if (!(file_rules->perms)) + if (!(rules->perms)) return &default_perms; - if (uid_eq(current_fsuid(), cond->uid)) - return &(file_rules->perms[index]); + if ((ACCEPT_TABLE2(rules->dfa)[state] & ACCEPT_FLAG_OWNER)) { + if (uid_eq(subj_uid, cond->uid)) + return &(rules->perms[index]); + return &(rules->perms[index + 1]); + } - return &(file_rules->perms[index + 1]); + return &(rules->perms[index]); } /** @@ -207,21 +217,22 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, { aa_state_t state; state = aa_dfa_match(file_rules->dfa, start, name); - *perms = *(aa_lookup_fperms(file_rules, state, cond)); + *perms = *(aa_lookup_condperms(current_fsuid(), file_rules, state, + cond)); return state; } -static int __aa_path_perm(const char *op, const struct cred *subj_cred, - struct aa_profile *profile, const char *name, - u32 request, struct path_cond *cond, int flags, - struct aa_perms *perms) +int __aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_profile *profile, const char *name, + u32 request, struct path_cond *cond, int flags, + struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int e = 0; - if (profile_unconfined(profile)) + if (profile_unconfined(profile) || + ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules))) return 0; aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], name, cond, perms); @@ -316,8 +327,7 @@ static int profile_path_link(const struct cred *subj_cred, const struct path *target, char *buffer2, struct path_cond *cond) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *lname, *tname = NULL; struct aa_perms lperms = {}, perms; const char *info = NULL; @@ -423,9 +433,11 @@ int aa_path_link(const struct cred *subj_cred, { struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry }; struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry }; + struct inode *inode = d_backing_inode(old_dentry); + vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode); struct path_cond cond = { - d_backing_inode(old_dentry)->i_uid, - d_backing_inode(old_dentry)->i_mode + .uid = vfsuid_into_kuid(vfsuid), + .mode = inode->i_mode, }; char *buffer = NULL, *buffer2 = NULL; struct aa_profile *profile; @@ -534,22 +546,19 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred, struct aa_label *flabel, struct file *file, u32 request, u32 denied) { - struct socket *sock = (struct socket *) file->private_data; int error; - AA_BUG(!sock); - /* revalidation due to label out of date. No revocation at this time */ if (!denied && aa_label_is_subset(flabel, label)) return 0; /* TODO: improve to skip profiles cached in flabel */ - error = aa_sock_file_perm(subj_cred, label, op, request, sock); + error = aa_sock_file_perm(subj_cred, label, op, request, file); if (denied) { /* TODO: improve to skip profiles checked above */ /* check every profile in file label to is cached */ last_error(error, aa_sock_file_perm(subj_cred, flabel, op, - request, sock)); + request, file)); } if (!error) update_file_ctx(file_ctx(file), label, request); @@ -557,6 +566,45 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred, return error; } +/* for now separate fn to indicate semantics of the check */ +static bool __file_is_delegated(struct aa_label *obj_label) +{ + return unconfined(obj_label); +} + +static bool __is_unix_file(struct file *file) +{ + struct socket *sock = (struct socket *) file->private_data; + + lockdep_assert_in_rcu_read_lock(); + + if (!S_ISSOCK(file_inode(file)->i_mode)) + return false; + /* sock and sock->sk can be NULL for sockets being set up or torn down */ + if (!sock || !sock->sk) + return false; + if (sock->sk->sk_family == PF_UNIX) + return true; + return false; +} + +static bool __unix_needs_revalidation(struct file *file, struct aa_label *label, + u32 request) +{ + struct socket *sock = (struct socket *) file->private_data; + + AA_BUG(!__is_unix_file(file)); + lockdep_assert_in_rcu_read_lock(); + + struct aa_sk_ctx *skctx = aa_sock(sock->sk); + + if (rcu_access_pointer(skctx->peer) != + rcu_access_pointer(skctx->peer_lastupdate)) + return true; + + return !__aa_subj_label_is_cached(rcu_dereference(skctx->label), label); +} + /** * aa_file_perm - do permission revalidation check & audit for @file * @op: operation being checked @@ -580,6 +628,10 @@ int aa_file_perm(const char *op, const struct cred *subj_cred, AA_BUG(!label); AA_BUG(!file); + /* don't reaudit files closed during inheritance */ + if (unlikely(file->f_path.dentry == aa_null.dentry)) + return -EACCES; + fctx = file_ctx(file); rcu_read_lock(); @@ -594,17 +646,18 @@ int aa_file_perm(const char *op, const struct cred *subj_cred, * delegation from unconfined tasks */ denied = request & ~fctx->allow; - if (unconfined(label) || unconfined(flabel) || - (!denied && aa_label_is_subset(flabel, label))) { + if (unconfined(label) || __file_is_delegated(flabel) || + (!denied && __is_unix_file(file) && !__unix_needs_revalidation(file, label, request)) || + (!denied && __aa_subj_label_is_cached(label, flabel))) { rcu_read_unlock(); goto done; } + /* slow path - revalidate access */ flabel = aa_get_newest_label(flabel); rcu_read_unlock(); - /* TODO: label cross check */ - if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) + if (path_mediated_fs(file->f_path.dentry)) error = __file_path_perm(op, subj_cred, label, flabel, file, request, denied, in_atomic); diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h new file mode 100644 index 000000000000..4a62e600d82b --- /dev/null +++ b/security/apparmor/include/af_unix.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor af_unix fine grained mediation + * + * Copyright 2023 Canonical Ltd. + * + * 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, version 2 of the + * License. + */ +#ifndef __AA_AF_UNIX_H + +#include <net/af_unix.h> + +#include "label.h" + +#define unix_addr(A) ((struct sockaddr_un *)(A)) +#define unix_addr_len(L) ((L) - sizeof(sa_family_t)) +#define unix_peer(sk) (unix_sk(sk)->peer) +#define is_unix_addr_abstract_name(B) ((B)[0] == 0) +#define is_unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0) +#define is_unix_addr_fs(A, L) (!is_unix_addr_anon(A, L) && \ + !is_unix_addr_abstract_name(unix_addr(A)->sun_path)) + +#define is_unix_anonymous(U) (!unix_sk(U)->addr) +#define is_unix_fs(U) (!is_unix_anonymous(U) && \ + unix_sk(U)->addr->name->sun_path[0]) +#define is_unix_connected(S) ((S)->state == SS_CONNECTED) + + +struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen); +int aa_unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, struct sock *peer_sk, + struct aa_label *peer_label); +int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock); +int aa_unix_create_perm(struct aa_label *label, int family, int type, + int protocol); +int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_unix_listen_perm(struct socket *sock, int backlog); +int aa_unix_accept_perm(struct socket *sock, struct socket *newsock); +int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size); +int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, + int optname); +int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct file *file); + +#endif /* __AA_AF_UNIX_H */ diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index f83934913b0f..cc6e3df1bc62 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -28,6 +28,7 @@ #define AA_CLASS_SIGNAL 10 #define AA_CLASS_XMATCH 11 #define AA_CLASS_NET 14 +#define AA_CLASS_NETV9 15 #define AA_CLASS_LABEL 16 #define AA_CLASS_POSIX_MQUEUE 17 #define AA_CLASS_MODULE 19 @@ -38,12 +39,13 @@ #define AA_CLASS_X 31 #define AA_CLASS_DBUS 32 +/* NOTE: if AA_CLASS_LAST > 63 need to update label->mediates */ #define AA_CLASS_LAST AA_CLASS_DBUS /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; extern bool aa_g_audit_header; -extern bool aa_g_debug; +extern int aa_g_debug; extern bool aa_g_hash_policy; extern bool aa_g_export_binary; extern int aa_g_rawdata_compression_level; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 1e94904f68d9..dd580594dfb7 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -104,6 +104,8 @@ enum aafs_prof_type { #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) +int aa_create_aafs(void); + void __aa_bump_ns_revision(struct aa_ns *ns); void __aafs_profile_rmdir(struct aa_profile *profile); void __aafs_profile_migrate_dents(struct aa_profile *old, diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index e27229349abb..aa00b34404f9 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -119,6 +119,8 @@ struct apparmor_audit_data { const char *info; u32 request; u32 denied; + u32 tags; + union { /* these entries require a custom callback fn */ struct { @@ -138,9 +140,12 @@ struct apparmor_audit_data { }; struct { int type, protocol; - struct sock *peer_sk; void *addr; int addrlen; + struct { + void *addr; + int addrlen; + } peer; } net; }; }; diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index d6dcc604ec0c..1ddcec2d1160 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -36,6 +36,7 @@ struct aa_caps { extern struct aa_sfs_entry aa_sfs_entry_caps[]; +kernel_cap_t aa_profile_capget(struct aa_profile *profile); int aa_capable(const struct cred *subj_cred, struct aa_label *label, int cap, unsigned int opts); diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 7265d2f81dd5..2b6098149b15 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -37,22 +37,6 @@ static inline void set_cred_label(const struct cred *cred, } /** - * aa_cred_raw_label - obtain cred's label - * @cred: cred to obtain label from (NOT NULL) - * - * Returns: confining label - * - * does NOT increment reference count - */ -static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) -{ - struct aa_label *label = cred_label(cred); - - AA_BUG(!label); - return label; -} - -/** * aa_get_newest_cred_label - obtain the newest label on a cred * @cred: cred to obtain label from (NOT NULL) * @@ -60,13 +44,13 @@ static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) */ static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred) { - return aa_get_newest_label(aa_cred_raw_label(cred)); + return aa_get_newest_label(cred_label(cred)); } static inline struct aa_label *aa_get_newest_cred_label_condref(const struct cred *cred, bool *needput) { - struct aa_label *l = aa_cred_raw_label(cred); + struct aa_label *l = cred_label(cred); if (unlikely(label_is_stale(l))) { *needput = true; @@ -93,7 +77,7 @@ static inline void aa_put_label_condref(struct aa_label *l, bool needput) */ static inline struct aa_label *aa_current_raw_label(void) { - return aa_cred_raw_label(current_cred()); + return cred_label(current_cred()); } /** @@ -114,10 +98,84 @@ static inline struct aa_label *aa_get_current_label(void) return aa_get_label(l); } -#define __end_current_label_crit_section(X) end_current_label_crit_section(X) +/** + * __end_cred_crit_section - end crit section begun with __begin_... + * @label: label obtained from __begin_cred_crit_section + * @needput: output: bool set by __begin_cred_crit_section + * + * While the cred passed to __begin is guaranteed to not change + * and the cred and label could be passed here instead of needput + * using needput with a local var makes it easier for the compiler + * and processor to optimize and speculatively execute the comparison + * than chasing a pointer in the cred struct. + */ +static inline void __end_cred_crit_section(struct aa_label *label, + bool needput) +{ + if (unlikely(needput)) + aa_put_label(label); +} + +/** + * __begin_cred_crit_section - @cred's confining label + * @cred: current's cred to start a crit section on its label + * @needput: store whether the label needs to be put when ending crit section + * + * Returns: up to date confining label or the ns unconfined label (NOT NULL) + * + * safe to call inside locks + * + * The returned reference must be put with __end_cred_crit_section() + * This must NOT be used if the task cred could be updated within the + * critical section between + * __begin_cred_crit_section() .. __end_cred_crit_section() + * + * The crit section is an optimization to avoid having to get and put + * the newest version of the label. While the cred won't change and + * hence the label it contains won't change, the newest version of the + * label can. During the crit section the newest versions of the label + * will be used until the end of the crit section. + * + * If the label has not been updated at the start of the crit section + * no refcount is taken, the cred's refcount is enough to hold the + * label for the duration of the crit section. + * + * If the label has been updated then a refcount will be taken and the + * newest version of the label will be returned. While the cred label + * and the returned label could be compared at the end of the crit + * section, needput is used because it allows better optimization by + * the compiler and the processor's speculative execution. + */ +static inline struct aa_label *__begin_cred_crit_section(const struct cred *cred, + bool *needput) +{ + struct aa_label *label = cred_label(cred); + + if (label_is_stale(label)) { + *needput = true; + return aa_get_newest_label(label); + } + + *needput = false; + return label; +} + +/** + * __end_current_label_crit_section - end crit section begun with __begin_... + * @label: label obtained from __begin_current_label_crit_section + * @needput: output: bool set by __begin_current_label_crit_section + * + * wrapper around __end_cred_crit_section() to pair nicely with + * __begin_current_label_crit_section() + */ +static inline void __end_current_label_crit_section(struct aa_label *label, + bool needput) +{ + __end_cred_crit_section(label, needput); +} /** - * end_label_crit_section - put a reference found with begin_current_label.. + * end_current_label_crit_section - put a reference found with begin_current_label.. * @label: label reference to put * * Should only be used with a reference obtained with @@ -132,6 +190,7 @@ static inline void end_current_label_crit_section(struct aa_label *label) /** * __begin_current_label_crit_section - current's confining label + * @needput: store whether the label needs to be put when ending crit section * * Returns: up to date confining label or the ns unconfined label (NOT NULL) * @@ -142,14 +201,9 @@ static inline void end_current_label_crit_section(struct aa_label *label) * critical section between __begin_current_label_crit_section() .. * __end_current_label_crit_section() */ -static inline struct aa_label *__begin_current_label_crit_section(void) +static inline struct aa_label *__begin_current_label_crit_section(bool *needput) { - struct aa_label *label = aa_current_raw_label(); - - if (label_is_stale(label)) - label = aa_get_newest_label(label); - - return label; + return __begin_cred_crit_section(current_cred(), needput); } /** @@ -184,10 +238,11 @@ static inline struct aa_ns *aa_get_current_ns(void) { struct aa_label *label; struct aa_ns *ns; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); ns = aa_get_ns(labels_ns(label)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return ns; } diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h index 636a04e20d91..f3ffd388cc58 100644 --- a/security/apparmor/include/crypto.h +++ b/security/apparmor/include/crypto.h @@ -13,6 +13,7 @@ #include "policy.h" #ifdef CONFIG_SECURITY_APPARMOR_HASH +int init_profile_hash(void); unsigned int aa_hash_size(void); char *aa_calc_hash(void *data, size_t len); int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 6e8f2aa66cd6..ef60f99bc5ae 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -77,12 +77,17 @@ int aa_audit_file(const struct cred *cred, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error); -struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, - aa_state_t state, struct path_cond *cond); +struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, + struct aa_policydb *file_rules, + aa_state_t state, struct path_cond *cond); aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, const char *name, struct path_cond *cond, struct aa_perms *perms); +int __aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_profile *profile, const char *name, + u32 request, struct path_cond *cond, int flags, + struct aa_perms *perms); int aa_path_perm(const char *op, const struct cred *subj_cred, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond); @@ -99,7 +104,7 @@ void aa_inherit_files(const struct cred *cred, struct files_struct *files); /** - * aa_map_file_perms - map file flags to AppArmor permissions + * aa_map_file_to_perms - map file flags to AppArmor permissions * @file: open file to map flags to AppArmor permissions * * Returns: apparmor permission set for the file diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index 74d17052f76b..323dd071afe9 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -13,6 +13,9 @@ #include <linux/sched.h> +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 + int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender, const struct cred *target_cred, struct aa_label *target, int sig); diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 93290ae300bb..335f21930702 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -19,6 +19,7 @@ #include "lib.h" struct aa_ns; +struct aa_ruleset; #define LOCAL_VEC_ENTRIES 8 #define DEFINE_VEC(T, V) \ @@ -101,7 +102,7 @@ enum label_flags { struct aa_label; struct aa_proxy { - struct kref count; + struct aa_common_ref count; struct aa_label __rcu *label; }; @@ -109,7 +110,7 @@ struct label_it { int i, j; }; -/* struct aa_label - lazy labeling struct +/* struct aa_label_base - base info of label * @count: ref count of active users * @node: rbtree position * @rcu: rcu callback struct @@ -118,10 +119,13 @@ struct label_it { * @flags: stale and other flags - values may change under label set lock * @secid: secid that references this label * @size: number of entries in @ent[] - * @ent: set of profiles for label, actual size determined by @size + * @mediates: bitmask for label_mediates + * profile: label vec when embedded in a profile FLAG_PROFILE is set + * rules: variable length rules in a profile FLAG_PROFILE is set + * vec: vector of profiles comprising the compound label */ struct aa_label { - struct kref count; + struct aa_common_ref count; struct rb_node node; struct rcu_head rcu; struct aa_proxy *proxy; @@ -129,7 +133,18 @@ struct aa_label { long flags; u32 secid; int size; - struct aa_profile *vec[]; + u64 mediates; + union { + struct { + /* only used is the label is a profile, size of + * rules[] is determined by the profile + * profile[1] is poison or null as guard + */ + struct aa_profile *profile[2]; + DECLARE_FLEX_ARRAY(struct aa_ruleset *, rules); + }; + DECLARE_FLEX_ARRAY(struct aa_profile *, vec); + }; }; #define last_error(E, FN) \ @@ -231,20 +246,17 @@ int aa_label_next_confined(struct aa_label *l, int i); #define fn_for_each_not_in_set(L1, L2, P, FN) \ fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) -#define LABEL_MEDIATES(L, C) \ -({ \ - struct aa_profile *profile; \ - struct label_it i; \ - int ret = 0; \ - label_for_each(i, (L), profile) { \ - if (RULE_MEDIATES(&profile->rules, (C))) { \ - ret = 1; \ - break; \ - } \ - } \ - ret; \ -}) +static inline bool label_mediates(struct aa_label *L, unsigned char C) +{ + return (L)->mediates & (((u64) 1) << (C)); +} +static inline bool label_mediates_safe(struct aa_label *L, unsigned char C) +{ + if (C > AA_CLASS_LAST) + return false; + return label_mediates(L, C); +} void aa_labelset_destroy(struct aa_labelset *ls); void aa_labelset_init(struct aa_labelset *ls); @@ -345,7 +357,7 @@ int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, */ static inline struct aa_label *__aa_get_label(struct aa_label *l) { - if (l && kref_get_unless_zero(&l->count)) + if (l && kref_get_unless_zero(&l->count.count)) return l; return NULL; @@ -354,7 +366,7 @@ static inline struct aa_label *__aa_get_label(struct aa_label *l) static inline struct aa_label *aa_get_label(struct aa_label *l) { if (l) - kref_get(&(l->count)); + kref_get(&(l->count.count)); return l; } @@ -374,7 +386,7 @@ static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l) rcu_read_lock(); do { c = rcu_dereference(*l); - } while (c && !kref_get_unless_zero(&c->count)); + } while (c && !kref_get_unless_zero(&c->count.count)); rcu_read_unlock(); return c; @@ -414,7 +426,14 @@ static inline struct aa_label *aa_get_newest_label(struct aa_label *l) static inline void aa_put_label(struct aa_label *l) { if (l) - kref_put(&l->count, aa_label_kref); + kref_put(&l->count.count, aa_label_kref); +} + +/* wrapper fn to indicate semantics of the check */ +static inline bool __aa_subj_label_is_cached(struct aa_label *subj_label, + struct aa_label *obj_label) +{ + return aa_label_is_subset(obj_label, subj_label); } @@ -424,7 +443,7 @@ void aa_proxy_kref(struct kref *kref); static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy) { if (proxy) - kref_get(&(proxy->count)); + kref_get(&(proxy->count.count)); return proxy; } @@ -432,7 +451,7 @@ static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy) static inline void aa_put_proxy(struct aa_proxy *proxy) { if (proxy) - kref_put(&proxy->count, aa_proxy_kref); + kref_put(&proxy->count.count, aa_proxy_kref); } void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new); diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index f11a0db7f51d..8c6ce8484552 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -19,22 +19,38 @@ extern struct aa_dfa *stacksplitdfa; /* - * DEBUG remains global (no per profile flag) since it is mostly used in sysctl - * which is not related to profile accesses. - */ - -#define DEBUG_ON (aa_g_debug) -/* * split individual debug cases out in preparation for finer grained * debug controls in the future. */ -#define AA_DEBUG_LABEL DEBUG_ON #define dbg_printk(__fmt, __args...) pr_debug(__fmt, ##__args) -#define AA_DEBUG(fmt, args...) \ + +#define DEBUG_NONE 0 +#define DEBUG_LABEL_ABS_ROOT 1 +#define DEBUG_LABEL 2 +#define DEBUG_DOMAIN 4 +#define DEBUG_POLICY 8 +#define DEBUG_INTERFACE 0x10 +#define DEBUG_UNPACK 0x20 +#define DEBUG_TAGS 0x40 + +#define DEBUG_ALL 0x7f /* update if new DEBUG_X added */ +#define DEBUG_PARSE_ERROR (-1) + +#define DEBUG_ON (aa_g_debug != DEBUG_NONE) +#define DEBUG_ABS_ROOT (aa_g_debug & DEBUG_LABEL_ABS_ROOT) + +#define AA_DEBUG(opt, fmt, args...) \ do { \ - if (DEBUG_ON) \ - pr_debug_ratelimited("AppArmor: " fmt, ##args); \ + if (aa_g_debug & opt) \ + pr_warn_ratelimited("%s: " fmt, __func__, ##args); \ } while (0) +#define AA_DEBUG_LABEL(LAB, X, fmt, args...) \ +do { \ + if ((LAB)->flags & FLAG_DEBUG1) \ + AA_DEBUG(X, fmt, ##args); \ +} while (0) + +#define AA_DEBUG_PROFILE(PROF, X, fmt...) AA_DEBUG_LABEL(&(PROF)->label, X, ##fmt) #define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __func__, #X) @@ -48,15 +64,35 @@ extern struct aa_dfa *stacksplitdfa; #define AA_BUG_FMT(X, fmt, args...) \ WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __func__, ##args) #else -#define AA_BUG_FMT(X, fmt, args...) no_printk(fmt, ##args) +#define AA_BUG_FMT(X, fmt, args...) \ + do { \ + BUILD_BUG_ON_INVALID(X); \ + no_printk(fmt, ##args); \ + } while (0) #endif +int aa_parse_debug_params(const char *str); +int aa_print_debug_params(char *buffer); + #define AA_ERROR(fmt, args...) \ pr_err_ratelimited("AppArmor: " fmt, ##args) /* Flag indicating whether initialization completed */ extern int apparmor_initialized; +/* semantic split of scope and view */ +#define aa_in_scope(SUBJ, OBJ) \ + aa_ns_visible(SUBJ, OBJ, false) + +#define aa_in_view(SUBJ, OBJ) \ + aa_ns_visible(SUBJ, OBJ, true) + +#define label_for_each_in_scope(I, NS, L, P) \ + label_for_each_in_ns(I, NS, L, P) + +#define fn_for_each_in_scope(L, P, FN) \ + fn_for_each_in_ns(L, P, FN) + /* fn's in lib */ const char *skipn_spaces(const char *str, size_t n); const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, @@ -66,6 +102,18 @@ void aa_info_message(const char *str); /* Security blob offsets */ extern struct lsm_blob_sizes apparmor_blob_sizes; +enum reftype { + REF_NS, + REF_PROXY, + REF_RAWDATA, +}; + +/* common reference count used by data the shows up in aafs */ +struct aa_common_ref { + struct kref count; + enum reftype reftype; +}; + /** * aa_strneq - compare null terminated @str to a non null terminated substring * @str: a null terminated string @@ -100,12 +148,19 @@ static inline bool path_mediated_fs(struct dentry *dentry) return !(dentry->d_sb->s_flags & SB_NOUSER); } +struct aa_str_table_ent { + int count; + int size; + char *strs; +}; + struct aa_str_table { int size; - char **table; + struct aa_str_table_ent *table; }; -void aa_free_str_table(struct aa_str_table *table); +bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp); +void aa_destroy_str_table(struct aa_str_table *table); struct counted_str { struct kref count; @@ -151,7 +206,7 @@ struct aa_policy { /** * basename - find the last component of an hname - * @name: hname to find the base profile name component of (NOT NULL) + * @hname: hname to find the base profile name component of (NOT NULL) * * Returns: the tail (base profile name) name component of an hname */ @@ -281,12 +336,12 @@ __do_cleanup: \ } \ __done: \ if (!__new_) \ - AA_DEBUG("label build failed\n"); \ + AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ (__new_); \ }) -#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \ +#define __fn_build_in_scope(NS, P, NS_FN, OTHER_FN) \ ({ \ struct aa_label *__new; \ if ((P)->ns != (NS)) \ @@ -296,10 +351,10 @@ __done: \ (__new); \ }) -#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \ +#define fn_label_build_in_scope(L, P, GFP, NS_FN, OTHER_FN) \ ({ \ fn_label_build((L), (P), (GFP), \ - __fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \ + __fn_build_in_scope(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \ }) #endif /* __AA_LIB_H */ diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 536ce3abd598..7accb1c39849 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -17,7 +17,7 @@ #define DFA_START 1 -/** +/* * The format used for transition tables is based on the GNU flex table * file format (--tables-file option; see Table File Format in the flex * info pages and the flex sources for documentation). The magic number @@ -104,16 +104,18 @@ struct aa_dfa { struct table_header *tables[YYTD_ID_TSIZE]; }; -#define byte_to_byte(X) (X) - #define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \ do { \ typeof(LEN) __i; \ TTYPE *__t = (TTYPE *) TABLE; \ BTYPE *__b = (BTYPE *) BLOB; \ - for (__i = 0; __i < LEN; __i++) { \ - __t[__i] = NTOHX(__b[__i]); \ - } \ + BUILD_BUG_ON(sizeof(TTYPE) != sizeof(BTYPE)); \ + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) \ + memcpy(__t, __b, (LEN) * sizeof(BTYPE)); \ + else /* copy & convert from big-endian */ \ + for (__i = 0; __i < LEN; __i++) { \ + __t[__i] = NTOHX(&__b[__i]); \ + } \ } while (0) static inline size_t table_size(size_t len, size_t el_size) @@ -137,17 +139,15 @@ aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, void aa_dfa_free_kref(struct kref *kref); -#define WB_HISTORY_SIZE 24 +/* This needs to be a power of 2 */ +#define WB_HISTORY_SIZE 32 struct match_workbuf { - unsigned int count; unsigned int pos; unsigned int len; - unsigned int size; /* power of 2, same as history size */ - unsigned int history[WB_HISTORY_SIZE]; + aa_state_t history[WB_HISTORY_SIZE]; }; #define DEFINE_MATCH_WB(N) \ struct match_workbuf N = { \ - .count = 0, \ .pos = 0, \ .len = 0, \ } @@ -185,6 +185,7 @@ static inline void aa_put_dfa(struct aa_dfa *dfa) #define MATCH_FLAG_DIFF_ENCODE 0x80000000 #define MARK_DIFF_ENCODE 0x40000000 #define MATCH_FLAG_OOB_TRANSITION 0x20000000 +#define MARK_DIFF_ENCODE_VERIFIED 0x10000000 #define MATCH_FLAGS_MASK 0xff000000 #define MATCH_FLAGS_VALID (MATCH_FLAG_DIFF_ENCODE | MATCH_FLAG_OOB_TRANSITION) #define MATCH_FLAGS_INVALID (MATCH_FLAGS_MASK & ~MATCH_FLAGS_VALID) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index c42ed8a73f1c..0d0b0ce42723 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -47,8 +47,9 @@ #define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \ AA_MAY_ACCEPT) struct aa_sk_ctx { - struct aa_label *label; - struct aa_label *peer; + struct aa_label __rcu *label; + struct aa_label __rcu *peer; + struct aa_label __rcu *peer_lastupdate; /* ptr cmp only, no deref */ }; static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) @@ -56,7 +57,7 @@ static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) return sk->sk_security + apparmor_blob_sizes.lbs_sock; } -#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \ +#define DEFINE_AUDIT_NET(NAME, OP, CRED, SK, F, T, P) \ struct lsm_network_audit NAME ## _net = { .sk = (SK), \ .family = (F)}; \ DEFINE_AUDIT_DATA(NAME, \ @@ -65,24 +66,15 @@ static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) AA_CLASS_NET, \ OP); \ NAME.common.u.net = &(NAME ## _net); \ + NAME.subj_cred = (CRED); \ NAME.net.type = (T); \ NAME.net.protocol = (P) -#define DEFINE_AUDIT_SK(NAME, OP, SK) \ - DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \ +#define DEFINE_AUDIT_SK(NAME, OP, CRED, SK) \ + DEFINE_AUDIT_NET(NAME, OP, CRED, SK, (SK)->sk_family, (SK)->sk_type, \ (SK)->sk_protocol) -#define af_select(FAMILY, FN, DEF_FN) \ -({ \ - int __e; \ - switch ((FAMILY)) { \ - default: \ - __e = DEF_FN; \ - } \ - __e; \ -}) - struct aa_secmark { u8 audit; u8 deny; @@ -91,11 +83,19 @@ struct aa_secmark { }; extern struct aa_sfs_entry aa_sfs_entry_network[]; - +extern struct aa_sfs_entry aa_sfs_entry_networkv9[]; + +int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, + aa_state_t state, u32 request, struct aa_perms *p, + struct apparmor_audit_data *ad); +/* passing in state returned by XXX_mediates_AF() */ +aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, + u32 request, u16 af, int type, int protocol, + struct aa_perms **p, const char **info); void audit_net_cb(struct audit_buffer *ab, void *va); int aa_profile_af_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, - u32 request, u16 family, int type); + u32 request, u16 family, int type, int protocol); int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, u16 family, int type, int protocol); @@ -105,13 +105,13 @@ static inline int aa_profile_af_sk_perm(struct aa_profile *profile, struct sock *sk) { return aa_profile_af_perm(profile, ad, request, sk->sk_family, - sk->sk_type); + sk->sk_type, sk->sk_protocol); } int aa_sk_perm(const char *op, u32 request, struct sock *sk); int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, - struct socket *sock); + struct file *file); int apparmor_secmark_check(struct aa_label *label, char *op, u32 request, u32 secid, const struct sock *sk); diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 343189903dba..8bb915d48dc7 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -13,6 +13,7 @@ enum path_flags { PATH_IS_DIR = 0x1, /* path is a directory */ + PATH_SOCK_COND = 0x2, PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */ PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index bbaa7d39a39a..37a3781b99a0 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -101,8 +101,8 @@ extern struct aa_perms allperms; /** * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms - * @accum - perms struct to accumulate into - * @addend - perms struct to add to @accum + * @accum: perms struct to accumulate into + * @addend: perms struct to add to @accum */ static inline void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) @@ -128,8 +128,8 @@ static inline void aa_perms_accum_raw(struct aa_perms *accum, /** * aa_perms_accum - accumulate perms, masking off overlapping perms - * @accum - perms struct to accumulate into - * @addend - perms struct to add to @accum + * @accum: perms struct to accumulate into + * @addend: perms struct to add to @accum */ static inline void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 757e3c232c57..3895f8774a3f 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -59,6 +59,11 @@ extern const char *const aa_profile_mode_names[]; #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) +/* flags in the dfa accept2 table */ +enum dfa_accept_flags { + ACCEPT_FLAG_OWNER = 1, +}; + /* * FIXME: currently need a clean way to replace and remove profiles as a * set. It should be done at the namespace level. @@ -74,11 +79,33 @@ enum profile_mode { }; +struct aa_tags_header { + u32 mask; /* bit mask matching permissions */ + u32 count; /* number of strings per entry */ + u32 size; /* size of all strings covered by count */ + u32 tags; /* index into string table */ +}; + +struct aa_tags_struct { + struct { + u32 size; /* number of entries in tagsets */ + u32 *table; /* indexes into headers & strs */ + } sets; + struct { + u32 size; /* number of headers == num of strs */ + struct aa_tags_header *table; + } hdrs; + struct aa_str_table strs; +}; + /* struct aa_policydb - match engine for a policy - * count: refcount for the pdb - * dfa: dfa pattern match - * perms: table of permissions - * strs: table of strings, index by x + * @count: refcount for the pdb + * @dfa: dfa pattern match + * @perms: table of permissions + * @size: number of entries in @perms + * @trans: table of strings, index by x + * @tags: table of tags that perms->tag indexes + * @start:_states to start in for each class * start: set of start states for the different classes of data */ struct aa_policydb { @@ -89,11 +116,13 @@ struct aa_policydb { u32 size; }; struct aa_str_table trans; + struct aa_tags_struct tags; aa_state_t start[AA_CLASS_LAST + 1]; }; extern struct aa_policydb *nullpdb; +void aa_destroy_tags(struct aa_tags_struct *tags); struct aa_policydb *aa_alloc_pdb(gfp_t gfp); void aa_pdb_free_kref(struct kref *kref); @@ -124,6 +153,7 @@ static inline void aa_put_pdb(struct aa_policydb *pdb) kref_put(&pdb->count, aa_pdb_free_kref); } +/* lookup perm that doesn't have and object conditional */ static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, aa_state_t state) { @@ -135,7 +165,6 @@ static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, return &(policy->perms[index]); } - /* struct aa_data - generic data structure * key: name for retrieving this data * size: size of data in bytes @@ -160,8 +189,6 @@ struct aa_data { * @secmark: secmark label match info */ struct aa_ruleset { - struct list_head list; - int size; /* TODO: merge policy and file */ @@ -175,6 +202,7 @@ struct aa_ruleset { struct aa_secmark *secmark; }; + /* struct aa_attachment - data and rules for a profiles attachment * @list: * @xmatch_str: human readable attachment string @@ -193,7 +221,6 @@ struct aa_attachment { /* struct aa_profile - basic confinement data * @base - base components of the profile (name, refcount, lists, lock ...) - * @label - label this profile is an extension of * @parent: parent of profile * @ns: namespace the profile is in * @rename: optional profile name that this profile renamed @@ -201,13 +228,20 @@ struct aa_attachment { * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile * @path_flags: flags controlling path generation behavior + * @signal: the signal that should be used when kill is used * @disconnected: what to prepend if attach_disconnected is specified * @attach: attachment rules for the profile * @rules: rules to be enforced * + * learning_cache: the accesses learned in complain mode + * raw_data: rawdata of the loaded profile policy + * hash: cryptographic hash of the profile * @dents: dentries for the profiles file entries in apparmorfs * @dirname: name of the profile dir in apparmorfs + * @dents: set of dentries associated with the profile * @data: hashtable for free-form policy aa_data + * @label - label this profile is an extension of + * @rules - label with the rule vec on its end * * The AppArmor profile contains the basic confinement data. Each profile * has a name, and exists in a namespace. The @name and @exec_match are @@ -231,16 +265,19 @@ struct aa_profile { enum audit_mode audit; long mode; u32 path_flags; + int signal; const char *disconnected; struct aa_attachment attach; - struct list_head rules; struct aa_loaddata *rawdata; unsigned char *hash; char *dirname; struct dentry *dents[AAFS_PROF_SIZEOF]; struct rhashtable *data; + + int n_rules; + /* special - variable length must be last entry in profile */ struct aa_label label; }; @@ -298,24 +335,38 @@ static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules, rules->policy->start[0], &class, 1); } -static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF) +static inline aa_state_t RULE_MEDIATES_v9NET(struct aa_ruleset *rules) +{ + return RULE_MEDIATES(rules, AA_CLASS_NETV9); +} + +static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) { - aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NET); - __be16 be_af = cpu_to_be16(AF); + /* can not use RULE_MEDIATE_v9AF here, because AF match fail + * can not be distiguished from class match fail, and we only + * fallback to checking older class on class match failure + */ + aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NETV9); + /* fallback and check v7/8 if v9 is NOT mediated */ if (!state) - return DFA_NOMATCH; - return aa_dfa_match_len(rules->policy->dfa, state, (char *) &be_af, 2); + state = RULE_MEDIATES(rules, AA_CLASS_NET); + + return state; } -static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, - unsigned char class) + +void aa_compute_profile_mediates(struct aa_profile *profile); +static inline bool profile_mediates(struct aa_profile *profile, + unsigned char class) { - struct aa_ruleset *rule; + return label_mediates(&profile->label, class); +} - /* TODO: change to list walk */ - rule = list_first_entry(head, typeof(*rule), list); - return RULE_MEDIATES(rule, class); +static inline bool profile_mediates_safe(struct aa_profile *profile, + unsigned char class) +{ + return label_mediates_safe(&profile->label, class); } /** @@ -328,7 +379,7 @@ static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, static inline struct aa_profile *aa_get_profile(struct aa_profile *p) { if (p) - kref_get(&(p->label.count)); + kref_get(&(p->label.count.count)); return p; } @@ -342,7 +393,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p) */ static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) { - if (p && kref_get_unless_zero(&p->label.count)) + if (p && kref_get_unless_zero(&p->label.count.count)) return p; return NULL; @@ -362,7 +413,7 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) rcu_read_lock(); do { c = rcu_dereference(*p); - } while (c && !kref_get_unless_zero(&c->label.count)); + } while (c && !kref_get_unless_zero(&c->label.count.count)); rcu_read_unlock(); return c; @@ -375,7 +426,7 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) static inline void aa_put_profile(struct aa_profile *p) { if (p) - kref_put(&p->label.count, aa_label_kref); + kref_put(&p->label.count.count, aa_label_kref); } static inline int AUDIT_MODE(struct aa_profile *profile) @@ -392,7 +443,7 @@ bool aa_policy_admin_capable(const struct cred *subj_cred, struct aa_label *label, struct aa_ns *ns); int aa_may_manage_policy(const struct cred *subj_cred, struct aa_label *label, struct aa_ns *ns, - u32 mask); + const struct cred *ocred, u32 mask); bool aa_current_policy_view_capable(struct aa_ns *ns); bool aa_current_policy_admin_capable(struct aa_ns *ns); diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index d646070fd966..cc6e84151812 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -18,6 +18,8 @@ #include "label.h" #include "policy.h" +/* Match max depth of user namespaces */ +#define MAX_NS_DEPTH 32 /* struct aa_ns_acct - accounting of profiles in namespace * @max_size: maximum space allowed for all profiles in namespace diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index a6f4611ee50c..e5a95dc4da1f 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -87,17 +87,29 @@ struct aa_ext { u32 version; }; -/* - * struct aa_loaddata - buffer of policy raw_data set +/* struct aa_loaddata - buffer of policy raw_data set + * @count: inode/filesystem refcount - use aa_get_i_loaddata() + * @pcount: profile refcount - use aa_get_profile_loaddata() + * @list: list the loaddata is on + * @work: used to do a delayed cleanup + * @dents: refs to dents created in aafs + * @ns: the namespace this loaddata was loaded into + * @name: + * @size: the size of the data that was loaded + * @compressed_size: the size of the data when it is compressed + * @revision: unique revision count that this data was loaded as + * @abi: the abi number the loaddata uses + * @hash: a hash of the loaddata, used to help dedup data * - * there is no loaddata ref for being on ns list, nor a ref from - * d_inode(@dentry) when grab a ref from these, @ns->lock must be held - * && __aa_get_loaddata() needs to be used, and the return value - * checked, if NULL the loaddata is already being reaped and should be - * considered dead. + * There is no loaddata ref for being on ns->rawdata_list, so + * @ns->lock must be held when walking the list. Dentries and + * inode opens hold refs on @count; profiles hold refs on @pcount. + * When the last @pcount drops, do_ploaddata_rmfs() removes the + * fs entries and drops the associated @count ref. */ struct aa_loaddata { - struct kref count; + struct aa_common_ref count; + struct kref pcount; struct list_head list; struct work_struct work; struct dentry *dents[AAFS_LOADDATA_NDENTS]; @@ -119,50 +131,53 @@ struct aa_loaddata { int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); /** - * __aa_get_loaddata - get a reference count to uncounted data reference + * aa_get_loaddata - get a reference count from a counted data reference * @data: reference to get a count on * - * Returns: pointer to reference OR NULL if race is lost and reference is - * being repeated. - * Requires: @data->ns->lock held, and the return code MUST be checked - * - * Use only from inode->i_private and @data->list found references + * Returns: pointer to reference + * Requires: @data to have a valid reference count on it. It is a bug + * if the race to reap can be encountered when it is used. */ static inline struct aa_loaddata * -__aa_get_loaddata(struct aa_loaddata *data) +aa_get_i_loaddata(struct aa_loaddata *data) { - if (data && kref_get_unless_zero(&(data->count))) - return data; - return NULL; + if (data) + kref_get(&(data->count.count)); + return data; } + /** - * aa_get_loaddata - get a reference count from a counted data reference + * aa_get_profile_loaddata - get a profile reference count on loaddata * @data: reference to get a count on * - * Returns: point to reference - * Requires: @data to have a valid reference count on it. It is a bug - * if the race to reap can be encountered when it is used. + * Returns: pointer to reference + * Requires: @data to have a valid reference count on it. */ static inline struct aa_loaddata * -aa_get_loaddata(struct aa_loaddata *data) +aa_get_profile_loaddata(struct aa_loaddata *data) { - struct aa_loaddata *tmp = __aa_get_loaddata(data); - - AA_BUG(data && !tmp); - - return tmp; + if (data) + kref_get(&(data->pcount)); + return data; } void __aa_loaddata_update(struct aa_loaddata *data, long revision); bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r); void aa_loaddata_kref(struct kref *kref); +void aa_ploaddata_kref(struct kref *kref); struct aa_loaddata *aa_loaddata_alloc(size_t size); -static inline void aa_put_loaddata(struct aa_loaddata *data) +static inline void aa_put_i_loaddata(struct aa_loaddata *data) +{ + if (data) + kref_put(&data->count.count, aa_loaddata_kref); +} + +static inline void aa_put_profile_loaddata(struct aa_loaddata *data) { if (data) - kref_put(&data->count, aa_loaddata_kref); + kref_put(&data->pcount, aa_ploaddata_kref); } #if IS_ENABLED(CONFIG_KUNIT) diff --git a/security/apparmor/include/sig_names.h b/security/apparmor/include/sig_names.h index cbf7a997ed84..c772668cdc62 100644 --- a/security/apparmor/include/sig_names.h +++ b/security/apparmor/include/sig_names.h @@ -1,9 +1,5 @@ #include <linux/signal.h> - -#define SIGUNKNOWN 0 -#define MAXMAPPED_SIG 35 -#define MAXMAPPED_SIGNAME (MAXMAPPED_SIG + 1) -#define SIGRT_BASE 128 +#include "signal.h" /* provide a mapping of arch signal to internal signal # for mediation * those that are always an alias SIGCLD for SIGCLHD and SIGPOLL for SIGIO diff --git a/security/apparmor/include/signal.h b/security/apparmor/include/signal.h new file mode 100644 index 000000000000..729763fa7ce6 --- /dev/null +++ b/security/apparmor/include/signal.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor ipc mediation function definitions. + * + * Copyright 2023 Canonical Ltd. + */ + +#ifndef __AA_SIGNAL_H +#define __AA_SIGNAL_H + +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 + +#define MAXMAPPED_SIGNAME (MAXMAPPED_SIG + 1) +#define SIGRT_BASE 128 + +#endif /* __AA_SIGNAL_H */ diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 0cdf4340b02d..df5712cea685 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -80,21 +80,20 @@ static int profile_signal_perm(const struct cred *cred, struct aa_label *peer, u32 request, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms; aa_state_t state; - if (profile_unconfined(profile) || - !ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_SIGNAL)) + if (profile_unconfined(profile)) return 0; ad->subj_cred = cred; ad->peer = peer; /* TODO: secondary cache check <profile, profile, perm> */ - state = aa_dfa_next(rules->policy->dfa, - rules->policy->start[AA_CLASS_SIGNAL], - ad->signal); + state = RULE_MEDIATES(rules, AA_CLASS_SIGNAL); + if (!state) + return 0; + state = aa_dfa_next(rules->policy->dfa, state, ad->signal); aa_label_match(profile, rules, peer, state, false, request, &perms); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, ad, audit_signal_cb); diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 91483ecacc16..3a721fdf1833 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -52,7 +52,8 @@ static void free_proxy(struct aa_proxy *proxy) void aa_proxy_kref(struct kref *kref) { - struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count); + struct aa_proxy *proxy = container_of(kref, struct aa_proxy, + count.count); free_proxy(proxy); } @@ -61,9 +62,10 @@ struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp) { struct aa_proxy *new; - new = kzalloc(sizeof(struct aa_proxy), gfp); + new = kzalloc_obj(struct aa_proxy, gfp); if (new) { - kref_init(&new->count); + kref_init(&new->count.count); + new->count.reftype = REF_PROXY; rcu_assign_pointer(new->label, aa_get_label(label)); } return new; @@ -198,21 +200,25 @@ static bool vec_is_stale(struct aa_profile **vec, int n) return false; } -static long accum_vec_flags(struct aa_profile **vec, int n) +static void accum_label_info(struct aa_label *new) { long u = FLAG_UNCONFINED; int i; - AA_BUG(!vec); + AA_BUG(!new); - for (i = 0; i < n; i++) { - u |= vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | - FLAG_STALE); - if (!(u & vec[i]->label.flags & FLAG_UNCONFINED)) + /* size == 1 is a profile and flags must be set as part of creation */ + if (new->size == 1) + return; + + for (i = 0; i < new->size; i++) { + u |= new->vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | + FLAG_STALE); + if (!(u & new->vec[i]->label.flags & FLAG_UNCONFINED)) u &= ~FLAG_UNCONFINED; + new->mediates |= new->vec[i]->label.mediates; } - - return u; + new->flags |= u; } static int sort_cmp(const void *a, const void *b) @@ -371,7 +377,8 @@ static void label_free_rcu(struct rcu_head *head) void aa_label_kref(struct kref *kref) { - struct aa_label *label = container_of(kref, struct aa_label, count); + struct aa_label *label = container_of(kref, struct aa_label, + count.count); struct aa_ns *ns = labels_ns(label); if (!ns) { @@ -408,7 +415,8 @@ bool aa_label_init(struct aa_label *label, int size, gfp_t gfp) label->size = size; /* doesn't include null */ label->vec[size] = NULL; /* null terminate */ - kref_init(&label->count); + kref_init(&label->count.count); + label->count.reftype = REF_NS; /* for aafs purposes */ RB_CLEAR_NODE(&label->node); return true; @@ -430,8 +438,8 @@ struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) AA_BUG(size < 1); /* + 1 for null terminator entry on vec */ - new = kzalloc(struct_size(new, vec, size + 1), gfp); - AA_DEBUG("%s (%p)\n", __func__, new); + new = kzalloc_flex(*new, vec, size + 1, gfp); + AA_DEBUG(DEBUG_LABEL, "%s (%p)\n", __func__, new); if (!new) goto fail; @@ -645,6 +653,7 @@ static bool __label_replace(struct aa_label *old, struct aa_label *new) rb_replace_node(&old->node, &new->node, &ls->root); old->flags &= ~FLAG_IN_TREE; new->flags |= FLAG_IN_TREE; + accum_label_info(new); return true; } @@ -705,6 +714,7 @@ static struct aa_label *__label_insert(struct aa_labelset *ls, rb_link_node(&label->node, parent, new); rb_insert_color(&label->node, &ls->root); label->flags |= FLAG_IN_TREE; + accum_label_info(label); return aa_get_label(label); } @@ -1085,7 +1095,6 @@ static struct aa_label *label_merge_insert(struct aa_label *new, else if (k == b->size) return aa_get_label(b); } - new->flags |= accum_vec_flags(new->vec, new->size); ls = labels_set(new); write_lock_irqsave(&ls->lock, flags); label = __label_insert(labels_set(new), new, false); @@ -1269,11 +1278,11 @@ static inline aa_state_t match_component(struct aa_profile *profile, * @rules: ruleset to search * @label: label to check access permissions for * @state: state to start match in - * @subns: whether to do permission checks on components in a subns + * @inview: whether to match labels in view or only in scope * @request: permissions to request * @perms: perms struct to set * - * Returns: 0 on success else ERROR + * Returns: state match stopped at or DFA_NOMATCH if aborted early * * For the label A//&B//&C this does the perm match for A//&B//&C * @perms should be preinitialized with allperms OR a previous permission @@ -1282,7 +1291,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, static int label_compound_match(struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, - aa_state_t state, bool subns, u32 request, + aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { struct aa_profile *tp; @@ -1290,7 +1299,7 @@ static int label_compound_match(struct aa_profile *profile, /* find first subcomponent that is visible */ label_for_each(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = match_component(profile, rules, tp, state); if (!state) @@ -1300,11 +1309,11 @@ static int label_compound_match(struct aa_profile *profile, /* no component visible */ *perms = allperms; - return 0; + return state; next: label_for_each_cont(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = aa_dfa_match(rules->policy->dfa, state, "//&"); state = match_component(profile, rules, tp, state); @@ -1312,15 +1321,11 @@ next: goto fail; } *perms = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, perms); - if ((perms->allow & request) != request) - return -EACCES; - - return 0; + return state; fail: *perms = nullperms; - return state; + return DFA_NOMATCH; } /** @@ -1329,11 +1334,11 @@ fail: * @rules: ruleset to search * @label: label to check access permissions for * @start: state to start match in - * @subns: whether to do permission checks on components in a subns + * @inview: whether to match labels in view or only in scope * @request: permissions to request * @perms: an initialized perms struct to add accumulation to * - * Returns: 0 on success else ERROR + * Returns: the state the match finished in, may be the none matching state * * For the label A//&B//&C this does the perm match for each of A and B and C * @perms should be preinitialized with allperms OR a previous permission @@ -1342,7 +1347,7 @@ fail: static int label_components_match(struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t start, - bool subns, u32 request, + bool inview, u32 request, struct aa_perms *perms) { struct aa_profile *tp; @@ -1352,7 +1357,7 @@ static int label_components_match(struct aa_profile *profile, /* find first subcomponent to test */ label_for_each(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = match_component(profile, rules, tp, start); if (!state) @@ -1361,31 +1366,29 @@ static int label_components_match(struct aa_profile *profile, } /* no subcomponents visible - no change in perms */ - return 0; + return state; next: tmp = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { - if (!aa_ns_visible(profile->ns, tp->ns, subns)) + if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; state = match_component(profile, rules, tp, start); if (!state) goto fail; tmp = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } if ((perms->allow & request) != request) - return -EACCES; + return DFA_NOMATCH; - return 0; + return state; fail: *perms = nullperms; - return -EACCES; + return DFA_NOMATCH; } /** @@ -1394,23 +1397,24 @@ fail: * @rules: ruleset to search * @label: label to match (NOT NULL) * @state: state to start in - * @subns: whether to match subns components + * @inview: whether to match labels in view or only in scope * @request: permission request * @perms: Returns computed perms (NOT NULL) * * Returns: the state the match finished in, may be the none matching state */ int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, - struct aa_label *label, aa_state_t state, bool subns, + struct aa_label *label, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { - int error = label_compound_match(profile, rules, label, state, subns, - request, perms); - if (!error) - return error; + aa_state_t tmp = label_compound_match(profile, rules, label, state, + inview, request, perms); + if ((perms->allow & request) == request) + return tmp; + /* failed compound_match try component matches */ *perms = allperms; - return label_components_match(profile, rules, label, state, subns, + return label_components_match(profile, rules, label, state, inview, request, perms); } @@ -1456,7 +1460,7 @@ bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) /* * cached label name is present and visible - * @label->hname only exists if label is namespace hierachical + * @label->hname only exists if label is namespace hierarchical */ static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label, int flags) @@ -1617,7 +1621,7 @@ int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, AA_BUG(!str && size != 0); AA_BUG(!label); - if (AA_DEBUG_LABEL && (flags & FLAG_ABS_ROOT)) { + if (DEBUG_ABS_ROOT && (flags & FLAG_ABS_ROOT)) { ns = root_ns; len = snprintf(str, size, "_"); update_for_len(total, len, size, str); @@ -1731,7 +1735,7 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, display_mode(ns, label, flags)) { len = aa_label_asxprint(&name, ns, label, flags, gfp); if (len < 0) { - AA_DEBUG("label print error"); + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } str = name; @@ -1759,7 +1763,7 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, len = aa_label_asxprint(&str, ns, label, flags, gfp); if (len < 0) { - AA_DEBUG("label print error"); + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } seq_puts(f, str); @@ -1782,7 +1786,7 @@ void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, len = aa_label_asxprint(&str, ns, label, flags, gfp); if (len < 0) { - AA_DEBUG("label print error"); + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } pr_info("%s", str); @@ -1865,7 +1869,7 @@ struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str, AA_BUG(!str); str = skipn_spaces(str, n); - if (str == NULL || (AA_DEBUG_LABEL && *str == '_' && + if (str == NULL || (DEBUG_ABS_ROOT && *str == '_' && base != &root_ns->unconfined->label)) return ERR_PTR(-EINVAL); diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7db62213e352..e41ff57798b2 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -25,11 +25,127 @@ struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .quiet = ALL_PERMS_MASK, .hide = ALL_PERMS_MASK }; +struct val_table_ent { + const char *str; + int value; +}; + +static struct val_table_ent debug_values_table[] = { + { "N", DEBUG_NONE }, + { "none", DEBUG_NONE }, + { "n", DEBUG_NONE }, + { "0", DEBUG_NONE }, + { "all", DEBUG_ALL }, + { "Y", DEBUG_ALL }, + { "y", DEBUG_ALL }, + { "1", DEBUG_ALL }, + { "abs_root", DEBUG_LABEL_ABS_ROOT }, + { "label", DEBUG_LABEL }, + { "domain", DEBUG_DOMAIN }, + { "policy", DEBUG_POLICY }, + { "interface", DEBUG_INTERFACE }, + { "unpack", DEBUG_UNPACK }, + { "tags", DEBUG_TAGS }, + { NULL, 0 } +}; + +static struct val_table_ent *val_table_find_ent(struct val_table_ent *table, + const char *name, size_t len) +{ + struct val_table_ent *entry; + + for (entry = table; entry->str != NULL; entry++) { + if (strncmp(entry->str, name, len) == 0 && + strlen(entry->str) == len) + return entry; + } + return NULL; +} + +int aa_parse_debug_params(const char *str) +{ + struct val_table_ent *ent; + const char *next; + int val = 0; + + do { + size_t n = strcspn(str, "\r\n,"); + + next = str + n; + ent = val_table_find_ent(debug_values_table, str, next - str); + if (ent) + val |= ent->value; + else + AA_DEBUG(DEBUG_INTERFACE, "unknown debug type '%.*s'", + (int)(next - str), str); + str = next + 1; + } while (*next != 0); + return val; +} + +/** + * val_mask_to_str - convert a perm mask to its short string + * @str: character buffer to store string in (at least 10 characters) + * @size: size of the @str buffer + * @table: NUL-terminated character buffer of permission characters (NOT NULL) + * @mask: permission mask to convert + */ +static int val_mask_to_str(char *str, size_t size, + const struct val_table_ent *table, u32 mask) +{ + const struct val_table_ent *ent; + int total = 0; + + for (ent = table; ent->str; ent++) { + if (ent->value && (ent->value & mask) == ent->value) { + int len = scnprintf(str, size, "%s%s", total ? "," : "", + ent->str); + size -= len; + str += len; + total += len; + mask &= ~ent->value; + } + } + + return total; +} + +int aa_print_debug_params(char *buffer) +{ + if (!aa_g_debug) + return sprintf(buffer, "N"); + return val_mask_to_str(buffer, PAGE_SIZE, debug_values_table, + aa_g_debug); +} + +bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp) +{ + struct aa_str_table_ent *n; + int i; + + if (t->size == newsize) + return true; + n = kzalloc_objs(*n, newsize, gfp); + if (!n) + return false; + for (i = 0; i < min(t->size, newsize); i++) + n[i] = t->table[i]; + for (; i < t->size; i++) + kfree_sensitive(t->table[i].strs); + if (newsize > t->size) + memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n)); + kfree_sensitive(t->table); + t->table = n; + t->size = newsize; + + return true; +} + /** - * aa_free_str_table - free entries str table + * aa_destroy_str_table - free entries str table * @t: the string table to free (MAYBE NULL) */ -void aa_free_str_table(struct aa_str_table *t) +void aa_destroy_str_table(struct aa_str_table *t) { int i; @@ -38,7 +154,7 @@ void aa_free_str_table(struct aa_str_table *t) return; for (i = 0; i < t->size; i++) - kfree_sensitive(t->table[i]); + kfree_sensitive(t->table[i].strs); kfree_sensitive(t->table); t->table = NULL; t->size = 0; @@ -119,7 +235,7 @@ __counted char *aa_str_alloc(int size, gfp_t gfp) { struct counted_str *str; - str = kmalloc(struct_size(str, name, size), gfp); + str = kmalloc_flex(*str, name, size, gfp); if (!str) return NULL; @@ -364,19 +480,17 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix, const char *name, gfp_t gfp) { char *hname; + size_t hname_sz; + hname_sz = (prefix ? strlen(prefix) + 2 : 0) + strlen(name) + 1; /* freed by policy_free */ - if (prefix) { - hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp); - if (hname) - sprintf(hname, "%s//%s", prefix, name); - } else { - hname = aa_str_alloc(strlen(name) + 1, gfp); - if (hname) - strcpy(hname, name); - } + hname = aa_str_alloc(hname_sz, gfp); if (!hname) return false; + if (prefix) + scnprintf(hname, hname_sz, "%s//%s", prefix, name); + else + strscpy(hname, name, hname_sz); policy->hname = hname; /* base.name is a substring of fqname */ policy->name = basename(policy->hname); @@ -398,3 +512,4 @@ void aa_policy_destroy(struct aa_policy *policy) /* don't free name as its a subset of hname */ aa_put_str(policy->hname); } + diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 9b6c2f157f83..3491e9f60194 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -17,6 +17,7 @@ #include <linux/ptrace.h> #include <linux/ctype.h> #include <linux/sysctl.h> +#include <linux/sysfs.h> #include <linux/audit.h> #include <linux/user_namespace.h> #include <linux/netfilter_ipv4.h> @@ -26,11 +27,13 @@ #include <uapi/linux/mount.h> #include <uapi/linux/lsm.h> +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" #include "include/capability.h" #include "include/cred.h" +#include "include/crypto.h" #include "include/file.h" #include "include/ipc.h" #include "include/net.h" @@ -111,7 +114,7 @@ static void apparmor_task_free(struct task_struct *task) } static int apparmor_task_alloc(struct task_struct *task, - unsigned long clone_flags) + u64 clone_flags) { struct aa_task_ctx *new = task_ctx(task); @@ -126,14 +129,15 @@ static int apparmor_ptrace_access_check(struct task_struct *child, struct aa_label *tracer, *tracee; const struct cred *cred; int error; + bool needput; cred = get_task_cred(child); tracee = cred_label(cred); /* ref count on cred */ - tracer = __begin_current_label_crit_section(); + tracer = __begin_current_label_crit_section(&needput); error = aa_may_ptrace(current_cred(), tracer, cred, tracee, (mode & PTRACE_MODE_READ) ? AA_PTRACE_READ : AA_PTRACE_TRACE); - __end_current_label_crit_section(tracer); + __end_current_label_crit_section(tracer, needput); put_cred(cred); return error; @@ -144,14 +148,15 @@ static int apparmor_ptrace_traceme(struct task_struct *parent) struct aa_label *tracer, *tracee; const struct cred *cred; int error; + bool needput; - tracee = __begin_current_label_crit_section(); + tracee = __begin_current_label_crit_section(&needput); cred = get_task_cred(parent); tracer = cred_label(cred); /* ref count on cred */ error = aa_may_ptrace(cred, tracer, current_cred(), tracee, AA_PTRACE_TRACE); put_cred(cred); - __end_current_label_crit_section(tracee); + __end_current_label_crit_section(tracee, needput); return error; } @@ -176,15 +181,11 @@ static int apparmor_capget(const struct task_struct *target, kernel_cap_t *effec struct label_it i; label_for_each_confined(i, label, profile) { - struct aa_ruleset *rules; - if (COMPLAIN_MODE(profile)) - continue; - rules = list_first_entry(&profile->rules, - typeof(*rules), list); - *effective = cap_intersect(*effective, - rules->caps.allow); - *permitted = cap_intersect(*permitted, - rules->caps.allow); + kernel_cap_t allowed; + + allowed = aa_profile_capget(profile); + *effective = cap_intersect(*effective, allowed); + *permitted = cap_intersect(*permitted, allowed); } } rcu_read_unlock(); @@ -221,12 +222,13 @@ static int common_perm(const char *op, const struct path *path, u32 mask, { struct aa_label *label; int error = 0; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_path_perm(op, current_cred(), label, path, 0, mask, cond); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -408,7 +410,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d struct path_cond cond_exchange = { .mode = d_backing_inode(new_dentry)->i_mode, }; - vfsuid = i_uid_into_vfsuid(idmap, d_backing_inode(old_dentry)); + vfsuid = i_uid_into_vfsuid(idmap, d_backing_inode(new_dentry)); cond_exchange.uid = vfsuid_into_kuid(vfsuid); error = aa_path_perm(OP_RENAME_SRC, current_cred(), @@ -519,32 +521,26 @@ static void apparmor_file_free_security(struct file *file) aa_put_label(rcu_access_pointer(ctx->label)); } -static int common_file_perm(const char *op, struct file *file, u32 mask, - bool in_atomic) +static int common_file_perm(const char *op, struct file *file, u32 mask) { struct aa_label *label; int error = 0; - /* don't reaudit files closed during inheritance */ - if (file->f_path.dentry == aa_null.dentry) - return -EACCES; - - label = __begin_current_label_crit_section(); - error = aa_file_perm(op, current_cred(), label, file, mask, in_atomic); - __end_current_label_crit_section(label); + label = begin_current_label_crit_section(); + error = aa_file_perm(op, current_cred(), label, file, mask, false); + end_current_label_crit_section(label); return error; } static int apparmor_file_receive(struct file *file) { - return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file), - false); + return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file)); } static int apparmor_file_permission(struct file *file, int mask) { - return common_file_perm(OP_FPERM, file, mask, false); + return common_file_perm(OP_FPERM, file, mask); } static int apparmor_file_lock(struct file *file, unsigned int cmd) @@ -554,11 +550,11 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd) if (cmd == F_WRLCK) mask |= MAY_WRITE; - return common_file_perm(OP_FLOCK, file, mask, false); + return common_file_perm(OP_FLOCK, file, mask); } static int common_mmap(const char *op, struct file *file, unsigned long prot, - unsigned long flags, bool in_atomic) + unsigned long flags) { int mask = 0; @@ -576,21 +572,20 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot, if (prot & PROT_EXEC) mask |= AA_EXEC_MMAP; - return common_file_perm(op, file, mask, in_atomic); + return common_file_perm(op, file, mask); } static int apparmor_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { - return common_mmap(OP_FMMAP, file, prot, flags, GFP_ATOMIC); + return common_mmap(OP_FMMAP, file, prot, flags); } static int apparmor_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot) { return common_mmap(OP_FMPROT, vma->vm_file, prot, - !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0, - false); + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } #ifdef CONFIG_IO_URING @@ -633,7 +628,7 @@ static int profile_uring(struct aa_profile *profile, u32 request, AA_BUG(!profile); - rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules = profile->label.rules[0]; state = RULE_MEDIATES(rules, AA_CLASS_IO_URING); if (state) { struct aa_perms perms = { }; @@ -664,15 +659,16 @@ static int apparmor_uring_override_creds(const struct cred *new) struct aa_profile *profile; struct aa_label *label; int error; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, OP_URING_OVERRIDE); ad.uring.target = cred_label(new); - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); error = fn_for_each(label, profile, profile_uring(profile, AA_MAY_OVERRIDE_CRED, cred_label(new), CAP_SYS_ADMIN, &ad)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -688,14 +684,15 @@ static int apparmor_uring_sqpoll(void) struct aa_profile *profile; struct aa_label *label; int error; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, OP_URING_SQPOLL); - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); error = fn_for_each(label, profile, profile_uring(profile, AA_MAY_CREATE_SQPOLL, NULL, CAP_SYS_ADMIN, &ad)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -706,6 +703,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path, { struct aa_label *label; int error = 0; + bool needput; /* Discard magic */ if ((flags & MS_MGC_MSK) == MS_MGC_VAL) @@ -713,7 +711,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path, flags &= ~AA_MS_IGNORE_MASK; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) { if (flags & MS_REMOUNT) error = aa_remount(current_cred(), label, path, flags, @@ -732,7 +730,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path, error = aa_new_mount(current_cred(), label, dev_name, path, type, flags, data); } - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -742,12 +740,13 @@ static int apparmor_move_mount(const struct path *from_path, { struct aa_label *label; int error = 0; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_move_mount(current_cred(), label, from_path, to_path); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -756,11 +755,12 @@ static int apparmor_sb_umount(struct vfsmount *mnt, int flags) { struct aa_label *label; int error = 0; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_umount(current_cred(), label, mnt, flags); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -823,25 +823,23 @@ static int apparmor_getprocattr(struct task_struct *task, const char *name, char **value) { int error = -ENOENT; - /* released below */ - const struct cred *cred = get_task_cred(task); - struct aa_task_ctx *ctx = task_ctx(current); struct aa_label *label = NULL; + rcu_read_lock(); if (strcmp(name, "current") == 0) - label = aa_get_newest_label(cred_label(cred)); - else if (strcmp(name, "prev") == 0 && ctx->previous) - label = aa_get_newest_label(ctx->previous); - else if (strcmp(name, "exec") == 0 && ctx->onexec) - label = aa_get_newest_label(ctx->onexec); + label = aa_get_newest_cred_label(__task_cred(task)); + else if (strcmp(name, "prev") == 0 && task_ctx(task)->previous) + label = aa_get_newest_label(task_ctx(task)->previous); + else if (strcmp(name, "exec") == 0 && task_ctx(task)->onexec) + label = aa_get_newest_label(task_ctx(task)->onexec); else error = -EINVAL; + rcu_read_unlock(); if (label) error = aa_getprocattr(label, value, true); aa_put_label(label); - put_cred(cred); return error; } @@ -859,12 +857,9 @@ static int do_setattr(u64 attr, void *value, size_t size) /* AppArmor requires that the buffer must be null terminated atm */ if (args[size - 1] != '\0') { - /* null terminate */ - largs = args = kmalloc(size + 1, GFP_KERNEL); + largs = args = kmemdup_nul(value, size, GFP_KERNEL); if (!args) return -ENOMEM; - memcpy(args, value, size); - args[size] = '\0'; } error = -EINVAL; @@ -984,10 +979,12 @@ static void apparmor_bprm_committed_creds(const struct linux_binprm *bprm) static void apparmor_current_getlsmprop_subj(struct lsm_prop *prop) { - struct aa_label *label = __begin_current_label_crit_section(); + struct aa_label *label; + bool needput; + label = __begin_current_label_crit_section(&needput); prop->apparmor.label = label; - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); } static void apparmor_task_getlsmprop_obj(struct task_struct *p, @@ -1002,13 +999,16 @@ static void apparmor_task_getlsmprop_obj(struct task_struct *p, static int apparmor_task_setrlimit(struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_label *label = __begin_current_label_crit_section(); + struct aa_label *label; int error = 0; + bool needput; + + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_task_setrlimit(current_cred(), label, task, resource, new_rlim); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -1019,6 +1019,7 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo const struct cred *tc; struct aa_label *cl, *tl; int error; + bool needput; tc = get_task_cred(target); tl = aa_get_newest_cred_label(tc); @@ -1030,9 +1031,9 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo error = aa_may_signal(cred, cl, tc, tl, sig); aa_put_label(cl); } else { - cl = __begin_current_label_crit_section(); + cl = __begin_current_label_crit_section(&needput); error = aa_may_signal(current_cred(), cl, tc, tl, sig); - __end_current_label_crit_section(cl); + __end_current_label_crit_section(cl, needput); } aa_put_label(tl); put_cred(tc); @@ -1061,12 +1062,29 @@ static int apparmor_userns_create(const struct cred *cred) return error; } +static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t gfp) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + struct aa_label *label; + bool needput; + + label = __begin_current_label_crit_section(&needput); + //spin_lock_init(&ctx->lock); + rcu_assign_pointer(ctx->label, aa_get_label(label)); + rcu_assign_pointer(ctx->peer, NULL); + rcu_assign_pointer(ctx->peer_lastupdate, NULL); + __end_current_label_crit_section(label, needput); + return 0; +} + static void apparmor_sk_free_security(struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); - aa_put_label(ctx->label); - aa_put_label(ctx->peer); + /* dead these won't be updated any more */ + aa_put_label(rcu_dereference_protected(ctx->label, true)); + aa_put_label(rcu_dereference_protected(ctx->peer, true)); + aa_put_label(rcu_dereference_protected(ctx->peer_lastupdate, true)); } /** @@ -1080,13 +1098,153 @@ static void apparmor_sk_clone_security(const struct sock *sk, struct aa_sk_ctx *ctx = aa_sock(sk); struct aa_sk_ctx *new = aa_sock(newsk); - if (new->label) - aa_put_label(new->label); - new->label = aa_get_label(ctx->label); + /* not actually in use yet */ + if (rcu_access_pointer(ctx->label) != rcu_access_pointer(new->label)) { + aa_put_label(rcu_dereference_protected(new->label, true)); + rcu_assign_pointer(new->label, aa_get_label_rcu(&ctx->label)); + } + + if (rcu_access_pointer(ctx->peer) != rcu_access_pointer(new->peer)) { + aa_put_label(rcu_dereference_protected(new->peer, true)); + rcu_assign_pointer(new->peer, aa_get_label_rcu(&ctx->peer)); + } + + if (rcu_access_pointer(ctx->peer_lastupdate) != rcu_access_pointer(new->peer_lastupdate)) { + aa_put_label(rcu_dereference_protected(new->peer_lastupdate, true)); + rcu_assign_pointer(new->peer_lastupdate, + aa_get_label_rcu(&ctx->peer_lastupdate)); + } +} + +static int unix_connect_perm(const struct cred *cred, struct aa_label *label, + struct sock *sk, struct sock *peer_sk) +{ + struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); + int error; + + error = aa_unix_peer_perm(cred, label, OP_CONNECT, + (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), + sk, peer_sk, + rcu_dereference_protected(peer_ctx->label, + lockdep_is_held(&unix_sk(peer_sk)->lock))); + if (!is_unix_fs(peer_sk)) { + last_error(error, + aa_unix_peer_perm(cred, + rcu_dereference_protected(peer_ctx->label, + lockdep_is_held(&unix_sk(peer_sk)->lock)), + OP_CONNECT, + (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE), + peer_sk, sk, label)); + } + + return error; +} + +/* lockdep check in unix_connect_perm - push sks here to check */ +static void unix_connect_peers(struct aa_sk_ctx *sk_ctx, + struct aa_sk_ctx *peer_ctx) +{ + /* Cross reference the peer labels for SO_PEERSEC */ + struct aa_label *label = rcu_dereference_protected(sk_ctx->label, true); + + aa_get_label(label); + aa_put_label(rcu_dereference_protected(peer_ctx->peer, + true)); + rcu_assign_pointer(peer_ctx->peer, label); /* transfer cnt */ + + label = aa_get_label(rcu_dereference_protected(peer_ctx->label, + true)); + //spin_unlock(&peer_ctx->lock); + + //spin_lock(&sk_ctx->lock); + aa_put_label(rcu_dereference_protected(sk_ctx->peer, + true)); + aa_put_label(rcu_dereference_protected(sk_ctx->peer_lastupdate, + true)); + + rcu_assign_pointer(sk_ctx->peer, aa_get_label(label)); + rcu_assign_pointer(sk_ctx->peer_lastupdate, label); /* transfer cnt */ + //spin_unlock(&sk_ctx->lock); +} + +/** + * apparmor_unix_stream_connect - check perms before making unix domain conn + * @sk: sk attempting to connect + * @peer_sk: sk that is accepting the connection + * @newsk: new sk created for this connection + * peer is locked when this hook is called + * + * Return: + * 0 if connection is permitted + * error code on denial or failure + */ +static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, + struct sock *newsk) +{ + struct aa_sk_ctx *sk_ctx = aa_sock(sk); + struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); + struct aa_sk_ctx *new_ctx = aa_sock(newsk); + struct aa_label *label; + int error; + bool needput; + + label = __begin_current_label_crit_section(&needput); + error = unix_connect_perm(current_cred(), label, sk, peer_sk); + __end_current_label_crit_section(label, needput); + + if (error) + return error; + + /* newsk doesn't go through post_create, but does go through + * security_sk_alloc() + */ + rcu_assign_pointer(new_ctx->label, + aa_get_label(rcu_dereference_protected(peer_ctx->label, + true))); - if (new->peer) - aa_put_label(new->peer); - new->peer = aa_get_label(ctx->peer); + /* Cross reference the peer labels for SO_PEERSEC */ + unix_connect_peers(sk_ctx, new_ctx); + + return 0; +} + +/** + * apparmor_unix_may_send - check perms before conn or sending unix dgrams + * @sock: socket sending the message + * @peer: socket message is being send to + * + * Performs bidirectional permission checks for Unix domain socket communication: + * 1. Verifies sender has AA_MAY_SEND to target socket + * 2. Verifies receiver has AA_MAY_RECEIVE from source socket + * + * sock and peer are locked when this hook is called + * called by: dgram_connect peer setup but path not copied to newsk + * + * Return: + * 0 if transmission is permitted + * error code on denial or failure + */ +static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) +{ + struct aa_sk_ctx *peer_ctx = aa_sock(peer->sk); + struct aa_label *label; + int error; + bool needput; + + label = __begin_current_label_crit_section(&needput); + error = xcheck(aa_unix_peer_perm(current_cred(), + label, OP_SENDMSG, AA_MAY_SEND, + sock->sk, peer->sk, + rcu_dereference_protected(peer_ctx->label, + true)), + aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL, + rcu_dereference_protected(peer_ctx->label, + true), + OP_SENDMSG, AA_MAY_RECEIVE, peer->sk, + sock->sk, label)); + __end_current_label_crit_section(label, needput); + + return error; } static int apparmor_socket_create(int family, int type, int protocol, int kern) @@ -1096,13 +1254,19 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) AA_BUG(in_interrupt()); + if (kern) + return 0; + label = begin_current_label_crit_section(); - if (!(kern || unconfined(label))) - error = af_select(family, - create_perm(label, family, type, protocol), - aa_af_perm(current_cred(), label, - OP_CREATE, AA_MAY_CREATE, - family, type, protocol)); + if (!unconfined(label)) { + if (family == PF_UNIX) + error = aa_unix_create_perm(label, family, type, + protocol); + else + error = aa_af_perm(current_cred(), label, OP_CREATE, + AA_MAY_CREATE, family, type, + protocol); + } end_current_label_crit_section(label); return error; @@ -1135,14 +1299,58 @@ static int apparmor_socket_post_create(struct socket *sock, int family, if (sock->sk) { struct aa_sk_ctx *ctx = aa_sock(sock->sk); - aa_put_label(ctx->label); - ctx->label = aa_get_label(label); + /* still not live */ + aa_put_label(rcu_dereference_protected(ctx->label, true)); + rcu_assign_pointer(ctx->label, aa_get_label(label)); } aa_put_label(label); return 0; } +static int apparmor_socket_socketpair(struct socket *socka, + struct socket *sockb) +{ + struct aa_sk_ctx *a_ctx = aa_sock(socka->sk); + struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk); + struct aa_label *label; + + /* socks not live yet - initial values set in sk_alloc */ + label = begin_current_label_crit_section(); + if (rcu_access_pointer(a_ctx->label) != label) { + AA_BUG("a_ctx != label"); + aa_put_label(rcu_dereference_protected(a_ctx->label, true)); + rcu_assign_pointer(a_ctx->label, aa_get_label(label)); + } + if (rcu_access_pointer(b_ctx->label) != label) { + AA_BUG("b_ctx != label"); + aa_put_label(rcu_dereference_protected(b_ctx->label, true)); + rcu_assign_pointer(b_ctx->label, aa_get_label(label)); + } + + if (socka->sk->sk_family == PF_UNIX) { + /* unix socket pairs by-pass unix_stream_connect */ + unix_connect_peers(a_ctx, b_ctx); + } + end_current_label_crit_section(label); + + return 0; +} + +/** + * apparmor_socket_bind - check perms before bind addr to socket + * @sock: socket to bind the address to (must be non-NULL) + * @address: address that is being bound (must be non-NULL) + * @addrlen: length of @address + * + * Performs security checks before allowing a socket to bind to an address. + * Handles Unix domain sockets specially through aa_unix_bind_perm(). + * For other socket families, uses generic permission check via aa_sk_perm(). + * + * Return: + * 0 if binding is permitted + * error code on denial or invalid parameters + */ static int apparmor_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) { @@ -1151,9 +1359,9 @@ static int apparmor_socket_bind(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - bind_perm(sock, address, addrlen), - aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk)); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_bind_perm(sock, address, addrlen); + return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); } static int apparmor_socket_connect(struct socket *sock, @@ -1164,9 +1372,10 @@ static int apparmor_socket_connect(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - connect_perm(sock, address, addrlen), - aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk)); + /* PF_UNIX goes through unix_stream_connect && unix_may_send */ + if (sock->sk->sk_family == PF_UNIX) + return 0; + return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); } static int apparmor_socket_listen(struct socket *sock, int backlog) @@ -1175,9 +1384,9 @@ static int apparmor_socket_listen(struct socket *sock, int backlog) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - listen_perm(sock, backlog), - aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk)); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_listen_perm(sock, backlog); + return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); } /* @@ -1191,9 +1400,9 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) AA_BUG(!newsock); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - accept_perm(sock, newsock), - aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk)); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_accept_perm(sock, newsock); + return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); } static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, @@ -1204,9 +1413,10 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!msg); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - msg_perm(op, request, sock, msg, size), - aa_sk_perm(op, request, sock->sk)); + /* PF_UNIX goes through unix_may_send */ + if (sock->sk->sk_family == PF_UNIX) + return 0; + return aa_sk_perm(op, request, sock->sk); } static int apparmor_socket_sendmsg(struct socket *sock, @@ -1228,9 +1438,9 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - sock_perm(op, request, sock), - aa_sk_perm(op, request, sock->sk)); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_sock_perm(op, request, sock); + return aa_sk_perm(op, request, sock->sk); } static int apparmor_socket_getsockname(struct socket *sock) @@ -1251,9 +1461,9 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - opt_perm(op, request, sock, level, optname), - aa_sk_perm(op, request, sock->sk)); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_opt_perm(op, request, sock, level, optname); + return aa_sk_perm(op, request, sock->sk); } static int apparmor_socket_getsockopt(struct socket *sock, int level, @@ -1289,6 +1499,7 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { struct aa_sk_ctx *ctx = aa_sock(sk); + int error; if (!skb->secmark) return 0; @@ -1297,21 +1508,25 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) * If reach here before socket_post_create hook is called, in which * case label is null, drop the packet. */ - if (!ctx->label) + if (!rcu_access_pointer(ctx->label)) return -EACCES; - return apparmor_secmark_check(ctx->label, OP_RECVMSG, AA_MAY_RECEIVE, - skb->secmark, sk); + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG, + AA_MAY_RECEIVE, skb->secmark, sk); + rcu_read_unlock(); + + return error; } #endif -static struct aa_label *sk_peer_label(struct sock *sk) +static struct aa_label *sk_peer_get_label(struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); - if (ctx->peer) - return ctx->peer; + if (rcu_access_pointer(ctx->peer)) + return aa_get_label_rcu(&ctx->peer); return ERR_PTR(-ENOPROTOOPT); } @@ -1335,19 +1550,19 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, struct aa_label *label; struct aa_label *peer; - label = begin_current_label_crit_section(); - peer = sk_peer_label(sock->sk); + peer = sk_peer_get_label(sock->sk); if (IS_ERR(peer)) { error = PTR_ERR(peer); goto done; } + label = begin_current_label_crit_section(); slen = aa_label_asxprint(&name, labels_ns(label), peer, FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED, GFP_KERNEL); /* don't include terminating \0 in slen, it breaks some apps */ if (slen < 0) { error = -ENOMEM; - goto done; + goto done_put; } if (slen > len) { error = -ERANGE; @@ -1359,8 +1574,11 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, done_len: if (copy_to_sockptr(optlen, &slen, sizeof(slen))) error = -EFAULT; -done: + +done_put: end_current_label_crit_section(label); + aa_put_label(peer); +done: kfree(name); return error; } @@ -1396,8 +1614,9 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent) { struct aa_sk_ctx *ctx = aa_sock(sk); - if (!ctx->label) - ctx->label = aa_get_current_label(); + /* setup - not live */ + if (!rcu_access_pointer(ctx->label)) + rcu_assign_pointer(ctx->label, aa_get_current_label()); } #ifdef CONFIG_NETWORK_SECMARK @@ -1405,12 +1624,17 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb struct request_sock *req) { struct aa_sk_ctx *ctx = aa_sock(sk); + int error; if (!skb->secmark) return 0; - return apparmor_secmark_check(ctx->label, OP_CONNECT, AA_MAY_CONNECT, - skb->secmark, sk); + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_CONNECT, + AA_MAY_CONNECT, skb->secmark, sk); + rcu_read_unlock(); + + return error; } #endif @@ -1467,11 +1691,16 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = { LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), + LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security), LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security), LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security), + LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect), + LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send), + LSM_HOOK_INIT(socket_create, apparmor_socket_create), LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create), + LSM_HOOK_INIT(socket_socketpair, apparmor_socket_socketpair), LSM_HOOK_INIT(socket_bind, apparmor_socket_bind), LSM_HOOK_INIT(socket_connect, apparmor_socket_connect), LSM_HOOK_INIT(socket_listen, apparmor_socket_listen), @@ -1571,6 +1800,9 @@ static const struct kernel_param_ops param_ops_aalockpolicy = { .get = param_get_aalockpolicy }; +static int param_set_debug(const char *val, const struct kernel_param *kp); +static int param_get_debug(char *buffer, const struct kernel_param *kp); + static int param_set_audit(const char *val, const struct kernel_param *kp); static int param_get_audit(char *buffer, const struct kernel_param *kp); @@ -1604,8 +1836,9 @@ module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level, aacompressionlevel, 0400); /* Debug mode */ -bool aa_g_debug = IS_ENABLED(CONFIG_SECURITY_APPARMOR_DEBUG_MESSAGES); -module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); +int aa_g_debug; +module_param_call(debug, param_set_debug, param_get_debug, + &aa_g_debug, 0600); /* Audit mode */ enum audit_mode aa_g_audit; @@ -1798,13 +2031,41 @@ static int param_get_aacompressionlevel(char *buffer, return param_get_int(buffer, kp); } +static int param_get_debug(char *buffer, const struct kernel_param *kp) +{ + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) + return -EPERM; + return aa_print_debug_params(buffer); +} + +static int param_set_debug(const char *val, const struct kernel_param *kp) +{ + int i; + + if (!apparmor_enabled) + return -EINVAL; + if (!val) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) + return -EPERM; + + i = aa_parse_debug_params(val); + if (i == DEBUG_PARSE_ERROR) + return -EINVAL; + + aa_g_debug = i; + return 0; +} + static int param_get_audit(char *buffer, const struct kernel_param *kp) { if (!apparmor_enabled) return -EINVAL; if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; - return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]); + return sysfs_emit(buffer, "%s\n", audit_mode_names[aa_g_audit]); } static int param_set_audit(const char *val, const struct kernel_param *kp) @@ -1832,8 +2093,7 @@ static int param_get_mode(char *buffer, const struct kernel_param *kp) return -EINVAL; if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; - - return sprintf(buffer, "%s", aa_profile_mode_names[aa_g_profile_mode]); + return sysfs_emit(buffer, "%s\n", aa_profile_mode_names[aa_g_profile_mode]); } static int param_set_mode(const char *val, const struct kernel_param *kp) @@ -1856,6 +2116,23 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) return 0; } +/* arbitrary cap on how long to hold buffer because contention was + * encountered before trying to put it back into the global pool + */ +#define MAX_HOLD_COUNT 64 + +/* the hold count is a heuristic for lock contention, and can be + * incremented async to actual buffer alloc/free. Because buffers + * may be put back onto a percpu cache different than the ->hold was + * added to the counts can be out of sync. Guard against underflow + * and overflow + */ +static void cache_hold_inc(unsigned int *hold) +{ + if (*hold > MAX_HOLD_COUNT) + (*hold)++; +} + char *aa_get_buffer(bool in_atomic) { union aa_buffer *aa_buf; @@ -1868,21 +2145,26 @@ char *aa_get_buffer(bool in_atomic) if (!list_empty(&cache->head)) { aa_buf = list_first_entry(&cache->head, union aa_buffer, list); list_del(&aa_buf->list); - cache->hold--; + if (cache->hold) + cache->hold--; cache->count--; put_cpu_ptr(&aa_local_buffers); return &aa_buf->buffer[0]; } + /* exit percpu as spinlocks may sleep on realtime kernels */ put_cpu_ptr(&aa_local_buffers); if (!spin_trylock(&aa_buffers_lock)) { + /* had contention on lock so increase hold count. Doesn't + * really matter if recorded before or after the spin lock + * as there is no way to guarantee the buffer will be put + * back on the same percpu cache. Instead rely on holds + * roughly averaging out over time. + */ cache = get_cpu_ptr(&aa_local_buffers); - cache->hold += 1; + cache_hold_inc(&cache->hold); put_cpu_ptr(&aa_local_buffers); spin_lock(&aa_buffers_lock); - } else { - cache = get_cpu_ptr(&aa_local_buffers); - put_cpu_ptr(&aa_local_buffers); } retry: if (buffer_count > reserve_count || @@ -1937,13 +2219,11 @@ void aa_put_buffer(char *buf) list_add(&aa_buf->list, &aa_global_buffers); buffer_count++; spin_unlock(&aa_buffers_lock); - cache = get_cpu_ptr(&aa_local_buffers); - put_cpu_ptr(&aa_local_buffers); return; } /* contention on global list, fallback to percpu */ cache = get_cpu_ptr(&aa_local_buffers); - cache->hold += 1; + cache_hold_inc(&cache->hold); } /* cache in percpu list */ @@ -2006,7 +2286,7 @@ static int __init alloc_buffers(void) * two should be enough, with more CPUs it is possible that more * buffers will be used simultaneously. The preallocated pool may grow. * This preallocation has also the side-effect that AppArmor will be - * disabled early at boot if aa_g_path_max is extremly high. + * disabled early at boot if aa_g_path_max is extremely high. */ if (num_online_cpus() > 1) num = 4 + RESERVE_COUNT; @@ -2082,6 +2362,7 @@ static unsigned int apparmor_ip_postroute(void *priv, { struct aa_sk_ctx *ctx; struct sock *sk; + int error; if (!skb->secmark) return NF_ACCEPT; @@ -2091,8 +2372,11 @@ static unsigned int apparmor_ip_postroute(void *priv, return NF_ACCEPT; ctx = aa_sock(sk); - if (!apparmor_secmark_check(ctx->label, OP_SENDMSG, AA_MAY_SEND, - skb->secmark, sk)) + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG, + AA_MAY_SEND, skb->secmark, sk); + rcu_read_unlock(); + if (!error) return NF_ACCEPT; return NF_DROP_ERR(-ECONNREFUSED); @@ -2146,15 +2430,14 @@ static int __init apparmor_nf_ip_init(void) return 0; } -__initcall(apparmor_nf_ip_init); #endif -static char nulldfa_src[] = { +static char nulldfa_src[] __aligned(8) = { #include "nulldfa.in" }; static struct aa_dfa *nulldfa; -static char stacksplitdfa_src[] = { +static char stacksplitdfa_src[] __aligned(8) = { #include "stacksplitdfa.in" }; struct aa_dfa *stacksplitdfa; @@ -2173,10 +2456,11 @@ static int __init aa_setup_dfa_engine(void) TO_ACCEPT2_FLAG(YYTD_DATA32)); if (IS_ERR(nulldfa)) { error = PTR_ERR(nulldfa); + nulldfa = NULL; goto fail; } nullpdb->dfa = aa_get_dfa(nulldfa); - nullpdb->perms = kcalloc(2, sizeof(struct aa_perms), GFP_KERNEL); + nullpdb->perms = kzalloc_objs(struct aa_perms, 2); if (!nullpdb->perms) goto fail; nullpdb->size = 2; @@ -2250,6 +2534,9 @@ static int __init apparmor_init(void) security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks), &apparmor_lsmid); + /* Inform the audit system that secctx is used */ + audit_cfg_lsm(&apparmor_lsmid, AUDIT_CFG_LSM_SECCTX_SUBJECT); + /* Report that AppArmor successfully initialized */ apparmor_initialized = 1; if (aa_g_profile_mode == APPARMOR_COMPLAIN) @@ -2272,9 +2559,16 @@ alloc_out: } DEFINE_LSM(apparmor) = { - .name = "apparmor", + .id = &apparmor_lsmid, .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE, .enabled = &apparmor_enabled, .blobs = &apparmor_blob_sizes, .init = apparmor_init, + .initcall_fs = aa_create_aafs, +#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) + .initcall_device = apparmor_nf_ip_init, +#endif +#ifdef CONFIG_SECURITY_APPARMOR_HASH + .initcall_late = init_profile_hash, +#endif }; diff --git a/security/apparmor/match.c b/security/apparmor/match.c index f2d9c57f8794..3a2c6cf02b3c 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -15,6 +15,7 @@ #include <linux/vmalloc.h> #include <linux/err.h> #include <linux/kref.h> +#include <linux/unaligned.h> #include "include/lib.h" #include "include/match.h" @@ -42,11 +43,11 @@ static struct table_header *unpack_table(char *blob, size_t bsize) /* loaded td_id's start at 1, subtract 1 now to avoid doing * it every time we use td_id as an index */ - th.td_id = be16_to_cpu(*(__be16 *) (blob)) - 1; + th.td_id = get_unaligned_be16(blob) - 1; if (th.td_id > YYTD_ID_MAX) goto out; - th.td_flags = be16_to_cpu(*(__be16 *) (blob + 2)); - th.td_lolen = be32_to_cpu(*(__be32 *) (blob + 8)); + th.td_flags = get_unaligned_be16(blob + 2); + th.td_lolen = get_unaligned_be32(blob + 8); blob += sizeof(struct table_header); if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 || @@ -66,14 +67,13 @@ static struct table_header *unpack_table(char *blob, size_t bsize) table->td_flags = th.td_flags; table->td_lolen = th.td_lolen; if (th.td_flags == YYTD_DATA8) - UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u8, u8, byte_to_byte); + memcpy(table->td_data, blob, th.td_lolen); else if (th.td_flags == YYTD_DATA16) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u16, __be16, be16_to_cpu); + u16, __be16, get_unaligned_be16); else if (th.td_flags == YYTD_DATA32) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u32, __be32, be32_to_cpu); + u32, __be32, get_unaligned_be32); else goto fail; /* if table was vmalloced make sure the page tables are synced @@ -157,12 +157,13 @@ static int verify_dfa(struct aa_dfa *dfa) state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen; - if (state_count == 0) + if (state_count < 2) goto out; for (i = 0; i < state_count; i++) { - if (!(BASE_TABLE(dfa)[i] & MATCH_FLAG_DIFF_ENCODE) && - (DEFAULT_TABLE(dfa)[i] >= state_count)) + if (DEFAULT_TABLE(dfa)[i] >= state_count) { + pr_err("AppArmor DFA default state out of bounds"); goto out; + } if (BASE_TABLE(dfa)[i] & MATCH_FLAGS_INVALID) { pr_err("AppArmor DFA state with invalid match flags"); goto out; @@ -201,16 +202,31 @@ static int verify_dfa(struct aa_dfa *dfa) size_t j, k; for (j = i; - (BASE_TABLE(dfa)[j] & MATCH_FLAG_DIFF_ENCODE) && - !(BASE_TABLE(dfa)[j] & MARK_DIFF_ENCODE); + ((BASE_TABLE(dfa)[j] & MATCH_FLAG_DIFF_ENCODE) && + !(BASE_TABLE(dfa)[j] & MARK_DIFF_ENCODE_VERIFIED)); j = k) { + if (BASE_TABLE(dfa)[j] & MARK_DIFF_ENCODE) + /* loop in current chain */ + goto out; k = DEFAULT_TABLE(dfa)[j]; if (j == k) + /* self loop */ goto out; - if (k < j) - break; /* already verified */ BASE_TABLE(dfa)[j] |= MARK_DIFF_ENCODE; } + /* move mark to verified */ + for (j = i; + (BASE_TABLE(dfa)[j] & MATCH_FLAG_DIFF_ENCODE); + j = k) { + k = DEFAULT_TABLE(dfa)[j]; + if (j < i) + /* jumps to state/chain that has been + * verified + */ + break; + BASE_TABLE(dfa)[j] &= ~MARK_DIFF_ENCODE; + BASE_TABLE(dfa)[j] |= MARK_DIFF_ENCODE_VERIFIED; + } } error = 0; @@ -301,7 +317,7 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) int error = -ENOMEM; char *data = blob; struct table_header *table = NULL; - struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL); + struct aa_dfa *dfa = kzalloc_obj(struct aa_dfa); if (!dfa) goto fail; @@ -313,14 +329,14 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) if (size < sizeof(struct table_set_header)) goto fail; - if (ntohl(*(__be32 *) data) != YYTH_MAGIC) + if (get_unaligned_be32(data) != YYTH_MAGIC) goto fail; - hsize = ntohl(*(__be32 *) (data + 4)); + hsize = get_unaligned_be32(data + 4); if (size < hsize) goto fail; - dfa->flags = ntohs(*(__be16 *) (data + 12)); + dfa->flags = get_unaligned_be16(data + 12); if (dfa->flags & ~(YYTH_FLAGS)) goto fail; @@ -329,7 +345,7 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) * if (dfa->flags & YYTH_FLAGS_OOB_TRANS) { * if (hsize < 16 + 4) * goto fail; - * dfa->max_oob = ntol(*(__be32 *) (data + 16)); + * dfa->max_oob = get_unaligned_be32(data + 16); * if (dfa->max <= MAX_OOB_SUPPORTED) { * pr_err("AppArmor DFA OOB greater than supported\n"); * goto fail; @@ -463,13 +479,18 @@ aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, if (dfa->tables[YYTD_ID_EC]) { /* Equivalence class table defined */ u8 *equiv = EQUIV_TABLE(dfa); - for (; len; len--) - match_char(state, def, base, next, check, - equiv[(u8) *str++]); + for (; len; len--) { + u8 c = equiv[(u8) *str]; + + match_char(state, def, base, next, check, c); + str++; + } } else { /* default is direct to next state */ - for (; len; len--) - match_char(state, def, base, next, check, (u8) *str++); + for (; len; len--) { + match_char(state, def, base, next, check, (u8) *str); + str++; + } } return state; @@ -503,13 +524,18 @@ aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str) /* Equivalence class table defined */ u8 *equiv = EQUIV_TABLE(dfa); /* default is direct to next state */ - while (*str) - match_char(state, def, base, next, check, - equiv[(u8) *str++]); + while (*str) { + u8 c = equiv[(u8) *str]; + + match_char(state, def, base, next, check, c); + str++; + } } else { /* default is direct to next state */ - while (*str) - match_char(state, def, base, next, check, (u8) *str++); + while (*str) { + match_char(state, def, base, next, check, (u8) *str); + str++; + } } return state; @@ -679,34 +705,35 @@ aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, return state; } -#define inc_wb_pos(wb) \ -do { \ +#define inc_wb_pos(wb) \ +do { \ + BUILD_BUG_ON_NOT_POWER_OF_2(WB_HISTORY_SIZE); \ wb->pos = (wb->pos + 1) & (WB_HISTORY_SIZE - 1); \ - wb->len = (wb->len + 1) & (WB_HISTORY_SIZE - 1); \ + wb->len = (wb->len + 1) > WB_HISTORY_SIZE ? WB_HISTORY_SIZE : \ + wb->len + 1; \ } while (0) /* For DFAs that don't support extended tagging of states */ +/* adjust is only set if is_loop returns true */ static bool is_loop(struct match_workbuf *wb, aa_state_t state, unsigned int *adjust) { - aa_state_t pos = wb->pos; - aa_state_t i; + int pos = wb->pos; + int i; if (wb->history[pos] < state) return false; - for (i = 0; i <= wb->len; i++) { + for (i = 0; i < wb->len; i++) { if (wb->history[pos] == state) { *adjust = i; return true; } - if (pos == 0) - pos = WB_HISTORY_SIZE; - pos--; + /* -1 wraps to WB_HISTORY_SIZE - 1 */ + pos = (pos - 1) & (WB_HISTORY_SIZE - 1); } - *adjust = i; - return true; + return false; } static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index bf8863253e07..523570aa1a5a 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -311,8 +311,7 @@ static int match_mnt_path_str(const struct cred *subj_cred, { struct aa_perms perms = { }; const char *mntpnt = NULL, *info = NULL; - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int pos, error; AA_BUG(!profile); @@ -371,8 +370,7 @@ static int match_mnt(const struct cred *subj_cred, bool binary) { const char *devname = NULL, *info = NULL; - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int error = -EACCES; AA_BUG(!profile); @@ -604,8 +602,7 @@ static int profile_umount(const struct cred *subj_cred, struct aa_profile *profile, const struct path *path, char *buffer) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms = { }; const char *name = NULL, *info = NULL; aa_state_t state; @@ -668,8 +665,7 @@ static struct aa_label *build_pivotroot(const struct cred *subj_cred, const struct path *old_path, char *old_buffer) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *old_name, *new_name = NULL, *info = NULL; const char *trans_name = NULL; struct aa_perms perms = { }; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 77413a519117..44c04102062f 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -8,6 +8,7 @@ * Copyright 2009-2017 Canonical Ltd. */ +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/audit.h" #include "include/cred.h" @@ -24,6 +25,12 @@ struct aa_sfs_entry aa_sfs_entry_network[] = { { } }; +struct aa_sfs_entry aa_sfs_entry_networkv9[] = { + AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK), + AA_SFS_FILE_BOOLEAN("af_unix", 1), + { } +}; + static const char * const net_mask_names[] = { "unknown", "send", @@ -66,6 +73,42 @@ static const char * const net_mask_names[] = { "unknown", }; +static void audit_unix_addr(struct audit_buffer *ab, const char *str, + struct sockaddr_un *addr, int addrlen) +{ + int len = unix_addr_len(addrlen); + + if (!addr || len <= 0) { + audit_log_format(ab, " %s=none", str); + } else if (addr->sun_path[0]) { + audit_log_format(ab, " %s=", str); + audit_log_untrustedstring(ab, addr->sun_path); + } else { + audit_log_format(ab, " %s=\"@", str); + if (audit_string_contains_control(&addr->sun_path[1], len - 1)) + audit_log_n_hex(ab, &addr->sun_path[1], len - 1); + else + audit_log_format(ab, "%.*s", len - 1, + &addr->sun_path[1]); + audit_log_format(ab, "\""); + } +} + +static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str, + const struct sock *sk) +{ + const struct unix_sock *u = unix_sk(sk); + + if (u && u->addr) { + int addrlen; + struct sockaddr_un *addr = aa_sunaddr(u, &addrlen); + + audit_unix_addr(ab, str, addr, addrlen); + } else { + audit_unix_addr(ab, str, NULL, 0); + + } +} /* audit callback for net specific fields */ void audit_net_cb(struct audit_buffer *ab, void *va) @@ -73,12 +116,12 @@ void audit_net_cb(struct audit_buffer *ab, void *va) struct common_audit_data *sa = va; struct apparmor_audit_data *ad = aad(sa); - if (address_family_names[sa->u.net->family]) + if (address_family_names[ad->common.u.net->family]) audit_log_format(ab, " family=\"%s\"", - address_family_names[sa->u.net->family]); + address_family_names[ad->common.u.net->family]); else audit_log_format(ab, " family=\"unknown(%d)\"", - sa->u.net->family); + ad->common.u.net->family); if (sock_type_names[ad->net.type]) audit_log_format(ab, " sock_type=\"%s\"", sock_type_names[ad->net.type]); @@ -98,6 +141,19 @@ void audit_net_cb(struct audit_buffer *ab, void *va) net_mask_names, NET_PERMS_MASK); } } + if (ad->common.u.net->family == PF_UNIX) { + if (ad->net.addr || !ad->common.u.net->sk) + audit_unix_addr(ab, "addr", + unix_addr(ad->net.addr), + ad->net.addrlen); + else + audit_unix_sk_addr(ab, "addr", ad->common.u.net->sk); + if (ad->request & NET_PEER_MASK) { + audit_unix_addr(ab, "peer_addr", + unix_addr(ad->net.peer.addr), + ad->net.peer.addrlen); + } + } if (ad->peer) { audit_log_format(ab, " peer="); aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, @@ -105,45 +161,123 @@ void audit_net_cb(struct audit_buffer *ab, void *va) } } +/* standard permission lookup pattern - supports early bailout */ +int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, + aa_state_t state, u32 request, + struct aa_perms *p, struct apparmor_audit_data *ad) +{ + struct aa_perms perms; + + AA_BUG(!profile); + AA_BUG(!policy); + + + if (state || !p) + p = aa_lookup_perms(policy, state); + perms = *p; + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, request, ad, + audit_net_cb); +} + +/* only continue match if + * insufficient current perms at current state + * indicates there are more perms in later state + * Returns: perms struct if early match + */ +static struct aa_perms *early_match(struct aa_policydb *policy, + aa_state_t state, u32 request) +{ + struct aa_perms *p; + + p = aa_lookup_perms(policy, state); + if (((p->allow & request) != request) && (p->allow & AA_CONT_MATCH)) + return NULL; + return p; +} + +static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state, + u16 data) +{ + __be16 buffer = cpu_to_be16(data); + + return aa_dfa_match_len(dfa, state, (char *) &buffer, 2); +} + +/** + * aa_match_to_prot - match the af, type, protocol triplet + * @policy: policy being matched + * @state: state to start in + * @request: permissions being requested, ignored if @p == NULL + * @af: socket address family + * @type: socket type + * @protocol: socket protocol + * @p: output - pointer to permission associated with match + * @info: output - pointer to string describing failure + * + * RETURNS: state match stopped in. + * + * If @(p) is assigned a value the returned state will be the + * corresponding state. Will not set @p on failure or if match completes + * only if an early match occurs + */ +aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, + u32 request, u16 af, int type, int protocol, + struct aa_perms **p, const char **info) +{ + state = aa_dfa_match_be16(policy->dfa, state, (u16)af); + if (!state) { + *info = "failed af match"; + return state; + } + state = aa_dfa_match_be16(policy->dfa, state, (u16)type); + if (state) { + if (p) + *p = early_match(policy, state, request); + if (!p || !*p) { + state = aa_dfa_match_be16(policy->dfa, state, (u16)protocol); + if (!state) + *info = "failed protocol match"; + } + } else { + *info = "failed type match"; + } + + return state; +} + /* Generic af perm */ int aa_profile_af_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, u32 request, u16 family, - int type) + int type, int protocol) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); - struct aa_perms perms = { }; + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; aa_state_t state; - __be16 buffer[2]; AA_BUG(family >= AF_MAX); AA_BUG(type < 0 || type >= SOCK_MAX); + AA_BUG(profile_unconfined(profile)); if (profile_unconfined(profile)) return 0; - state = RULE_MEDIATES(rules, AA_CLASS_NET); + state = RULE_MEDIATES_NET(rules); if (!state) return 0; - - buffer[0] = cpu_to_be16(family); - buffer[1] = cpu_to_be16((u16) type); - state = aa_dfa_match_len(rules->policy->dfa, state, (char *) &buffer, - 4); - perms = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, &perms); - - return aa_check_perms(profile, &perms, request, ad, audit_net_cb); + state = aa_match_to_prot(rules->policy, state, request, family, type, + protocol, &p, &ad->info); + return aa_do_perms(profile, rules->policy, state, request, p, ad); } int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, u16 family, int type, int protocol) { struct aa_profile *profile; - DEFINE_AUDIT_NET(ad, op, NULL, family, type, protocol); + DEFINE_AUDIT_NET(ad, op, subj_cred, NULL, family, type, protocol); return fn_for_each_confined(label, profile, aa_profile_af_perm(profile, &ad, request, family, - type)); + type, protocol)); } static int aa_label_sk_perm(const struct cred *subj_cred, @@ -157,9 +291,9 @@ static int aa_label_sk_perm(const struct cred *subj_cred, AA_BUG(!label); AA_BUG(!sk); - if (ctx->label != kernel_t && !unconfined(label)) { + if (rcu_access_pointer(ctx->label) != kernel_t && !unconfined(label)) { struct aa_profile *profile; - DEFINE_AUDIT_SK(ad, op, sk); + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); ad.subj_cred = subj_cred; error = fn_for_each_confined(label, profile, @@ -187,12 +321,18 @@ int aa_sk_perm(const char *op, u32 request, struct sock *sk) int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, - const char *op, u32 request, struct socket *sock) + const char *op, u32 request, struct file *file) { + struct socket *sock = (struct socket *) file->private_data; + AA_BUG(!label); - AA_BUG(!sock); - AA_BUG(!sock->sk); + /* sock && sock->sk can be NULL for sockets being set up or torn down */ + if (!sock || !sock->sk) + return 0; + + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_file_perm(subj_cred, label, op, request, file); return aa_label_sk_perm(subj_cred, label, op, request, sock->sk); } @@ -223,8 +363,7 @@ static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid, { int i, ret; struct aa_perms perms = { }; - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; if (rules->secmark_count == 0) return 0; @@ -257,7 +396,7 @@ int apparmor_secmark_check(struct aa_label *label, char *op, u32 request, u32 secid, const struct sock *sk) { struct aa_profile *profile; - DEFINE_AUDIT_SK(ad, op, sk); + DEFINE_AUDIT_SK(ad, op, NULL, sk); return fn_for_each_confined(label, profile, aa_secmark_perm(profile, request, secid, diff --git a/security/apparmor/path.c b/security/apparmor/path.c index d6c74c357ffd..2494e8101538 100644 --- a/security/apparmor/path.c +++ b/security/apparmor/path.c @@ -164,12 +164,17 @@ static int d_namespace_path(const struct path *path, char *buf, char **name, } out: - /* - * Append "/" to the pathname. The root directory is a special - * case; it already ends in slash. + /* Append "/" to directory paths and reterminate string, except for + * root "/" which already ends in a slash. */ - if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/')) - strcpy(&buf[aa_g_path_max - 2], "/"); + if (!error && isdir) { + bool is_root = (*name)[0] == '/' && (*name)[1] == '\0'; + + if (!is_root) { + buf[aa_g_path_max - 2] = '/'; + buf[aa_g_path_max - 1] = '\0'; + } + } return error; } diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index d0244fab0653..b6a5eb4021db 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -98,13 +98,21 @@ const char *const aa_profile_mode_names[] = { "user", }; +void aa_destroy_tags(struct aa_tags_struct *tags) +{ + kfree_sensitive(tags->hdrs.table); + kfree_sensitive(tags->sets.table); + aa_destroy_str_table(&tags->strs); + memset(tags, 0, sizeof(*tags)); +} static void aa_free_pdb(struct aa_policydb *pdb) { if (pdb) { aa_put_dfa(pdb->dfa); kvfree(pdb->perms); - aa_free_str_table(&pdb->trans); + aa_destroy_str_table(&pdb->trans); + aa_destroy_tags(&pdb->tags); kfree(pdb); } } @@ -123,7 +131,7 @@ void aa_pdb_free_kref(struct kref *kref) struct aa_policydb *aa_alloc_pdb(gfp_t gfp) { - struct aa_policydb *pdb = kzalloc(sizeof(struct aa_policydb), gfp); + struct aa_policydb *pdb = kzalloc_obj(struct aa_policydb, gfp); if (!pdb) return NULL; @@ -183,19 +191,43 @@ static void __list_remove_profile(struct aa_profile *profile) } /** - * __remove_profile - remove old profile, and children - * @profile: profile to be replaced (NOT NULL) + * __remove_profile - remove profile, and children + * @profile: profile to be removed (NOT NULL) * * Requires: namespace list lock be held, or list not be shared */ static void __remove_profile(struct aa_profile *profile) { + struct aa_profile *curr, *to_remove; + AA_BUG(!profile); AA_BUG(!profile->ns); AA_BUG(!mutex_is_locked(&profile->ns->lock)); /* release any children lists first */ - __aa_profile_list_release(&profile->base.profiles); + if (!list_empty(&profile->base.profiles)) { + curr = list_first_entry(&profile->base.profiles, struct aa_profile, base.list); + + while (curr != profile) { + + while (!list_empty(&curr->base.profiles)) + curr = list_first_entry(&curr->base.profiles, + struct aa_profile, base.list); + + to_remove = curr; + if (!list_is_last(&to_remove->base.list, + &aa_deref_parent(curr)->base.profiles)) + curr = list_next_entry(to_remove, base.list); + else + curr = aa_deref_parent(curr); + + /* released by free_profile */ + aa_label_remove(&to_remove->label); + __aafs_profile_rmdir(to_remove); + __list_remove_profile(to_remove); + } + } + /* released by free_profile */ aa_label_remove(&profile->label); __aafs_profile_rmdir(profile); @@ -224,6 +256,9 @@ static void aa_free_data(void *ptr, void *arg) { struct aa_data *data = ptr; + if (!ptr) + return; + kvfree_sensitive(data->data, data->size); kfree_sensitive(data->key); kfree_sensitive(data); @@ -233,6 +268,9 @@ static void free_attachment(struct aa_attachment *attach) { int i; + if (!attach) + return; + for (i = 0; i < attach->xattr_count; i++) kfree_sensitive(attach->xattrs[i]); kfree_sensitive(attach->xattrs); @@ -243,6 +281,9 @@ static void free_ruleset(struct aa_ruleset *rules) { int i; + if (!rules) + return; + aa_put_pdb(rules->file); aa_put_pdb(rules->policy); aa_free_cap_rules(&rules->caps); @@ -258,9 +299,7 @@ struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp) { struct aa_ruleset *rules; - rules = kzalloc(sizeof(*rules), gfp); - if (rules) - INIT_LIST_HEAD(&rules->list); + rules = kzalloc_obj(*rules, gfp); return rules; } @@ -277,10 +316,9 @@ struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp) */ void aa_free_profile(struct aa_profile *profile) { - struct aa_ruleset *rule, *tmp; struct rhashtable *rht; - AA_DEBUG("%s(%p)\n", __func__, profile); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, profile); if (!profile) return; @@ -299,10 +337,9 @@ void aa_free_profile(struct aa_profile *profile) * at this point there are no tasks that can have a reference * to rules */ - list_for_each_entry_safe(rule, tmp, &profile->rules, list) { - list_del_init(&rule->list); - free_ruleset(rule); - } + for (int i = 0; i < profile->n_rules; i++) + free_ruleset(profile->label.rules[i]); + kfree_sensitive(profile->dirname); if (profile->data) { @@ -313,7 +350,7 @@ void aa_free_profile(struct aa_profile *profile) } kfree_sensitive(profile->hash); - aa_put_loaddata(profile->rawdata); + aa_put_profile_loaddata(profile->rawdata); aa_label_destroy(&profile->label); kfree_sensitive(profile); @@ -331,10 +368,12 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, gfp_t gfp) { struct aa_profile *profile; - struct aa_ruleset *rules; - /* freed by free_profile - usually through aa_put_profile */ - profile = kzalloc(struct_size(profile, label.vec, 2), gfp); + /* freed by free_profile - usually through aa_put_profile + * this adds space for a single ruleset in the rules section of the + * label + */ + profile = kzalloc_flex(*profile, label.rules, 1, gfp); if (!profile) return NULL; @@ -343,13 +382,11 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, if (!aa_label_init(&profile->label, 1, gfp)) goto fail; - INIT_LIST_HEAD(&profile->rules); - /* allocate the first ruleset, but leave it empty */ - rules = aa_alloc_ruleset(gfp); - if (!rules) + profile->label.rules[0] = aa_alloc_ruleset(gfp); + if (!profile->label.rules[0]) goto fail; - list_add(&rules->list, &profile->rules); + profile->n_rules = 1; /* update being set needed by fs interface */ if (!proxy) { @@ -364,6 +401,7 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, profile->label.flags |= FLAG_PROFILE; profile->label.vec[0] = profile; + profile->signal = SIGKILL; /* refcount released by caller */ return profile; @@ -373,6 +411,41 @@ fail: return NULL; } +static inline bool ANY_RULE_MEDIATES(struct aa_profile *profile, + unsigned char class) +{ + int i; + + for (i = 0; i < profile->n_rules; i++) { + if (RULE_MEDIATES(profile->label.rules[i], class)) + return true; + } + return false; +} + +/* set of rules that are mediated by unconfined */ +static int unconfined_mediates[] = { AA_CLASS_NS, AA_CLASS_IO_URING, 0 }; + +/* must be called after profile rulesets and start information is setup */ +void aa_compute_profile_mediates(struct aa_profile *profile) +{ + int c; + + if (profile_unconfined(profile)) { + int *pos; + + for (pos = unconfined_mediates; *pos; pos++) { + if (ANY_RULE_MEDIATES(profile, *pos)) + profile->label.mediates |= ((u64) 1) << AA_CLASS_NS; + } + return; + } + for (c = 0; c <= AA_CLASS_LAST; c++) { + if (ANY_RULE_MEDIATES(profile, c)) + profile->label.mediates |= ((u64) 1) << c; + } +} + /* TODO: profile accounting - setup in remove */ /** @@ -463,7 +536,7 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns, } /** - * __create_missing_ancestors - create place holders for missing ancestores + * __create_missing_ancestors - create place holders for missing ancestors * @ns: namespace to lookup profile in (NOT NULL) * @hname: hierarchical profile name to find parent of (NOT NULL) * @gfp: type of allocation. @@ -621,13 +694,15 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, /* TODO: ideally we should inherit abi from parent */ profile->label.flags |= FLAG_NULL; profile->attach.xmatch = aa_get_pdb(nullpdb); - rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules = profile->label.rules[0]; rules->file = aa_get_pdb(nullpdb); rules->policy = aa_get_pdb(nullpdb); + aa_compute_profile_mediates(profile); if (parent) { profile->path_flags = parent->path_flags; - + /* override/inherit what is mediated from parent */ + profile->label.mediates = parent->label.mediates; /* released on free_profile */ rcu_assign_pointer(profile->parent, aa_get_profile(parent)); profile->ns = aa_get_ns(parent->ns); @@ -660,24 +735,27 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, struct aa_profile *p, *profile; const char *bname; char *name = NULL; + size_t name_sz; AA_BUG(!parent); if (base) { - name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base), - gfp); + name_sz = strlen(parent->base.hname) + 8 + strlen(base); + name = kmalloc(name_sz, gfp); if (name) { - sprintf(name, "%s//null-%s", parent->base.hname, base); + snprintf(name, name_sz, "%s//null-%s", + parent->base.hname, base); goto name; } /* fall through to try shorter uniq */ } - name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, gfp); + name_sz = strlen(parent->base.hname) + 2 + 7 + 8; + name = kmalloc(name_sz, gfp); if (!name) return NULL; - sprintf(name, "%s//null-%x", parent->base.hname, - atomic_inc_return(&parent->ns->uniq_null)); + snprintf(name, name_sz, "%s//null-%x", parent->base.hname, + atomic_inc_return(&parent->ns->uniq_null)); name: /* lookup to see if this is a dup creation */ @@ -833,8 +911,8 @@ bool aa_policy_admin_capable(const struct cred *subj_cred, bool capable = policy_ns_capable(subj_cred, label, user_ns, CAP_MAC_ADMIN) == 0; - AA_DEBUG("cap_mac_admin? %d\n", capable); - AA_DEBUG("policy locked? %d\n", aa_g_lock_policy); + AA_DEBUG(DEBUG_POLICY, "cap_mac_admin? %d\n", capable); + AA_DEBUG(DEBUG_POLICY, "policy locked? %d\n", aa_g_lock_policy); return aa_policy_view_capable(subj_cred, label, ns) && capable && !aa_g_lock_policy; @@ -843,11 +921,11 @@ bool aa_policy_admin_capable(const struct cred *subj_cred, bool aa_current_policy_view_capable(struct aa_ns *ns) { struct aa_label *label; - bool res; + bool needput, res; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); res = aa_policy_view_capable(current_cred(), label, ns); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return res; } @@ -855,26 +933,53 @@ bool aa_current_policy_view_capable(struct aa_ns *ns) bool aa_current_policy_admin_capable(struct aa_ns *ns) { struct aa_label *label; - bool res; + bool needput, res; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); res = aa_policy_admin_capable(current_cred(), label, ns); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return res; } +static bool is_subset_of_obj_privilege(const struct cred *cred, + struct aa_label *label, + const struct cred *ocred) +{ + if (cred == ocred) + return true; + + if (!aa_label_is_subset(label, cred_label(ocred))) + return false; + /* don't allow crossing userns for now */ + if (cred->user_ns != ocred->user_ns) + return false; + if (!cap_issubset(cred->cap_inheritable, ocred->cap_inheritable)) + return false; + if (!cap_issubset(cred->cap_permitted, ocred->cap_permitted)) + return false; + if (!cap_issubset(cred->cap_effective, ocred->cap_effective)) + return false; + if (!cap_issubset(cred->cap_bset, ocred->cap_bset)) + return false; + if (!cap_issubset(cred->cap_ambient, ocred->cap_ambient)) + return false; + return true; +} + + /** * aa_may_manage_policy - can the current task manage policy * @subj_cred: subjects cred * @label: label to check if it can manage policy * @ns: namespace being managed by @label (may be NULL if @label's ns) + * @ocred: object cred if request is coming from an open object * @mask: contains the policy manipulation operation being done * * Returns: 0 if the task is allowed to manipulate policy else error */ int aa_may_manage_policy(const struct cred *subj_cred, struct aa_label *label, - struct aa_ns *ns, u32 mask) + struct aa_ns *ns, const struct cred *ocred, u32 mask) { const char *op; @@ -890,6 +995,11 @@ int aa_may_manage_policy(const struct cred *subj_cred, struct aa_label *label, return audit_policy(label, op, NULL, NULL, "policy_locked", -EACCES); + if (ocred && !is_subset_of_obj_privilege(subj_cred, label, ocred)) + return audit_policy(label, op, NULL, NULL, + "not privileged for target profile", + -EACCES); + if (!aa_policy_admin_capable(subj_cred, label, ns)) return audit_policy(label, op, NULL, NULL, "not policy admin", -EACCES); @@ -1061,14 +1171,14 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, LIST_HEAD(lh); op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; - aa_get_loaddata(udata); + aa_get_profile_loaddata(udata); /* released below */ error = aa_unpack(udata, &lh, &ns_name); if (error) goto out; /* ensure that profiles are all for the same ns - * TODO: update locking to remove this constaint. All profiles in + * TODO: update locking to remove this constraint. All profiles in * the load set must succeed as a set or the load will * fail. Sort ent list and take ns locks in hierarchy order */ @@ -1088,6 +1198,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, goto fail; } ns_name = ent->ns_name; + ent->ns_name = NULL; } else count++; } @@ -1112,10 +1223,10 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, if (aa_rawdata_eq(rawdata_ent, udata)) { struct aa_loaddata *tmp; - tmp = __aa_get_loaddata(rawdata_ent); + tmp = aa_get_profile_loaddata(rawdata_ent); /* check we didn't fail the race */ if (tmp) { - aa_put_loaddata(udata); + aa_put_profile_loaddata(udata); udata = tmp; break; } @@ -1128,7 +1239,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, struct aa_profile *p; if (aa_g_export_binary) - ent->new->rawdata = aa_get_loaddata(udata); + ent->new->rawdata = aa_get_profile_loaddata(udata); error = __lookup_replace(ns, ent->new->base.hname, !(mask & AA_MAY_REPLACE_POLICY), &ent->old, &info); @@ -1261,7 +1372,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, out: aa_put_ns(ns); - aa_put_loaddata(udata); + aa_put_profile_loaddata(udata); kfree(ns_name); if (error) diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index 423227670e68..5fc16d56fbf4 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -158,7 +158,7 @@ static struct aa_perms *compute_fperms(struct aa_dfa *dfa, state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; /* DFAs are restricted from having a state_count of less than 2 */ - table = kvcalloc(state_count * 2, sizeof(struct aa_perms), GFP_KERNEL); + table = kvzalloc_objs(struct aa_perms, state_count * 2); if (!table) return NULL; *size = state_count * 2; @@ -182,7 +182,7 @@ static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch, state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen; /* DFAs are restricted from having a state_count of less than 2 */ - perms = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL); + perms = kvzalloc_objs(struct aa_perms, state_count); if (!perms) return NULL; *size = state_count; @@ -257,15 +257,21 @@ static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version, state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; /* DFAs are restricted from having a state_count of less than 2 */ - table = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL); + table = kvzalloc_objs(struct aa_perms, state_count); if (!table) return NULL; *size = state_count; /* zero init so skip the trap state (state == 0) */ - for (state = 1; state < state_count; state++) + for (state = 1; state < state_count; state++) { table[state] = compute_perms_entry(dfa, state, version); - + AA_DEBUG(DEBUG_UNPACK, + "[%d]: (0x%x/0x%x/0x%x//0x%x/0x%x//0x%x), converted from accept1: 0x%x, accept2: 0x%x", + state, table[state].allow, table[state].deny, + table[state].prompt, table[state].audit, + table[state].quiet, table[state].xindex, + ACCEPT_TABLE(dfa)[state], ACCEPT_TABLE2(dfa)[state]); + } return table; } @@ -286,10 +292,10 @@ static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor) AA_BUG(!dfa); - for (state = 0; state < state_count; state++) + for (state = 0; state < state_count; state++) { ACCEPT_TABLE(dfa)[state] = state * factor; - kvfree(dfa->tables[YYTD_ID_ACCEPT2]); - dfa->tables[YYTD_ID_ACCEPT2] = NULL; + ACCEPT_TABLE2(dfa)[state] = factor > 1 ? ACCEPT_FLAG_OWNER : 0; + } } /* TODO: merge different dfa mappings into single map_policy fn */ diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 1f02cfe1d974..5a907a875d8f 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -106,8 +106,8 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) { struct aa_ns *ns; - ns = kzalloc(sizeof(*ns), GFP_KERNEL); - AA_DEBUG("%s(%p)\n", __func__, ns); + ns = kzalloc_obj(*ns); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, ns); if (!ns) return NULL; if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL)) @@ -223,6 +223,8 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, AA_BUG(!name); AA_BUG(!mutex_is_locked(&parent->lock)); + if (parent->level > MAX_NS_DEPTH) + return ERR_PTR(-ENOSPC); ns = alloc_ns(parent->base.hname, name); if (!ns) return ERR_PTR(-ENOMEM); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 992b74c50d64..9f45d5513d2c 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -29,6 +29,7 @@ #include "include/policy.h" #include "include/policy_unpack.h" #include "include/policy_compat.h" +#include "include/signal.h" /* audit callback for unpack fields */ static void audit_cb(struct audit_buffer *ab, void *va) @@ -108,34 +109,48 @@ bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r) return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0; } +static void do_loaddata_free(struct aa_loaddata *d) +{ + kfree_sensitive(d->hash); + kfree_sensitive(d->name); + kvfree(d->data); + kfree_sensitive(d); +} + +void aa_loaddata_kref(struct kref *kref) +{ + struct aa_loaddata *d = container_of(kref, struct aa_loaddata, + count.count); + + do_loaddata_free(d); +} + /* * need to take the ns mutex lock which is NOT safe most places that * put_loaddata is called, so we have to delay freeing it */ -static void do_loaddata_free(struct work_struct *work) +static void do_ploaddata_rmfs(struct work_struct *work) { struct aa_loaddata *d = container_of(work, struct aa_loaddata, work); struct aa_ns *ns = aa_get_ns(d->ns); if (ns) { mutex_lock_nested(&ns->lock, ns->level); + /* remove fs ref to loaddata */ __aa_fs_remove_rawdata(d); mutex_unlock(&ns->lock); aa_put_ns(ns); } - - kfree_sensitive(d->hash); - kfree_sensitive(d->name); - kvfree(d->data); - kfree_sensitive(d); + /* called by dropping last pcount, so drop its associated icount */ + aa_put_i_loaddata(d); } -void aa_loaddata_kref(struct kref *kref) +void aa_ploaddata_kref(struct kref *kref) { - struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); + struct aa_loaddata *d = container_of(kref, struct aa_loaddata, pcount); if (d) { - INIT_WORK(&d->work, do_loaddata_free); + INIT_WORK(&d->work, do_ploaddata_rmfs); schedule_work(&d->work); } } @@ -144,7 +159,7 @@ struct aa_loaddata *aa_loaddata_alloc(size_t size) { struct aa_loaddata *d; - d = kzalloc(sizeof(*d), GFP_KERNEL); + d = kzalloc_obj(*d); if (d == NULL) return ERR_PTR(-ENOMEM); d->data = kvzalloc(size, GFP_KERNEL); @@ -152,7 +167,9 @@ struct aa_loaddata *aa_loaddata_alloc(size_t size) kfree(d); return ERR_PTR(-ENOMEM); } - kref_init(&d->count); + kref_init(&d->count.count); + d->count.reftype = REF_RAWDATA; + kref_init(&d->pcount); INIT_LIST_HEAD(&d->list); return d; @@ -449,20 +466,73 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags) return dfa; } +static int process_strs_entry(char *str, int size, bool multi) +{ + int c = 1; + + if (size <= 0) + return -1; + if (multi) { + if (size < 2) + return -2; + /* multi ends with double \0 */ + if (str[size - 2]) + return -3; + } + + char *save = str; + char *pos = str; + char *end = multi ? str + size - 2 : str + size - 1; + /* count # of internal \0 */ + while (str < end) { + if (str == pos) { + /* starts with ... */ + if (!*str) { + AA_DEBUG(DEBUG_UNPACK, + "starting with null save=%lu size %d c=%d", + (unsigned long)(str - save), size, c); + return -4; + } + if (isspace(*str)) + return -5; + if (*str == ':') { + /* :ns_str\0str\0 + * first character after : must be valid + */ + if (!str[1]) + return -6; + } + } else if (!*str) { + if (*pos == ':') + *str = ':'; + else + c++; + pos = str + 1; + } + str++; + } /* while */ + + return c; +} + /** - * unpack_trans_table - unpack a profile transition table + * unpack_strs_table - unpack a profile transition table * @e: serialized data extent information (NOT NULL) + * @name: name of table (MAY BE NULL) + * @multi: allow multiple strings on a single entry * @strs: str table to unpack to (NOT NULL) * - * Returns: true if table successfully unpacked or not present + * Returns: 0 if table successfully unpacked or not present, else error */ -static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) +static int unpack_strs_table(struct aa_ext *e, const char *name, bool multi, + struct aa_str_table *strs) { void *saved_pos = e->pos; - char **table = NULL; + struct aa_str_table_ent *table = NULL; + int error = -EPROTO; /* exec table is optional */ - if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) { + if (aa_unpack_nameX(e, AA_STRUCT, name)) { u16 size; int i; @@ -474,61 +544,46 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) * for size check here */ goto fail; - table = kcalloc(size, sizeof(char *), GFP_KERNEL); - if (!table) + table = kzalloc_objs(struct aa_str_table_ent, size); + if (!table) { + error = -ENOMEM; goto fail; - + } strs->table = table; strs->size = size; for (i = 0; i < size; i++) { char *str; - int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL); + int c, size2 = aa_unpack_strdup(e, &str, NULL); /* aa_unpack_strdup verifies that the last character is * null termination byte. */ - if (!size2) - goto fail; - table[i] = str; - /* verify that name doesn't start with space */ - if (isspace(*str)) + c = process_strs_entry(str, size2, multi); + if (c <= 0) { + AA_DEBUG(DEBUG_UNPACK, "process_strs %d i %d pos %ld", + c, i, + (unsigned long)(e->pos - saved_pos)); goto fail; - - /* count internal # of internal \0 */ - for (c = j = 0; j < size2 - 1; j++) { - if (!str[j]) { - pos = j; - c++; - } } - if (*str == ':') { - /* first character after : must be valid */ - if (!str[1]) - goto fail; - /* beginning with : requires an embedded \0, - * verify that exactly 1 internal \0 exists - * trailing \0 already verified by aa_unpack_strdup - * - * convert \0 back to : for label_parse - */ - if (c == 1) - str[pos] = ':'; - else if (c > 1) - goto fail; - } else if (c) + if (!multi && c > 1) { + AA_DEBUG(DEBUG_UNPACK, "!multi && c > 1"); /* fail - all other cases with embedded \0 */ goto fail; + } + table[i].strs = str; + table[i].count = c; + table[i].size = size2; } if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; } - return true; + return 0; fail: - aa_free_str_table(strs); + aa_destroy_str_table(strs); e->pos = saved_pos; - return false; + return error; } static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) @@ -572,8 +627,7 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules) if (!aa_unpack_array(e, NULL, &size)) goto fail; - rules->secmark = kcalloc(size, sizeof(struct aa_secmark), - GFP_KERNEL); + rules->secmark = kzalloc_objs(struct aa_secmark, size); if (!rules->secmark) goto fail; @@ -598,8 +652,8 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules) fail: if (rules->secmark) { for (i = 0; i < size; i++) - kfree(rules->secmark[i].label); - kfree(rules->secmark); + kfree_sensitive(rules->secmark[i].label); + kfree_sensitive(rules->secmark); rules->secmark_count = 0; rules->secmark = NULL; } @@ -643,6 +697,205 @@ fail: return false; } + +static bool verify_tags(struct aa_tags_struct *tags, const char **info) +{ + if ((tags->hdrs.size && !tags->hdrs.table) || + (!tags->hdrs.size && tags->hdrs.table)) { + *info = "failed verification tag.hdrs disagree"; + return false; + } + if ((tags->sets.size && !tags->sets.table) || + (!tags->sets.size && tags->sets.table)) { + *info = "failed verification tag.sets disagree"; + return false; + } + if ((tags->strs.size && !tags->strs.table) || + (!tags->strs.size && tags->strs.table)) { + *info = "failed verification tags->strs disagree"; + return false; + } + /* no data present */ + if (!tags->sets.size && !tags->hdrs.size && !tags->strs.size) { + return true; + } else if (!(tags->sets.size && tags->hdrs.size && tags->strs.size)) { + /* some data present but not all */ + *info = "failed verification tags partial data present"; + return false; + } + + u32 i; + + for (i = 0; i < tags->sets.size; i++) { + /* count followed by count indexes into hdrs */ + u32 cnt = tags->sets.table[i]; + + if (i+cnt >= tags->sets.size) { + AA_DEBUG(DEBUG_UNPACK, + "tagset too large %d+%d > sets.table[%d]", + i, cnt, tags->sets.size); + *info = "failed verification tagset too large"; + return false; + } + for (; cnt; cnt--) { + if (tags->sets.table[++i] >= tags->hdrs.size) { + AA_DEBUG(DEBUG_UNPACK, + "tagsets idx out of bounds cnt %d sets.table[%d] >= %d", + cnt, i-1, tags->hdrs.size); + *info = "failed verification tagsets idx out of bounds"; + return false; + } + } + } + for (i = 0; i < tags->hdrs.size; i++) { + u32 idx = tags->hdrs.table[i].tags; + + if (idx >= tags->strs.size) { + AA_DEBUG(DEBUG_UNPACK, + "tag.hdrs idx oob idx %d > tags->strs.size=%d", + idx, tags->strs.size); + *info = "failed verification tags.hdrs idx out of bounds"; + return false; + } + if (tags->hdrs.table[i].count != tags->strs.table[idx].count) { + AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].count=%d != tags->strs.table[%d]=%d", + i, tags->hdrs.table[i].count, idx, tags->strs.table[idx].count); + *info = "failed verification tagd.hdrs[idx].count"; + return false; + } + if (tags->hdrs.table[i].size != tags->strs.table[idx].size) { + AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].size=%d != strs.table[%d].size=%d", + i, tags->hdrs.table[i].size, idx, tags->strs.table[idx].size); + *info = "failed verification tagd.hdrs[idx].size"; + return false; + } + } + + return true; +} + +static int unpack_tagsets(struct aa_ext *e, struct aa_tags_struct *tags) +{ + u32 *sets; + u16 i, size; + int error = -EPROTO; + void *pos = e->pos; + + if (!aa_unpack_array(e, "sets", &size)) + goto fail_reset; + sets = kcalloc(size, sizeof(u32), GFP_KERNEL); + if (!sets) { + error = -ENOMEM; + goto fail_reset; + } + for (i = 0; i < size; i++) { + if (!aa_unpack_u32(e, &sets[i], NULL)) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + + tags->sets.size = size; + tags->sets.table = sets; + + return 0; + +fail: + kfree_sensitive(sets); +fail_reset: + e->pos = pos; + return error; +} + +static bool unpack_tag_header_ent(struct aa_ext *e, struct aa_tags_header *h) +{ + return aa_unpack_u32(e, &h->mask, NULL) && + aa_unpack_u32(e, &h->count, NULL) && + aa_unpack_u32(e, &h->size, NULL) && + aa_unpack_u32(e, &h->tags, NULL); +} + +static int unpack_tag_headers(struct aa_ext *e, struct aa_tags_struct *tags) +{ + struct aa_tags_header *hdrs; + u16 i, size; + int error = -EPROTO; + void *pos = e->pos; + + if (!aa_unpack_array(e, "hdrs", &size)) + goto fail_reset; + hdrs = kzalloc_objs(struct aa_tags_header, size); + if (!hdrs) { + error = -ENOMEM; + goto fail_reset; + } + for (i = 0; i < size; i++) { + if (!unpack_tag_header_ent(e, &hdrs[i])) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + + tags->hdrs.size = size; + tags->hdrs.table = hdrs; + AA_DEBUG(DEBUG_UNPACK, "headers %ld size %d", (long) hdrs, size); + return true; + +fail: + kfree_sensitive(hdrs); +fail_reset: + e->pos = pos; + return error; +} + + +static int unpack_tags(struct aa_ext *e, struct aa_tags_struct *tags, + const char **info) +{ + int error = -EPROTO; + void *pos = e->pos; + + AA_BUG(!tags); + /* policy tags are optional */ + if (aa_unpack_nameX(e, AA_STRUCT, "tags")) { + u32 version; + + if (!aa_unpack_u32(e, &version, "version") || version != 1) { + *info = "invalid tags version"; + goto fail_reset; + } + error = unpack_strs_table(e, "strs", true, &tags->strs); + if (error) { + *info = "failed to unpack profile tag.strs"; + goto fail; + } + error = unpack_tag_headers(e, tags); + if (error) { + *info = "failed to unpack profile tag.headers"; + goto fail; + } + error = unpack_tagsets(e, tags); + if (error) { + *info = "failed to unpack profile tag.sets"; + goto fail; + } + error = -EPROTO; + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + + if (!verify_tags(tags, info)) + goto fail; + } + + return 0; + +fail: + aa_destroy_tags(tags); +fail_reset: + e->pos = pos; + return error; +} + static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm) { u32 reserved; @@ -685,9 +938,11 @@ static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms) goto fail_reset; if (!aa_unpack_array(e, NULL, &size)) goto fail_reset; - *perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL); - if (!*perms) - goto fail_reset; + *perms = kzalloc_objs(struct aa_perms, size); + if (!*perms) { + e->pos = pos; + return -ENOMEM; + } for (i = 0; i < size; i++) { if (!unpack_perm(e, version, &(*perms)[i])) goto fail; @@ -716,11 +971,17 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, void *pos = e->pos; int i, flags, error = -EPROTO; ssize_t size; + u32 version = 0; pdb = aa_alloc_pdb(GFP_KERNEL); if (!pdb) return -ENOMEM; + AA_DEBUG(DEBUG_UNPACK, "unpacking tags"); + if (unpack_tags(e, &pdb->tags, info) < 0) + goto fail; + AA_DEBUG(DEBUG_UNPACK, "done unpacking tags"); + size = unpack_perms_table(e, &pdb->perms); if (size < 0) { error = size; @@ -733,6 +994,9 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, if (pdb->perms) { /* perms table present accept is index */ flags = TO_ACCEPT1_FLAG(YYTD_DATA32); + if (aa_unpack_u32(e, &version, "permsv") && version > 2) + /* accept2 used for dfa flags */ + flags |= TO_ACCEPT2_FLAG(YYTD_DATA32); } else { /* packed perms in accept1 and accept2 */ flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | @@ -763,29 +1027,59 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, if (!aa_unpack_u32(e, &pdb->start[AA_CLASS_FILE], "dfa_start")) { /* default start state for xmatch and file dfa */ pdb->start[AA_CLASS_FILE] = DFA_START; - } /* setup class index */ + } + + size_t state_count = pdb->dfa->tables[YYTD_ID_BASE]->td_lolen; + + if (pdb->start[0] >= state_count || + pdb->start[AA_CLASS_FILE] >= state_count) { + *info = "invalid dfa start state"; + goto fail; + } + + /* setup class index */ for (i = AA_CLASS_FILE + 1; i <= AA_CLASS_LAST; i++) { pdb->start[i] = aa_dfa_next(pdb->dfa, pdb->start[0], i); } } + /* accept2 is in some cases being allocated, even with perms */ + if (pdb->perms && !pdb->dfa->tables[YYTD_ID_ACCEPT2]) { + /* add dfa flags table missing in v2 */ + u32 noents = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_lolen; + u16 tdflags = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_flags; + size_t tsize = table_size(noents, tdflags); + + pdb->dfa->tables[YYTD_ID_ACCEPT2] = kvzalloc(tsize, GFP_KERNEL); + if (!pdb->dfa->tables[YYTD_ID_ACCEPT2]) { + *info = "failed to alloc dfa flags table"; + goto out; + } + pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_lolen = noents; + pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_flags = tdflags; + } /* * Unfortunately due to a bug in earlier userspaces, a * transition table may be present even when the dfa is * not. For compatibility reasons unpack and discard. */ - if (!unpack_trans_table(e, &pdb->trans) && required_trans) { + error = unpack_strs_table(e, "xtable", false, &pdb->trans); + if (error && required_trans) { *info = "failed to unpack profile transition table"; goto fail; } if (!pdb->dfa && pdb->trans.table) - aa_free_str_table(&pdb->trans); - - /* TODO: move compat mapping here, requires dfa merging first */ - /* TODO: move verify here, it has to be done after compat mappings */ + aa_destroy_str_table(&pdb->trans); + /* TODO: + * - move compat mapping here, requires dfa merging first + * - move verify here, it has to be done after compat mappings + * - move free of unneeded trans table here, has to be done + * after perm mapping. + */ +out: *policy = pdb; return 0; @@ -862,7 +1156,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) error = -ENOMEM; goto fail; } - rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules = profile->label.rules[0]; /* profile renaming is optional */ (void) aa_unpack_str(e, &profile->rename, "rename"); @@ -898,6 +1192,12 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) (void) aa_unpack_strdup(e, &disconnected, "disconnected"); profile->disconnected = disconnected; + /* optional */ + (void) aa_unpack_u32(e, &profile->signal, "kill"); + if (profile->signal < 1 || profile->signal > MAXMAPPED_SIG) { + info = "profile kill.signal invalid value"; + goto fail; + } /* per profile debug flags (complain, audit) */ if (!aa_unpack_nameX(e, AA_STRUCT, "flags")) { info = "profile missing flags"; @@ -1028,6 +1328,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) goto fail; } else if (rules->file->dfa) { if (!rules->file->perms) { + AA_DEBUG(DEBUG_UNPACK, "compat mapping perms"); error = aa_compat_map_file(rules->file); if (error) { info = "failed to remap file permission table"; @@ -1045,7 +1346,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) error = -EPROTO; if (aa_unpack_nameX(e, AA_STRUCT, "data")) { info = "out of memory"; - profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL); + profile->data = kzalloc_obj(*profile->data); if (!profile->data) { error = -ENOMEM; goto fail; @@ -1063,7 +1364,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) } while (aa_unpack_strdup(e, &key, NULL)) { - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = kzalloc_obj(*data); if (!data) { kfree_sensitive(key); error = -ENOMEM; @@ -1101,6 +1402,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) goto fail; } + aa_compute_profile_mediates(profile); + return profile; fail: @@ -1133,7 +1436,6 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) { int error = -EPROTONOSUPPORT; const char *name = NULL; - *ns = NULL; /* get the interface version */ if (!aa_unpack_u32(e, &e->version, "version")) { @@ -1164,6 +1466,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) if (*ns && strcmp(*ns, name)) { audit_iface(NULL, NULL, NULL, "invalid ns change", e, error); + return error; } else if (!*ns) { *ns = kstrdup(name, GFP_KERNEL); if (!*ns) @@ -1215,21 +1518,32 @@ static bool verify_perm(struct aa_perms *perm) static bool verify_perms(struct aa_policydb *pdb) { int i; + int xidx, xmax = -1; for (i = 0; i < pdb->size; i++) { if (!verify_perm(&pdb->perms[i])) return false; /* verify indexes into str table */ - if ((pdb->perms[i].xindex & AA_X_TYPE_MASK) == AA_X_TABLE && - (pdb->perms[i].xindex & AA_X_INDEX_MASK) >= pdb->trans.size) - return false; - if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->trans.size) + if ((pdb->perms[i].xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + xidx = pdb->perms[i].xindex & AA_X_INDEX_MASK; + if (xidx >= pdb->trans.size) + return false; + if (xmax < xidx) + xmax = xidx; + } + if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->tags.sets.size) return false; if (pdb->perms[i].label && pdb->perms[i].label >= pdb->trans.size) return false; } - + /* deal with incorrectly constructed string tables */ + if (xmax == -1) { + aa_destroy_str_table(&pdb->trans); + } else if (pdb->trans.size > xmax + 1) { + if (!aa_resize_str_table(&pdb->trans, xmax + 1, GFP_KERNEL)) + return false; + } return true; } @@ -1243,8 +1557,8 @@ static bool verify_perms(struct aa_policydb *pdb) */ static int verify_profile(struct aa_profile *profile) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; + if (!rules) return 0; @@ -1295,7 +1609,7 @@ void aa_load_ent_free(struct aa_load_ent *ent) struct aa_load_ent *aa_load_ent_alloc(void) { - struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL); + struct aa_load_ent *ent = kzalloc_obj(*ent); if (ent) INIT_LIST_HEAD(&ent->list); return ent; diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index 5b2ba88ae9e2..cf18744dafe2 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -9,6 +9,8 @@ #include "include/policy.h" #include "include/policy_unpack.h" +#include <linux/unaligned.h> + #define TEST_STRING_NAME "TEST_STRING" #define TEST_STRING_DATA "testing" #define TEST_STRING_BUF_OFFSET \ @@ -80,7 +82,7 @@ static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf, *(buf + 1) = strlen(TEST_U32_NAME) + 1; strscpy(buf + 3, TEST_U32_NAME, e->end - (void *)(buf + 3)); *(buf + 3 + strlen(TEST_U32_NAME) + 1) = AA_U32; - *((__le32 *)(buf + 3 + strlen(TEST_U32_NAME) + 2)) = cpu_to_le32(TEST_U32_DATA); + put_unaligned_le32(TEST_U32_DATA, buf + 3 + strlen(TEST_U32_NAME) + 2); buf = e->start + TEST_NAMED_U64_BUF_OFFSET; *buf = AA_NAME; @@ -103,7 +105,7 @@ static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf, *(buf + 1) = strlen(TEST_ARRAY_NAME) + 1; strscpy(buf + 3, TEST_ARRAY_NAME, e->end - (void *)(buf + 3)); *(buf + 3 + strlen(TEST_ARRAY_NAME) + 1) = AA_ARRAY; - *((__le16 *)(buf + 3 + strlen(TEST_ARRAY_NAME) + 2)) = cpu_to_le16(TEST_ARRAY_SIZE); + put_unaligned_le16(TEST_ARRAY_SIZE, buf + 3 + strlen(TEST_ARRAY_NAME) + 2); return e; } diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index e3857e3d7c6c..ce40f15d4952 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -125,12 +125,14 @@ int aa_setprocattr_changehat(char *args, size_t size, int flags) for (count = 0; (hat < end) && count < 16; ++count) { char *next = hat + strlen(hat) + 1; hats[count] = hat; - AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n" + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d hat '%s'\n" , __func__, current->pid, token, count, hat); hat = next; } } else - AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", __func__, current->pid, token, count, "<NULL>"); return aa_change_hat(hats, count, token, flags); diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index dcc94c3153d5..64212b39ba4b 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -89,8 +89,7 @@ static int profile_setrlimit(const struct cred *subj_cred, struct aa_profile *profile, unsigned int resource, struct rlimit *new_rlim) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int e = 0; if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max > @@ -165,9 +164,7 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) * to the lesser of the tasks hard limit and the init tasks soft limit */ label_for_each_confined(i, old_l, old) { - struct aa_ruleset *rules = list_first_entry(&old->rules, - typeof(*rules), - list); + struct aa_ruleset *rules = old->label.rules[0]; if (rules->rlimits.mask) { int j; @@ -185,9 +182,7 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) /* set any new hard limits as dictated by the new profile */ label_for_each_confined(i, new_l, new) { - struct aa_ruleset *rules = list_first_entry(&new->rules, - typeof(*rules), - list); + struct aa_ruleset *rules = new->label.rules[0]; int j; if (!rules->rlimits.mask) @@ -201,6 +196,11 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) rules->rlimits.limits[j].rlim_max); /* soft limit should not exceed hard limit */ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + if (j == RLIMIT_CPU && + rlim->rlim_cur != RLIM_INFINITY && + IS_ENABLED(CONFIG_POSIX_TIMERS)) + (void) update_rlimit_cpu(current->group_leader, + rlim->rlim_cur); } } } diff --git a/security/apparmor/task.c b/security/apparmor/task.c index c87fb9f4ac18..0db0e81b4600 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -15,6 +15,7 @@ #include <linux/gfp.h> #include <linux/ptrace.h> +#include "include/path.h" #include "include/audit.h" #include "include/cred.h" #include "include/policy.h" @@ -228,8 +229,7 @@ static int profile_ptrace_perm(const struct cred *cred, struct aa_label *peer, u32 request, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms = { }; ad->subj_cred = cred; @@ -246,7 +246,7 @@ static int profile_tracee_perm(const struct cred *cred, struct apparmor_audit_data *ad) { if (profile_unconfined(tracee) || unconfined(tracer) || - !ANY_RULE_MEDIATES(&tracee->rules, AA_CLASS_PTRACE)) + !label_mediates(&tracee->label, AA_CLASS_PTRACE)) return 0; return profile_ptrace_perm(cred, tracee, tracer, request, ad); @@ -260,7 +260,7 @@ static int profile_tracer_perm(const struct cred *cred, if (profile_unconfined(tracer)) return 0; - if (ANY_RULE_MEDIATES(&tracer->rules, AA_CLASS_PTRACE)) + if (label_mediates(&tracer->label, AA_CLASS_PTRACE)) return profile_ptrace_perm(cred, tracer, tracee, request, ad); /* profile uses the old style capability check for ptrace */ @@ -301,16 +301,47 @@ int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer, xrequest, &sa)); } +static const char *get_current_exe_path(char *buffer, int buffer_size) +{ + struct file *exe_file; + struct path p; + const char *path_str; + + exe_file = get_task_exe_file(current); + if (!exe_file) + return ERR_PTR(-ENOENT); + p = exe_file->f_path; + path_get(&p); + + if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL)) + return ERR_PTR(-ENOMEM); + + fput(exe_file); + path_put(&p); + + return path_str; +} + /* call back to audit ptrace fields */ static void audit_ns_cb(struct audit_buffer *ab, void *va) { struct apparmor_audit_data *ad = aad_of_va(va); + char *buffer; + const char *path; if (ad->request & AA_USERNS_CREATE) audit_log_format(ab, " requested=\"userns_create\""); if (ad->denied & AA_USERNS_CREATE) audit_log_format(ab, " denied=\"userns_create\""); + + buffer = aa_get_buffer(false); + if (!buffer) + return; // OOM + path = get_current_exe_path(buffer, aa_g_path_max); + if (!IS_ERR(path)) + audit_log_format(ab, " execpath=\"%s\"", path); + aa_put_buffer(buffer); } int aa_profile_ns_perm(struct aa_profile *profile, @@ -324,9 +355,7 @@ int aa_profile_ns_perm(struct aa_profile *profile, ad->request = request; if (!profile_unconfined(profile)) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), - list); + struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state; state = RULE_MEDIATES(rules, ad->class); diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c index db759025abe1..40efde233f3a 100644 --- a/security/bpf/hooks.c +++ b/security/bpf/hooks.c @@ -33,7 +33,7 @@ struct lsm_blob_sizes bpf_lsm_blob_sizes __ro_after_init = { }; DEFINE_LSM(bpf) = { - .name = "bpf", + .id = &bpf_lsmid, .init = bpf_lsm_init, .blobs = &bpf_lsm_blob_sizes }; diff --git a/security/commoncap.c b/security/commoncap.c index 58a0c1c3e409..3399535808fe 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -115,10 +115,11 @@ static inline int cap_capable_helper(const struct cred *cred, * Determine whether the nominated task has the specified capability amongst * its effective set, returning 0 if it does, -ve if it does not. * - * NOTE WELL: cap_has_capability() cannot be used like the kernel's capable() - * and has_capability() functions. That is, it has the reverse semantics: - * cap_has_capability() returns 0 when a task has a capability, but the - * kernel's capable() and has_capability() returns 1 for this case. + * NOTE WELL: cap_capable() has reverse semantics to the capable() call + * and friends. That is cap_capable() returns an int 0 when a task has + * a capability, while the kernel's capable(), has_ns_capability(), + * has_ns_capability_noaudit(), and has_capability_noaudit() return a + * bool true (1) for this case. */ int cap_capable(const struct cred *cred, struct user_namespace *target_ns, int cap, unsigned int opts) @@ -357,17 +358,17 @@ int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry) return error; } -static bool rootid_owns_currentns(vfsuid_t rootvfsuid) +/** + * kuid_root_in_ns - check whether the given kuid is root in the given ns + * @kuid: the kuid to be tested + * @ns: the user namespace to test against + * + * Returns true if @kuid represents the root user in @ns, false otherwise. + */ +static bool kuid_root_in_ns(kuid_t kuid, struct user_namespace *ns) { - struct user_namespace *ns; - kuid_t kroot; - - if (!vfsuid_valid(rootvfsuid)) - return false; - - kroot = vfsuid_into_kuid(rootvfsuid); - for (ns = current_user_ns();; ns = ns->parent) { - if (from_kuid(ns, kroot) == 0) + for (;; ns = ns->parent) { + if (from_kuid(ns, kuid) == 0) return true; if (ns == &init_user_ns) break; @@ -376,6 +377,16 @@ static bool rootid_owns_currentns(vfsuid_t rootvfsuid) return false; } +static bool vfsuid_root_in_currentns(vfsuid_t vfsuid) +{ + kuid_t kuid; + + if (!vfsuid_valid(vfsuid)) + return false; + kuid = vfsuid_into_kuid(vfsuid); + return kuid_root_in_ns(kuid, current_user_ns()); +} + static __u32 sansflags(__u32 m) { return m & ~VFS_CAP_FLAGS_EFFECTIVE; @@ -480,7 +491,7 @@ int cap_inode_getsecurity(struct mnt_idmap *idmap, goto out_free; } - if (!rootid_owns_currentns(vfsroot)) { + if (!vfsuid_root_in_currentns(vfsroot)) { size = -EOVERFLOW; goto out_free; } @@ -721,7 +732,7 @@ int get_vfs_caps_from_disk(struct mnt_idmap *idmap, /* Limit the caps to the mounter of the filesystem * or the more limited uid specified in the xattr. */ - if (!rootid_owns_currentns(rootvfsuid)) + if (!vfsuid_root_in_currentns(rootvfsuid)) return -ENODATA; cpu_caps->permitted.val = le32_to_cpu(caps->data[0].permitted); @@ -855,12 +866,6 @@ static void handle_privileged_root(struct linux_binprm *bprm, bool has_fcap, #define __cap_full(field, cred) \ cap_issubset(CAP_FULL_SET, cred->cap_##field) -static inline bool __is_setuid(struct cred *new, const struct cred *old) -{ return !uid_eq(new->euid, old->uid); } - -static inline bool __is_setgid(struct cred *new, const struct cred *old) -{ return !gid_eq(new->egid, old->gid); } - /* * 1) Audit candidate if current->cap_effective is set * @@ -890,7 +895,7 @@ static inline bool nonroot_raised_pE(struct cred *new, const struct cred *old, (root_privileged() && __is_suid(root, new) && !__cap_full(effective, new)) || - (!__is_setuid(new, old) && + (uid_eq(new->euid, old->euid) && ((has_fcap && __cap_gained(permitted, new, old)) || __cap_gained(ambient, new, old)))) @@ -916,7 +921,7 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file) /* Process setpcap binaries and capabilities for uid 0 */ const struct cred *old = current_cred(); struct cred *new = bprm->cred; - bool effective = false, has_fcap = false, is_setid; + bool effective = false, has_fcap = false, id_changed; int ret; kuid_t root_uid; @@ -940,9 +945,9 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file) * * In addition, if NO_NEW_PRIVS, then ensure we get no new privs. */ - is_setid = __is_setuid(new, old) || __is_setgid(new, old); + id_changed = !uid_eq(new->euid, old->euid) || !in_group_p(new->egid); - if ((is_setid || __cap_gained(permitted, new, old)) && + if ((id_changed || __cap_gained(permitted, new, old)) && ((bprm->unsafe & ~LSM_UNSAFE_PTRACE) || !ptracer_capable(current, new->user_ns))) { /* downgrade; they get no more than they had, and maybe less */ @@ -959,7 +964,7 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file) new->sgid = new->fsgid = new->egid; /* File caps or setid cancels ambient. */ - if (has_fcap || is_setid) + if (has_fcap || id_changed) cap_clear(new->cap_ambient); /* @@ -992,7 +997,9 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file) return -EPERM; /* Check for privilege-elevated exec. */ - if (is_setid || + if (id_changed || + !uid_eq(new->euid, old->uid) || + !gid_eq(new->egid, old->gid) || (!__is_real(root_uid, new) && (effective || __cap_grew(permitted, ambient, new)))) @@ -1508,9 +1515,13 @@ static int __init capability_init(void) } DEFINE_LSM(capability) = { - .name = "capability", + .id = &capability_lsmid, .order = LSM_ORDER_FIRST, .init = capability_init, }; #endif /* CONFIG_SECURITY */ + +#ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST +#include "commoncap_test.c" +#endif diff --git a/security/commoncap_test.c b/security/commoncap_test.c new file mode 100644 index 000000000000..e9b278be37f1 --- /dev/null +++ b/security/commoncap_test.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KUnit tests for commoncap.c security functions + * + * Tests for security-critical functions in the capability subsystem, + * particularly namespace-related capability checks. + */ + +#include <kunit/test.h> +#include <linux/user_namespace.h> +#include <linux/uidgid.h> +#include <linux/cred.h> +#include <linux/mnt_idmapping.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/refcount.h> + +#ifdef CONFIG_SECURITY_COMMONCAP_KUNIT_TEST + +/* Functions are static in commoncap.c, but we can call them since we're + * included in the same compilation unit when tests are enabled. + */ + +/** + * test_vfsuid_root_in_currentns_init_ns - Test vfsuid_root_in_currentns with init ns + * + * Verifies that UID 0 in the init namespace correctly owns the current + * namespace when running in init_user_ns. + * + * @test: KUnit test context + */ +static void test_vfsuid_root_in_currentns_init_ns(struct kunit *test) +{ + vfsuid_t vfsuid; + kuid_t kuid; + + /* Create UID 0 in init namespace */ + kuid = KUIDT_INIT(0); + vfsuid = VFSUIDT_INIT(kuid); + + /* In init namespace, UID 0 should own current namespace */ + KUNIT_EXPECT_TRUE(test, vfsuid_root_in_currentns(vfsuid)); +} + +/** + * test_vfsuid_root_in_currentns_invalid - Test vfsuid_root_in_currentns with invalid vfsuid + * + * Verifies that an invalid vfsuid correctly returns false. + * + * @test: KUnit test context + */ +static void test_vfsuid_root_in_currentns_invalid(struct kunit *test) +{ + vfsuid_t invalid_vfsuid; + + /* Use the predefined invalid vfsuid */ + invalid_vfsuid = INVALID_VFSUID; + + /* Invalid vfsuid should return false */ + KUNIT_EXPECT_FALSE(test, vfsuid_root_in_currentns(invalid_vfsuid)); +} + +/** + * test_vfsuid_root_in_currentns_nonzero - Test vfsuid_root_in_currentns with non-zero UID + * + * Verifies that a non-zero UID correctly returns false. + * + * @test: KUnit test context + */ +static void test_vfsuid_root_in_currentns_nonzero(struct kunit *test) +{ + vfsuid_t vfsuid; + kuid_t kuid; + + /* Create a non-zero UID */ + kuid = KUIDT_INIT(1000); + vfsuid = VFSUIDT_INIT(kuid); + + /* Non-zero UID should return false */ + KUNIT_EXPECT_FALSE(test, vfsuid_root_in_currentns(vfsuid)); +} + +/** + * test_kuid_root_in_ns_init_ns_uid0 - Test kuid_root_in_ns with init namespace and UID 0 + * + * Verifies that kuid_root_in_ns correctly identifies UID 0 in init namespace. + * This tests the core namespace traversal logic. In init namespace, UID 0 + * maps to itself, so it should own the namespace. + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_init_ns_uid0(struct kunit *test) +{ + kuid_t kuid; + struct user_namespace *init_ns; + + kuid = KUIDT_INIT(0); + init_ns = &init_user_ns; + + /* UID 0 should own init namespace */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(kuid, init_ns)); +} + +/** + * test_kuid_root_in_ns_init_ns_nonzero - Test kuid_root_in_ns with init namespace and non-zero UID + * + * Verifies that kuid_root_in_ns correctly rejects non-zero UIDs in init namespace. + * Only UID 0 should own a namespace. + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_init_ns_nonzero(struct kunit *test) +{ + kuid_t kuid; + struct user_namespace *init_ns; + + kuid = KUIDT_INIT(1000); + init_ns = &init_user_ns; + + /* Non-zero UID should not own namespace */ + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(kuid, init_ns)); +} + +/** + * create_test_user_ns_with_mapping - Create a mock user namespace with UID mapping + * + * Creates a minimal user namespace structure for testing where uid 0 in the + * namespace maps to a specific kuid in the parent namespace. + * + * @test: KUnit test context + * @parent_ns: Parent namespace (typically init_user_ns) + * @mapped_kuid: The kuid that uid 0 in this namespace maps to in parent + * + * Returns: Pointer to allocated namespace, or NULL on failure + */ +static struct user_namespace *create_test_user_ns_with_mapping(struct kunit *test, + struct user_namespace *parent_ns, + kuid_t mapped_kuid) +{ + struct user_namespace *ns; + struct uid_gid_extent extent; + + /* Allocate a test namespace - use kzalloc to zero all fields */ + ns = kunit_kzalloc(test, sizeof(*ns), GFP_KERNEL); + if (!ns) + return NULL; + + /* Initialize basic namespace structure fields */ + ns->parent = parent_ns; + ns->level = parent_ns ? parent_ns->level + 1 : 0; + ns->owner = mapped_kuid; + ns->group = KGIDT_INIT(0); + + /* Initialize ns_common structure */ + refcount_set(&ns->ns.__ns_ref, 1); + ns->ns.inum = 0; /* Mock inum */ + + /* Set up uid mapping: uid 0 in this namespace maps to mapped_kuid in parent + * Format: first (uid in ns) : lower_first (kuid in parent) : count + * So: uid 0 in ns -> kuid mapped_kuid in parent + * This means from_kuid(ns, mapped_kuid) returns 0 + */ + extent.first = 0; /* uid 0 in this namespace */ + extent.lower_first = __kuid_val(mapped_kuid); /* maps to this kuid in parent */ + extent.count = 1; + + ns->uid_map.extent[0] = extent; + ns->uid_map.nr_extents = 1; + + /* Set up gid mapping: gid 0 maps to gid 0 in parent (simplified) */ + extent.first = 0; + extent.lower_first = 0; + extent.count = 1; + + ns->gid_map.extent[0] = extent; + ns->gid_map.nr_extents = 1; + + return ns; +} + +/** + * test_kuid_root_in_ns_with_mapping - Test kuid_root_in_ns with namespace where uid 0 + * maps to different kuid + * + * Creates a user namespace where uid 0 maps to kuid 1000 in the parent namespace. + * Verifies that kuid_root_in_ns correctly identifies kuid 1000 as owning the namespace. + * + * Note: kuid_root_in_ns walks up the namespace hierarchy, so it checks the current + * namespace first, then parent, then parent's parent, etc. So: + * - kuid 1000 owns test_ns because from_kuid(test_ns, 1000) == 0 + * - kuid 0 also owns test_ns because from_kuid(init_user_ns, 0) == 0 + * (checked in parent) + * + * This tests the actual functionality as requested: creating namespaces with + * different values for the namespace's uid 0. + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_with_mapping(struct kunit *test) +{ + struct user_namespace *test_ns; + struct user_namespace *parent_ns; + kuid_t mapped_kuid, other_kuid; + + parent_ns = &init_user_ns; + mapped_kuid = KUIDT_INIT(1000); + other_kuid = KUIDT_INIT(2000); + + test_ns = create_test_user_ns_with_mapping(test, parent_ns, mapped_kuid); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, test_ns); + + /* kuid 1000 should own test_ns because it maps to uid 0 in test_ns */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(mapped_kuid, test_ns)); + + /* kuid 0 should also own test_ns (checked via parent init_user_ns) */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), test_ns)); + + /* Other kuids should not own test_ns */ + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(other_kuid, test_ns)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(500), test_ns)); +} + +/** + * test_kuid_root_in_ns_with_different_mappings - Test with multiple namespaces + * + * Creates multiple user namespaces with different UID mappings to verify + * that kuid_root_in_ns correctly distinguishes between namespaces. + * + * Each namespace maps uid 0 to a different kuid, and we verify that each + * kuid only owns its corresponding namespace (plus kuid 0 owns all via + * init_user_ns parent). + * + * @test: KUnit test context + */ +static void test_kuid_root_in_ns_with_different_mappings(struct kunit *test) +{ + struct user_namespace *ns1, *ns2, *ns3; + + /* Create three independent namespaces, each mapping uid 0 to different kuids */ + ns1 = create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(1000)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns1); + + ns2 = create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(2000)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns2); + + ns3 = create_test_user_ns_with_mapping(test, &init_user_ns, KUIDT_INIT(3000)); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ns3); + + /* Test ns1: kuid 1000 owns it, kuid 0 owns it (via parent), others do not */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns1)); + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns1)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns1)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(3000), ns1)); + + /* Test ns2: kuid 2000 owns it, kuid 0 owns it (via parent), others do not */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns2)); + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns2)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns2)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(3000), ns2)); + + /* Test ns3: kuid 3000 owns it, kuid 0 owns it (via parent), others do not */ + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(3000), ns3)); + KUNIT_EXPECT_TRUE(test, kuid_root_in_ns(KUIDT_INIT(0), ns3)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(1000), ns3)); + KUNIT_EXPECT_FALSE(test, kuid_root_in_ns(KUIDT_INIT(2000), ns3)); +} + +static struct kunit_case commoncap_test_cases[] = { + KUNIT_CASE(test_vfsuid_root_in_currentns_init_ns), + KUNIT_CASE(test_vfsuid_root_in_currentns_invalid), + KUNIT_CASE(test_vfsuid_root_in_currentns_nonzero), + KUNIT_CASE(test_kuid_root_in_ns_init_ns_uid0), + KUNIT_CASE(test_kuid_root_in_ns_init_ns_nonzero), + KUNIT_CASE(test_kuid_root_in_ns_with_mapping), + KUNIT_CASE(test_kuid_root_in_ns_with_different_mappings), + {} +}; + +static struct kunit_suite commoncap_test_suite = { + .name = "commoncap", + .test_cases = commoncap_test_cases, +}; + +kunit_test_suite(commoncap_test_suite); + +MODULE_LICENSE("GPL"); + +#endif /* CONFIG_SECURITY_COMMONCAP_KUNIT_TEST */ diff --git a/security/device_cgroup.c b/security/device_cgroup.c index dc4df7475081..cd166dee66b7 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -223,7 +223,7 @@ devcgroup_css_alloc(struct cgroup_subsys_state *parent_css) { struct dev_cgroup *dev_cgroup; - dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); + dev_cgroup = kzalloc_obj(*dev_cgroup); if (!dev_cgroup) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&dev_cgroup->exceptions); @@ -244,45 +244,40 @@ static void devcgroup_css_free(struct cgroup_subsys_state *css) #define DEVCG_DENY 2 #define DEVCG_LIST 3 -#define MAJMINLEN 13 -#define ACCLEN 4 - -static void set_access(char *acc, short access) +static void seq_putaccess(struct seq_file *m, short access) { - int idx = 0; - memset(acc, 0, ACCLEN); if (access & DEVCG_ACC_READ) - acc[idx++] = 'r'; + seq_putc(m, 'r'); if (access & DEVCG_ACC_WRITE) - acc[idx++] = 'w'; + seq_putc(m, 'w'); if (access & DEVCG_ACC_MKNOD) - acc[idx++] = 'm'; + seq_putc(m, 'm'); } -static char type_to_char(short type) +static void seq_puttype(struct seq_file *m, short type) { if (type == DEVCG_DEV_ALL) - return 'a'; - if (type == DEVCG_DEV_CHAR) - return 'c'; - if (type == DEVCG_DEV_BLOCK) - return 'b'; - return 'X'; + seq_putc(m, 'a'); + else if (type == DEVCG_DEV_CHAR) + seq_putc(m, 'c'); + else if (type == DEVCG_DEV_BLOCK) + seq_putc(m, 'b'); + else + seq_putc(m, 'X'); } -static void set_majmin(char *str, unsigned m) +static void seq_putversion(struct seq_file *m, unsigned int version) { - if (m == ~0) - strcpy(str, "*"); + if (version == ~0) + seq_putc(m, '*'); else - sprintf(str, "%u", m); + seq_printf(m, "%u", version); } static int devcgroup_seq_show(struct seq_file *m, void *v) { struct dev_cgroup *devcgroup = css_to_devcgroup(seq_css(m)); struct dev_exception_item *ex; - char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; rcu_read_lock(); /* @@ -292,18 +287,17 @@ static int devcgroup_seq_show(struct seq_file *m, void *v) * This way, the file remains as a "whitelist of devices" */ if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { - set_access(acc, DEVCG_ACC_MASK); - set_majmin(maj, ~0); - set_majmin(min, ~0); - seq_printf(m, "%c %s:%s %s\n", type_to_char(DEVCG_DEV_ALL), - maj, min, acc); + seq_puts(m, "a *:* rwm\n"); } else { list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) { - set_access(acc, ex->access); - set_majmin(maj, ex->major); - set_majmin(min, ex->minor); - seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type), - maj, min, acc); + seq_puttype(m, ex->type); + seq_putc(m, ' '); + seq_putversion(m, ex->major); + seq_putc(m, ':'); + seq_putversion(m, ex->minor); + seq_putc(m, ' '); + seq_putaccess(m, ex->access); + seq_putc(m, '\n'); } } rcu_read_unlock(); diff --git a/security/inode.c b/security/inode.c index da3ab44c8e57..080402367674 100644 --- a/security/inode.c +++ b/security/inode.c @@ -22,13 +22,15 @@ #include <linux/lsm_hooks.h> #include <linux/magic.h> +#include "lsm.h" + static struct vfsmount *mount; static int mount_count; static void securityfs_free_inode(struct inode *inode) { if (S_ISLNK(inode->i_mode)) - kfree(inode->i_link); + kfree_const(inode->i_link); free_inode_nonrcu(inode); } @@ -70,7 +72,7 @@ static struct file_system_type fs_type = { .owner = THIS_MODULE, .name = "securityfs", .init_fs_context = securityfs_init_fs_context, - .kill_sb = kill_litter_super, + .kill_sb = kill_anon_super, }; /** @@ -112,37 +114,34 @@ static struct dentry *securityfs_create_dentry(const char *name, umode_t mode, struct dentry *dentry; struct inode *dir, *inode; int error; + bool pinned = false; if (!(mode & S_IFMT)) mode = (mode & S_IALLUGO) | S_IFREG; pr_debug("securityfs: creating file '%s'\n",name); - error = simple_pin_fs(&fs_type, &mount, &mount_count); - if (error) - return ERR_PTR(error); - - if (!parent) + if (!parent) { + error = simple_pin_fs(&fs_type, &mount, &mount_count); + if (error) + return ERR_PTR(error); + pinned = true; parent = mount->mnt_root; + } - dir = d_inode(parent); - - inode_lock(dir); - dentry = lookup_one_len(name, parent, strlen(name)); - if (IS_ERR(dentry)) + inode = new_inode(parent->d_sb); + if (unlikely(!inode)) { + dentry = ERR_PTR(-ENOMEM); goto out; - - if (d_really_is_positive(dentry)) { - error = -EEXIST; - goto out1; } - inode = new_inode(dir->i_sb); - if (!inode) { - error = -ENOMEM; - goto out1; - } + dir = d_inode(parent); + dentry = simple_start_creating(parent, name); + if (IS_ERR(dentry)) { + iput(inode); + goto out; + } inode->i_ino = get_next_ino(); inode->i_mode = mode; simple_inode_init_ts(inode); @@ -158,17 +157,13 @@ static struct dentry *securityfs_create_dentry(const char *name, umode_t mode, } else { inode->i_fop = fops; } - d_instantiate(dentry, inode); - dget(dentry); - inode_unlock(dir); - return dentry; + d_make_persistent(dentry, inode); + simple_done_creating(dentry); + return dentry; // borrowed -out1: - dput(dentry); - dentry = ERR_PTR(error); out: - inode_unlock(dir); - simple_release_fs(&mount, &mount_count); + if (pinned) + simple_release_fs(&mount, &mount_count); return dentry; } @@ -263,22 +258,28 @@ struct dentry *securityfs_create_symlink(const char *name, const struct inode_operations *iops) { struct dentry *dent; - char *link = NULL; + const char *link = NULL; if (target) { - link = kstrdup(target, GFP_KERNEL); + link = kstrdup_const(target, GFP_KERNEL); if (!link) return ERR_PTR(-ENOMEM); } dent = securityfs_create_dentry(name, S_IFLNK | 0444, parent, - link, NULL, iops); + (void *)link, NULL, iops); if (IS_ERR(dent)) - kfree(link); + kfree_const(link); return dent; } EXPORT_SYMBOL_GPL(securityfs_create_symlink); +static void remove_one(struct dentry *victim) +{ + if (victim->d_parent == victim->d_sb->s_root) + simple_release_fs(&mount, &mount_count); +} + /** * securityfs_remove - removes a file or directory from the securityfs filesystem * @@ -291,43 +292,11 @@ EXPORT_SYMBOL_GPL(securityfs_create_symlink); * This function is required to be called in order for the file to be * removed. No automatic cleanup of files will happen when a module is * removed; you are responsible here. - */ -void securityfs_remove(struct dentry *dentry) -{ - struct inode *dir; - - if (IS_ERR_OR_NULL(dentry)) - return; - - dir = d_inode(dentry->d_parent); - inode_lock(dir); - if (simple_positive(dentry)) { - if (d_is_dir(dentry)) - simple_rmdir(dir, dentry); - else - simple_unlink(dir, dentry); - dput(dentry); - } - inode_unlock(dir); - simple_release_fs(&mount, &mount_count); -} -EXPORT_SYMBOL_GPL(securityfs_remove); - -static void remove_one(struct dentry *victim) -{ - simple_release_fs(&mount, &mount_count); -} - -/** - * securityfs_recursive_remove - recursively removes a file or directory - * - * @dentry: a pointer to a the dentry of the file or directory to be removed. * - * This function recursively removes a file or directory in securityfs that was - * previously created with a call to another securityfs function (like - * securityfs_create_file() or variants thereof.) + * AV: when applied to directory it will take all children out; no need to call + * it for descendents if ancestor is getting killed. */ -void securityfs_recursive_remove(struct dentry *dentry) +void securityfs_remove(struct dentry *dentry) { if (IS_ERR_OR_NULL(dentry)) return; @@ -336,15 +305,52 @@ void securityfs_recursive_remove(struct dentry *dentry) simple_recursive_removal(dentry, remove_one); simple_release_fs(&mount, &mount_count); } -EXPORT_SYMBOL_GPL(securityfs_recursive_remove); +EXPORT_SYMBOL_GPL(securityfs_remove); #ifdef CONFIG_SECURITY +#include <linux/spinlock.h> + static struct dentry *lsm_dentry; + static ssize_t lsm_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { - return simple_read_from_buffer(buf, count, ppos, lsm_names, - strlen(lsm_names)); + int i; + static char *str; + static size_t len; + static DEFINE_SPINLOCK(lock); + + /* NOTE: we never free or modify the string once it is set */ + + if (unlikely(!str || !len)) { + char *str_tmp; + size_t len_tmp = 0; + + for (i = 0; i < lsm_active_cnt; i++) + /* the '+ 1' accounts for either a comma or a NUL */ + len_tmp += strlen(lsm_idlist[i]->name) + 1; + + str_tmp = kmalloc(len_tmp, GFP_KERNEL); + if (!str_tmp) + return -ENOMEM; + str_tmp[0] = '\0'; + + for (i = 0; i < lsm_active_cnt; i++) { + if (i > 0) + strcat(str_tmp, ","); + strcat(str_tmp, lsm_idlist[i]->name); + } + + spin_lock(&lock); + if (!str) { + str = str_tmp; + len = len_tmp - 1; + } else + kfree(str_tmp); + spin_unlock(&lock); + } + + return simple_read_from_buffer(buf, count, ppos, str, len); } static const struct file_operations lsm_ops = { @@ -353,7 +359,7 @@ static const struct file_operations lsm_ops = { }; #endif -static int __init securityfs_init(void) +int __init securityfs_init(void) { int retval; @@ -372,4 +378,3 @@ static int __init securityfs_init(void) #endif return 0; } -core_initcall(securityfs_init); diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 3c45f4f3455f..916d4f2bfc44 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -36,6 +36,7 @@ config INTEGRITY_ASYMMETRIC_KEYS default n select ASYMMETRIC_KEY_TYPE select ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select CRYPTO select CRYPTO_RSA select X509_CERTIFICATE_PARSER help diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 92b63039c654..45dfdedbdad4 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -18,6 +18,7 @@ integrity-$(CONFIG_LOAD_IPL_KEYS) += platform_certs/load_ipl_s390.o integrity-$(CONFIG_LOAD_PPC_KEYS) += platform_certs/efi_parser.o \ platform_certs/load_powerpc.o \ platform_certs/keyring_handler.o +integrity-$(CONFIG_EFI) += efi_secureboot.o # The relative order of the 'ima' and 'evm' LSMs depends on the order below. obj-$(CONFIG_IMA) += ima/ obj-$(CONFIG_EVM) += evm/ diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 45c3e5dda355..1ed686154d7a 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -59,7 +59,7 @@ static struct key *integrity_keyring_from_id(const unsigned int id) } int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, - const char *digest, int digestlen) + const char *digest, int digestlen, u8 algo) { struct key *keyring; @@ -76,9 +76,11 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, return digsig_verify(keyring, sig + 1, siglen - 1, digest, digestlen); case 2: /* regular file data hash based signature */ - case 3: /* struct ima_file_id data based signature */ return asymmetric_verify(keyring, sig, siglen, digest, - digestlen); + digestlen); + case 3: /* struct ima_file_id data based signature */ + return asymmetric_verify_v3(keyring, sig, siglen, digest, + digestlen, algo); } return -EOPNOTSUPP; @@ -141,7 +143,7 @@ int __init integrity_init_keyring(const unsigned int id) if (!IS_ENABLED(CONFIG_INTEGRITY_TRUSTED_KEYRING)) return 0; - restriction = kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + restriction = kzalloc_obj(struct key_restriction); if (!restriction) return -ENOMEM; diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index 457c0a396caf..6e68ec3becbd 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -121,8 +121,8 @@ int asymmetric_verify(struct key *keyring, const char *sig, goto out; } - pks.digest = (u8 *)data; - pks.digest_size = datalen; + pks.m = (u8 *)data; + pks.m_size = datalen; pks.s = hdr->sig; pks.s_size = siglen; ret = verify_signature(key, &pks); @@ -131,3 +131,62 @@ out: pr_debug("%s() = %d\n", __func__, ret); return ret; } + +/* + * calc_file_id_hash - calculate the hash of the ima_file_id struct data + * @type: xattr type [enum evm_ima_xattr_type] + * @algo: hash algorithm [enum hash_algo] + * @digest: pointer to the digest to be hashed + * @hash: (out) pointer to the hash + * + * IMA signature version 3 disambiguates the data that is signed by + * indirectly signing the hash of the ima_file_id structure data. + * + * Return 0 on success, error code otherwise. + */ +static int calc_file_id_hash(enum evm_ima_xattr_type type, + enum hash_algo algo, const u8 *digest, + struct ima_max_digest_data *hash) +{ + struct ima_file_id file_id = {.hash_type = type, .hash_algorithm = algo}; + size_t digest_size = hash_digest_size[algo]; + struct crypto_shash *tfm; + size_t file_id_size; + int rc; + + if (type != IMA_VERITY_DIGSIG && type != EVM_IMA_XATTR_DIGSIG && + type != EVM_XATTR_PORTABLE_DIGSIG) + return -EINVAL; + + tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + memcpy(file_id.hash, digest, digest_size); + + /* Calculate the ima_file_id struct hash on the portion used. */ + file_id_size = sizeof(file_id) - (HASH_MAX_DIGESTSIZE - digest_size); + + hash->hdr.algo = algo; + hash->hdr.length = digest_size; + rc = crypto_shash_tfm_digest(tfm, (const u8 *)&file_id, file_id_size, + hash->digest); + + crypto_free_shash(tfm); + return rc; +} + +int asymmetric_verify_v3(struct key *keyring, const char *sig, int siglen, + const char *data, int datalen, u8 algo) +{ + struct signature_v2_hdr *hdr = (struct signature_v2_hdr *)sig; + struct ima_max_digest_data hash; + int rc; + + rc = calc_file_id_hash(hdr->type, algo, data, &hash); + if (rc) + return -EINVAL; + + return asymmetric_verify(keyring, sig, siglen, hash.digest, + hash.hdr.length); +} diff --git a/security/integrity/efi_secureboot.c b/security/integrity/efi_secureboot.c new file mode 100644 index 000000000000..bfd4260a83a3 --- /dev/null +++ b/security/integrity/efi_secureboot.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-1.0+ +/* + * Copyright (C) 2018 IBM Corporation + */ +#include <linux/efi.h> +#include <linux/secure_boot.h> +#include <asm/efi.h> + +#ifndef arch_efi_boot_mode +#define arch_efi_boot_mode efi_secureboot_mode_unset +#endif + +static enum efi_secureboot_mode get_sb_mode(void) +{ + enum efi_secureboot_mode mode; + + if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) { + pr_info("integrity: secureboot mode unknown, no efi\n"); + return efi_secureboot_mode_unknown; + } + + mode = efi_get_secureboot_mode(efi.get_variable); + if (mode == efi_secureboot_mode_disabled) + pr_info("integrity: secureboot mode disabled\n"); + else if (mode == efi_secureboot_mode_unknown) + pr_info("integrity: secureboot mode unknown\n"); + else + pr_info("integrity: secureboot mode enabled\n"); + return mode; +} + +/* + * Query secure boot status + * + * Note don't call this function too early e.g. in __setup hook otherwise the + * kernel may hang when calling efi_get_secureboot_mode. + * + */ +bool arch_get_secureboot(void) +{ + static enum efi_secureboot_mode sb_mode; + static bool initialized; + + if (!initialized && efi_enabled(EFI_BOOT)) { + sb_mode = arch_efi_boot_mode; + + if (sb_mode == efi_secureboot_mode_unset) + sb_mode = get_sb_mode(); + initialized = true; + } + + if (sb_mode == efi_secureboot_mode_enabled) + return true; + else + return false; +} diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index 51aba5a54275..694552aceaf8 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -20,11 +20,12 @@ #define EVM_INIT_HMAC 0x0001 #define EVM_INIT_X509 0x0002 #define EVM_ALLOW_METADATA_WRITES 0x0004 +#define EVM_SIGV3_REQUIRED 0x0008 #define EVM_SETUP_COMPLETE 0x80000000 /* userland has signaled key load */ #define EVM_KEY_MASK (EVM_INIT_HMAC | EVM_INIT_X509) #define EVM_INIT_MASK (EVM_INIT_HMAC | EVM_INIT_X509 | EVM_SETUP_COMPLETE | \ - EVM_ALLOW_METADATA_WRITES) + EVM_ALLOW_METADATA_WRITES | EVM_SIGV3_REQUIRED) struct xattr_list { struct list_head list; diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index 7c06ffd633d2..1c41af2f91a6 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -13,6 +13,7 @@ #define pr_fmt(fmt) "EVM: "fmt #include <linux/export.h> +#include <linux/hex.h> #include <linux/crypto.h> #include <linux/xattr.h> #include <linux/evm.h> @@ -143,6 +144,12 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode, char type, char *digest) { struct h_misc { + /* + * Although inode->i_ino is now u64, this field remains + * unsigned long to allow existing HMAC and signatures from + * 32-bit hosts to continue working when i_ino hasn't changed + * and fits in a u32. + */ unsigned long ino; __u32 generation; uid_t uid; @@ -180,7 +187,7 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode, } /* - * Dump large security xattr values as a continuous ascii hexademical string. + * Dump large security xattr values as a continuous ascii hexadecimal string. * (pr_debug is limited to 64 bytes.) */ static void dump_security_xattr_l(const char *prefix, const void *src, @@ -401,6 +408,7 @@ int evm_init_hmac(struct inode *inode, const struct xattr *xattrs, { struct shash_desc *desc; const struct xattr *xattr; + struct xattr_list *xattr_entry; desc = init_desc(EVM_XATTR_HMAC, HASH_ALGO_SHA1); if (IS_ERR(desc)) { @@ -408,11 +416,16 @@ int evm_init_hmac(struct inode *inode, const struct xattr *xattrs, return PTR_ERR(desc); } - for (xattr = xattrs; xattr->name; xattr++) { - if (!evm_protected_xattr(xattr->name)) - continue; + list_for_each_entry_lockless(xattr_entry, &evm_config_xattrnames, + list) { + for (xattr = xattrs; xattr->name; xattr++) { + if (strcmp(xattr_entry->name + + XATTR_SECURITY_PREFIX_LEN, xattr->name) != 0) + continue; - crypto_shash_update(desc, xattr->value, xattr->value_len); + crypto_shash_update(desc, xattr->value, + xattr->value_len); + } } hmac_add_misc(desc, inode, EVM_XATTR_HMAC, hmac_val); diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 377e57e9084f..b59e3f121b8a 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -72,17 +72,25 @@ static struct xattr_list evm_config_default_xattrnames[] = { LIST_HEAD(evm_config_xattrnames); +static char *evm_cmdline __initdata; +core_param(evm, evm_cmdline, charp, 0); + static int evm_fixmode __ro_after_init; -static int __init evm_set_fixmode(char *str) +static void __init evm_set_fixmode(void) { - if (strncmp(str, "fix", 3) == 0) - evm_fixmode = 1; - else - pr_err("invalid \"%s\" mode", str); + if (!evm_cmdline) + return; - return 1; + if (strncmp(evm_cmdline, "fix", 3) == 0) { + if (arch_get_secureboot()) { + pr_info("Secure boot enabled: ignoring evm=fix"); + return; + } + evm_fixmode = 1; + } else { + pr_err("invalid \"%s\" mode", evm_cmdline); + } } -__setup("evm=", evm_set_fixmode); static void __init evm_init_config(void) { @@ -128,6 +136,14 @@ static bool evm_hmac_disabled(void) return true; } +static bool evm_sigv3_required(void) +{ + if (evm_initialized & EVM_SIGV3_REQUIRED) + return true; + + return false; +} + static int evm_find_protected_xattrs(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); @@ -169,7 +185,7 @@ static int is_unsupported_hmac_fs(struct dentry *dentry) * and compare it against the stored security.evm xattr. * * For performance: - * - use the previoulsy retrieved xattr value and length to calculate the + * - use the previously retrieved xattr value and length to calculate the * HMAC.) * - cache the verification result in the iint, when available. * @@ -250,6 +266,12 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, } hdr = (struct signature_v2_hdr *)xattr_data; + + if (evm_sigv3_required() && hdr->version != 3) { + evm_status = INTEGRITY_FAIL; + goto out; + } + digest.hdr.algo = hdr->hash_algo; rc = evm_calc_hash(dentry, xattr_name, xattr_value, xattr_value_len, xattr_data->type, &digest, @@ -258,7 +280,8 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, break; rc = integrity_digsig_verify(INTEGRITY_KEYRING_EVM, (const char *)xattr_data, xattr_len, - digest.digest, digest.hdr.length); + digest.digest, digest.hdr.length, + digest.hdr.algo); if (!rc) { if (xattr_data->type == EVM_XATTR_PORTABLE_DIGSIG) { if (iint) @@ -788,6 +811,34 @@ bool evm_revalidate_status(const char *xattr_name) } /** + * evm_fix_hmac - Calculate the HMAC and add it to security.evm for fix mode + * @dentry: pointer to the affected dentry which doesn't yet have security.evm + * xattr + * @xattr_name: pointer to the affected extended attribute name + * @xattr_value: pointer to the new extended attribute value + * @xattr_value_len: pointer to the new extended attribute value length + * + * Expects to be called with i_mutex locked. + * + * Return: 0 on success, -EPERM/-ENOMEM/-EOPNOTSUPP on failure + */ +int evm_fix_hmac(struct dentry *dentry, const char *xattr_name, + const char *xattr_value, size_t xattr_value_len) + +{ + if (!evm_fixmode || !evm_revalidate_status((xattr_name))) + return -EPERM; + + if (!(evm_initialized & EVM_INIT_HMAC)) + return -EPERM; + + if (is_unsupported_hmac_fs(dentry)) + return -EOPNOTSUPP; + + return evm_update_evmxattr(dentry, xattr_name, xattr_value, xattr_value_len); +} + +/** * evm_inode_post_setxattr - update 'security.evm' to reflect the changes * @dentry: pointer to the affected dentry * @xattr_name: pointer to the affected extended attribute name @@ -1045,7 +1096,7 @@ int evm_inode_init_security(struct inode *inode, struct inode *dir, "%s: xattrs terminator is not the first non-filled slot\n", __func__); - xattr_data = kzalloc(sizeof(*xattr_data), GFP_NOFS); + xattr_data = kzalloc_obj(*xattr_data, GFP_NOFS); if (!xattr_data) return -ENOMEM; @@ -1119,6 +1170,8 @@ static int __init init_evm(void) evm_init_config(); + evm_set_fixmode(); + error = integrity_init_keyring(INTEGRITY_KEYRING_EVM); if (error) goto error; @@ -1175,10 +1228,9 @@ struct lsm_blob_sizes evm_blob_sizes __ro_after_init = { }; DEFINE_LSM(evm) = { - .name = "evm", + .id = &evm_lsmid, .init = init_evm_lsm, .order = LSM_ORDER_LAST, .blobs = &evm_blob_sizes, + .initcall_late = init_evm, }; - -late_initcall(init_evm); diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c index 9b907c2fee60..acd840461902 100644 --- a/security/integrity/evm/evm_secfs.c +++ b/security/integrity/evm/evm_secfs.c @@ -17,7 +17,6 @@ #include "evm.h" static struct dentry *evm_dir; -static struct dentry *evm_init_tpm; static struct dentry *evm_symlink; #ifdef CONFIG_EVM_ADD_XATTRS @@ -200,7 +199,7 @@ static ssize_t evm_write_xattrs(struct file *file, const char __user *buf, if (!ab && IS_ENABLED(CONFIG_AUDIT)) return -ENOMEM; - xattr = kmalloc(sizeof(struct xattr_list), GFP_KERNEL); + xattr = kmalloc_obj(struct xattr_list); if (!xattr) { err = -ENOMEM; goto out; @@ -286,7 +285,7 @@ static int evm_init_xattrs(void) { evm_xattrs = securityfs_create_file("evm_xattrs", 0660, evm_dir, NULL, &evm_xattr_ops); - if (!evm_xattrs || IS_ERR(evm_xattrs)) + if (IS_ERR(evm_xattrs)) return -EFAULT; return 0; @@ -301,21 +300,28 @@ static int evm_init_xattrs(void) int __init evm_init_secfs(void) { int error = 0; + struct dentry *dentry; - evm_dir = securityfs_create_dir("evm", integrity_dir); - if (!evm_dir || IS_ERR(evm_dir)) + error = integrity_fs_init(); + if (error < 0) return -EFAULT; - evm_init_tpm = securityfs_create_file("evm", 0660, - evm_dir, NULL, &evm_key_ops); - if (!evm_init_tpm || IS_ERR(evm_init_tpm)) { + evm_dir = securityfs_create_dir("evm", integrity_dir); + if (IS_ERR(evm_dir)) { + error = -EFAULT; + goto out; + } + + dentry = securityfs_create_file("evm", 0660, + evm_dir, NULL, &evm_key_ops); + if (IS_ERR(dentry)) { error = -EFAULT; goto out; } evm_symlink = securityfs_create_symlink("evm", NULL, "integrity/evm/evm", NULL); - if (!evm_symlink || IS_ERR(evm_symlink)) { + if (IS_ERR(evm_symlink)) { error = -EFAULT; goto out; } @@ -328,7 +334,7 @@ int __init evm_init_secfs(void) return 0; out: securityfs_remove(evm_symlink); - securityfs_remove(evm_init_tpm); securityfs_remove(evm_dir); + integrity_fs_fini(); return error; } diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 068ac6c2ae1e..8ec1a3436a71 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -42,8 +42,11 @@ void __init integrity_load_keys(void) evm_load_x509(); } -static int __init integrity_fs_init(void) +int __init integrity_fs_init(void) { + if (integrity_dir) + return 0; + integrity_dir = securityfs_create_dir("integrity", NULL); if (IS_ERR(integrity_dir)) { int ret = PTR_ERR(integrity_dir); @@ -58,4 +61,11 @@ static int __init integrity_fs_init(void) return 0; } -late_initcall(integrity_fs_init) +void __init integrity_fs_fini(void) +{ + if (!integrity_dir || !simple_empty(integrity_dir)) + return; + + securityfs_remove(integrity_dir); + integrity_dir = NULL; +} diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 475c32615006..862fbee2b174 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -111,7 +111,7 @@ choice config IMA_DEFAULT_HASH_SM3 bool "SM3" - depends on CRYPTO_SM3_GENERIC=y + depends on CRYPTO_SM3=y endchoice config IMA_DEFAULT_HASH @@ -321,4 +321,15 @@ config IMA_DISABLE_HTABLE help This option disables htable to allow measurement of duplicate records. +config IMA_KEXEC_EXTRA_MEMORY_KB + int "Extra memory for IMA measurements added during kexec soft reboot" + range 0 40 + depends on IMA_KEXEC + default 0 + help + IMA_KEXEC_EXTRA_MEMORY_KB determines the extra memory to be + allocated (in kb) for IMA measurements added during kexec soft reboot. + If set to the default value of 0, an extra half page of memory for those + additional measurements will be allocated. + endif diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 24d09ea91b87..69e9bf0b82c6 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -53,6 +53,7 @@ extern atomic_t ima_setxattr_allowed_hash_algorithms; struct ima_algo_desc { struct crypto_shash *tfm; enum hash_algo algo; + unsigned int digest_size; }; /* set during initialization */ @@ -144,11 +145,15 @@ struct ima_kexec_hdr { #define IMA_DIGSIG_REQUIRED 0x01000000 #define IMA_PERMIT_DIRECTIO 0x02000000 #define IMA_NEW_FILE 0x04000000 +#define IMA_SIGV3_REQUIRED 0x08000000 #define IMA_FAIL_UNVERIFIABLE_SIGS 0x10000000 #define IMA_MODSIG_ALLOWED 0x20000000 #define IMA_CHECK_BLACKLIST 0x40000000 #define IMA_VERITY_REQUIRED 0x80000000 +/* Exclude non-action flags which are not rule-specific. */ +#define IMA_NONACTION_RULE_FLAGS (IMA_NONACTION_FLAGS & ~IMA_NEW_FILE) + #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ IMA_HASH | IMA_APPRAISE_SUBMASK) #define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_AUDITED | \ @@ -173,12 +178,38 @@ struct ima_kexec_hdr { IMA_BPRM_APPRAISED | IMA_READ_APPRAISED | \ IMA_CREDS_APPRAISED) -/* IMA iint cache atomic_flags */ +/* + * IMA iint cache atomic_flags + * + * IMA_CHANGE_ATTR - indicates that chATTR() was called (chmod, chown, chgrp) + * and file attributes have changed. On file open, it causes IMA to clear + * iint->flags to re-evaluate policy and perform IMA functions again. + * + * IMA_CHANGE_XATTR - indicates that setxattr or removexattr was called and + * extended attributes have changed. On file open, it causes IMA to clear + * iint->flags IMA_DONE_MASK to re-appraise. + * + * IMA_UPDATE_XATTR - indicates that security.ima needs to be updated. It is + * cleared if file policy changes and no update is needed. + * + * IMA_DIGSIG - indicates that file security.ima has signature and file + * security.ima must not update on file close. + * + * IMA_MAY_EMIT_TOMTOU - indicates to add Time-of-Measure-Time-of-Use (ToMToU) + * integrity violation (a file that is already opened for read is opened for + * write) to the measurement list and to also emit an audit message. + * + * IMA_EMITTED_OPENWRITERS - indicates to add open-writers integrity violation + * (a file that is already opened for write is opened for read) to the + * measurement list and to also emit an audit message. + * + */ #define IMA_CHANGE_XATTR 0 #define IMA_UPDATE_XATTR 1 #define IMA_CHANGE_ATTR 2 #define IMA_DIGSIG 3 -#define IMA_MUST_MEASURE 4 +#define IMA_MAY_EMIT_TOMTOU 4 +#define IMA_EMITTED_OPENWRITERS 5 /* IMA integrity metadata associated with an inode */ struct ima_iint_cache { @@ -240,6 +271,12 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key, unsigned long flags, bool create); #endif +#ifdef CONFIG_IMA_KEXEC +void ima_measure_kexec_event(const char *event_name); +#else +static inline void ima_measure_kexec_event(const char *event_name) {} +#endif + /* * The default binary_runtime_measurements list format is defined as the * platform native format. The canonical format is defined as little-endian. @@ -431,7 +468,8 @@ int ima_check_blacklist(struct ima_iint_cache *iint, int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, - int xattr_len, const struct modsig *modsig); + int xattr_len, const struct modsig *modsig, + bool bprm_is_check); int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func); void ima_update_xattr(struct ima_iint_cache *iint, struct file *file); @@ -456,7 +494,8 @@ static inline int ima_appraise_measurement(enum ima_hooks func, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, - const struct modsig *modsig) + const struct modsig *modsig, + bool bprm_is_check) { return INTEGRITY_UNKNOWN; } diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index c35ea613c9f8..0916f24f005f 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -11,6 +11,7 @@ #include <linux/slab.h> #include <linux/file.h> #include <linux/fs.h> +#include <linux/hex.h> #include <linux/xattr.h> #include <linux/evm.h> #include <linux/fsverity.h> @@ -47,13 +48,14 @@ int ima_alloc_init_template(struct ima_event_data *event_data, else template_desc = ima_template_desc_current(); - *entry = kzalloc(struct_size(*entry, template_data, - template_desc->num_fields), GFP_NOFS); + *entry = kzalloc_flex(**entry, template_data, template_desc->num_fields, + GFP_NOFS); if (!*entry) return -ENOMEM; - digests = kcalloc(NR_BANKS(ima_tpm_chip) + ima_extra_slots, - sizeof(*digests), GFP_NOFS); + digests = kzalloc_objs(*digests, + NR_BANKS(ima_tpm_chip) + ima_extra_slots, + GFP_NOFS); if (!digests) { kfree(*entry); *entry = NULL; @@ -267,15 +269,20 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file, goto out; /* - * Detecting file change is based on i_version. On filesystems - * which do not support i_version, support was originally limited - * to an initial measurement/appraisal/audit, but was modified to - * assume the file changed. + * Detect file change based on STATX_CHANGE_COOKIE, when supported, + * and fallback to detecting file change based on i_version. + * + * On filesystems which did not support i_version, support was + * originally limited to an initial measurement/appraisal/audit, + * but was later modified to assume the file changed. */ result = vfs_getattr_nosec(&file->f_path, &stat, STATX_CHANGE_COOKIE, AT_STATX_SYNC_AS_STAT); if (!result && (stat.result_mask & STATX_CHANGE_COOKIE)) i_version = stat.change_cookie; + else if (IS_I_VERSION(real_inode)) + i_version = inode_peek_iversion(real_inode); + hash.hdr.algo = algo; hash.hdr.length = hash_digest_size[algo]; diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index f435eff4667f..de963b9f3634 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -27,7 +27,7 @@ core_param(ima_appraise, ima_appraise_cmdline_default, charp, 0); void __init ima_appraise_parse_cmdline(void) { const char *str = ima_appraise_cmdline_default; - bool sb_state = arch_ima_get_secureboot(); + bool sb_state = arch_get_secureboot(); int appraisal_state = ima_appraise; if (!str) @@ -235,40 +235,6 @@ int ima_read_xattr(struct dentry *dentry, } /* - * calc_file_id_hash - calculate the hash of the ima_file_id struct data - * @type: xattr type [enum evm_ima_xattr_type] - * @algo: hash algorithm [enum hash_algo] - * @digest: pointer to the digest to be hashed - * @hash: (out) pointer to the hash - * - * IMA signature version 3 disambiguates the data that is signed by - * indirectly signing the hash of the ima_file_id structure data. - * - * Signing the ima_file_id struct is currently only supported for - * IMA_VERITY_DIGSIG type xattrs. - * - * Return 0 on success, error code otherwise. - */ -static int calc_file_id_hash(enum evm_ima_xattr_type type, - enum hash_algo algo, const u8 *digest, - struct ima_digest_data *hash) -{ - struct ima_file_id file_id = { - .hash_type = IMA_VERITY_DIGSIG, .hash_algorithm = algo}; - unsigned int unused = HASH_MAX_DIGESTSIZE - hash_digest_size[algo]; - - if (type != IMA_VERITY_DIGSIG) - return -EINVAL; - - memcpy(file_id.hash, digest, hash_digest_size[algo]); - - hash->algo = algo; - hash->length = hash_digest_size[algo]; - - return ima_calc_buffer_hash(&file_id, sizeof(file_id) - unused, hash); -} - -/* * xattr_verify - verify xattr digest or signature * * Verify whether the hash or signature matches the file contents. @@ -279,7 +245,6 @@ static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint, struct evm_ima_xattr_data *xattr_value, int xattr_len, enum integrity_status *status, const char **cause) { - struct ima_max_digest_data hash; struct signature_v2_hdr *sig; int rc = -EINVAL, hash_start = 0; int mask; @@ -332,16 +297,24 @@ static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint, } sig = (typeof(sig))xattr_value; - if (sig->version >= 3) { + if (sig->version > 3) { *cause = "invalid-signature-version"; *status = INTEGRITY_FAIL; break; } + + if ((iint->flags & IMA_SIGV3_REQUIRED) && sig->version != 3) { + *cause = "IMA-sigv3-required"; + *status = INTEGRITY_FAIL; + break; + } + rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, (const char *)xattr_value, xattr_len, iint->ima_hash->digest, - iint->ima_hash->length); + iint->ima_hash->length, + iint->ima_hash->algo); if (rc == -EOPNOTSUPP) { *status = INTEGRITY_UNKNOWN; break; @@ -352,7 +325,9 @@ static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint, (const char *)xattr_value, xattr_len, iint->ima_hash->digest, - iint->ima_hash->length); + iint->ima_hash->length, + iint->ima_hash->algo); + if (rc) { *cause = "invalid-signature"; *status = INTEGRITY_FAIL; @@ -378,21 +353,16 @@ static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint, break; } - rc = calc_file_id_hash(IMA_VERITY_DIGSIG, iint->ima_hash->algo, - iint->ima_hash->digest, - container_of(&hash.hdr, - struct ima_digest_data, hdr)); - if (rc) { - *cause = "sigv3-hashing-error"; - *status = INTEGRITY_FAIL; - break; - } - rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, (const char *)xattr_value, - xattr_len, hash.digest, - hash.hdr.length); - if (rc) { + xattr_len, + iint->ima_hash->digest, + iint->ima_hash->length, + iint->ima_hash->algo); + if (rc == -EOPNOTSUPP) { + *status = INTEGRITY_UNKNOWN; + break; + } else if (rc) { *cause = "invalid-verity-signature"; *status = INTEGRITY_FAIL; } else { @@ -470,17 +440,6 @@ int ima_check_blacklist(struct ima_iint_cache *iint, return rc; } -static bool is_bprm_creds_for_exec(enum ima_hooks func, struct file *file) -{ - struct linux_binprm *bprm; - - if (func == BPRM_CHECK) { - bprm = container_of(&file, struct linux_binprm, file); - return bprm->is_check; - } - return false; -} - /* * ima_appraise_measurement - appraise file measurement * @@ -492,7 +451,8 @@ static bool is_bprm_creds_for_exec(enum ima_hooks func, struct file *file) int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, - int xattr_len, const struct modsig *modsig) + int xattr_len, const struct modsig *modsig, + bool bprm_is_check) { static const char op[] = "appraise_data"; int audit_msgno = AUDIT_INTEGRITY_DATA; @@ -514,7 +474,7 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint, * of the script interpreter(userspace). Differentiate kernel and * userspace enforced integrity audit messages. */ - if (is_bprm_creds_for_exec(func, file)) + if (bprm_is_check) audit_msgno = AUDIT_INTEGRITY_USERSPACE; /* If reading the xattr failed and there's no modsig, error out. */ @@ -601,6 +561,11 @@ out: xattr_value->type != EVM_IMA_XATTR_DIGSIG)) { if (!ima_fix_xattr(dentry, iint)) status = INTEGRITY_PASS; + } else if (status == INTEGRITY_NOLABEL) { + if (!evm_fix_hmac(dentry, XATTR_NAME_IMA, + (const char *)xattr_value, + xattr_len)) + status = INTEGRITY_PASS; } /* @@ -694,6 +659,15 @@ static int ima_protect_xattr(struct dentry *dentry, const char *xattr_name, return 0; } +/* + * ima_reset_appraise_flags - reset ima_iint_cache flags + * + * @digsig: whether to clear/set IMA_DIGSIG flag, tristate values + * 0: clear IMA_DIGSIG + * 1: set IMA_DIGSIG + * -1: don't change IMA_DIGSIG + * + */ static void ima_reset_appraise_flags(struct inode *inode, int digsig) { struct ima_iint_cache *iint; @@ -706,9 +680,9 @@ static void ima_reset_appraise_flags(struct inode *inode, int digsig) return; iint->measured_pcrs = 0; set_bit(IMA_CHANGE_XATTR, &iint->atomic_flags); - if (digsig) + if (digsig == 1) set_bit(IMA_DIGSIG, &iint->atomic_flags); - else + else if (digsig == 0) clear_bit(IMA_DIGSIG, &iint->atomic_flags); } @@ -794,6 +768,8 @@ static int ima_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, digsig = (xvalue->type == EVM_IMA_XATTR_DIGSIG); } else if (!strcmp(xattr_name, XATTR_NAME_EVM) && xattr_value_len > 0) { digsig = (xvalue->type == EVM_XATTR_PORTABLE_DIGSIG); + } else { + digsig = -1; } if (result == 1 || evm_revalidate_status(xattr_name)) { ima_reset_appraise_flags(d_backing_inode(dentry), digsig); @@ -807,7 +783,7 @@ static int ima_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { if (evm_revalidate_status(acl_name)) - ima_reset_appraise_flags(d_backing_inode(dentry), 0); + ima_reset_appraise_flags(d_backing_inode(dentry), -1); return 0; } @@ -815,11 +791,13 @@ static int ima_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, static int ima_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name) { - int result; + int result, digsig = -1; result = ima_protect_xattr(dentry, xattr_name, NULL, 0); if (result == 1 || evm_revalidate_status(xattr_name)) { - ima_reset_appraise_flags(d_backing_inode(dentry), 0); + if (!strcmp(xattr_name, XATTR_NAME_IMA)) + digsig = 0; + ima_reset_appraise_flags(d_backing_inode(dentry), digsig); if (result == 1) result = 0; } diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 6f5696d999d0..0d72b48249ee 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -11,51 +11,15 @@ */ #include <linux/kernel.h> -#include <linux/moduleparam.h> -#include <linux/ratelimit.h> #include <linux/file.h> #include <linux/crypto.h> -#include <linux/scatterlist.h> #include <linux/err.h> #include <linux/slab.h> #include <crypto/hash.h> #include "ima.h" -/* minimum file size for ahash use */ -static unsigned long ima_ahash_minsize; -module_param_named(ahash_minsize, ima_ahash_minsize, ulong, 0644); -MODULE_PARM_DESC(ahash_minsize, "Minimum file size for ahash use"); - -/* default is 0 - 1 page. */ -static int ima_maxorder; -static unsigned int ima_bufsize = PAGE_SIZE; - -static int param_set_bufsize(const char *val, const struct kernel_param *kp) -{ - unsigned long long size; - int order; - - size = memparse(val, NULL); - order = get_order(size); - if (order > MAX_PAGE_ORDER) - return -EINVAL; - ima_maxorder = order; - ima_bufsize = PAGE_SIZE << order; - return 0; -} - -static const struct kernel_param_ops param_ops_bufsize = { - .set = param_set_bufsize, - .get = param_get_uint, -}; -#define param_check_bufsize(name, p) __param_check(name, p, unsigned int) - -module_param_named(ahash_bufsize, ima_bufsize, bufsize, 0644); -MODULE_PARM_DESC(ahash_bufsize, "Maximum ahash buffer size"); - static struct crypto_shash *ima_shash_tfm; -static struct crypto_ahash *ima_ahash_tfm; int ima_sha1_idx __ro_after_init; int ima_hash_algo_idx __ro_after_init; @@ -109,6 +73,7 @@ static struct crypto_shash *ima_alloc_tfm(enum hash_algo algo) int __init ima_init_crypto(void) { + unsigned int digest_size; enum hash_algo algo; long rc; int i; @@ -138,8 +103,8 @@ int __init ima_init_crypto(void) if (ima_hash_algo_idx < 0) ima_hash_algo_idx = NR_BANKS(ima_tpm_chip) + ima_extra_slots++; - ima_algo_array = kcalloc(NR_BANKS(ima_tpm_chip) + ima_extra_slots, - sizeof(*ima_algo_array), GFP_KERNEL); + ima_algo_array = kzalloc_objs(*ima_algo_array, + NR_BANKS(ima_tpm_chip) + ima_extra_slots); if (!ima_algo_array) { rc = -ENOMEM; goto out; @@ -147,7 +112,9 @@ int __init ima_init_crypto(void) for (i = 0; i < NR_BANKS(ima_tpm_chip); i++) { algo = ima_tpm_chip->allocated_banks[i].crypto_id; + digest_size = ima_tpm_chip->allocated_banks[i].digest_size; ima_algo_array[i].algo = algo; + ima_algo_array[i].digest_size = digest_size; /* unknown TPM algorithm */ if (algo == HASH_ALGO__LAST) @@ -183,12 +150,15 @@ int __init ima_init_crypto(void) } ima_algo_array[ima_sha1_idx].algo = HASH_ALGO_SHA1; + ima_algo_array[ima_sha1_idx].digest_size = SHA1_DIGEST_SIZE; } if (ima_hash_algo_idx >= NR_BANKS(ima_tpm_chip) && ima_hash_algo_idx != ima_sha1_idx) { + digest_size = hash_digest_size[ima_hash_algo]; ima_algo_array[ima_hash_algo_idx].tfm = ima_shash_tfm; ima_algo_array[ima_hash_algo_idx].algo = ima_hash_algo; + ima_algo_array[ima_hash_algo_idx].digest_size = digest_size; } return 0; @@ -220,234 +190,6 @@ static void ima_free_tfm(struct crypto_shash *tfm) crypto_free_shash(tfm); } -/** - * ima_alloc_pages() - Allocate contiguous pages. - * @max_size: Maximum amount of memory to allocate. - * @allocated_size: Returned size of actual allocation. - * @last_warn: Should the min_size allocation warn or not. - * - * Tries to do opportunistic allocation for memory first trying to allocate - * max_size amount of memory and then splitting that until zero order is - * reached. Allocation is tried without generating allocation warnings unless - * last_warn is set. Last_warn set affects only last allocation of zero order. - * - * By default, ima_maxorder is 0 and it is equivalent to kmalloc(GFP_KERNEL) - * - * Return pointer to allocated memory, or NULL on failure. - */ -static void *ima_alloc_pages(loff_t max_size, size_t *allocated_size, - int last_warn) -{ - void *ptr; - int order = ima_maxorder; - gfp_t gfp_mask = __GFP_RECLAIM | __GFP_NOWARN | __GFP_NORETRY; - - if (order) - order = min(get_order(max_size), order); - - for (; order; order--) { - ptr = (void *)__get_free_pages(gfp_mask, order); - if (ptr) { - *allocated_size = PAGE_SIZE << order; - return ptr; - } - } - - /* order is zero - one page */ - - gfp_mask = GFP_KERNEL; - - if (!last_warn) - gfp_mask |= __GFP_NOWARN; - - ptr = (void *)__get_free_pages(gfp_mask, 0); - if (ptr) { - *allocated_size = PAGE_SIZE; - return ptr; - } - - *allocated_size = 0; - return NULL; -} - -/** - * ima_free_pages() - Free pages allocated by ima_alloc_pages(). - * @ptr: Pointer to allocated pages. - * @size: Size of allocated buffer. - */ -static void ima_free_pages(void *ptr, size_t size) -{ - if (!ptr) - return; - free_pages((unsigned long)ptr, get_order(size)); -} - -static struct crypto_ahash *ima_alloc_atfm(enum hash_algo algo) -{ - struct crypto_ahash *tfm = ima_ahash_tfm; - int rc; - - if (algo < 0 || algo >= HASH_ALGO__LAST) - algo = ima_hash_algo; - - if (algo != ima_hash_algo || !tfm) { - tfm = crypto_alloc_ahash(hash_algo_name[algo], 0, 0); - if (!IS_ERR(tfm)) { - if (algo == ima_hash_algo) - ima_ahash_tfm = tfm; - } else { - rc = PTR_ERR(tfm); - pr_err("Can not allocate %s (reason: %d)\n", - hash_algo_name[algo], rc); - } - } - return tfm; -} - -static void ima_free_atfm(struct crypto_ahash *tfm) -{ - if (tfm != ima_ahash_tfm) - crypto_free_ahash(tfm); -} - -static inline int ahash_wait(int err, struct crypto_wait *wait) -{ - - err = crypto_wait_req(err, wait); - - if (err) - pr_crit_ratelimited("ahash calculation failed: err: %d\n", err); - - return err; -} - -static int ima_calc_file_hash_atfm(struct file *file, - struct ima_digest_data *hash, - struct crypto_ahash *tfm) -{ - loff_t i_size, offset; - char *rbuf[2] = { NULL, }; - int rc, rbuf_len, active = 0, ahash_rc = 0; - struct ahash_request *req; - struct scatterlist sg[1]; - struct crypto_wait wait; - size_t rbuf_size[2]; - - hash->length = crypto_ahash_digestsize(tfm); - - req = ahash_request_alloc(tfm, GFP_KERNEL); - if (!req) - return -ENOMEM; - - crypto_init_wait(&wait); - ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | - CRYPTO_TFM_REQ_MAY_SLEEP, - crypto_req_done, &wait); - - rc = ahash_wait(crypto_ahash_init(req), &wait); - if (rc) - goto out1; - - i_size = i_size_read(file_inode(file)); - - if (i_size == 0) - goto out2; - - /* - * Try to allocate maximum size of memory. - * Fail if even a single page cannot be allocated. - */ - rbuf[0] = ima_alloc_pages(i_size, &rbuf_size[0], 1); - if (!rbuf[0]) { - rc = -ENOMEM; - goto out1; - } - - /* Only allocate one buffer if that is enough. */ - if (i_size > rbuf_size[0]) { - /* - * Try to allocate secondary buffer. If that fails fallback to - * using single buffering. Use previous memory allocation size - * as baseline for possible allocation size. - */ - rbuf[1] = ima_alloc_pages(i_size - rbuf_size[0], - &rbuf_size[1], 0); - } - - for (offset = 0; offset < i_size; offset += rbuf_len) { - if (!rbuf[1] && offset) { - /* Not using two buffers, and it is not the first - * read/request, wait for the completion of the - * previous ahash_update() request. - */ - rc = ahash_wait(ahash_rc, &wait); - if (rc) - goto out3; - } - /* read buffer */ - rbuf_len = min_t(loff_t, i_size - offset, rbuf_size[active]); - rc = integrity_kernel_read(file, offset, rbuf[active], - rbuf_len); - if (rc != rbuf_len) { - if (rc >= 0) - rc = -EINVAL; - /* - * Forward current rc, do not overwrite with return value - * from ahash_wait() - */ - ahash_wait(ahash_rc, &wait); - goto out3; - } - - if (rbuf[1] && offset) { - /* Using two buffers, and it is not the first - * read/request, wait for the completion of the - * previous ahash_update() request. - */ - rc = ahash_wait(ahash_rc, &wait); - if (rc) - goto out3; - } - - sg_init_one(&sg[0], rbuf[active], rbuf_len); - ahash_request_set_crypt(req, sg, NULL, rbuf_len); - - ahash_rc = crypto_ahash_update(req); - - if (rbuf[1]) - active = !active; /* swap buffers, if we use two */ - } - /* wait for the last update request to complete */ - rc = ahash_wait(ahash_rc, &wait); -out3: - ima_free_pages(rbuf[0], rbuf_size[0]); - ima_free_pages(rbuf[1], rbuf_size[1]); -out2: - if (!rc) { - ahash_request_set_crypt(req, NULL, hash->digest, 0); - rc = ahash_wait(crypto_ahash_final(req), &wait); - } -out1: - ahash_request_free(req); - return rc; -} - -static int ima_calc_file_ahash(struct file *file, struct ima_digest_data *hash) -{ - struct crypto_ahash *tfm; - int rc; - - tfm = ima_alloc_atfm(hash->algo); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); - - rc = ima_calc_file_hash_atfm(file, hash, tfm); - - ima_free_atfm(tfm); - - return rc; -} - static int ima_calc_file_hash_tfm(struct file *file, struct ima_digest_data *hash, struct crypto_shash *tfm) @@ -499,41 +241,15 @@ out: return rc; } -static int ima_calc_file_shash(struct file *file, struct ima_digest_data *hash) -{ - struct crypto_shash *tfm; - int rc; - - tfm = ima_alloc_tfm(hash->algo); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); - - rc = ima_calc_file_hash_tfm(file, hash, tfm); - - ima_free_tfm(tfm); - - return rc; -} - /* * ima_calc_file_hash - calculate file hash - * - * Asynchronous hash (ahash) allows using HW acceleration for calculating - * a hash. ahash performance varies for different data sizes on different - * crypto accelerators. shash performance might be better for smaller files. - * The 'ima.ahash_minsize' module parameter allows specifying the best - * minimum file size for using ahash on the system. - * - * If the ima.ahash_minsize parameter is not specified, this function uses - * shash for the hash calculation. If ahash fails, it falls back to using - * shash. */ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) { - loff_t i_size; int rc; struct file *f = file; bool new_file_instance = false; + struct crypto_shash *tfm; /* * For consistency, fail file's opened with the O_DIRECT flag on @@ -557,16 +273,13 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) new_file_instance = true; } - i_size = i_size_read(file_inode(f)); - - if (ima_ahash_minsize && i_size >= ima_ahash_minsize) { - rc = ima_calc_file_ahash(f, hash); - if (!rc) - goto out; + tfm = ima_alloc_tfm(hash->algo); + if (IS_ERR(tfm)) { + rc = PTR_ERR(tfm); + } else { + rc = ima_calc_file_hash_tfm(f, hash, tfm); + ima_free_tfm(tfm); } - - rc = ima_calc_file_shash(f, hash); -out: if (new_file_instance) fput(f); return rc; @@ -655,63 +368,6 @@ int ima_calc_field_array_hash(struct ima_field_data *field_data, return rc; } -static int calc_buffer_ahash_atfm(const void *buf, loff_t len, - struct ima_digest_data *hash, - struct crypto_ahash *tfm) -{ - struct ahash_request *req; - struct scatterlist sg; - struct crypto_wait wait; - int rc, ahash_rc = 0; - - hash->length = crypto_ahash_digestsize(tfm); - - req = ahash_request_alloc(tfm, GFP_KERNEL); - if (!req) - return -ENOMEM; - - crypto_init_wait(&wait); - ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | - CRYPTO_TFM_REQ_MAY_SLEEP, - crypto_req_done, &wait); - - rc = ahash_wait(crypto_ahash_init(req), &wait); - if (rc) - goto out; - - sg_init_one(&sg, buf, len); - ahash_request_set_crypt(req, &sg, NULL, len); - - ahash_rc = crypto_ahash_update(req); - - /* wait for the update request to complete */ - rc = ahash_wait(ahash_rc, &wait); - if (!rc) { - ahash_request_set_crypt(req, NULL, hash->digest, 0); - rc = ahash_wait(crypto_ahash_final(req), &wait); - } -out: - ahash_request_free(req); - return rc; -} - -static int calc_buffer_ahash(const void *buf, loff_t len, - struct ima_digest_data *hash) -{ - struct crypto_ahash *tfm; - int rc; - - tfm = ima_alloc_atfm(hash->algo); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); - - rc = calc_buffer_ahash_atfm(buf, len, hash, tfm); - - ima_free_atfm(tfm); - - return rc; -} - static int calc_buffer_shash_tfm(const void *buf, loff_t size, struct ima_digest_data *hash, struct crypto_shash *tfm) @@ -742,8 +398,8 @@ static int calc_buffer_shash_tfm(const void *buf, loff_t size, return rc; } -static int calc_buffer_shash(const void *buf, loff_t len, - struct ima_digest_data *hash) +int ima_calc_buffer_hash(const void *buf, loff_t len, + struct ima_digest_data *hash) { struct crypto_shash *tfm; int rc; @@ -758,20 +414,6 @@ static int calc_buffer_shash(const void *buf, loff_t len, return rc; } -int ima_calc_buffer_hash(const void *buf, loff_t len, - struct ima_digest_data *hash) -{ - int rc; - - if (ima_ahash_minsize && len >= ima_ahash_minsize) { - rc = calc_buffer_ahash(buf, len, hash); - if (!rc) - return 0; - } - - return calc_buffer_shash(buf, len, hash); -} - static void ima_pcrread(u32 idx, struct tpm_digest *d) { if (!ima_tpm_chip) @@ -832,7 +474,7 @@ static int ima_calc_boot_aggregate_tfm(char *digest, u16 alg_id, } } if (!rc) - crypto_shash_final(shash, digest); + rc = crypto_shash_final(shash, digest); return rc; } diff --git a/security/integrity/ima/ima_efi.c b/security/integrity/ima/ima_efi.c index 138029bfcce1..bca57d836cb9 100644 --- a/security/integrity/ima/ima_efi.c +++ b/security/integrity/ima/ima_efi.c @@ -2,52 +2,9 @@ /* * Copyright (C) 2018 IBM Corporation */ -#include <linux/efi.h> #include <linux/module.h> #include <linux/ima.h> -#include <asm/efi.h> - -#ifndef arch_ima_efi_boot_mode -#define arch_ima_efi_boot_mode efi_secureboot_mode_unset -#endif - -static enum efi_secureboot_mode get_sb_mode(void) -{ - enum efi_secureboot_mode mode; - - if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) { - pr_info("ima: secureboot mode unknown, no efi\n"); - return efi_secureboot_mode_unknown; - } - - mode = efi_get_secureboot_mode(efi.get_variable); - if (mode == efi_secureboot_mode_disabled) - pr_info("ima: secureboot mode disabled\n"); - else if (mode == efi_secureboot_mode_unknown) - pr_info("ima: secureboot mode unknown\n"); - else - pr_info("ima: secureboot mode enabled\n"); - return mode; -} - -bool arch_ima_get_secureboot(void) -{ - static enum efi_secureboot_mode sb_mode; - static bool initialized; - - if (!initialized && efi_enabled(EFI_BOOT)) { - sb_mode = arch_ima_efi_boot_mode; - - if (sb_mode == efi_secureboot_mode_unset) - sb_mode = get_sb_mode(); - initialized = true; - } - - if (sb_mode == efi_secureboot_mode_enabled) - return true; - else - return false; -} +#include <linux/secure_boot.h> /* secureboot arch rules */ static const char * const sb_arch_rules[] = { @@ -67,11 +24,9 @@ static const char * const sb_arch_rules[] = { const char * const *arch_get_ima_policy(void) { - if (IS_ENABLED(CONFIG_IMA_ARCH_POLICY) && arch_ima_get_secureboot()) { - if (IS_ENABLED(CONFIG_MODULE_SIG)) - set_module_sig_enforced(); - if (IS_ENABLED(CONFIG_KEXEC_SIG)) - set_kexec_sig_enforced(); + if (IS_ENABLED(CONFIG_IMA_ARCH_POLICY) && arch_get_secureboot()) { + set_module_sig_enforced(); + set_kexec_sig_enforced(); return sb_arch_rules; } return NULL; diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index e4a79a9b2d58..ca4931a95098 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -116,28 +116,6 @@ void ima_putc(struct seq_file *m, void *data, int datalen) seq_putc(m, *(char *)data++); } -static struct dentry **ascii_securityfs_measurement_lists __ro_after_init; -static struct dentry **binary_securityfs_measurement_lists __ro_after_init; -static int securityfs_measurement_list_count __ro_after_init; - -static void lookup_template_data_hash_algo(int *algo_idx, enum hash_algo *algo, - struct seq_file *m, - struct dentry **lists) -{ - struct dentry *dentry; - int i; - - dentry = file_dentry(m->file); - - for (i = 0; i < securityfs_measurement_list_count; i++) { - if (dentry == lists[i]) { - *algo_idx = i; - *algo = ima_algo_array[i].algo; - break; - } - } -} - /* print format: * 32bit-le=pcr# * char[n]=template digest @@ -154,15 +132,12 @@ int ima_measurements_show(struct seq_file *m, void *v) char *template_name; u32 pcr, namelen, template_data_len; /* temporary fields */ bool is_ima_template = false; - enum hash_algo algo; int i, algo_idx; algo_idx = ima_sha1_idx; - algo = HASH_ALGO_SHA1; if (m->file != NULL) - lookup_template_data_hash_algo(&algo_idx, &algo, m, - binary_securityfs_measurement_lists); + algo_idx = (unsigned long)file_inode(m->file)->i_private; /* get entry */ e = qe->entry; @@ -181,7 +156,8 @@ int ima_measurements_show(struct seq_file *m, void *v) ima_putc(m, &pcr, sizeof(e->pcr)); /* 2nd: template digest */ - ima_putc(m, e->digests[algo_idx].digest, hash_digest_size[algo]); + ima_putc(m, e->digests[algo_idx].digest, + ima_algo_array[algo_idx].digest_size); /* 3rd: template name size */ namelen = !ima_canonical_fmt ? strlen(template_name) : @@ -250,15 +226,12 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) struct ima_queue_entry *qe = v; struct ima_template_entry *e; char *template_name; - enum hash_algo algo; int i, algo_idx; algo_idx = ima_sha1_idx; - algo = HASH_ALGO_SHA1; if (m->file != NULL) - lookup_template_data_hash_algo(&algo_idx, &algo, m, - ascii_securityfs_measurement_lists); + algo_idx = (unsigned long)file_inode(m->file)->i_private; /* get entry */ e = qe->entry; @@ -272,7 +245,8 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) seq_printf(m, "%2d ", e->pcr); /* 2nd: template hash */ - ima_print_digest(m, e->digests[algo_idx].digest, hash_digest_size[algo]); + ima_print_digest(m, e->digests[algo_idx].digest, + ima_algo_array[algo_idx].digest_size); /* 3th: template name */ seq_printf(m, " %s", template_name); @@ -396,11 +370,6 @@ out: static struct dentry *ima_dir; static struct dentry *ima_symlink; -static struct dentry *binary_runtime_measurements; -static struct dentry *ascii_runtime_measurements; -static struct dentry *runtime_measurements_count; -static struct dentry *violations; -static struct dentry *ima_policy; enum ima_fs_flags { IMA_FS_BUSY, @@ -417,64 +386,41 @@ static const struct seq_operations ima_policy_seqops = { }; #endif -static void __init remove_securityfs_measurement_lists(struct dentry **lists) -{ - int i; - - if (lists) { - for (i = 0; i < securityfs_measurement_list_count; i++) - securityfs_remove(lists[i]); - - kfree(lists); - } -} - static int __init create_securityfs_measurement_lists(void) { - char file_name[NAME_MAX + 1]; - struct dentry *dentry; - u16 algo; - int i; - - securityfs_measurement_list_count = NR_BANKS(ima_tpm_chip); + int count = NR_BANKS(ima_tpm_chip); if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip)) - securityfs_measurement_list_count++; - - ascii_securityfs_measurement_lists = - kcalloc(securityfs_measurement_list_count, sizeof(struct dentry *), - GFP_KERNEL); - if (!ascii_securityfs_measurement_lists) - return -ENOMEM; - - binary_securityfs_measurement_lists = - kcalloc(securityfs_measurement_list_count, sizeof(struct dentry *), - GFP_KERNEL); - if (!binary_securityfs_measurement_lists) - return -ENOMEM; - - for (i = 0; i < securityfs_measurement_list_count; i++) { - algo = ima_algo_array[i].algo; - - sprintf(file_name, "ascii_runtime_measurements_%s", - hash_algo_name[algo]); + count++; + + for (int i = 0; i < count; i++) { + u16 algo = ima_algo_array[i].algo; + char file_name[NAME_MAX + 1]; + struct dentry *dentry; + + if (algo == HASH_ALGO__LAST) + sprintf(file_name, "ascii_runtime_measurements_tpm_alg_%x", + ima_tpm_chip->allocated_banks[i].alg_id); + else + sprintf(file_name, "ascii_runtime_measurements_%s", + hash_algo_name[algo]); dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, - ima_dir, NULL, + ima_dir, (void *)(uintptr_t)i, &ima_ascii_measurements_ops); if (IS_ERR(dentry)) return PTR_ERR(dentry); - ascii_securityfs_measurement_lists[i] = dentry; - - sprintf(file_name, "binary_runtime_measurements_%s", - hash_algo_name[algo]); + if (algo == HASH_ALGO__LAST) + sprintf(file_name, "binary_runtime_measurements_tpm_alg_%x", + ima_tpm_chip->allocated_banks[i].alg_id); + else + sprintf(file_name, "binary_runtime_measurements_%s", + hash_algo_name[algo]); dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, - ima_dir, NULL, + ima_dir, (void *)(uintptr_t)i, &ima_measurements_ops); if (IS_ERR(dentry)) return PTR_ERR(dentry); - - binary_securityfs_measurement_lists[i] = dentry; } return 0; @@ -533,8 +479,7 @@ static int ima_release_policy(struct inode *inode, struct file *file) ima_update_policy(); #if !defined(CONFIG_IMA_WRITE_POLICY) && !defined(CONFIG_IMA_READ_POLICY) - securityfs_remove(ima_policy); - ima_policy = NULL; + securityfs_remove(file->f_path.dentry); #elif defined(CONFIG_IMA_WRITE_POLICY) clear_bit(IMA_FS_BUSY, &ima_fs_flags); #elif defined(CONFIG_IMA_READ_POLICY) @@ -553,14 +498,18 @@ static const struct file_operations ima_measure_policy_ops = { int __init ima_fs_init(void) { + struct dentry *dentry; int ret; - ascii_securityfs_measurement_lists = NULL; - binary_securityfs_measurement_lists = NULL; + ret = integrity_fs_init(); + if (ret < 0) + return ret; ima_dir = securityfs_create_dir("ima", integrity_dir); - if (IS_ERR(ima_dir)) - return PTR_ERR(ima_dir); + if (IS_ERR(ima_dir)) { + ret = PTR_ERR(ima_dir); + goto out; + } ima_symlink = securityfs_create_symlink("ima", NULL, "integrity/ima", NULL); @@ -573,59 +522,48 @@ int __init ima_fs_init(void) if (ret != 0) goto out; - binary_runtime_measurements = - securityfs_create_symlink("binary_runtime_measurements", ima_dir, + dentry = securityfs_create_symlink("binary_runtime_measurements", ima_dir, "binary_runtime_measurements_sha1", NULL); - if (IS_ERR(binary_runtime_measurements)) { - ret = PTR_ERR(binary_runtime_measurements); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto out; } - ascii_runtime_measurements = - securityfs_create_symlink("ascii_runtime_measurements", ima_dir, + dentry = securityfs_create_symlink("ascii_runtime_measurements", ima_dir, "ascii_runtime_measurements_sha1", NULL); - if (IS_ERR(ascii_runtime_measurements)) { - ret = PTR_ERR(ascii_runtime_measurements); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto out; } - runtime_measurements_count = - securityfs_create_file("runtime_measurements_count", + dentry = securityfs_create_file("runtime_measurements_count", S_IRUSR | S_IRGRP, ima_dir, NULL, &ima_measurements_count_ops); - if (IS_ERR(runtime_measurements_count)) { - ret = PTR_ERR(runtime_measurements_count); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto out; } - violations = - securityfs_create_file("violations", S_IRUSR | S_IRGRP, + dentry = securityfs_create_file("violations", S_IRUSR | S_IRGRP, ima_dir, NULL, &ima_htable_violations_ops); - if (IS_ERR(violations)) { - ret = PTR_ERR(violations); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto out; } - ima_policy = securityfs_create_file("policy", POLICY_FILE_FLAGS, + dentry = securityfs_create_file("policy", POLICY_FILE_FLAGS, ima_dir, NULL, &ima_measure_policy_ops); - if (IS_ERR(ima_policy)) { - ret = PTR_ERR(ima_policy); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto out; } return 0; out: - securityfs_remove(ima_policy); - securityfs_remove(violations); - securityfs_remove(runtime_measurements_count); - securityfs_remove(ascii_runtime_measurements); - securityfs_remove(binary_runtime_measurements); - remove_securityfs_measurement_lists(ascii_securityfs_measurement_lists); - remove_securityfs_measurement_lists(binary_securityfs_measurement_lists); - securityfs_measurement_list_count = 0; securityfs_remove(ima_symlink); securityfs_remove(ima_dir); + integrity_fs_fini(); return ret; } diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 9d45f4d26f73..36a34c54de58 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -12,66 +12,120 @@ #include <linux/kexec.h> #include <linux/of.h> #include <linux/ima.h> +#include <linux/mm.h> +#include <linux/overflow.h> +#include <linux/reboot.h> +#include <asm/page.h> #include "ima.h" #ifdef CONFIG_IMA_KEXEC +#define IMA_KEXEC_EVENT_LEN 256 + +static bool ima_kexec_update_registered; +static struct seq_file ima_kexec_file; +static size_t kexec_segment_size; +static void *ima_kexec_buffer; + +static void ima_free_kexec_file_buf(struct seq_file *sf) +{ + vfree(sf->buf); + sf->buf = NULL; + sf->size = 0; + sf->read_pos = 0; + sf->count = 0; +} + +void ima_measure_kexec_event(const char *event_name) +{ + char ima_kexec_event[IMA_KEXEC_EVENT_LEN]; + size_t buf_size = 0; + long len; + int n; + + buf_size = ima_get_binary_runtime_size(); + len = atomic_long_read(&ima_htable.len); + + n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN, + "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;" + "ima_runtime_measurements_count=%ld;", + kexec_segment_size, buf_size, len); + + ima_measure_critical_data("ima_kexec", event_name, ima_kexec_event, n, false, NULL, 0); +} + +static int ima_alloc_kexec_file_buf(size_t segment_size) +{ + /* + * kexec 'load' may be called multiple times. + * Free and realloc the buffer only if the segment_size is + * changed from the previous kexec 'load' call. + */ + if (ima_kexec_file.buf && ima_kexec_file.size == segment_size) + goto out; + + ima_free_kexec_file_buf(&ima_kexec_file); + + /* segment size can't change between kexec load and execute */ + ima_kexec_file.buf = vmalloc(segment_size); + if (!ima_kexec_file.buf) + return -ENOMEM; + + ima_kexec_file.size = segment_size; + +out: + ima_kexec_file.read_pos = 0; + ima_kexec_file.count = sizeof(struct ima_kexec_hdr); /* reserved space */ + ima_measure_kexec_event("kexec_load"); + + return 0; +} + static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer, unsigned long segment_size) { struct ima_queue_entry *qe; - struct seq_file file; struct ima_kexec_hdr khdr; int ret = 0; /* segment size can't change between kexec load and execute */ - file.buf = vmalloc(segment_size); - if (!file.buf) { - ret = -ENOMEM; - goto out; + if (!ima_kexec_file.buf) { + pr_err("Kexec file buf not allocated\n"); + return -EINVAL; } - file.file = NULL; - file.size = segment_size; - file.read_pos = 0; - file.count = sizeof(khdr); /* reserved space */ - memset(&khdr, 0, sizeof(khdr)); khdr.version = 1; /* This is an append-only list, no need to hold the RCU read lock */ list_for_each_entry_rcu(qe, &ima_measurements, later, true) { - if (file.count < file.size) { + if (ima_kexec_file.count < ima_kexec_file.size) { khdr.count++; - ima_measurements_show(&file, qe); + ima_measurements_show(&ima_kexec_file, qe); } else { ret = -EINVAL; break; } } - if (ret < 0) - goto out; - /* * fill in reserved space with some buffer details * (eg. version, buffer size, number of measurements) */ - khdr.buffer_size = file.count; + khdr.buffer_size = ima_kexec_file.count; if (ima_canonical_fmt) { khdr.version = cpu_to_le16(khdr.version); khdr.count = cpu_to_le64(khdr.count); khdr.buffer_size = cpu_to_le64(khdr.buffer_size); } - memcpy(file.buf, &khdr, sizeof(khdr)); + memcpy(ima_kexec_file.buf, &khdr, sizeof(khdr)); print_hex_dump_debug("ima dump: ", DUMP_PREFIX_NONE, 16, 1, - file.buf, file.count < 100 ? file.count : 100, + ima_kexec_file.buf, ima_kexec_file.count < 100 ? + ima_kexec_file.count : 100, true); - *buffer_size = file.count; - *buffer = file.buf; -out: - if (ret == -EINVAL) - vfree(file.buf); + *buffer_size = ima_kexec_file.count; + *buffer = ima_kexec_file.buf; + return ret; } @@ -87,32 +141,39 @@ void ima_add_kexec_buffer(struct kimage *image) .buf_min = 0, .buf_max = ULONG_MAX, .top_down = true }; unsigned long binary_runtime_size; + unsigned long extra_memory; /* use more understandable variable names than defined in kbuf */ + size_t kexec_buffer_size = 0; void *kexec_buffer = NULL; - size_t kexec_buffer_size; - size_t kexec_segment_size; int ret; + if (image->type == KEXEC_TYPE_CRASH) + return; + /* - * Reserve an extra half page of memory for additional measurements - * added during the kexec load. + * Reserve extra memory for measurements added during kexec. */ - binary_runtime_size = ima_get_binary_runtime_size(); + if (CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB <= 0) + extra_memory = PAGE_SIZE / 2; + else + extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024; + + binary_runtime_size = ima_get_binary_runtime_size() + extra_memory; + if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE) kexec_segment_size = ULONG_MAX; else - kexec_segment_size = ALIGN(ima_get_binary_runtime_size() + - PAGE_SIZE / 2, PAGE_SIZE); + kexec_segment_size = ALIGN(binary_runtime_size, PAGE_SIZE); + if ((kexec_segment_size == ULONG_MAX) || ((kexec_segment_size >> PAGE_SHIFT) > totalram_pages() / 2)) { pr_err("Binary measurement list too large.\n"); return; } - ima_dump_measurement_list(&kexec_buffer_size, &kexec_buffer, - kexec_segment_size); - if (!kexec_buffer) { + ret = ima_alloc_kexec_file_buf(kexec_segment_size); + if (ret < 0) { pr_err("Not enough memory for the kexec measurement buffer.\n"); return; } @@ -120,6 +181,7 @@ void ima_add_kexec_buffer(struct kimage *image) kbuf.buffer = kexec_buffer; kbuf.bufsz = kexec_buffer_size; kbuf.memsz = kexec_segment_size; + image->is_ima_segment_index_set = false; ret = kexec_add_buffer(&kbuf); if (ret) { pr_err("Error passing over kexec measurement buffer.\n"); @@ -130,10 +192,78 @@ void ima_add_kexec_buffer(struct kimage *image) image->ima_buffer_addr = kbuf.mem; image->ima_buffer_size = kexec_segment_size; image->ima_buffer = kexec_buffer; + image->ima_segment_index = image->nr_segments - 1; + image->is_ima_segment_index_set = true; kexec_dprintk("kexec measurement buffer for the loaded kernel at 0x%lx.\n", kbuf.mem); } + +/* + * Called during kexec execute so that IMA can update the measurement list. + */ +static int ima_update_kexec_buffer(struct notifier_block *self, + unsigned long action, void *data) +{ + size_t buf_size = 0; + int ret = NOTIFY_OK; + void *buf = NULL; + + if (!kexec_in_progress) { + pr_info("No kexec in progress.\n"); + return ret; + } + + if (!ima_kexec_buffer) { + pr_err("Kexec buffer not set.\n"); + return ret; + } + + ret = ima_dump_measurement_list(&buf_size, &buf, kexec_segment_size); + + if (ret) + pr_err("Dump measurements failed. Error:%d\n", ret); + + if (buf_size != 0) + memcpy(ima_kexec_buffer, buf, buf_size); + + kimage_unmap_segment(ima_kexec_buffer); + ima_kexec_buffer = NULL; + + return ret; +} + +static struct notifier_block update_buffer_nb = { + .notifier_call = ima_update_kexec_buffer, + .priority = INT_MIN +}; + +/* + * Create a mapping for the source pages that contain the IMA buffer + * so we can update it later. + */ +void ima_kexec_post_load(struct kimage *image) +{ + if (ima_kexec_buffer) { + kimage_unmap_segment(ima_kexec_buffer); + ima_kexec_buffer = NULL; + } + + if (!image->ima_buffer_addr) + return; + + ima_kexec_buffer = kimage_map_segment(image, image->ima_segment_index); + if (!ima_kexec_buffer) { + pr_err("Could not map measurements buffer.\n"); + return; + } + + if (!ima_kexec_update_registered) { + register_reboot_notifier(&update_buffer_nb); + ima_kexec_update_registered = true; + } +} + #endif /* IMA_KEXEC */ /* @@ -166,3 +296,36 @@ void __init ima_load_kexec_buffer(void) pr_debug("Error restoring the measurement list: %d\n", rc); } } + +/* + * ima_validate_range - verify a physical buffer lies in addressable RAM + * @phys: physical start address of the buffer from previous kernel + * @size: size of the buffer + * + * On success return 0. On failure returns -EINVAL so callers can skip + * restoring. + */ +int ima_validate_range(phys_addr_t phys, size_t size) +{ + unsigned long start_pfn, end_pfn; + phys_addr_t end_phys; + + if (check_add_overflow(phys, (phys_addr_t)size - 1, &end_phys)) + return -EINVAL; + + start_pfn = PHYS_PFN(phys); + end_pfn = PHYS_PFN(end_phys); + +#ifdef CONFIG_X86 + if (!pfn_range_is_mapped(start_pfn, end_pfn)) +#else + if (!page_is_ram(start_pfn) || !page_is_ram(end_pfn)) +#endif + { + pr_warn("IMA: previous kernel measurement buffer %pa (size 0x%zx) lies outside available memory\n", + &phys, size); + return -EINVAL; + } + + return 0; +} diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 9f9897a7c217..5cea53fc36df 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -27,6 +27,7 @@ #include <linux/fs.h> #include <linux/iversion.h> #include <linux/evm.h> +#include <linux/crash_dump.h> #include "ima.h" @@ -38,11 +39,30 @@ int ima_appraise; int __ro_after_init ima_hash_algo = HASH_ALGO_SHA1; static int hash_setup_done; +static int ima_disabled __ro_after_init; static struct notifier_block ima_lsm_policy_notifier = { .notifier_call = ima_lsm_policy_change, }; +static int __init ima_setup(char *str) +{ + if (!is_kdump_kernel()) { + pr_info("Warning: ima setup option only permitted in kdump"); + return 1; + } + + if (strncmp(str, "off", 3) == 0) + ima_disabled = 1; + else if (strncmp(str, "on", 2) == 0) + ima_disabled = 0; + else + pr_err("Invalid ima setup option: \"%s\" , please specify ima=on|off.", str); + + return 1; +} +__setup("ima=", ima_setup); + static int __init hash_setup(char *str) { struct ima_template_desc *template_desc = ima_template_desc_current(); @@ -129,16 +149,22 @@ static void ima_rdwr_violation_check(struct file *file, if (atomic_read(&inode->i_readcount) && IS_IMA(inode)) { if (!iint) iint = ima_iint_find(inode); + /* IMA_MEASURE is set from reader side */ - if (iint && test_bit(IMA_MUST_MEASURE, - &iint->atomic_flags)) + if (iint && test_and_clear_bit(IMA_MAY_EMIT_TOMTOU, + &iint->atomic_flags)) send_tomtou = true; } } else { if (must_measure) - set_bit(IMA_MUST_MEASURE, &iint->atomic_flags); - if (inode_is_open_for_write(inode) && must_measure) - send_writers = true; + set_bit(IMA_MAY_EMIT_TOMTOU, &iint->atomic_flags); + + /* Limit number of open_writers violations */ + if (inode_is_open_for_write(inode) && must_measure) { + if (!test_and_set_bit(IMA_EMITTED_OPENWRITERS, + &iint->atomic_flags)) + send_writers = true; + } } if (!send_tomtou && !send_writers) @@ -154,6 +180,29 @@ static void ima_rdwr_violation_check(struct file *file, "invalid_pcr", "open_writers"); } +/* + * Detect file change based on STATX_CHANGE_COOKIE, when supported, and + * fallback to detecting file change based on i_version. On filesystems + * which do not support either, assume the file changed. + */ +static bool ima_detect_file_change(struct ima_iint_cache *iint, + struct inode *inode, struct file *file) +{ + struct kstat stat; + int result; + + result = vfs_getattr_nosec(&file->f_path, &stat, STATX_CHANGE_COOKIE, + AT_STATX_SYNC_AS_STAT); + + if (!result && stat.result_mask & STATX_CHANGE_COOKIE) + return stat.change_cookie != iint->real_inode.version; + + if (IS_I_VERSION(inode)) + return !inode_eq_iversion(inode, iint->real_inode.version); + + return true; +} + static void ima_check_last_writer(struct ima_iint_cache *iint, struct inode *inode, struct file *file) { @@ -165,16 +214,13 @@ static void ima_check_last_writer(struct ima_iint_cache *iint, mutex_lock(&iint->mutex); if (atomic_read(&inode->i_writecount) == 1) { - struct kstat stat; + clear_bit(IMA_EMITTED_OPENWRITERS, &iint->atomic_flags); update = test_and_clear_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); - if ((iint->flags & IMA_NEW_FILE) || - vfs_getattr_nosec(&file->f_path, &stat, - STATX_CHANGE_COOKIE, - AT_STATX_SYNC_AS_STAT) || - !(stat.result_mask & STATX_CHANGE_COOKIE) || - stat.change_cookie != iint->real_inode.version) { + + if (iint->flags & IMA_NEW_FILE || + ima_detect_file_change(iint, inode, file)) { iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE); iint->measured_pcrs = 0; if (update) @@ -207,7 +253,9 @@ static void ima_file_free(struct file *file) static int process_measurement(struct file *file, const struct cred *cred, struct lsm_prop *prop, char *buf, loff_t size, - int mask, enum ima_hooks func) + int mask, enum ima_hooks func, + enum kernel_read_file_id read_id, + bool bprm_is_check) { struct inode *real_inode, *inode = file_inode(file); struct ima_iint_cache *iint = NULL; @@ -237,7 +285,9 @@ static int process_measurement(struct file *file, const struct cred *cred, &allowed_algos); violation_check = ((func == FILE_CHECK || func == MMAP_CHECK || func == MMAP_CHECK_REQPROT) && - (ima_policy_flag & IMA_MEASURE)); + (ima_policy_flag & IMA_MEASURE) && + ((action & IMA_MEASURE) || + (file->f_mode & FMODE_WRITE))); if (!action && !violation_check) return 0; @@ -269,10 +319,13 @@ static int process_measurement(struct file *file, const struct cred *cred, mutex_lock(&iint->mutex); if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags)) - /* reset appraisal flags if ima_inode_post_setattr was called */ + /* + * Reset appraisal flags (action and non-action rule-specific) + * if ima_inode_post_setattr was called. + */ iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED | IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK | - IMA_NONACTION_FLAGS); + IMA_NONACTION_RULE_FLAGS); /* * Re-evaulate the file if either the xattr has changed or the @@ -373,6 +426,12 @@ static int process_measurement(struct file *file, const struct cred *cred, if (rc != 0 && rc != -EBADF && rc != -EINVAL) goto out_locked; + /* Defer measuring/appraising kernel modules to READING_MODULE */ + if (read_id == READING_MODULE_COMPRESSED) { + must_appraise = 0; + goto out_locked; + } + if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */ pathname = ima_d_path(&file->f_path, &pathbuf, filename); @@ -386,7 +445,8 @@ static int process_measurement(struct file *file, const struct cred *cred, inode_lock(inode); rc = ima_appraise_measurement(func, iint, file, pathname, xattr_value, - xattr_len, modsig); + xattr_len, modsig, + bprm_is_check); inode_unlock(inode); } if (!rc) @@ -453,14 +513,15 @@ static int ima_file_mmap(struct file *file, unsigned long reqprot, if (reqprot & PROT_EXEC) { ret = process_measurement(file, current_cred(), &prop, NULL, - 0, MAY_EXEC, MMAP_CHECK_REQPROT); + 0, MAY_EXEC, MMAP_CHECK_REQPROT, 0, + false); if (ret) return ret; } if (prot & PROT_EXEC) return process_measurement(file, current_cred(), &prop, NULL, - 0, MAY_EXEC, MMAP_CHECK); + 0, MAY_EXEC, MMAP_CHECK, 0, false); return 0; } @@ -540,18 +601,42 @@ static int ima_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, */ static int ima_bprm_check(struct linux_binprm *bprm) { - int ret; struct lsm_prop prop; security_current_getlsmprop_subj(&prop); - ret = process_measurement(bprm->file, current_cred(), - &prop, NULL, 0, MAY_EXEC, BPRM_CHECK); - if (ret) - return ret; - - security_cred_getlsmprop(bprm->cred, &prop); - return process_measurement(bprm->file, bprm->cred, &prop, NULL, 0, - MAY_EXEC, CREDS_CHECK); + return process_measurement(bprm->file, current_cred(), + &prop, NULL, 0, MAY_EXEC, BPRM_CHECK, 0, + bprm->is_check); +} + +/** + * ima_creds_check - based on policy, collect/store measurement. + * @bprm: contains the linux_binprm structure + * @file: contains the file descriptor of the binary being executed + * + * The OS protects against an executable file, already open for write, + * from being executed in deny_write_access() and an executable file, + * already open for execute, from being modified in get_write_access(). + * So we can be certain that what we verify and measure here is actually + * what is being executed. + * + * The difference from ima_bprm_check() is that ima_creds_check() is invoked + * only after determining the final binary to be executed without interpreter, + * and not when searching for intermediate binaries. The reason is that since + * commit 56305aa9b6fab ("exec: Compute file based creds only once"), the + * credentials to be applied to the process are calculated only at that stage + * (bprm_creds_from_file security hook instead of bprm_check_security). + * + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. + */ +static int ima_creds_check(struct linux_binprm *bprm, const struct file *file) +{ + struct lsm_prop prop; + + security_current_getlsmprop_subj(&prop); + return process_measurement((struct file *)file, bprm->cred, &prop, NULL, + 0, MAY_EXEC, CREDS_CHECK, 0, false); } /** @@ -599,7 +684,7 @@ static int ima_file_check(struct file *file, int mask) security_current_getlsmprop_subj(&prop); return process_measurement(file, current_cred(), &prop, NULL, 0, mask & (MAY_READ | MAY_WRITE | MAY_EXEC | - MAY_APPEND), FILE_CHECK); + MAY_APPEND), FILE_CHECK, 0, false); } static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf, @@ -818,12 +903,13 @@ static int ima_read_file(struct file *file, enum kernel_read_file_id read_id, func = read_idmap[read_id] ?: FILE_CHECK; security_current_getlsmprop_subj(&prop); return process_measurement(file, current_cred(), &prop, NULL, 0, - MAY_READ, func); + MAY_READ, func, 0, false); } const int read_idmap[READING_MAX_ID] = { [READING_FIRMWARE] = FIRMWARE_CHECK, [READING_MODULE] = MODULE_CHECK, + [READING_MODULE_COMPRESSED] = MODULE_CHECK, [READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK, [READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK, [READING_POLICY] = POLICY_CHECK @@ -861,7 +947,7 @@ static int ima_post_read_file(struct file *file, char *buf, loff_t size, func = read_idmap[read_id] ?: FILE_CHECK; security_current_getlsmprop_subj(&prop); return process_measurement(file, current_cred(), &prop, buf, size, - MAY_READ, func); + MAY_READ, func, read_id, false); } /** @@ -885,8 +971,7 @@ static int ima_load_data(enum kernel_load_data_id id, bool contents) switch (id) { case LOADING_KEXEC_IMAGE: - if (IS_ENABLED(CONFIG_KEXEC_SIG) - && arch_ima_get_secureboot()) { + if (IS_ENABLED(CONFIG_KEXEC_SIG) && arch_get_secureboot()) { pr_err("impossible to appraise a kernel image without a file descriptor; try using kexec_file_load syscall.\n"); return -EACCES; } @@ -1011,9 +1096,9 @@ int process_buffer_measurement(struct mnt_idmap *idmap, } /* - * Both LSM hooks and auxilary based buffer measurements are - * based on policy. To avoid code duplication, differentiate - * between the LSM hooks and auxilary buffer measurements, + * Both LSM hooks and auxiliary based buffer measurements are + * based on policy. To avoid code duplication, differentiate + * between the LSM hooks and auxiliary buffer measurements, * retrieving the policy rule information only for the LSM hook * buffer measurements. */ @@ -1173,6 +1258,12 @@ static int __init init_ima(void) { int error; + /*Note that turning IMA off is intentionally limited to kdump kernel.*/ + if (ima_disabled && is_kdump_kernel()) { + pr_info("IMA functionality is disabled"); + return 0; + } + ima_appraise_parse_cmdline(); ima_init_template_list(); hash_setup(CONFIG_IMA_DEFAULT_HASH); @@ -1203,6 +1294,7 @@ static int __init init_ima(void) static struct security_hook_list ima_hooks[] __ro_after_init = { LSM_HOOK_INIT(bprm_check_security, ima_bprm_check), LSM_HOOK_INIT(bprm_creds_for_exec, ima_bprm_creds_for_exec), + LSM_HOOK_INIT(bprm_creds_from_file, ima_creds_check), LSM_HOOK_INIT(file_post_open, ima_file_check), LSM_HOOK_INIT(inode_post_create_tmpfile, ima_post_create_tmpfile), LSM_HOOK_INIT(file_release, ima_file_free), @@ -1240,10 +1332,10 @@ struct lsm_blob_sizes ima_blob_sizes __ro_after_init = { }; DEFINE_LSM(ima) = { - .name = "ima", + .id = &ima_lsmid, .init = init_ima_lsm, .order = LSM_ORDER_LAST, .blobs = &ima_blob_sizes, + /* Start IMA after the TPM is available */ + .initcall_late = init_ima, }; - -late_initcall(init_ima); /* Start IMA after the TPM is available */ diff --git a/security/integrity/ima/ima_modsig.c b/security/integrity/ima/ima_modsig.c index 3265d744d5ce..632c746fd81e 100644 --- a/security/integrity/ima/ima_modsig.c +++ b/security/integrity/ima/ima_modsig.c @@ -40,7 +40,7 @@ struct modsig { int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, struct modsig **modsig) { - const size_t marker_len = strlen(MODULE_SIG_STRING); + const size_t marker_len = strlen(MODULE_SIGNATURE_MARKER); const struct module_signature *sig; struct modsig *hdr; size_t sig_len; @@ -51,7 +51,7 @@ int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, return -ENOENT; p = buf + buf_len - marker_len; - if (memcmp(p, MODULE_SIG_STRING, marker_len)) + if (memcmp(p, MODULE_SIGNATURE_MARKER, marker_len)) return -ENOENT; buf_len -= marker_len; @@ -65,7 +65,7 @@ int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, buf_len -= sig_len + sizeof(*sig); /* Allocate sig_len additional bytes to hold the raw PKCS#7 data. */ - hdr = kzalloc(struct_size(hdr, raw_pkcs7, sig_len), GFP_KERNEL); + hdr = kzalloc_flex(*hdr, raw_pkcs7, sig_len); if (!hdr) return -ENOMEM; @@ -105,7 +105,7 @@ void ima_collect_modsig(struct modsig *modsig, const void *buf, loff_t size) * Provide the file contents (minus the appended sig) so that the PKCS7 * code can calculate the file hash. */ - size -= modsig->raw_pkcs7_len + strlen(MODULE_SIG_STRING) + + size -= modsig->raw_pkcs7_len + strlen(MODULE_SIGNATURE_MARKER) + sizeof(struct module_signature); rc = pkcs7_supply_detached_data(modsig->pkcs7_msg, buf, size); if (rc) diff --git a/security/integrity/ima/ima_mok.c b/security/integrity/ima/ima_mok.c index 95cc31525c57..14d93d573a6a 100644 --- a/security/integrity/ima/ima_mok.c +++ b/security/integrity/ima/ima_mok.c @@ -27,7 +27,7 @@ static __init int ima_mok_init(void) pr_notice("Allocating IMA blacklist keyring.\n"); - restriction = kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + restriction = kzalloc_obj(struct key_restriction); if (!restriction) panic("Can't allocate IMA blacklist restriction."); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 128fab897930..f7f940a76922 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -38,6 +38,7 @@ #define IMA_GID 0x2000 #define IMA_EGID 0x4000 #define IMA_FGROUP 0x8000 +#define IMA_FS_SUBTYPE 0x10000 #define UNKNOWN 0 #define MEASURE 0x0001 /* same as IMA_MEASURE */ @@ -45,6 +46,7 @@ #define APPRAISE 0x0004 /* same as IMA_APPRAISE */ #define DONT_APPRAISE 0x0008 #define AUDIT 0x0040 +#define DONT_AUDIT 0x0080 #define HASH 0x0100 #define DONT_HASH 0x0200 @@ -119,6 +121,7 @@ struct ima_rule_entry { int type; /* audit type */ } lsm[MAX_LSM_RULES]; char *fsname; + char *fs_subtype; struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */ struct ima_rule_opt_list *label; /* Measure data grouped under this label */ struct ima_template_desc *template; @@ -241,7 +244,8 @@ static struct ima_rule_entry build_appraise_rules[] __ro_after_init = { static struct ima_rule_entry secure_boot_rules[] __ro_after_init = { {.action = APPRAISE, .func = MODULE_CHECK, - .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED | + IMA_CHECK_BLACKLIST}, {.action = APPRAISE, .func = FIRMWARE_CHECK, .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, {.action = APPRAISE, .func = KEXEC_KERNEL_CHECK, @@ -338,7 +342,7 @@ static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src) return ERR_PTR(-EINVAL); } - opt_list = kzalloc(struct_size(opt_list, items, count), GFP_KERNEL); + opt_list = kzalloc_flex(*opt_list, items, count); if (!opt_list) { kfree(src_copy); return ERR_PTR(-ENOMEM); @@ -397,6 +401,7 @@ static void ima_free_rule(struct ima_rule_entry *entry) * the defined_templates list and cannot be freed here */ kfree(entry->fsname); + kfree(entry->fs_subtype); ima_free_rule_opt_list(entry->keyrings); ima_lsm_free_rule(entry); kfree(entry); @@ -601,6 +606,12 @@ static bool ima_match_rules(struct ima_rule_entry *rule, if ((rule->flags & IMA_FSNAME) && strcmp(rule->fsname, inode->i_sb->s_type->name)) return false; + if (rule->flags & IMA_FS_SUBTYPE) { + if (!inode->i_sb->s_subtype) + return false; + if (strcmp(rule->fs_subtype, inode->i_sb->s_subtype)) + return false; + } if ((rule->flags & IMA_FSUUID) && !uuid_equal(&rule->fsuuid, &inode->i_sb->s_uuid)) return false; @@ -674,7 +685,7 @@ retry: goto retry; } } - if (!rc) { + if (rc <= 0) { result = false; goto out; } @@ -910,8 +921,7 @@ static int __init ima_init_arch_policy(void) for (rules = arch_rules; *rules != NULL; rules++) arch_entries++; - arch_policy_entry = kcalloc(arch_entries + 1, - sizeof(*arch_policy_entry), GFP_KERNEL); + arch_policy_entry = kzalloc_objs(*arch_policy_entry, arch_entries + 1); if (!arch_policy_entry) return 0; @@ -1064,10 +1074,10 @@ void ima_update_policy(void) enum policy_opt { Opt_measure, Opt_dont_measure, Opt_appraise, Opt_dont_appraise, - Opt_audit, Opt_hash, Opt_dont_hash, + Opt_audit, Opt_dont_audit, Opt_hash, Opt_dont_hash, Opt_obj_user, Opt_obj_role, Opt_obj_type, Opt_subj_user, Opt_subj_role, Opt_subj_type, - Opt_func, Opt_mask, Opt_fsmagic, Opt_fsname, Opt_fsuuid, + Opt_func, Opt_mask, Opt_fsmagic, Opt_fsname, Opt_fs_subtype, Opt_fsuuid, Opt_uid_eq, Opt_euid_eq, Opt_gid_eq, Opt_egid_eq, Opt_fowner_eq, Opt_fgroup_eq, Opt_uid_gt, Opt_euid_gt, Opt_gid_gt, Opt_egid_gt, @@ -1086,6 +1096,7 @@ static const match_table_t policy_tokens = { {Opt_appraise, "appraise"}, {Opt_dont_appraise, "dont_appraise"}, {Opt_audit, "audit"}, + {Opt_dont_audit, "dont_audit"}, {Opt_hash, "hash"}, {Opt_dont_hash, "dont_hash"}, {Opt_obj_user, "obj_user=%s"}, @@ -1098,6 +1109,7 @@ static const match_table_t policy_tokens = { {Opt_mask, "mask=%s"}, {Opt_fsmagic, "fsmagic=%s"}, {Opt_fsname, "fsname=%s"}, + {Opt_fs_subtype, "fs_subtype=%s"}, {Opt_fsuuid, "fsuuid=%s"}, {Opt_uid_eq, "uid=%s"}, {Opt_euid_eq, "euid=%s"}, @@ -1282,10 +1294,12 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC | IMA_UID | IMA_FOWNER | IMA_FSUUID | IMA_INMASK | IMA_EUID | IMA_PCR | - IMA_FSNAME | IMA_GID | IMA_EGID | + IMA_FSNAME | IMA_FS_SUBTYPE | + IMA_GID | IMA_EGID | IMA_FGROUP | IMA_DIGSIG_REQUIRED | IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS | - IMA_CHECK_BLACKLIST | IMA_VERITY_REQUIRED)) + IMA_CHECK_BLACKLIST | IMA_VERITY_REQUIRED | + IMA_SIGV3_REQUIRED)) return false; break; @@ -1295,7 +1309,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC | IMA_UID | IMA_FOWNER | IMA_FSUUID | IMA_INMASK | IMA_EUID | IMA_PCR | - IMA_FSNAME | IMA_GID | IMA_EGID | + IMA_FSNAME | IMA_FS_SUBTYPE | + IMA_GID | IMA_EGID | IMA_FGROUP | IMA_DIGSIG_REQUIRED | IMA_PERMIT_DIRECTIO | IMA_MODSIG_ALLOWED | IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS)) @@ -1308,7 +1323,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) if (entry->flags & ~(IMA_FUNC | IMA_FSMAGIC | IMA_UID | IMA_FOWNER | IMA_FSUUID | IMA_EUID | - IMA_PCR | IMA_FSNAME | IMA_GID | IMA_EGID | + IMA_PCR | IMA_FSNAME | IMA_FS_SUBTYPE | + IMA_GID | IMA_EGID | IMA_FGROUP)) return false; @@ -1478,6 +1494,14 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->action = AUDIT; break; + case Opt_dont_audit: + ima_log_string(ab, "action", "dont_audit"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_AUDIT; + break; case Opt_hash: ima_log_string(ab, "action", "hash"); @@ -1587,6 +1611,22 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) result = 0; entry->flags |= IMA_FSNAME; break; + case Opt_fs_subtype: + ima_log_string(ab, "fs_subtype", args[0].from); + + if (entry->fs_subtype) { + result = -EINVAL; + break; + } + + entry->fs_subtype = kstrdup(args[0].from, GFP_KERNEL); + if (!entry->fs_subtype) { + result = -ENOMEM; + break; + } + result = 0; + entry->flags |= IMA_FS_SUBTYPE; + break; case Opt_keyrings: ima_log_string(ab, "keyrings", args[0].from); @@ -1794,9 +1834,7 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) break; case Opt_digest_type: ima_log_string(ab, "digest_type", args[0].from); - if (entry->flags & IMA_DIGSIG_REQUIRED) - result = -EINVAL; - else if ((strcmp(args[0].from, "verity")) == 0) + if ((strcmp(args[0].from, "verity")) == 0) entry->flags |= IMA_VERITY_REQUIRED; else result = -EINVAL; @@ -1810,14 +1848,13 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) else entry->flags |= IMA_DIGSIG_REQUIRED | IMA_CHECK_BLACKLIST; } else if (strcmp(args[0].from, "sigv3") == 0) { - /* Only fsverity supports sigv3 for now */ - if (entry->flags & IMA_VERITY_REQUIRED) - entry->flags |= IMA_DIGSIG_REQUIRED | IMA_CHECK_BLACKLIST; - else - result = -EINVAL; + entry->flags |= IMA_SIGV3_REQUIRED | + IMA_DIGSIG_REQUIRED | + IMA_CHECK_BLACKLIST; } else if (IS_ENABLED(CONFIG_IMA_APPRAISE_MODSIG) && strcmp(args[0].from, "imasig|modsig") == 0) { - if (entry->flags & IMA_VERITY_REQUIRED) + if ((entry->flags & IMA_VERITY_REQUIRED) || + (entry->flags & IMA_SIGV3_REQUIRED)) result = -EINVAL; else entry->flags |= IMA_DIGSIG_REQUIRED | @@ -1902,7 +1939,7 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) /* d-ngv2 template field recommended for unsigned fs-verity digests */ if (!result && entry->action == MEASURE && - entry->flags & IMA_VERITY_REQUIRED) { + (entry->flags & IMA_VERITY_REQUIRED)) { template_desc = entry->template ? entry->template : ima_template_desc_current(); check_template_field(template_desc, "d-ngv2", @@ -1936,7 +1973,7 @@ ssize_t ima_parse_add_rule(char *rule) if (*p == '#' || *p == '\0') return len; - entry = kzalloc(sizeof(*entry), GFP_KERNEL); + entry = kzalloc_obj(*entry); if (!entry) { integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, op, "-ENOMEM", -ENOMEM, audit_info); @@ -2097,6 +2134,8 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, pt(Opt_dont_appraise)); if (entry->action & AUDIT) seq_puts(m, pt(Opt_audit)); + if (entry->action & DONT_AUDIT) + seq_puts(m, pt(Opt_dont_audit)); if (entry->action & HASH) seq_puts(m, pt(Opt_hash)); if (entry->action & DONT_HASH) @@ -2133,6 +2172,12 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, " "); } + if (entry->flags & IMA_FS_SUBTYPE) { + snprintf(tbuf, sizeof(tbuf), "%s", entry->fs_subtype); + seq_printf(m, pt(Opt_fs_subtype), tbuf); + seq_puts(m, " "); + } + if (entry->flags & IMA_KEYRINGS) { seq_puts(m, "keyrings="); ima_show_rule_opt_list(m, entry->keyrings); @@ -2262,7 +2307,7 @@ int ima_policy_show(struct seq_file *m, void *v) if (entry->template) seq_printf(m, "template=%s ", entry->template->name); if (entry->flags & IMA_DIGSIG_REQUIRED) { - if (entry->flags & IMA_VERITY_REQUIRED) + if (entry->flags & IMA_SIGV3_REQUIRED) seq_puts(m, "appraise_type=sigv3 "); else if (entry->flags & IMA_MODSIG_ALLOWED) seq_puts(m, "appraise_type=imasig|modsig "); diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 83d53824aa98..319522450854 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -103,7 +103,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, struct ima_queue_entry *qe; unsigned int key; - qe = kmalloc(sizeof(*qe), GFP_KERNEL); + qe = kmalloc_obj(*qe); if (qe == NULL) { pr_err("OUT OF MEMORY ERROR creating queue entry\n"); return -ENOMEM; @@ -241,6 +241,11 @@ static int ima_reboot_notifier(struct notifier_block *nb, unsigned long action, void *data) { +#ifdef CONFIG_IMA_KEXEC + if (action == SYS_RESTART && data && !strcmp(data, "kexec reboot")) + ima_measure_kexec_event("kexec_execute"); +#endif + ima_measurements_suspend(); return NOTIFY_DONE; @@ -264,8 +269,8 @@ int __init ima_init_digests(void) if (!ima_tpm_chip) return 0; - digests = kcalloc(ima_tpm_chip->nr_allocated_banks, sizeof(*digests), - GFP_NOFS); + digests = kzalloc_objs(*digests, ima_tpm_chip->nr_allocated_banks, + GFP_NOFS); if (!digests) return -ENOMEM; diff --git a/security/integrity/ima/ima_queue_keys.c b/security/integrity/ima/ima_queue_keys.c index 4f0aea155bf9..b5ed33cbb272 100644 --- a/security/integrity/ima/ima_queue_keys.c +++ b/security/integrity/ima/ima_queue_keys.c @@ -72,7 +72,7 @@ static struct ima_key_entry *ima_alloc_key_entry(struct key *keyring, const char *audit_cause = "ENOMEM"; struct ima_key_entry *entry; - entry = kzalloc(sizeof(*entry), GFP_KERNEL); + entry = kzalloc_obj(*entry); if (entry) { entry->payload = kmemdup(payload, payload_len, GFP_KERNEL); entry->keyring_name = kstrdup(keyring->description, diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 04c49f05cb74..7034573fb41e 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -245,7 +245,7 @@ int template_desc_init_fields(const char *template_fmt, } if (fields && num_fields) { - *fields = kmalloc_array(i, sizeof(**fields), GFP_KERNEL); + *fields = kmalloc_objs(**fields, i); if (*fields == NULL) return -ENOMEM; @@ -334,7 +334,7 @@ static struct ima_template_desc *restore_template_fmt(char *template_name) goto out; } - template_desc = kzalloc(sizeof(*template_desc), GFP_KERNEL); + template_desc = kzalloc_obj(*template_desc); if (!template_desc) goto out; @@ -362,13 +362,14 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc, int ret = 0; int i; - *entry = kzalloc(struct_size(*entry, template_data, - template_desc->num_fields), GFP_NOFS); + *entry = kzalloc_flex(**entry, template_data, template_desc->num_fields, + GFP_NOFS); if (!*entry) return -ENOMEM; - digests = kcalloc(NR_BANKS(ima_tpm_chip) + ima_extra_slots, - sizeof(*digests), GFP_NOFS); + digests = kzalloc_objs(*digests, + NR_BANKS(ima_tpm_chip) + ima_extra_slots, + GFP_NOFS); if (!digests) { kfree(*entry); return -ENOMEM; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index c2c2da691123..0c581c03c5da 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -14,6 +14,7 @@ #include <linux/types.h> #include <linux/integrity.h> +#include <linux/secure_boot.h> #include <crypto/sha1.h> #include <crypto/hash.h> #include <linux/key.h> @@ -114,6 +115,8 @@ struct ima_file_id { int integrity_kernel_read(struct file *file, loff_t offset, void *addr, unsigned long count); +int __init integrity_fs_init(void); +void __init integrity_fs_fini(void); #define INTEGRITY_KEYRING_EVM 0 #define INTEGRITY_KEYRING_IMA 1 @@ -128,7 +131,7 @@ struct modsig; #ifdef CONFIG_INTEGRITY_SIGNATURE int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, - const char *digest, int digestlen); + const char *digest, int digestlen, u8 algo); int integrity_modsig_verify(unsigned int id, const struct modsig *modsig); int __init integrity_init_keyring(const unsigned int id); @@ -139,7 +142,8 @@ int __init integrity_load_cert(const unsigned int id, const char *source, static inline int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, - const char *digest, int digestlen) + const char *digest, int digestlen, + u8 algo) { return -EOPNOTSUPP; } @@ -167,12 +171,21 @@ static inline int __init integrity_load_cert(const unsigned int id, #ifdef CONFIG_INTEGRITY_ASYMMETRIC_KEYS int asymmetric_verify(struct key *keyring, const char *sig, int siglen, const char *data, int datalen); +int asymmetric_verify_v3(struct key *keyring, const char *sig, + int siglen, const char *data, int datalen, u8 algo); #else static inline int asymmetric_verify(struct key *keyring, const char *sig, int siglen, const char *data, int datalen) { return -EOPNOTSUPP; } + +static inline int asymmetric_verify_v3(struct key *keyring, + const char *sig, int siglen, + const char *data, int datalen, u8 algo) +{ + return -EOPNOTSUPP; +} #endif #ifdef CONFIG_IMA_APPRAISE_MODSIG diff --git a/security/integrity/integrity_audit.c b/security/integrity/integrity_audit.c index 0ec5e4c22cb2..d8d9e5ff1cd2 100644 --- a/security/integrity/integrity_audit.c +++ b/security/integrity/integrity_audit.c @@ -62,7 +62,7 @@ void integrity_audit_message(int audit_msgno, struct inode *inode, if (inode) { audit_log_format(ab, " dev="); audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " ino=%llu", inode->i_ino); } audit_log_format(ab, " res=%d errno=%d", !result, errno); audit_log_end(ab); diff --git a/security/integrity/platform_certs/load_powerpc.c b/security/integrity/platform_certs/load_powerpc.c index c85febca3343..714c961a00f5 100644 --- a/security/integrity/platform_certs/load_powerpc.c +++ b/security/integrity/platform_certs/load_powerpc.c @@ -75,12 +75,13 @@ static int __init load_powerpc_certs(void) return -ENODEV; // Check for known secure boot implementations from OPAL or PLPKS - if (strcmp("ibm,edk2-compat-v1", buf) && strcmp("ibm,plpks-sb-v1", buf)) { + if (strcmp("ibm,edk2-compat-v1", buf) && strcmp("ibm,plpks-sb-v1", buf) && + strcmp("ibm,plpks-sb-v0", buf)) { pr_err("Unsupported secvar implementation \"%s\", not loading certs\n", buf); return -ENODEV; } - if (strcmp("ibm,plpks-sb-v1", buf) == 0) + if (strcmp("ibm,plpks-sb-v1", buf) == 0 || strcmp("ibm,plpks-sb-v0", buf) == 0) /* PLPKS authenticated variables ESL data is prefixed with 8 bytes of timestamp */ offset = 8; diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c index d1fdd113450a..c0d6948446c3 100644 --- a/security/integrity/platform_certs/load_uefi.c +++ b/security/integrity/platform_certs/load_uefi.c @@ -212,7 +212,7 @@ static int __init load_uefi_certs(void) } /* the MOK/MOKx can not be trusted when secure boot is disabled */ - if (!arch_ima_get_secureboot()) + if (!arch_get_secureboot()) return 0; mokx = get_cert_list(L"MokListXRT", &mok_var, &mokxsize, &status); diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig index 3c75bf267da4..a110a6cd848b 100644 --- a/security/ipe/Kconfig +++ b/security/ipe/Kconfig @@ -6,6 +6,7 @@ menuconfig SECURITY_IPE bool "Integrity Policy Enforcement (IPE)" depends on SECURITY && SECURITYFS && AUDIT && AUDITSYSCALL + select CRYPTO_LIB_SHA256 select PKCS7_MESSAGE_PARSER select SYSTEM_DATA_VERIFICATION select IPE_PROP_DM_VERITY if DM_VERITY diff --git a/security/ipe/audit.c b/security/ipe/audit.c index f05f0caa4850..93fb59fbddd6 100644 --- a/security/ipe/audit.c +++ b/security/ipe/audit.c @@ -6,7 +6,7 @@ #include <linux/slab.h> #include <linux/audit.h> #include <linux/types.h> -#include <crypto/hash.h> +#include <crypto/sha2.h> #include "ipe.h" #include "eval.h" @@ -17,10 +17,12 @@ #define ACTSTR(x) ((x) == IPE_ACTION_ALLOW ? "ALLOW" : "DENY") -#define IPE_AUDIT_HASH_ALG "sha256" +#define IPE_AUDIT_HASH_ALG "sha256" /* keep in sync with audit_policy() */ #define AUDIT_POLICY_LOAD_FMT "policy_name=\"%s\" policy_version=%hu.%hu.%hu "\ "policy_digest=" IPE_AUDIT_HASH_ALG ":" +#define AUDIT_POLICY_LOAD_NULL_FMT "policy_name=? policy_version=? "\ + "policy_digest=?" #define AUDIT_OLD_ACTIVE_POLICY_FMT "old_active_pol_name=\"%s\" "\ "old_active_pol_version=%hu.%hu.%hu "\ "old_policy_digest=" IPE_AUDIT_HASH_ALG ":" @@ -44,6 +46,7 @@ static const char *const audit_op_names[__IPE_OP_MAX + 1] = { static const char *const audit_hook_names[__IPE_HOOK_MAX] = { "BPRM_CHECK", + "BPRM_CREDS_FOR_EXEC", "MMAP", "MPROTECT", "KERNEL_READ", @@ -150,7 +153,7 @@ void ipe_audit_match(const struct ipe_eval_ctx *const ctx, if (inode) { audit_log_format(ab, " dev="); audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " ino=%llu", inode->i_ino); } else { audit_log_format(ab, " dev=? ino=?"); } @@ -180,37 +183,14 @@ static void audit_policy(struct audit_buffer *ab, const char *audit_format, const struct ipe_policy *const p) { - SHASH_DESC_ON_STACK(desc, tfm); - struct crypto_shash *tfm; - u8 *digest = NULL; + u8 digest[SHA256_DIGEST_SIZE]; - tfm = crypto_alloc_shash(IPE_AUDIT_HASH_ALG, 0, 0); - if (IS_ERR(tfm)) - return; - - desc->tfm = tfm; - - digest = kzalloc(crypto_shash_digestsize(tfm), GFP_KERNEL); - if (!digest) - goto out; - - if (crypto_shash_init(desc)) - goto out; - - if (crypto_shash_update(desc, p->pkcs7, p->pkcs7len)) - goto out; - - if (crypto_shash_final(desc, digest)) - goto out; + sha256(p->pkcs7, p->pkcs7len, digest); audit_log_format(ab, audit_format, p->parsed->name, p->parsed->version.major, p->parsed->version.minor, p->parsed->version.rev); - audit_log_n_hex(ab, digest, crypto_shash_digestsize(tfm)); - -out: - kfree(digest); - crypto_free_shash(tfm); + audit_log_n_hex(ab, digest, sizeof(digest)); } /** @@ -248,22 +228,29 @@ void ipe_audit_policy_activation(const struct ipe_policy *const op, } /** - * ipe_audit_policy_load() - Audit a policy being loaded into the kernel. - * @p: Supplies a pointer to the policy to audit. + * ipe_audit_policy_load() - Audit a policy loading event. + * @p: Supplies a pointer to the policy to audit or an error pointer. */ void ipe_audit_policy_load(const struct ipe_policy *const p) { struct audit_buffer *ab; + int err = 0; ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_IPE_POLICY_LOAD); if (!ab) return; - audit_policy(ab, AUDIT_POLICY_LOAD_FMT, p); - audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=1", + if (!IS_ERR(p)) { + audit_policy(ab, AUDIT_POLICY_LOAD_FMT, p); + } else { + audit_log_format(ab, AUDIT_POLICY_LOAD_NULL_FMT); + err = PTR_ERR(p); + } + + audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=%d errno=%d", from_kuid(&init_user_ns, audit_get_loginuid(current)), - audit_get_sessionid(current)); + audit_get_sessionid(current), !err, err); audit_log_end(ab); } diff --git a/security/ipe/digest.c b/security/ipe/digest.c index 493716370570..6e597a6b7633 100644 --- a/security/ipe/digest.c +++ b/security/ipe/digest.c @@ -3,6 +3,7 @@ * Copyright (C) 2020-2024 Microsoft Corporation. All rights reserved. */ +#include <linux/hex.h> #include "digest.h" /** @@ -28,7 +29,7 @@ struct digest_info *ipe_digest_parse(const char *valstr) char *alg = NULL; int rc = 0; - info = kzalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc_obj(*info); if (!info) return ERR_PTR(-ENOMEM); diff --git a/security/ipe/fs.c b/security/ipe/fs.c index 5b6d19fb844a..076c111c85c8 100644 --- a/security/ipe/fs.c +++ b/security/ipe/fs.c @@ -12,11 +12,8 @@ #include "policy.h" #include "audit.h" -static struct dentry *np __ro_after_init; static struct dentry *root __ro_after_init; struct dentry *policy_root __ro_after_init; -static struct dentry *audit_node __ro_after_init; -static struct dentry *enforce_node __ro_after_init; /** * setaudit() - Write handler for the securityfs node, "ipe/success_audit" @@ -133,6 +130,8 @@ static ssize_t getenforce(struct file *f, char __user *data, * * %-ERANGE - Policy version number overflow * * %-EINVAL - Policy version parsing error * * %-EEXIST - Same name policy already deployed + * * %-ENOKEY - Policy signing key not found + * * %-EKEYREJECTED - Policy signature verification failed */ static ssize_t new_policy(struct file *f, const char __user *data, size_t len, loff_t *offset) @@ -141,12 +140,17 @@ static ssize_t new_policy(struct file *f, const char __user *data, char *copy = NULL; int rc = 0; - if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) - return -EPERM; + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) { + rc = -EPERM; + goto out; + } copy = memdup_user_nul(data, len); - if (IS_ERR(copy)) - return PTR_ERR(copy); + if (IS_ERR(copy)) { + rc = PTR_ERR(copy); + copy = NULL; + goto out; + } p = ipe_new_policy(NULL, 0, copy, len); if (IS_ERR(p)) { @@ -158,12 +162,14 @@ static ssize_t new_policy(struct file *f, const char __user *data, if (rc) goto out; - ipe_audit_policy_load(p); - out: - if (rc < 0) - ipe_free_policy(p); kfree(copy); + if (rc < 0) { + ipe_free_policy(p); + ipe_audit_policy_load(ERR_PTR(rc)); + } else { + ipe_audit_policy_load(p); + } return (rc < 0) ? rc : len; } @@ -187,31 +193,30 @@ static const struct file_operations enforce_fops = { * Return: %0 on success. If an error occurs, the function will return * the -errno. */ -static int __init ipe_init_securityfs(void) +int __init ipe_init_securityfs(void) { int rc = 0; struct ipe_policy *ap; + struct dentry *dentry; if (!ipe_enabled) return -EOPNOTSUPP; root = securityfs_create_dir("ipe", NULL); - if (IS_ERR(root)) { - rc = PTR_ERR(root); - goto err; - } + if (IS_ERR(root)) + return PTR_ERR(root); - audit_node = securityfs_create_file("success_audit", 0600, root, + dentry = securityfs_create_file("success_audit", 0600, root, NULL, &audit_fops); - if (IS_ERR(audit_node)) { - rc = PTR_ERR(audit_node); + if (IS_ERR(dentry)) { + rc = PTR_ERR(dentry); goto err; } - enforce_node = securityfs_create_file("enforce", 0600, root, NULL, + dentry = securityfs_create_file("enforce", 0600, root, NULL, &enforce_fops); - if (IS_ERR(enforce_node)) { - rc = PTR_ERR(enforce_node); + if (IS_ERR(dentry)) { + rc = PTR_ERR(dentry); goto err; } @@ -228,20 +233,14 @@ static int __init ipe_init_securityfs(void) goto err; } - np = securityfs_create_file("new_policy", 0200, root, NULL, &np_fops); - if (IS_ERR(np)) { - rc = PTR_ERR(np); + dentry = securityfs_create_file("new_policy", 0200, root, NULL, &np_fops); + if (IS_ERR(dentry)) { + rc = PTR_ERR(dentry); goto err; } return 0; err: - securityfs_remove(np); - securityfs_remove(policy_root); - securityfs_remove(enforce_node); - securityfs_remove(audit_node); securityfs_remove(root); return rc; } - -fs_initcall(ipe_init_securityfs); diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c index d0323b81cd8f..0ae54a880405 100644 --- a/security/ipe/hooks.c +++ b/security/ipe/hooks.c @@ -36,6 +36,33 @@ int ipe_bprm_check_security(struct linux_binprm *bprm) } /** + * ipe_bprm_creds_for_exec() - ipe security hook function for bprm creds check. + * @bprm: Supplies a pointer to a linux_binprm structure to source the file + * being evaluated. + * + * This LSM hook is called when userspace signals the kernel to check a file + * for execution through the execveat syscall with the AT_EXECVE_CHECK flag. + * The hook triggers IPE policy evaluation on the script file and returns + * the policy decision to userspace. The userspace program receives the + * return code and can decide whether to proceed with script execution. + * + * Return: + * * %0 - Success + * * %-EACCES - Did not pass IPE policy + */ +int ipe_bprm_creds_for_exec(struct linux_binprm *bprm) +{ + struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT; + + if (!bprm->is_check) + return 0; + + ipe_build_eval_ctx(&ctx, bprm->file, IPE_OP_EXEC, + IPE_HOOK_BPRM_CREDS_FOR_EXEC); + return ipe_evaluate_event(&ctx); +} + +/** * ipe_mmap_file() - ipe security hook function for mmap check. * @f: File being mmap'd. Can be NULL in the case of anonymous memory. * @reqprot: The requested protection on the mmap, passed from usermode. @@ -118,6 +145,7 @@ int ipe_kernel_read_file(struct file *file, enum kernel_read_file_id id, op = IPE_OP_FIRMWARE; break; case READING_MODULE: + case READING_MODULE_COMPRESSED: op = IPE_OP_KERNEL_MODULE; break; case READING_KEXEC_INITRAMFS: @@ -259,7 +287,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ } digest = value; - info = kzalloc(sizeof(*info), GFP_KERNEL); + info = kzalloc_obj(*info); if (!info) return -ENOMEM; @@ -311,4 +339,4 @@ int ipe_inode_setintegrity(const struct inode *inode, return -EINVAL; } -#endif /* CONFIG_CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +#endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ diff --git a/security/ipe/hooks.h b/security/ipe/hooks.h index 38d4a387d039..07db37332740 100644 --- a/security/ipe/hooks.h +++ b/security/ipe/hooks.h @@ -13,6 +13,7 @@ enum ipe_hook_type { IPE_HOOK_BPRM_CHECK = 0, + IPE_HOOK_BPRM_CREDS_FOR_EXEC, IPE_HOOK_MMAP, IPE_HOOK_MPROTECT, IPE_HOOK_KERNEL_READ, @@ -24,6 +25,8 @@ enum ipe_hook_type { int ipe_bprm_check_security(struct linux_binprm *bprm); +int ipe_bprm_creds_for_exec(struct linux_binprm *bprm); + int ipe_mmap_file(struct file *f, unsigned long reqprot, unsigned long prot, unsigned long flags); diff --git a/security/ipe/ipe.c b/security/ipe/ipe.c index 4317134cb0da..495bb765de1b 100644 --- a/security/ipe/ipe.c +++ b/security/ipe/ipe.c @@ -47,6 +47,7 @@ struct ipe_inode *ipe_inode(const struct inode *inode) static struct security_hook_list ipe_hooks[] __ro_after_init = { LSM_HOOK_INIT(bprm_check_security, ipe_bprm_check_security), + LSM_HOOK_INIT(bprm_creds_for_exec, ipe_bprm_creds_for_exec), LSM_HOOK_INIT(mmap_file, ipe_mmap_file), LSM_HOOK_INIT(file_mprotect, ipe_file_mprotect), LSM_HOOK_INIT(kernel_read_file, ipe_kernel_read_file), @@ -92,7 +93,8 @@ static int __init ipe_init(void) } DEFINE_LSM(ipe) = { - .name = "ipe", + .id = &ipe_lsmid, .init = ipe_init, .blobs = &ipe_blobs, + .initcall_fs = ipe_init_securityfs, }; diff --git a/security/ipe/ipe.h b/security/ipe/ipe.h index fb37513812dd..25cfdb8f0c20 100644 --- a/security/ipe/ipe.h +++ b/security/ipe/ipe.h @@ -23,4 +23,6 @@ struct ipe_bdev *ipe_bdev(struct block_device *b); struct ipe_inode *ipe_inode(const struct inode *inode); #endif /* CONFIG_IPE_PROP_FS_VERITY_BUILTIN_SIG */ +int ipe_init_securityfs(void); + #endif /* _IPE_H */ diff --git a/security/ipe/policy.c b/security/ipe/policy.c index b628f696e32b..827867be4fac 100644 --- a/security/ipe/policy.c +++ b/security/ipe/policy.c @@ -84,8 +84,11 @@ static int set_pkcs7_data(void *ctx, const void *data, size_t len, * ipe_new_policy. * * Context: Requires root->i_rwsem to be held. - * Return: %0 on success. If an error occurs, the function will return - * the -errno. + * Return: + * * %0 - Success + * * %-ENOENT - Policy was deleted while updating + * * %-EINVAL - Policy name mismatch + * * %-ESTALE - Policy version too old */ int ipe_update_policy(struct inode *root, const char *text, size_t textlen, const char *pkcs7, size_t pkcs7len) @@ -146,10 +149,12 @@ err: * * Return: * * a pointer to the ipe_policy structure - Success - * * %-EBADMSG - Policy is invalid - * * %-ENOMEM - Out of memory (OOM) - * * %-ERANGE - Policy version number overflow - * * %-EINVAL - Policy version parsing error + * * %-EBADMSG - Policy is invalid + * * %-ENOMEM - Out of memory (OOM) + * * %-ERANGE - Policy version number overflow + * * %-EINVAL - Policy version parsing error + * * %-ENOKEY - Policy signing key not found + * * %-EKEYREJECTED - Policy signature verification failed */ struct ipe_policy *ipe_new_policy(const char *text, size_t textlen, const char *pkcs7, size_t pkcs7len) @@ -157,7 +162,7 @@ struct ipe_policy *ipe_new_policy(const char *text, size_t textlen, struct ipe_policy *new = NULL; int rc = 0; - new = kzalloc(sizeof(*new), GFP_KERNEL); + new = kzalloc_obj(*new); if (!new) return ERR_PTR(-ENOMEM); diff --git a/security/ipe/policy_fs.c b/security/ipe/policy_fs.c index 3bcd8cbd09df..9d92d8a14b13 100644 --- a/security/ipe/policy_fs.c +++ b/security/ipe/policy_fs.c @@ -12,11 +12,16 @@ #include "policy.h" #include "eval.h" #include "fs.h" +#include "audit.h" #define MAX_VERSION_SIZE ARRAY_SIZE("65535.65535.65535") /** - * ipefs_file - defines a file in securityfs. + * struct ipefs_file - defines a file in securityfs. + * + * @name: file name inside the policy subdirectory + * @access: file permissions + * @fops: &file_operations specific to this file */ struct ipefs_file { const char *name; @@ -282,8 +287,13 @@ static ssize_t getactive(struct file *f, char __user *data, * On success this updates the policy represented by $name, * in-place. * - * Return: Length of buffer written on success. If an error occurs, - * the function will return the -errno. + * Return: + * * Length of buffer written - Success + * * %-EPERM - Insufficient permission + * * %-ENOMEM - Out of memory (OOM) + * * %-ENOENT - Policy was deleted while updating + * * %-EINVAL - Policy name mismatch + * * %-ESTALE - Policy version too old */ static ssize_t update_policy(struct file *f, const char __user *data, size_t len, loff_t *offset) @@ -292,21 +302,29 @@ static ssize_t update_policy(struct file *f, const char __user *data, char *copy = NULL; int rc = 0; - if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) - return -EPERM; + if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) { + rc = -EPERM; + goto out; + } copy = memdup_user(data, len); - if (IS_ERR(copy)) - return PTR_ERR(copy); + if (IS_ERR(copy)) { + rc = PTR_ERR(copy); + copy = NULL; + goto out; + } root = d_inode(f->f_path.dentry->d_parent); inode_lock(root); rc = ipe_update_policy(root, NULL, 0, copy, len); inode_unlock(root); +out: kfree(copy); - if (rc) + if (rc) { + ipe_audit_policy_load(ERR_PTR(rc)); return rc; + } return len; } @@ -401,7 +419,7 @@ static const struct file_operations delete_fops = { .write = delete_policy, }; -/** +/* * policy_subdir - files under a policy subdirectory */ static const struct ipefs_file policy_subdir[] = { @@ -420,7 +438,7 @@ static const struct ipefs_file policy_subdir[] = { */ void ipe_del_policyfs_node(struct ipe_policy *p) { - securityfs_recursive_remove(p->policyfs); + securityfs_remove(p->policyfs); p->policyfs = NULL; } @@ -467,6 +485,6 @@ int ipe_new_policyfs_node(struct ipe_policy *p) return 0; err: - securityfs_recursive_remove(policyfs); + securityfs_remove(policyfs); return rc; } diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c index 7f27e39931d6..6fa5bebf8471 100644 --- a/security/ipe/policy_parser.c +++ b/security/ipe/policy_parser.c @@ -30,7 +30,7 @@ static struct ipe_parsed_policy *new_parsed_policy(void) struct ipe_op_table *t = NULL; size_t i = 0; - p = kzalloc(sizeof(*p), GFP_KERNEL); + p = kzalloc_obj(*p); if (!p) return ERR_PTR(-ENOMEM); @@ -305,7 +305,7 @@ static int parse_property(char *t, struct ipe_rule *r) int token; char *dup = NULL; - p = kzalloc(sizeof(*p), GFP_KERNEL); + p = kzalloc_obj(*p); if (!p) return -ENOMEM; @@ -373,7 +373,7 @@ static int parse_rule(char *line, struct ipe_parsed_policy *p) if (IS_ERR_OR_NULL(line)) return -EBADMSG; - r = kzalloc(sizeof(*r), GFP_KERNEL); + r = kzalloc_obj(*r); if (!r) return -ENOMEM; diff --git a/security/keys/Kconfig b/security/keys/Kconfig index abb03a1b2a5c..84f39e50ca36 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -3,7 +3,7 @@ # Key management configuration # -config KEYS +menuconfig KEYS bool "Enable access key retention support" select ASSOCIATIVE_ARRAY help @@ -21,9 +21,10 @@ config KEYS If you are unsure as to whether this is required, answer N. +if KEYS + config KEYS_REQUEST_CACHE bool "Enable temporary caching of the last request_key() result" - depends on KEYS help This option causes the result of the last successful request_key() call that didn't upcall to the kernel to be cached temporarily in the @@ -41,7 +42,6 @@ config KEYS_REQUEST_CACHE config PERSISTENT_KEYRINGS bool "Enable register of persistent per-UID keyrings" - depends on KEYS help This option provides a register of persistent per-UID keyrings, primarily aimed at Kerberos key storage. The keyrings are persistent @@ -58,9 +58,8 @@ config PERSISTENT_KEYRINGS config BIG_KEYS bool "Large payload keys" - depends on KEYS depends on TMPFS - depends on CRYPTO_LIB_CHACHA20POLY1305 = y + select CRYPTO_LIB_CHACHA20POLY1305 help This option provides support for holding large keys within the kernel (for example Kerberos ticket caches). The data may be stored out to @@ -70,7 +69,6 @@ config BIG_KEYS config TRUSTED_KEYS tristate "TRUSTED KEYS" - depends on KEYS help This option provides support for creating, sealing, and unsealing keys in the kernel. Trusted keys are random number symmetric keys, @@ -85,12 +83,10 @@ endif config ENCRYPTED_KEYS tristate "ENCRYPTED KEYS" - depends on KEYS select CRYPTO - select CRYPTO_HMAC select CRYPTO_AES select CRYPTO_CBC - select CRYPTO_SHA256 + select CRYPTO_LIB_SHA256 select CRYPTO_RNG help This option provides support for create/encrypting/decrypting keys @@ -114,7 +110,6 @@ config USER_DECRYPTED_DATA config KEY_DH_OPERATIONS bool "Diffie-Hellman operations on retained keys" - depends on KEYS select CRYPTO select CRYPTO_KDF800108_CTR select CRYPTO_DH @@ -127,9 +122,11 @@ config KEY_DH_OPERATIONS config KEY_NOTIFICATIONS bool "Provide key/keyring change notifications" - depends on KEYS && WATCH_QUEUE + depends on WATCH_QUEUE help This option provides support for getting change notifications on keys and keyrings on which the caller has View permission. This makes use of pipes to handle the notification buffer and provides KEYCTL_WATCH_KEY to enable/disable watches. + +endif # KEYS diff --git a/security/keys/big_key.c b/security/keys/big_key.c index c3367622c683..268f702df380 100644 --- a/security/keys/big_key.c +++ b/security/keys/big_key.c @@ -66,7 +66,7 @@ int big_key_preparse(struct key_preparsed_payload *prep) BUILD_BUG_ON(sizeof(*payload) != sizeof(prep->payload.data)); - if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data) + if (datalen == 0 || datalen > 1024 * 1024 || !prep->data) return -EINVAL; /* Set an arbitrary quota */ @@ -103,7 +103,7 @@ int big_key_preparse(struct key_preparsed_payload *prep) 0, enckey); /* save aligned data to file */ - file = shmem_kernel_file_setup("", enclen, 0); + file = shmem_kernel_file_setup("", enclen, EMPTY_VMA_FLAGS); if (IS_ERR(file)) { ret = PTR_ERR(file); goto err_enckey; diff --git a/security/keys/encrypted-keys/ecryptfs_format.c b/security/keys/encrypted-keys/ecryptfs_format.c index 8fdd76105ce3..2fc6f3a66135 100644 --- a/security/keys/encrypted-keys/ecryptfs_format.c +++ b/security/keys/encrypted-keys/ecryptfs_format.c @@ -54,8 +54,7 @@ int ecryptfs_fill_auth_tok(struct ecryptfs_auth_tok *auth_tok, auth_tok->version = (((uint16_t)(major << 8) & 0xFF00) | ((uint16_t)minor & 0x00FF)); auth_tok->token_type = ECRYPTFS_PASSWORD; - strncpy((char *)auth_tok->token.password.signature, key_desc, - ECRYPTFS_PASSWORD_SIG_SIZE); + strscpy_pad(auth_tok->token.password.signature, key_desc); auth_tok->token.password.session_key_encryption_key_bytes = ECRYPTFS_MAX_KEY_BYTES; /* diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 831cb84fd75a..56b531587a1e 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -13,6 +13,7 @@ #include <linux/uaccess.h> #include <linux/module.h> +#include <linux/hex.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/parser.h> @@ -27,7 +28,6 @@ #include <linux/scatterlist.h> #include <linux/ctype.h> #include <crypto/aes.h> -#include <crypto/hash.h> #include <crypto/sha2.h> #include <crypto/skcipher.h> #include <crypto/utils.h> @@ -37,8 +37,6 @@ static const char KEY_TRUSTED_PREFIX[] = "trusted:"; static const char KEY_USER_PREFIX[] = "user:"; -static const char hash_alg[] = "sha256"; -static const char hmac_alg[] = "hmac(sha256)"; static const char blkcipher_alg[] = "cbc(aes)"; static const char key_format_default[] = "default"; static const char key_format_ecryptfs[] = "ecryptfs"; @@ -54,8 +52,6 @@ static int blksize; #define MIN_DATA_SIZE 20 #define KEY_ENC32_PAYLOAD_LEN 32 -static struct crypto_shash *hash_tfm; - enum { Opt_new, Opt_load, Opt_update, Opt_err }; @@ -329,26 +325,6 @@ error: return ukey; } -static int calc_hmac(u8 *digest, const u8 *key, unsigned int keylen, - const u8 *buf, unsigned int buflen) -{ - struct crypto_shash *tfm; - int err; - - tfm = crypto_alloc_shash(hmac_alg, 0, 0); - if (IS_ERR(tfm)) { - pr_err("encrypted_key: can't alloc %s transform: %ld\n", - hmac_alg, PTR_ERR(tfm)); - return PTR_ERR(tfm); - } - - err = crypto_shash_setkey(tfm, key, keylen); - if (!err) - err = crypto_shash_tfm_digest(tfm, buf, buflen, digest); - crypto_free_shash(tfm); - return err; -} - enum derived_key_type { ENC_KEY, AUTH_KEY }; /* Derive authentication/encryption key from trusted key */ @@ -357,7 +333,6 @@ static int get_derived_key(u8 *derived_key, enum derived_key_type key_type, { u8 *derived_buf; unsigned int derived_buf_len; - int ret; derived_buf_len = strlen("AUTH_KEY") + 1 + master_keylen; if (derived_buf_len < HASH_SIZE) @@ -374,10 +349,9 @@ static int get_derived_key(u8 *derived_key, enum derived_key_type key_type, memcpy(derived_buf + strlen(derived_buf) + 1, master_key, master_keylen); - ret = crypto_shash_tfm_digest(hash_tfm, derived_buf, derived_buf_len, - derived_key); + sha256(derived_buf, derived_buf_len, derived_key); kfree_sensitive(derived_buf); - return ret; + return 0; } static struct skcipher_request *init_skcipher_req(const u8 *key, @@ -503,10 +477,10 @@ static int datablob_hmac_append(struct encrypted_key_payload *epayload, goto out; digest = epayload->format + epayload->datablob_len; - ret = calc_hmac(digest, derived_key, sizeof derived_key, - epayload->format, epayload->datablob_len); - if (!ret) - dump_hmac(NULL, digest, HASH_SIZE); + hmac_sha256_usingrawkey(derived_key, sizeof(derived_key), + epayload->format, epayload->datablob_len, + digest); + dump_hmac(NULL, digest, HASH_SIZE); out: memzero_explicit(derived_key, sizeof(derived_key)); return ret; @@ -534,9 +508,8 @@ static int datablob_hmac_verify(struct encrypted_key_payload *epayload, } else p = epayload->format; - ret = calc_hmac(digest, derived_key, sizeof derived_key, p, len); - if (ret < 0) - goto out; + hmac_sha256_usingrawkey(derived_key, sizeof(derived_key), p, len, + digest); ret = crypto_memneq(digest, epayload->format + epayload->datablob_len, sizeof(digest)); if (ret) { @@ -823,7 +796,7 @@ static int encrypted_instantiate(struct key *key, size_t datalen = prep->datalen; int ret; - if (datalen <= 0 || datalen > 32767 || !prep->data) + if (datalen == 0 || datalen > 32767 || !prep->data) return -EINVAL; datablob = kmalloc(datalen + 1, GFP_KERNEL); @@ -884,7 +857,7 @@ static int encrypted_update(struct key *key, struct key_preparsed_payload *prep) if (key_is_negative(key)) return -ENOKEY; - if (datalen <= 0 || datalen > 32767 || !prep->data) + if (datalen == 0 || datalen > 32767 || !prep->data) return -EINVAL; buf = kmalloc(datalen + 1, GFP_KERNEL); @@ -1011,29 +984,14 @@ static int __init init_encrypted(void) { int ret; - hash_tfm = crypto_alloc_shash(hash_alg, 0, 0); - if (IS_ERR(hash_tfm)) { - pr_err("encrypted_key: can't allocate %s transform: %ld\n", - hash_alg, PTR_ERR(hash_tfm)); - return PTR_ERR(hash_tfm); - } - ret = aes_get_sizes(); if (ret < 0) - goto out; - ret = register_key_type(&key_type_encrypted); - if (ret < 0) - goto out; - return 0; -out: - crypto_free_shash(hash_tfm); - return ret; - + return ret; + return register_key_type(&key_type_encrypted); } static void __exit cleanup_encrypted(void) { - crypto_free_shash(hash_tfm); unregister_key_type(&key_type_encrypted); } diff --git a/security/keys/gc.c b/security/keys/gc.c index 7d687b0962b1..748e83818a76 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -218,8 +218,10 @@ continue_scanning: key = rb_entry(cursor, struct key, serial_node); cursor = rb_next(cursor); - if (refcount_read(&key->usage) == 0) + if (!test_bit_acquire(KEY_FLAG_USER_ALIVE, &key->flags)) { + /* Clobber key->user after final put seen. */ goto found_unreferenced_key; + } if (unlikely(gc_state & KEY_GC_REAPING_DEAD_1)) { if (key->type == key_gc_dead_keytype) { diff --git a/security/keys/key.c b/security/keys/key.c index 3d7d185019d3..091ee084bc30 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -77,7 +77,7 @@ try_again: spin_unlock(&key_user_lock); user = NULL; - candidate = kmalloc(sizeof(struct key_user), GFP_KERNEL); + candidate = kmalloc_obj(struct key_user); if (unlikely(!candidate)) goto out; @@ -298,6 +298,7 @@ struct key *key_alloc(struct key_type *type, const char *desc, key->restrict_link = restrict_link; key->last_used_at = ktime_get_real_seconds(); + key->flags |= 1 << KEY_FLAG_USER_ALIVE; if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) key->flags |= 1 << KEY_FLAG_IN_QUOTA; if (flags & KEY_ALLOC_BUILT_IN) @@ -658,6 +659,8 @@ void key_put(struct key *key) key->user->qnbytes -= key->quotalen; spin_unlock_irqrestore(&key->user->lock, flags); } + /* Mark key as safe for GC after key->user done. */ + clear_bit_unlock(KEY_FLAG_USER_ALIVE, &key->flags); schedule_work(&key_gc_work); } } diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index ab927a142f51..ef855d69c97a 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -1796,13 +1796,13 @@ long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id) if (watch_id >= 0) { ret = -ENOMEM; if (!key->watchers) { - wlist = kzalloc(sizeof(*wlist), GFP_KERNEL); + wlist = kzalloc_obj(*wlist); if (!wlist) goto err_wqueue; init_watch_list(wlist, NULL); } - watch = kzalloc(sizeof(*watch), GFP_KERNEL); + watch = kzalloc_obj(*watch); if (!watch) goto err_wlist; diff --git a/security/keys/keyring.c b/security/keys/keyring.c index f331725d5a37..5a9887d6b7be 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -977,7 +977,7 @@ static struct key_restriction *keyring_restriction_alloc( key_restrict_link_func_t check) { struct key_restriction *keyres = - kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + kzalloc_obj(struct key_restriction); if (!keyres) return ERR_PTR(-ENOMEM); @@ -1109,6 +1109,7 @@ key_ref_t find_key_to_update(key_ref_t keyring_ref, kenter("{%d},{%s,%s}", keyring->serial, index_key->type->name, index_key->description); + guard(rcu)(); object = assoc_array_find(&keyring->keys, &keyring_assoc_array_ops, index_key); diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index b5d5333ab330..a63c46bb2d14 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -51,7 +51,7 @@ static struct key *get_user_register(struct user_namespace *user_ns) if (!reg_keyring) { reg_keyring = keyring_alloc(".user_reg", user_ns->owner, INVALID_GID, - &init_cred, + kernel_cred(), KEY_POS_WRITE | KEY_POS_SEARCH | KEY_USR_VIEW | KEY_USR_READ, 0, diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 8f33cd170e42..a7d7538c1f70 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -171,7 +171,7 @@ struct key *request_key_auth_new(struct key *target, const char *op, kenter("%d,", target->serial); /* allocate a auth record */ - rka = kzalloc(sizeof(*rka), GFP_KERNEL); + rka = kzalloc_obj(*rka); if (!rka) goto error; rka->callout_info = kmemdup(callout_info, callout_len, GFP_KERNEL); diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig index 1fb8aa001995..9e00482d886a 100644 --- a/security/keys/trusted-keys/Kconfig +++ b/security/keys/trusted-keys/Kconfig @@ -5,10 +5,9 @@ config TRUSTED_KEYS_TPM bool "TPM-based trusted keys" depends on TCG_TPM >= TRUSTED_KEYS default y - select CRYPTO - select CRYPTO_HMAC - select CRYPTO_SHA1 select CRYPTO_HASH_INFO + select CRYPTO_LIB_SHA1 + select CRYPTO_LIB_UTILS select ASN1_ENCODER select OID_REGISTRY select ASN1 @@ -47,6 +46,14 @@ config TRUSTED_KEYS_DCP help Enable use of NXP's DCP (Data Co-Processor) as trusted key backend. +config TRUSTED_KEYS_PKWM + bool "PKWM-based trusted keys" + depends on PSERIES_PLPKS >= TRUSTED_KEYS + default y + select HAVE_TRUSTED_KEYS + help + Enable use of IBM PowerVM Key Wrapping Module (PKWM) as a trusted key backend. + if !HAVE_TRUSTED_KEYS comment "No trust source selected!" endif diff --git a/security/keys/trusted-keys/Makefile b/security/keys/trusted-keys/Makefile index f0f3b27f688b..5fc053a21dad 100644 --- a/security/keys/trusted-keys/Makefile +++ b/security/keys/trusted-keys/Makefile @@ -16,3 +16,5 @@ trusted-$(CONFIG_TRUSTED_KEYS_TEE) += trusted_tee.o trusted-$(CONFIG_TRUSTED_KEYS_CAAM) += trusted_caam.o trusted-$(CONFIG_TRUSTED_KEYS_DCP) += trusted_dcp.o + +trusted-$(CONFIG_TRUSTED_KEYS_PKWM) += trusted_pkwm.o diff --git a/security/keys/trusted-keys/trusted_caam.c b/security/keys/trusted-keys/trusted_caam.c index e3415c520c0a..601943ce0d60 100644 --- a/security/keys/trusted-keys/trusted_caam.c +++ b/security/keys/trusted-keys/trusted_caam.c @@ -1,12 +1,14 @@ // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021 Pengutronix, Ahmad Fatoum <kernel@pengutronix.de> + * Copyright 2025 NXP */ #include <keys/trusted_caam.h> #include <keys/trusted-type.h> #include <linux/build_bug.h> #include <linux/key-type.h> +#include <linux/parser.h> #include <soc/fsl/caam-blob.h> static struct caam_blob_priv *blobifier; @@ -16,6 +18,77 @@ static struct caam_blob_priv *blobifier; static_assert(MAX_KEY_SIZE + CAAM_BLOB_OVERHEAD <= CAAM_BLOB_MAX_LEN); static_assert(MAX_BLOB_SIZE <= CAAM_BLOB_MAX_LEN); +enum { + opt_err, + opt_key_enc_algo, +}; + +static const match_table_t key_tokens = { + {opt_key_enc_algo, "key_enc_algo=%s"}, + {opt_err, NULL} +}; + +#ifdef CAAM_DEBUG +static inline void dump_options(const struct caam_pkey_info *pkey_info) +{ + pr_info("key encryption algo %d\n", pkey_info->key_enc_algo); +} +#else +static inline void dump_options(const struct caam_pkey_info *pkey_info) +{ +} +#endif + +static int get_pkey_options(char *c, + struct caam_pkey_info *pkey_info) +{ + substring_t args[MAX_OPT_ARGS]; + unsigned long token_mask = 0; + u16 key_enc_algo; + char *p = c; + int token; + int res; + + if (!c) + return 0; + + while ((p = strsep(&c, " \t"))) { + if (*p == '\0' || *p == ' ' || *p == '\t') + continue; + token = match_token(p, key_tokens, args); + if (test_and_set_bit(token, &token_mask)) + return -EINVAL; + + switch (token) { + case opt_key_enc_algo: + res = kstrtou16(args[0].from, 16, &key_enc_algo); + if (res < 0) + return -EINVAL; + pkey_info->key_enc_algo = key_enc_algo; + break; + default: + return -EINVAL; + } + } + return 0; +} + +static bool is_key_pkey(char **datablob) +{ + char *c = NULL; + + do { + /* Second argument onwards, + * determine if tied to HW + */ + c = strsep(datablob, " \t"); + if (c && (strcmp(c, "pk") == 0)) + return true; + } while (c); + + return false; +} + static int trusted_caam_seal(struct trusted_key_payload *p, char *datablob) { int ret; @@ -25,11 +98,30 @@ static int trusted_caam_seal(struct trusted_key_payload *p, char *datablob) .key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1, }; + /* + * If it is to be treated as protected key, + * read next arguments too. + */ + if (is_key_pkey(&datablob)) { + info.pkey_info.plain_key_sz = p->key_len; + info.pkey_info.is_pkey = 1; + ret = get_pkey_options(datablob, &info.pkey_info); + if (ret < 0) + return 0; + dump_options(&info.pkey_info); + } + ret = caam_encap_blob(blobifier, &info); if (ret) return ret; p->blob_len = info.output_len; + if (info.pkey_info.is_pkey) { + p->key_len = p->blob_len + sizeof(struct caam_pkey_info); + memcpy(p->key, &info.pkey_info, sizeof(struct caam_pkey_info)); + memcpy(p->key + sizeof(struct caam_pkey_info), p->blob, p->blob_len); + } + return 0; } @@ -42,11 +134,27 @@ static int trusted_caam_unseal(struct trusted_key_payload *p, char *datablob) .key_mod = KEYMOD, .key_mod_len = sizeof(KEYMOD) - 1, }; + if (is_key_pkey(&datablob)) { + info.pkey_info.plain_key_sz = p->blob_len - CAAM_BLOB_OVERHEAD; + info.pkey_info.is_pkey = 1; + ret = get_pkey_options(datablob, &info.pkey_info); + if (ret < 0) + return 0; + dump_options(&info.pkey_info); + + p->key_len = p->blob_len + sizeof(struct caam_pkey_info); + memcpy(p->key, &info.pkey_info, sizeof(struct caam_pkey_info)); + memcpy(p->key + sizeof(struct caam_pkey_info), p->blob, p->blob_len); + + return 0; + } + ret = caam_decap_blob(blobifier, &info); if (ret) return ret; p->key_len = info.output_len; + return 0; } diff --git a/security/keys/trusted-keys/trusted_core.c b/security/keys/trusted-keys/trusted_core.c index e2d9644efde1..0b142d941cd2 100644 --- a/security/keys/trusted-keys/trusted_core.c +++ b/security/keys/trusted-keys/trusted_core.c @@ -12,8 +12,10 @@ #include <keys/trusted_caam.h> #include <keys/trusted_dcp.h> #include <keys/trusted_tpm.h> +#include <keys/trusted_pkwm.h> #include <linux/capability.h> #include <linux/err.h> +#include <linux/hex.h> #include <linux/init.h> #include <linux/key-type.h> #include <linux/module.h> @@ -31,7 +33,7 @@ MODULE_PARM_DESC(rng, "Select trusted key RNG"); static char *trusted_key_source; module_param_named(source, trusted_key_source, charp, 0); -MODULE_PARM_DESC(source, "Select trusted keys source (tpm, tee, caam or dcp)"); +MODULE_PARM_DESC(source, "Select trusted keys source (tpm, tee, caam, dcp or pkwm)"); static const struct trusted_key_source trusted_key_sources[] = { #if defined(CONFIG_TRUSTED_KEYS_TPM) @@ -46,6 +48,9 @@ static const struct trusted_key_source trusted_key_sources[] = { #if defined(CONFIG_TRUSTED_KEYS_DCP) { "dcp", &dcp_trusted_key_ops }, #endif +#if defined(CONFIG_TRUSTED_KEYS_PKWM) + { "pkwm", &pkwm_trusted_key_ops }, +#endif }; DEFINE_STATIC_CALL_NULL(trusted_key_seal, *trusted_key_sources[0].ops->seal); @@ -129,7 +134,7 @@ static struct trusted_key_payload *trusted_payload_alloc(struct key *key) ret = key_payload_reserve(key, sizeof(*p)); if (ret < 0) goto err; - p = kzalloc(sizeof(*p), GFP_KERNEL); + p = kzalloc_obj(*p); if (!p) goto err; @@ -157,7 +162,7 @@ static int trusted_instantiate(struct key *key, int key_cmd; size_t key_len; - if (datalen <= 0 || datalen > 32767 || !prep->data) + if (datalen == 0 || datalen > 32767 || !prep->data) return -EINVAL; orig_datablob = datablob = kmalloc(datalen + 1, GFP_KERNEL); @@ -240,7 +245,7 @@ static int trusted_update(struct key *key, struct key_preparsed_payload *prep) p = key->payload.data[0]; if (!p->migratable) return -EPERM; - if (datalen <= 0 || datalen > 32767 || !prep->data) + if (datalen == 0 || datalen > 32767 || !prep->data) return -EINVAL; orig_datablob = datablob = kmalloc(datalen + 1, GFP_KERNEL); diff --git a/security/keys/trusted-keys/trusted_pkwm.c b/security/keys/trusted-keys/trusted_pkwm.c new file mode 100644 index 000000000000..bf42c6679245 --- /dev/null +++ b/security/keys/trusted-keys/trusted_pkwm.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 IBM Corporation, Srish Srinivasan <ssrish@linux.ibm.com> + */ + +#include <keys/trusted_pkwm.h> +#include <keys/trusted-type.h> +#include <linux/build_bug.h> +#include <linux/key-type.h> +#include <linux/parser.h> +#include <asm/plpks.h> + +enum { + Opt_err, + Opt_wrap_flags, +}; + +static const match_table_t key_tokens = { + {Opt_wrap_flags, "wrap_flags=%s"}, + {Opt_err, NULL} +}; + +static int getoptions(char *datablob, struct trusted_key_options *opt) +{ + substring_t args[MAX_OPT_ARGS]; + char *p = datablob; + int token; + int res; + u16 wrap_flags; + unsigned long token_mask = 0; + struct trusted_pkwm_options *pkwm; + + if (!datablob) + return 0; + + pkwm = opt->private; + + while ((p = strsep(&datablob, " \t"))) { + if (*p == '\0' || *p == ' ' || *p == '\t') + continue; + + token = match_token(p, key_tokens, args); + if (test_and_set_bit(token, &token_mask)) + return -EINVAL; + + switch (token) { + case Opt_wrap_flags: + res = kstrtou16(args[0].from, 16, &wrap_flags); + if (res < 0 || wrap_flags > 2) + return -EINVAL; + pkwm->wrap_flags = wrap_flags; + break; + default: + return -EINVAL; + } + } + return 0; +} + +static struct trusted_key_options *trusted_options_alloc(void) +{ + struct trusted_key_options *options; + struct trusted_pkwm_options *pkwm; + + options = kzalloc_obj(*options); + + if (options) { + pkwm = kzalloc_obj(*pkwm); + + if (!pkwm) { + kfree_sensitive(options); + options = NULL; + } else { + options->private = pkwm; + } + } + + return options; +} + +static int trusted_pkwm_seal(struct trusted_key_payload *p, char *datablob) +{ + struct trusted_key_options *options = NULL; + struct trusted_pkwm_options *pkwm = NULL; + u8 *input_buf, *output_buf; + u32 output_len, input_len; + int rc; + + options = trusted_options_alloc(); + + if (!options) + return -ENOMEM; + + rc = getoptions(datablob, options); + if (rc < 0) + goto out; + dump_options(options); + + input_len = p->key_len; + input_buf = kmalloc(ALIGN(input_len, 4096), GFP_KERNEL); + if (!input_buf) { + pr_err("Input buffer allocation failed. Returning -ENOMEM."); + rc = -ENOMEM; + goto out; + } + + memcpy(input_buf, p->key, p->key_len); + + pkwm = options->private; + + rc = plpks_wrap_object(&input_buf, input_len, pkwm->wrap_flags, + &output_buf, &output_len); + if (!rc) { + memcpy(p->blob, output_buf, output_len); + p->blob_len = output_len; + dump_payload(p); + } else { + pr_err("Wrapping of payload key failed: %d\n", rc); + } + + kfree(input_buf); + kfree(output_buf); + +out: + kfree_sensitive(options->private); + kfree_sensitive(options); + return rc; +} + +static int trusted_pkwm_unseal(struct trusted_key_payload *p, char *datablob) +{ + u8 *input_buf, *output_buf; + u32 input_len, output_len; + int rc; + + input_len = p->blob_len; + input_buf = kmalloc(ALIGN(input_len, 4096), GFP_KERNEL); + if (!input_buf) { + pr_err("Input buffer allocation failed. Returning -ENOMEM."); + return -ENOMEM; + } + + memcpy(input_buf, p->blob, p->blob_len); + + rc = plpks_unwrap_object(&input_buf, input_len, &output_buf, + &output_len); + if (!rc) { + memcpy(p->key, output_buf, output_len); + p->key_len = output_len; + dump_payload(p); + } else { + pr_err("Unwrapping of payload failed: %d\n", rc); + } + + kfree(input_buf); + kfree(output_buf); + + return rc; +} + +static int trusted_pkwm_init(void) +{ + int ret; + + if (!plpks_wrapping_is_supported()) { + pr_err("H_PKS_WRAP_OBJECT interface not supported\n"); + return -ENODEV; + } + + ret = plpks_gen_wrapping_key(); + if (ret) { + pr_err("Failed to generate default wrapping key\n"); + return -EINVAL; + } + + return register_key_type(&key_type_trusted); +} + +static void trusted_pkwm_exit(void) +{ + unregister_key_type(&key_type_trusted); +} + +struct trusted_key_ops pkwm_trusted_key_ops = { + .migratable = 0, /* non-migratable */ + .init = trusted_pkwm_init, + .seal = trusted_pkwm_seal, + .unseal = trusted_pkwm_unseal, + .exit = trusted_pkwm_exit, +}; diff --git a/security/keys/trusted-keys/trusted_tee.c b/security/keys/trusted-keys/trusted_tee.c index aa3d477de6db..6e465c8bef5e 100644 --- a/security/keys/trusted-keys/trusted_tee.c +++ b/security/keys/trusted-keys/trusted_tee.c @@ -202,9 +202,9 @@ static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) return 0; } -static int trusted_key_probe(struct device *dev) +static int trusted_key_probe(struct tee_client_device *rng_device) { - struct tee_client_device *rng_device = to_tee_client_device(dev); + struct device *dev = &rng_device->dev; int ret; struct tee_ioctl_open_session_arg sess_arg; @@ -244,13 +244,11 @@ out_ctx: return ret; } -static int trusted_key_remove(struct device *dev) +static void trusted_key_remove(struct tee_client_device *dev) { unregister_key_type(&key_type_trusted); tee_client_close_session(pvt_data.ctx, pvt_data.session_id); tee_client_close_context(pvt_data.ctx); - - return 0; } static const struct tee_client_device_id trusted_key_id_table[] = { @@ -261,23 +259,22 @@ static const struct tee_client_device_id trusted_key_id_table[] = { MODULE_DEVICE_TABLE(tee, trusted_key_id_table); static struct tee_client_driver trusted_key_driver = { + .probe = trusted_key_probe, + .remove = trusted_key_remove, .id_table = trusted_key_id_table, .driver = { .name = DRIVER_NAME, - .bus = &tee_bus_type, - .probe = trusted_key_probe, - .remove = trusted_key_remove, }, }; static int trusted_tee_init(void) { - return driver_register(&trusted_key_driver.driver); + return tee_client_driver_register(&trusted_key_driver); } static void trusted_tee_exit(void) { - driver_unregister(&trusted_key_driver.driver); + tee_client_driver_unregister(&trusted_key_driver); } struct trusted_key_ops trusted_key_tee_ops = { diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index 89c9798d1800..6ea728f1eae6 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -7,6 +7,9 @@ */ #include <crypto/hash_info.h> +#include <crypto/sha1.h> +#include <crypto/utils.h> +#include <linux/hex.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/parser.h> @@ -14,78 +17,92 @@ #include <linux/err.h> #include <keys/trusted-type.h> #include <linux/key-type.h> -#include <linux/crypto.h> -#include <crypto/hash.h> -#include <crypto/sha1.h> #include <linux/tpm.h> #include <linux/tpm_command.h> #include <keys/trusted_tpm.h> -static const char hmac_alg[] = "hmac(sha1)"; -static const char hash_alg[] = "sha1"; static struct tpm_chip *chip; static struct tpm_digest *digests; -struct sdesc { - struct shash_desc shash; - char ctx[]; +/* implementation specific TPM constants */ +#define TPM_SIZE_OFFSET 2 +#define TPM_RETURN_OFFSET 6 +#define TPM_DATA_OFFSET 10 + +#define LOAD32(buffer, offset) (ntohl(*(uint32_t *)&buffer[offset])) +#define LOAD32N(buffer, offset) (*(uint32_t *)&buffer[offset]) +#define LOAD16(buffer, offset) (ntohs(*(uint16_t *)&buffer[offset])) + +struct osapsess { + uint32_t handle; + unsigned char secret[SHA1_DIGEST_SIZE]; + unsigned char enonce[TPM_NONCE_SIZE]; }; -static struct crypto_shash *hashalg; -static struct crypto_shash *hmacalg; +/* discrete values, but have to store in uint16_t for TPM use */ +enum { + SEAL_keytype = 1, + SRK_keytype = 4 +}; -static struct sdesc *init_sdesc(struct crypto_shash *alg) +#define TPM_DEBUG 0 + +#if TPM_DEBUG +static inline void dump_options(struct trusted_key_options *o) { - struct sdesc *sdesc; - int size; - - size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); - sdesc = kmalloc(size, GFP_KERNEL); - if (!sdesc) - return ERR_PTR(-ENOMEM); - sdesc->shash.tfm = alg; - return sdesc; + pr_info("sealing key type %d\n", o->keytype); + pr_info("sealing key handle %0X\n", o->keyhandle); + pr_info("pcrlock %d\n", o->pcrlock); + pr_info("pcrinfo %d\n", o->pcrinfo_len); + print_hex_dump(KERN_INFO, "pcrinfo ", DUMP_PREFIX_NONE, + 16, 1, o->pcrinfo, o->pcrinfo_len, 0); } -static int TSS_sha1(const unsigned char *data, unsigned int datalen, - unsigned char *digest) +static inline void dump_sess(struct osapsess *s) { - struct sdesc *sdesc; - int ret; + print_hex_dump(KERN_INFO, "trusted-key: handle ", DUMP_PREFIX_NONE, + 16, 1, &s->handle, 4, 0); + pr_info("secret:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->secret, SHA1_DIGEST_SIZE, 0); + pr_info("trusted-key: enonce:\n"); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, + 16, 1, &s->enonce, SHA1_DIGEST_SIZE, 0); +} - sdesc = init_sdesc(hashalg); - if (IS_ERR(sdesc)) { - pr_info("can't alloc %s\n", hash_alg); - return PTR_ERR(sdesc); - } +static inline void dump_tpm_buf(unsigned char *buf) +{ + int len; - ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); - kfree_sensitive(sdesc); - return ret; + pr_info("\ntpm buffer\n"); + len = LOAD32(buf, TPM_SIZE_OFFSET); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 16, 1, buf, len, 0); +} +#else +static inline void dump_options(struct trusted_key_options *o) +{ } +static inline void dump_sess(struct osapsess *s) +{ +} + +static inline void dump_tpm_buf(unsigned char *buf) +{ +} +#endif + static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, unsigned int keylen, ...) { - struct sdesc *sdesc; + struct hmac_sha1_ctx hmac_ctx; va_list argp; unsigned int dlen; unsigned char *data; - int ret; + int ret = 0; - sdesc = init_sdesc(hmacalg); - if (IS_ERR(sdesc)) { - pr_info("can't alloc %s\n", hmac_alg); - return PTR_ERR(sdesc); - } - - ret = crypto_shash_setkey(hmacalg, key, keylen); - if (ret < 0) - goto out; - ret = crypto_shash_init(&sdesc->shash); - if (ret < 0) - goto out; + hmac_sha1_init_usingrawkey(&hmac_ctx, key, keylen); va_start(argp, keylen); for (;;) { @@ -97,46 +114,34 @@ static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, ret = -EINVAL; break; } - ret = crypto_shash_update(&sdesc->shash, data, dlen); - if (ret < 0) - break; + hmac_sha1_update(&hmac_ctx, data, dlen); } va_end(argp); if (!ret) - ret = crypto_shash_final(&sdesc->shash, digest); -out: - kfree_sensitive(sdesc); + hmac_sha1_final(&hmac_ctx, digest); return ret; } /* * calculate authorization info fields to send to TPM */ -int TSS_authhmac(unsigned char *digest, const unsigned char *key, +static int TSS_authhmac(unsigned char *digest, const unsigned char *key, unsigned int keylen, unsigned char *h1, unsigned char *h2, unsigned int h3, ...) { unsigned char paramdigest[SHA1_DIGEST_SIZE]; - struct sdesc *sdesc; + struct sha1_ctx sha_ctx; unsigned int dlen; unsigned char *data; unsigned char c; - int ret; + int ret = 0; va_list argp; if (!chip) return -ENODEV; - sdesc = init_sdesc(hashalg); - if (IS_ERR(sdesc)) { - pr_info("can't alloc %s\n", hash_alg); - return PTR_ERR(sdesc); - } - c = !!h3; - ret = crypto_shash_init(&sdesc->shash); - if (ret < 0) - goto out; + sha1_init(&sha_ctx); va_start(argp, h3); for (;;) { dlen = va_arg(argp, unsigned int); @@ -147,27 +152,22 @@ int TSS_authhmac(unsigned char *digest, const unsigned char *key, ret = -EINVAL; break; } - ret = crypto_shash_update(&sdesc->shash, data, dlen); - if (ret < 0) - break; + sha1_update(&sha_ctx, data, dlen); } va_end(argp); if (!ret) - ret = crypto_shash_final(&sdesc->shash, paramdigest); + sha1_final(&sha_ctx, paramdigest); if (!ret) ret = TSS_rawhmac(digest, key, keylen, SHA1_DIGEST_SIZE, paramdigest, TPM_NONCE_SIZE, h1, TPM_NONCE_SIZE, h2, 1, &c, 0, 0); -out: - kfree_sensitive(sdesc); return ret; } -EXPORT_SYMBOL_GPL(TSS_authhmac); /* * verify the AUTH1_COMMAND (Seal) result from TPM */ -int TSS_checkhmac1(unsigned char *buffer, +static int TSS_checkhmac1(unsigned char *buffer, const uint32_t command, const unsigned char *ononce, const unsigned char *key, @@ -182,7 +182,7 @@ int TSS_checkhmac1(unsigned char *buffer, unsigned char *authdata; unsigned char testhmac[SHA1_DIGEST_SIZE]; unsigned char paramdigest[SHA1_DIGEST_SIZE]; - struct sdesc *sdesc; + struct sha1_ctx sha_ctx; unsigned int dlen; unsigned int dpos; va_list argp; @@ -203,51 +203,30 @@ int TSS_checkhmac1(unsigned char *buffer, continueflag = authdata - 1; enonce = continueflag - TPM_NONCE_SIZE; - sdesc = init_sdesc(hashalg); - if (IS_ERR(sdesc)) { - pr_info("can't alloc %s\n", hash_alg); - return PTR_ERR(sdesc); - } - ret = crypto_shash_init(&sdesc->shash); - if (ret < 0) - goto out; - ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, - sizeof result); - if (ret < 0) - goto out; - ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, - sizeof ordinal); - if (ret < 0) - goto out; + sha1_init(&sha_ctx); + sha1_update(&sha_ctx, (const u8 *)&result, sizeof(result)); + sha1_update(&sha_ctx, (const u8 *)&ordinal, sizeof(ordinal)); va_start(argp, keylen); for (;;) { dlen = va_arg(argp, unsigned int); if (dlen == 0) break; dpos = va_arg(argp, unsigned int); - ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); - if (ret < 0) - break; + sha1_update(&sha_ctx, buffer + dpos, dlen); } va_end(argp); - if (!ret) - ret = crypto_shash_final(&sdesc->shash, paramdigest); - if (ret < 0) - goto out; + sha1_final(&sha_ctx, paramdigest); ret = TSS_rawhmac(testhmac, key, keylen, SHA1_DIGEST_SIZE, paramdigest, TPM_NONCE_SIZE, enonce, TPM_NONCE_SIZE, ononce, 1, continueflag, 0, 0); if (ret < 0) - goto out; + return ret; - if (memcmp(testhmac, authdata, SHA1_DIGEST_SIZE)) - ret = -EINVAL; -out: - kfree_sensitive(sdesc); - return ret; + if (crypto_memneq(testhmac, authdata, SHA1_DIGEST_SIZE)) + return -EINVAL; + return 0; } -EXPORT_SYMBOL_GPL(TSS_checkhmac1); /* * verify the AUTH2_COMMAND (unseal) result from TPM @@ -273,7 +252,7 @@ static int TSS_checkhmac2(unsigned char *buffer, unsigned char testhmac1[SHA1_DIGEST_SIZE]; unsigned char testhmac2[SHA1_DIGEST_SIZE]; unsigned char paramdigest[SHA1_DIGEST_SIZE]; - struct sdesc *sdesc; + struct sha1_ctx sha_ctx; unsigned int dlen; unsigned int dpos; va_list argp; @@ -296,22 +275,9 @@ static int TSS_checkhmac2(unsigned char *buffer, enonce1 = continueflag1 - TPM_NONCE_SIZE; enonce2 = continueflag2 - TPM_NONCE_SIZE; - sdesc = init_sdesc(hashalg); - if (IS_ERR(sdesc)) { - pr_info("can't alloc %s\n", hash_alg); - return PTR_ERR(sdesc); - } - ret = crypto_shash_init(&sdesc->shash); - if (ret < 0) - goto out; - ret = crypto_shash_update(&sdesc->shash, (const u8 *)&result, - sizeof result); - if (ret < 0) - goto out; - ret = crypto_shash_update(&sdesc->shash, (const u8 *)&ordinal, - sizeof ordinal); - if (ret < 0) - goto out; + sha1_init(&sha_ctx); + sha1_update(&sha_ctx, (const u8 *)&result, sizeof(result)); + sha1_update(&sha_ctx, (const u8 *)&ordinal, sizeof(ordinal)); va_start(argp, keylen2); for (;;) { @@ -319,42 +285,33 @@ static int TSS_checkhmac2(unsigned char *buffer, if (dlen == 0) break; dpos = va_arg(argp, unsigned int); - ret = crypto_shash_update(&sdesc->shash, buffer + dpos, dlen); - if (ret < 0) - break; + sha1_update(&sha_ctx, buffer + dpos, dlen); } va_end(argp); - if (!ret) - ret = crypto_shash_final(&sdesc->shash, paramdigest); - if (ret < 0) - goto out; + sha1_final(&sha_ctx, paramdigest); ret = TSS_rawhmac(testhmac1, key1, keylen1, SHA1_DIGEST_SIZE, paramdigest, TPM_NONCE_SIZE, enonce1, TPM_NONCE_SIZE, ononce, 1, continueflag1, 0, 0); if (ret < 0) - goto out; - if (memcmp(testhmac1, authdata1, SHA1_DIGEST_SIZE)) { - ret = -EINVAL; - goto out; - } + return ret; + if (crypto_memneq(testhmac1, authdata1, SHA1_DIGEST_SIZE)) + return -EINVAL; ret = TSS_rawhmac(testhmac2, key2, keylen2, SHA1_DIGEST_SIZE, paramdigest, TPM_NONCE_SIZE, enonce2, TPM_NONCE_SIZE, ononce, 1, continueflag2, 0, 0); if (ret < 0) - goto out; - if (memcmp(testhmac2, authdata2, SHA1_DIGEST_SIZE)) - ret = -EINVAL; -out: - kfree_sensitive(sdesc); - return ret; + return ret; + if (crypto_memneq(testhmac2, authdata2, SHA1_DIGEST_SIZE)) + return -EINVAL; + return 0; } /* * For key specific tpm requests, we will generate and send our * own TPM command packets using the drivers send function. */ -int trusted_tpm_send(unsigned char *cmd, size_t buflen) +static int trusted_tpm_send(unsigned char *cmd, size_t buflen) { struct tpm_buf buf; int rc; @@ -380,7 +337,6 @@ int trusted_tpm_send(unsigned char *cmd, size_t buflen) tpm_put_ops(chip); return rc; } -EXPORT_SYMBOL_GPL(trusted_tpm_send); /* * Lock a trusted key, by extending a selected PCR. @@ -434,7 +390,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s, /* * Create an object independent authorisation protocol (oiap) session */ -int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) +static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) { int ret; @@ -451,7 +407,6 @@ int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) TPM_NONCE_SIZE); return 0; } -EXPORT_SYMBOL_GPL(oiap); struct tpm_digests { unsigned char encauth[SHA1_DIGEST_SIZE]; @@ -485,7 +440,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, int i; /* alloc some work space for all the hashes */ - td = kmalloc(sizeof *td, GFP_KERNEL); + td = kmalloc_obj(*td); if (!td) return -ENOMEM; @@ -498,9 +453,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, /* calculate encrypted authorization value */ memcpy(td->xorwork, sess.secret, SHA1_DIGEST_SIZE); memcpy(td->xorwork + SHA1_DIGEST_SIZE, sess.enonce, SHA1_DIGEST_SIZE); - ret = TSS_sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); - if (ret < 0) - goto out; + sha1(td->xorwork, SHA1_DIGEST_SIZE * 2, td->xorhash); ret = tpm_get_random(chip, td->nonceodd, TPM_NONCE_SIZE); if (ret < 0) @@ -885,7 +838,7 @@ static struct trusted_key_options *trusted_options_alloc(void) if (tpm2 < 0) return NULL; - options = kzalloc(sizeof *options, GFP_KERNEL); + options = kzalloc_obj(*options); if (options) { /* set any non-zero defaults */ options->keytype = SRK_keytype; @@ -989,46 +942,11 @@ static int trusted_tpm_get_random(unsigned char *key, size_t key_len) return tpm_get_random(chip, key, key_len); } -static void trusted_shash_release(void) -{ - if (hashalg) - crypto_free_shash(hashalg); - if (hmacalg) - crypto_free_shash(hmacalg); -} - -static int __init trusted_shash_alloc(void) -{ - int ret; - - hmacalg = crypto_alloc_shash(hmac_alg, 0, 0); - if (IS_ERR(hmacalg)) { - pr_info("could not allocate crypto %s\n", - hmac_alg); - return PTR_ERR(hmacalg); - } - - hashalg = crypto_alloc_shash(hash_alg, 0, 0); - if (IS_ERR(hashalg)) { - pr_info("could not allocate crypto %s\n", - hash_alg); - ret = PTR_ERR(hashalg); - goto hashalg_fail; - } - - return 0; - -hashalg_fail: - crypto_free_shash(hmacalg); - return ret; -} - static int __init init_digests(void) { int i; - digests = kcalloc(chip->nr_allocated_banks, sizeof(*digests), - GFP_KERNEL); + digests = kzalloc_objs(*digests, chip->nr_allocated_banks); if (!digests) return -ENOMEM; @@ -1049,15 +967,10 @@ static int __init trusted_tpm_init(void) ret = init_digests(); if (ret < 0) goto err_put; - ret = trusted_shash_alloc(); - if (ret < 0) - goto err_free; ret = register_key_type(&key_type_trusted); if (ret < 0) - goto err_release; + goto err_free; return 0; -err_release: - trusted_shash_release(); err_free: kfree(digests); err_put: @@ -1070,7 +983,6 @@ static void trusted_tpm_exit(void) if (chip) { put_device(&chip->dev); kfree(digests); - trusted_shash_release(); unregister_key_type(&key_type_trusted); } } diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 024be262702f..6340823f8b53 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -18,14 +18,6 @@ #include "tpm2key.asn1.h" -static struct tpm2_hash tpm2_hash_map[] = { - {HASH_ALGO_SHA1, TPM_ALG_SHA1}, - {HASH_ALGO_SHA256, TPM_ALG_SHA256}, - {HASH_ALGO_SHA384, TPM_ALG_SHA384}, - {HASH_ALGO_SHA512, TPM_ALG_SHA512}, - {HASH_ALGO_SM3_256, TPM_ALG_SM3_256}, -}; - static u32 tpm2key_oid[] = { 2, 23, 133, 10, 1, 5 }; static int tpm2_key_encode(struct trusted_key_payload *payload, @@ -244,20 +236,13 @@ int tpm2_seal_trusted(struct tpm_chip *chip, off_t offset = TPM_HEADER_SIZE; struct tpm_buf buf, sized; int blob_len = 0; - u32 hash; + int hash; u32 flags; - int i; int rc; - for (i = 0; i < ARRAY_SIZE(tpm2_hash_map); i++) { - if (options->hash == tpm2_hash_map[i].crypto_id) { - hash = tpm2_hash_map[i].tpm_id; - break; - } - } - - if (i == ARRAY_SIZE(tpm2_hash_map)) - return -EINVAL; + hash = tpm2_find_hash_alg(options->hash); + if (hash < 0) + return hash; if (!options->keyhandle) return -EINVAL; @@ -283,7 +268,10 @@ int tpm2_seal_trusted(struct tpm_chip *chip, goto out_put; } - tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + if (rc) + goto out; + tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, options->keyauth, TPM_DIGEST_SIZE); @@ -331,7 +319,10 @@ int tpm2_seal_trusted(struct tpm_chip *chip, goto out; } - tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, &buf); + if (rc) + goto out; + rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data"); rc = tpm_buf_check_hmac_response(chip, &buf, rc); if (rc) @@ -348,25 +339,19 @@ int tpm2_seal_trusted(struct tpm_chip *chip, } blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len); + if (blob_len < 0) + rc = blob_len; out: tpm_buf_destroy(&sized); tpm_buf_destroy(&buf); - if (rc > 0) { - if (tpm2_rc_value(rc) == TPM2_RC_HASH) - rc = -EINVAL; - else - rc = -EPERM; - } - if (blob_len < 0) - rc = blob_len; - else + if (!rc) payload->blob_len = blob_len; out_put: tpm_put_ops(chip); - return rc; + return tpm_ret_to_err(rc); } /** @@ -387,6 +372,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, struct trusted_key_options *options, u32 *blob_handle) { + u8 *blob_ref __free(kfree) = NULL; struct tpm_buf buf; unsigned int private_len; unsigned int public_len; @@ -400,6 +386,9 @@ static int tpm2_load_cmd(struct tpm_chip *chip, /* old form */ blob = payload->blob; payload->old_format = 1; + } else { + /* Bind for cleanup: */ + blob_ref = blob; } /* new format carries keyhandle but old format doesn't */ @@ -444,7 +433,10 @@ static int tpm2_load_cmd(struct tpm_chip *chip, return rc; } - tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + if (rc) + goto out; + tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth, TPM_DIGEST_SIZE); @@ -456,7 +448,10 @@ static int tpm2_load_cmd(struct tpm_chip *chip, goto out; } - tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, &buf); + if (rc) + goto out; + rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob"); rc = tpm_buf_check_hmac_response(chip, &buf, rc); if (!rc) @@ -464,18 +459,13 @@ static int tpm2_load_cmd(struct tpm_chip *chip, (__be32 *) &buf.data[TPM_HEADER_SIZE]); out: - if (blob != payload->blob) - kfree(blob); tpm_buf_destroy(&buf); - if (rc > 0) - rc = -EPERM; - - return rc; + return tpm_ret_to_err(rc); } /** - * tpm2_unseal_cmd() - execute a TPM2_Unload command + * tpm2_unseal_cmd() - execute a TPM2_Unseal command * * @chip: TPM chip to use * @payload: the key data in clear and encrypted form @@ -491,8 +481,10 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, struct trusted_key_options *options, u32 blob_handle) { + struct tpm_header *head; struct tpm_buf buf; u16 data_len; + int offset; u8 *data; int rc; @@ -506,7 +498,9 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, return rc; } - tpm_buf_append_name(chip, &buf, blob_handle, NULL); + rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL); + if (rc) + goto out; if (!options->policyhandle) { tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, @@ -527,15 +521,22 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, tpm2_buf_append_auth(&buf, options->policyhandle, NULL /* nonce */, 0, 0, options->blobauth, options->blobauth_len); - tpm_buf_append_hmac_session_opt(chip, &buf, TPM2_SA_ENCRYPT, - NULL, 0); + if (tpm2_chip_auth(chip)) { + tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0); + } else { + offset = buf.handles * 4 + TPM_HEADER_SIZE; + head = (struct tpm_header *)buf.data; + if (tpm_buf_length(&buf) == offset) + head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); + } } - tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, &buf); + if (rc) + goto out; + rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing"); rc = tpm_buf_check_hmac_response(chip, &buf, rc); - if (rc > 0) - rc = -EPERM; if (!rc) { data_len = be16_to_cpup( @@ -568,7 +569,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, out: tpm_buf_destroy(&buf); - return rc; + return tpm_ret_to_err(rc); } /** @@ -600,6 +601,5 @@ int tpm2_unseal_trusted(struct tpm_chip *chip, out: tpm_put_ops(chip); - - return rc; + return tpm_ret_to_err(rc); } diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c index 749e2a4dcb13..686d56e4cc85 100644 --- a/security/keys/user_defined.c +++ b/security/keys/user_defined.c @@ -61,7 +61,7 @@ int user_preparse(struct key_preparsed_payload *prep) struct user_key_payload *upayload; size_t datalen = prep->datalen; - if (datalen <= 0 || datalen > 32767 || !prep->data) + if (datalen == 0 || datalen > 32767 || !prep->data) return -EINVAL; upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig index 03e119466604..f9423f01ac5b 100644 --- a/security/landlock/.kunitconfig +++ b/security/landlock/.kunitconfig @@ -1,4 +1,6 @@ +CONFIG_AUDIT=y CONFIG_KUNIT=y +CONFIG_NET=y CONFIG_SECURITY=y CONFIG_SECURITY_LANDLOCK=y CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y diff --git a/security/landlock/Makefile b/security/landlock/Makefile index b4538b7cf7d2..ffa7646d99f3 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -1,6 +1,18 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o -landlock-y := setup.o syscalls.o object.o ruleset.o \ - cred.o task.o fs.o +landlock-y := \ + setup.o \ + syscalls.o \ + object.o \ + ruleset.o \ + cred.o \ + task.o \ + fs.o \ + tsync.o landlock-$(CONFIG_INET) += net.o + +landlock-$(CONFIG_AUDIT) += \ + id.o \ + audit.o \ + domain.o diff --git a/security/landlock/access.h b/security/landlock/access.h index 74fd8f399fbd..c19d5bc13944 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Landlock LSM - Access types and helpers + * Landlock - Access types and helpers * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI @@ -28,7 +28,13 @@ LANDLOCK_ACCESS_FS_REFER) /* clang-format on */ -typedef u16 access_mask_t; +/* clang-format off */ +#define _LANDLOCK_ACCESS_FS_OPTIONAL ( \ + LANDLOCK_ACCESS_FS_TRUNCATE | \ + LANDLOCK_ACCESS_FS_IOCTL_DEV) +/* clang-format on */ + +typedef u32 access_mask_t; /* Makes sure all filesystem access rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); @@ -44,7 +50,7 @@ struct access_masks { access_mask_t fs : LANDLOCK_NUM_ACCESS_FS; access_mask_t net : LANDLOCK_NUM_ACCESS_NET; access_mask_t scope : LANDLOCK_NUM_SCOPE; -}; +} __packed __aligned(sizeof(u32)); union access_masks_all { struct access_masks masks; @@ -55,10 +61,43 @@ union access_masks_all { static_assert(sizeof(typeof_member(union access_masks_all, masks)) == sizeof(typeof_member(union access_masks_all, all))); -typedef u16 layer_mask_t; +/** + * struct layer_access_masks - A boolean matrix of layers and access rights + * + * This has a bit for each combination of layer numbers and access rights. + * During access checks, it is used to represent the access rights for each + * layer which still need to be fulfilled. When all bits are 0, the access + * request is considered to be fulfilled. + */ +struct layer_access_masks { + /** + * @access: The unfulfilled access rights for each layer. + */ + access_mask_t access[LANDLOCK_MAX_NUM_LAYERS]; +}; + +/* + * Tracks domains responsible of a denied access. This avoids storing in each + * object the full matrix of per-layer unfulfilled access rights, which is + * required by update_request(). + * + * Each nibble represents the layer index of the newest layer which denied a + * certain access right. For file system access rights, the upper four bits are + * the index of the layer which denies LANDLOCK_ACCESS_FS_IOCTL_DEV and the + * lower nibble represents LANDLOCK_ACCESS_FS_TRUNCATE. + */ +typedef u8 deny_masks_t; -/* Makes sure all layers can be checked. */ -static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS); +/* + * Makes sure all optional access rights can be tied to a layer index (cf. + * get_deny_mask). + */ +static_assert(BITS_PER_TYPE(deny_masks_t) >= + (HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1) * + HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL))); + +/* LANDLOCK_MAX_NUM_LAYERS must be a power of two (cf. deny_masks_t assert). */ +static_assert(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS) == 1); /* Upgrades with all initially denied by default access rights. */ static inline struct access_masks @@ -74,4 +113,11 @@ landlock_upgrade_handled_access_masks(struct access_masks access_masks) return access_masks; } +/* Checks the subset relation between access masks. */ +static inline bool access_mask_subset(access_mask_t subset, + access_mask_t superset) +{ + return (subset | superset) == superset; +} + #endif /* _SECURITY_LANDLOCK_ACCESS_H */ diff --git a/security/landlock/audit.c b/security/landlock/audit.c new file mode 100644 index 000000000000..8d0edf94037d --- /dev/null +++ b/security/landlock/audit.c @@ -0,0 +1,492 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Audit helpers + * + * Copyright © 2023-2025 Microsoft Corporation + */ + +#include <kunit/test.h> +#include <linux/audit.h> +#include <linux/bitops.h> +#include <linux/lsm_audit.h> +#include <linux/pid.h> +#include <uapi/linux/landlock.h> + +#include "access.h" +#include "audit.h" +#include "common.h" +#include "cred.h" +#include "domain.h" +#include "limits.h" +#include "ruleset.h" + +static const char *const fs_access_strings[] = { + [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute", + [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file", + [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file", + [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir", + [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir", + [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file", + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char", + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir", + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg", + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock", + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo", + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block", + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym", + [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer", + [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate", + [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev", + [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix", +}; + +static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS); + +static const char *const net_access_strings[] = { + [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp", + [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp", +}; + +static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET); + +static __attribute_const__ const char * +get_blocker(const enum landlock_request_type type, + const unsigned long access_bit) +{ + switch (type) { + case LANDLOCK_REQUEST_PTRACE: + WARN_ON_ONCE(access_bit != -1); + return "ptrace"; + + case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: + WARN_ON_ONCE(access_bit != -1); + return "fs.change_topology"; + + case LANDLOCK_REQUEST_FS_ACCESS: + if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings))) + return "unknown"; + return fs_access_strings[access_bit]; + + case LANDLOCK_REQUEST_NET_ACCESS: + if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings))) + return "unknown"; + return net_access_strings[access_bit]; + + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + WARN_ON_ONCE(access_bit != -1); + return "scope.abstract_unix_socket"; + + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + WARN_ON_ONCE(access_bit != -1); + return "scope.signal"; + } + + WARN_ON_ONCE(1); + return "unknown"; +} + +static void log_blockers(struct audit_buffer *const ab, + const enum landlock_request_type type, + const access_mask_t access) +{ + const unsigned long access_mask = access; + unsigned long access_bit; + bool is_first = true; + + for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) { + audit_log_format(ab, "%s%s", is_first ? "" : ",", + get_blocker(type, access_bit)); + is_first = false; + } + if (is_first) + audit_log_format(ab, "%s", get_blocker(type, -1)); +} + +static void log_domain(struct landlock_hierarchy *const hierarchy) +{ + struct audit_buffer *ab; + + /* Ignores already logged domains. */ + if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED) + return; + + /* Uses consistent allocation flags wrt common_lsm_audit(). */ + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, + AUDIT_LANDLOCK_DOMAIN); + if (!ab) + return; + + WARN_ON_ONCE(hierarchy->id == 0); + audit_log_format( + ab, + "domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=", + hierarchy->id, pid_nr(hierarchy->details->pid), + hierarchy->details->uid); + audit_log_untrustedstring(ab, hierarchy->details->exe_path); + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, hierarchy->details->comm); + audit_log_end(ab); + + /* + * There may be race condition leading to logging of the same domain + * several times but that is OK. + */ + WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED); +} + +static struct landlock_hierarchy * +get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer) +{ + struct landlock_hierarchy *hierarchy = domain->hierarchy; + ssize_t i; + + if (WARN_ON_ONCE(layer >= domain->num_layers)) + return hierarchy; + + for (i = domain->num_layers - 1; i > layer; i--) { + if (WARN_ON_ONCE(!hierarchy->parent)) + break; + + hierarchy = hierarchy->parent; + } + + return hierarchy; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_get_hierarchy(struct kunit *const test) +{ + struct landlock_hierarchy dom0_hierarchy = { + .id = 10, + }; + struct landlock_hierarchy dom1_hierarchy = { + .parent = &dom0_hierarchy, + .id = 20, + }; + struct landlock_hierarchy dom2_hierarchy = { + .parent = &dom1_hierarchy, + .id = 30, + }; + struct landlock_ruleset dom2 = { + .hierarchy = &dom2_hierarchy, + .num_layers = 3, + }; + + KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id); + KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id); + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id); + /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */ +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +/* Get the youngest layer that denied the access_request. */ +static size_t get_denied_layer(const struct landlock_ruleset *const domain, + access_mask_t *const access_request, + const struct layer_access_masks *masks) +{ + for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) { + if (masks->access[i] & *access_request) { + *access_request &= masks->access[i]; + return i; + } + } + + /* Not found - fall back to default values */ + *access_request = 0; + return domain->num_layers - 1; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_get_denied_layer(struct kunit *const test) +{ + const struct landlock_ruleset dom = { + .num_layers = 5, + }; + const struct layer_access_masks masks = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_READ_DIR, + .access[1] = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_READ_DIR, + .access[2] = LANDLOCK_ACCESS_FS_REMOVE_DIR, + }; + access_mask_t access; + + access = LANDLOCK_ACCESS_FS_EXECUTE; + KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE); + + access = LANDLOCK_ACCESS_FS_READ_FILE; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE); + + access = LANDLOCK_ACCESS_FS_READ_DIR; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); + + access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_READ_DIR); + + access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); + + access = LANDLOCK_ACCESS_FS_WRITE_FILE; + KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, 0); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +static size_t +get_layer_from_deny_masks(access_mask_t *const access_request, + const access_mask_t all_existing_optional_access, + const deny_masks_t deny_masks) +{ + const unsigned long access_opt = all_existing_optional_access; + const unsigned long access_req = *access_request; + access_mask_t missing = 0; + size_t youngest_layer = 0; + size_t access_index = 0; + unsigned long access_bit; + + /* This will require change with new object types. */ + WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL); + + for_each_set_bit(access_bit, &access_opt, + BITS_PER_TYPE(access_mask_t)) { + if (access_req & BIT(access_bit)) { + const size_t layer = + (deny_masks >> (access_index * 4)) & + (LANDLOCK_MAX_NUM_LAYERS - 1); + + if (layer > youngest_layer) { + youngest_layer = layer; + missing = BIT(access_bit); + } else if (layer == youngest_layer) { + missing |= BIT(access_bit); + } + } + access_index++; + } + + *access_request = missing; + return youngest_layer; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_get_layer_from_deny_masks(struct kunit *const test) +{ + deny_masks_t deny_mask; + access_mask_t access; + + /* truncate:0 ioctl_dev:2 */ + deny_mask = 0x20; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks(&access, + _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks(&access, + _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + + /* truncate:15 ioctl_dev:15 */ + deny_mask = 0xff; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks(&access, + _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks(&access, + _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask)); + KUNIT_EXPECT_EQ(test, access, + LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_IOCTL_DEV); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +static bool is_valid_request(const struct landlock_request *const request) +{ + if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) + return false; + + if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access))) + return false; + + if (request->access) { + if (WARN_ON_ONCE(!(!!request->layer_masks ^ + !!request->all_existing_optional_access))) + return false; + } else { + if (WARN_ON_ONCE(request->layer_masks || + request->all_existing_optional_access)) + return false; + } + + if (request->deny_masks) { + if (WARN_ON_ONCE(!request->all_existing_optional_access)) + return false; + } + + return true; +} + +/** + * landlock_log_denial - Create audit records related to a denial + * + * @subject: The Landlock subject's credential denying an action. + * @request: Detail of the user space request. + */ +void landlock_log_denial(const struct landlock_cred_security *const subject, + const struct landlock_request *const request) +{ + struct audit_buffer *ab; + struct landlock_hierarchy *youngest_denied; + size_t youngest_layer; + access_mask_t missing; + + if (WARN_ON_ONCE(!subject || !subject->domain || + !subject->domain->hierarchy || !request)) + return; + + if (!is_valid_request(request)) + return; + + missing = request->access; + if (missing) { + /* Gets the nearest domain that denies the request. */ + if (request->layer_masks) { + youngest_layer = get_denied_layer(subject->domain, + &missing, + request->layer_masks); + } else { + youngest_layer = get_layer_from_deny_masks( + &missing, _LANDLOCK_ACCESS_FS_OPTIONAL, + request->deny_masks); + } + youngest_denied = + get_hierarchy(subject->domain, youngest_layer); + } else { + youngest_layer = request->layer_plus_one - 1; + youngest_denied = + get_hierarchy(subject->domain, youngest_layer); + } + + if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED) + return; + + /* + * Consistently keeps track of the number of denied access requests + * even if audit is currently disabled, or if audit rules currently + * exclude this record type, or if landlock_restrict_self(2)'s flags + * quiet logs. + */ + atomic64_inc(&youngest_denied->num_denials); + + if (!audit_enabled) + return; + + /* Checks if the current exec was restricting itself. */ + if (subject->domain_exec & BIT(youngest_layer)) { + /* Ignores denials for the same execution. */ + if (!youngest_denied->log_same_exec) + return; + } else { + /* Ignores denials after a new execution. */ + if (!youngest_denied->log_new_exec) + return; + } + + /* Uses consistent allocation flags wrt common_lsm_audit(). */ + ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, + AUDIT_LANDLOCK_ACCESS); + if (!ab) + return; + + audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id); + log_blockers(ab, request->type, missing); + audit_log_lsm_data(ab, &request->audit); + audit_log_end(ab); + + /* Logs this domain the first time it shows in log. */ + log_domain(youngest_denied); +} + +/** + * landlock_log_drop_domain - Create an audit record on domain deallocation + * + * @hierarchy: The domain's hierarchy being deallocated. + * + * Only domains which previously appeared in the audit logs are logged again. + * This is useful to know when a domain will never show again in the audit log. + * + * Called in a work queue scheduled by landlock_put_ruleset_deferred() called + * by hook_cred_free(). + */ +void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) +{ + struct audit_buffer *ab; + + if (WARN_ON_ONCE(!hierarchy)) + return; + + if (!audit_enabled) + return; + + /* Ignores domains that were not logged. */ + if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED) + return; + + /* + * If logging of domain allocation succeeded, warns about failure to log + * domain deallocation to highlight unbalanced domain lifetime logs. + */ + ab = audit_log_start(audit_context(), GFP_KERNEL, + AUDIT_LANDLOCK_DOMAIN); + if (!ab) + return; + + audit_log_format(ab, "domain=%llx status=deallocated denials=%llu", + hierarchy->id, atomic64_read(&hierarchy->num_denials)); + audit_log_end(ab); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_get_hierarchy), + KUNIT_CASE(test_get_denied_layer), + KUNIT_CASE(test_get_layer_from_deny_masks), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_audit", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/audit.h b/security/landlock/audit.h new file mode 100644 index 000000000000..56778331b58c --- /dev/null +++ b/security/landlock/audit.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Audit helpers + * + * Copyright © 2023-2025 Microsoft Corporation + */ + +#ifndef _SECURITY_LANDLOCK_AUDIT_H +#define _SECURITY_LANDLOCK_AUDIT_H + +#include <linux/audit.h> +#include <linux/lsm_audit.h> + +#include "access.h" +#include "cred.h" + +enum landlock_request_type { + LANDLOCK_REQUEST_PTRACE = 1, + LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, + LANDLOCK_REQUEST_FS_ACCESS, + LANDLOCK_REQUEST_NET_ACCESS, + LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, + LANDLOCK_REQUEST_SCOPE_SIGNAL, +}; + +/* + * We should be careful to only use a variable of this type for + * landlock_log_denial(). This way, the compiler can remove it entirely if + * CONFIG_AUDIT is not set. + */ +struct landlock_request { + /* Mandatory fields. */ + enum landlock_request_type type; + struct common_audit_data audit; + + /** + * layer_plus_one: First layer level that denies the request + 1. The + * extra one is useful to detect uninitialized field. + */ + size_t layer_plus_one; + + /* Required field for configurable access control. */ + access_mask_t access; + + /* Required fields for requests with layer masks. */ + const struct layer_access_masks *layer_masks; + + /* Required fields for requests with deny masks. */ + const access_mask_t all_existing_optional_access; + deny_masks_t deny_masks; +}; + +#ifdef CONFIG_AUDIT + +void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy); + +void landlock_log_denial(const struct landlock_cred_security *const subject, + const struct landlock_request *const request); + +#else /* CONFIG_AUDIT */ + +static inline void +landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) +{ +} + +static inline void +landlock_log_denial(const struct landlock_cred_security *const subject, + const struct landlock_request *const request) +{ +} + +#endif /* CONFIG_AUDIT */ + +#endif /* _SECURITY_LANDLOCK_AUDIT_H */ diff --git a/security/landlock/cred.c b/security/landlock/cred.c index db9fe7d906ba..cc419de75cd6 100644 --- a/security/landlock/cred.c +++ b/security/landlock/cred.c @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Landlock LSM - Credential hooks + * Landlock - Credential hooks * * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2024-2025 Microsoft Corporation */ +#include <linux/binfmts.h> #include <linux/cred.h> #include <linux/lsm_hooks.h> @@ -17,12 +19,11 @@ static void hook_cred_transfer(struct cred *const new, const struct cred *const old) { - struct landlock_ruleset *const old_dom = landlock_cred(old)->domain; + const struct landlock_cred_security *const old_llcred = + landlock_cred(old); - if (old_dom) { - landlock_get_ruleset(old_dom); - landlock_cred(new)->domain = old_dom; - } + landlock_get_ruleset(old_llcred->domain); + *landlock_cred(new) = *old_llcred; } static int hook_cred_prepare(struct cred *const new, @@ -40,10 +41,25 @@ static void hook_cred_free(struct cred *const cred) landlock_put_ruleset_deferred(dom); } +#ifdef CONFIG_AUDIT + +static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm) +{ + /* Resets for each execution. */ + landlock_cred(bprm->cred)->domain_exec = 0; + return 0; +} + +#endif /* CONFIG_AUDIT */ + static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(cred_prepare, hook_cred_prepare), LSM_HOOK_INIT(cred_transfer, hook_cred_transfer), LSM_HOOK_INIT(cred_free, hook_cred_free), + +#ifdef CONFIG_AUDIT + LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec), +#endif /* CONFIG_AUDIT */ }; __init void landlock_add_cred_hooks(void) diff --git a/security/landlock/cred.h b/security/landlock/cred.h index bf755459838a..f287c56b5fd4 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -1,24 +1,65 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Landlock LSM - Credential hooks + * Landlock - Credential hooks * * Copyright © 2019-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2019-2020 ANSSI + * Copyright © 2021-2025 Microsoft Corporation */ #ifndef _SECURITY_LANDLOCK_CRED_H #define _SECURITY_LANDLOCK_CRED_H +#include <linux/container_of.h> #include <linux/cred.h> #include <linux/init.h> #include <linux/rcupdate.h> +#include "access.h" +#include "limits.h" #include "ruleset.h" #include "setup.h" +/** + * struct landlock_cred_security - Credential security blob + * + * This structure is packed to minimize the size of struct + * landlock_file_security. However, it is always aligned in the LSM cred blob, + * see lsm_set_blob_size(). + * + * When updating this, also update landlock_cred_copy() if needed. + */ struct landlock_cred_security { + /** + * @domain: Immutable ruleset enforced on a task. + */ struct landlock_ruleset *domain; -}; + +#ifdef CONFIG_AUDIT + /** + * @domain_exec: Bitmask identifying the domain layers that were enforced by + * the current task's executed file (i.e. no new execve(2) since + * landlock_restrict_self(2)). + */ + u16 domain_exec; + /** + * @log_subdomains_off: Set if the domain descendants's log_status should be + * set to %LANDLOCK_LOG_DISABLED. This is not a landlock_hierarchy + * configuration because it applies to future descendant domains and it does + * not require a current domain. + */ + u8 log_subdomains_off : 1; +#endif /* CONFIG_AUDIT */ +} __packed; + +#ifdef CONFIG_AUDIT + +/* Makes sure all layer executions can be stored. */ +static_assert(BITS_PER_TYPE(typeof_member(struct landlock_cred_security, + domain_exec)) >= + LANDLOCK_MAX_NUM_LAYERS); + +#endif /* CONFIG_AUDIT */ static inline struct landlock_cred_security * landlock_cred(const struct cred *cred) @@ -26,6 +67,16 @@ landlock_cred(const struct cred *cred) return cred->security + landlock_blob_sizes.lbs_cred; } +static inline void landlock_cred_copy(struct landlock_cred_security *dst, + const struct landlock_cred_security *src) +{ + landlock_put_ruleset(dst->domain); + + *dst = *src; + + landlock_get_ruleset(src->domain); +} + static inline struct landlock_ruleset *landlock_get_current_domain(void) { return landlock_cred(current_cred())->domain; @@ -53,6 +104,55 @@ static inline bool landlocked(const struct task_struct *const task) return has_dom; } +/** + * landlock_get_applicable_subject - Return the subject's Landlock credential + * if its enforced domain applies to (i.e. + * handles) at least one of the access rights + * specified in @masks + * + * @cred: credential + * @masks: access masks + * @handle_layer: returned youngest layer handling a subset of @masks. Not set + * if the function returns NULL. + * + * Return: landlock_cred(@cred) if any access rights specified in @masks is + * handled, or NULL otherwise. + */ +static inline const struct landlock_cred_security * +landlock_get_applicable_subject(const struct cred *const cred, + const struct access_masks masks, + size_t *const handle_layer) +{ + const union access_masks_all masks_all = { + .masks = masks, + }; + const struct landlock_ruleset *domain; + ssize_t layer_level; + + if (!cred) + return NULL; + + domain = landlock_cred(cred)->domain; + if (!domain) + return NULL; + + for (layer_level = domain->num_layers - 1; layer_level >= 0; + layer_level--) { + union access_masks_all layer = { + .masks = domain->access_masks[layer_level], + }; + + if (layer.all & masks_all.all) { + if (handle_layer) + *handle_layer = layer_level; + + return landlock_cred(cred); + } + } + + return NULL; +} + __init void landlock_add_cred_hooks(void); #endif /* _SECURITY_LANDLOCK_CRED_H */ diff --git a/security/landlock/domain.c b/security/landlock/domain.c new file mode 100644 index 000000000000..06b6bd845060 --- /dev/null +++ b/security/landlock/domain.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Domain management + * + * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> + * Copyright © 2018-2020 ANSSI + * Copyright © 2024-2025 Microsoft Corporation + */ + +#include <kunit/test.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/cred.h> +#include <linux/file.h> +#include <linux/mm.h> +#include <linux/path.h> +#include <linux/pid.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/uidgid.h> + +#include "access.h" +#include "common.h" +#include "domain.h" +#include "id.h" + +#ifdef CONFIG_AUDIT + +/** + * get_current_exe - Get the current's executable path, if any + * + * @exe_str: Returned pointer to a path string with a lifetime tied to the + * returned buffer, if any. + * @exe_size: Returned size of @exe_str (including the trailing null + * character), if any. + * + * Return: A pointer to an allocated buffer where @exe_str point to, %NULL if + * there is no executable path, or an error otherwise. + */ +static const void *get_current_exe(const char **const exe_str, + size_t *const exe_size) +{ + const size_t buffer_size = LANDLOCK_PATH_MAX_SIZE; + struct mm_struct *mm = current->mm; + struct file *file __free(fput) = NULL; + char *buffer __free(kfree) = NULL; + const char *exe; + ssize_t size; + + if (!mm) + return NULL; + + file = get_mm_exe_file(mm); + if (!file) + return NULL; + + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return ERR_PTR(-ENOMEM); + + exe = d_path(&file->f_path, buffer, buffer_size); + if (WARN_ON_ONCE(IS_ERR(exe))) + /* Should never happen according to LANDLOCK_PATH_MAX_SIZE. */ + return ERR_CAST(exe); + + size = buffer + buffer_size - exe; + if (WARN_ON_ONCE(size <= 0)) + return ERR_PTR(-ENAMETOOLONG); + + *exe_size = size; + *exe_str = exe; + return no_free_ptr(buffer); +} + +/* + * Return: A newly allocated object describing a domain, or an error + * otherwise. + */ +static struct landlock_details *get_current_details(void) +{ + /* Cf. audit_log_d_path_exe() */ + static const char null_path[] = "(null)"; + const char *path_str = null_path; + size_t path_size = sizeof(null_path); + const void *buffer __free(kfree) = NULL; + struct landlock_details *details; + + buffer = get_current_exe(&path_str, &path_size); + if (IS_ERR(buffer)) + return ERR_CAST(buffer); + + /* + * Create the new details according to the path's length. Do not + * allocate with GFP_KERNEL_ACCOUNT because it is independent from the + * caller. + */ + details = kzalloc_flex(*details, exe_path, path_size); + if (!details) + return ERR_PTR(-ENOMEM); + + memcpy(details->exe_path, path_str, path_size); + details->pid = get_pid(task_tgid(current)); + details->uid = from_kuid(&init_user_ns, current_uid()); + get_task_comm(details->comm, current); + return details; +} + +/** + * landlock_init_hierarchy_log - Partially initialize landlock_hierarchy + * + * @hierarchy: The hierarchy to initialize. + * + * The current task is referenced as the domain that is enforcing the + * restriction. The subjective credentials must not be in an overridden state. + * + * @hierarchy->parent and @hierarchy->usage should already be set. + * + * Return: 0 on success, -errno on failure. + */ +int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) +{ + struct landlock_details *details; + + details = get_current_details(); + if (IS_ERR(details)) + return PTR_ERR(details); + + hierarchy->details = details; + hierarchy->id = landlock_get_id_range(1); + hierarchy->log_status = LANDLOCK_LOG_PENDING; + hierarchy->log_same_exec = true; + hierarchy->log_new_exec = false; + atomic64_set(&hierarchy->num_denials, 0); + return 0; +} + +static deny_masks_t +get_layer_deny_mask(const access_mask_t all_existing_optional_access, + const unsigned long access_bit, const size_t layer) +{ + unsigned long access_weight; + + /* This may require change with new object types. */ + WARN_ON_ONCE(all_existing_optional_access != + _LANDLOCK_ACCESS_FS_OPTIONAL); + + if (WARN_ON_ONCE(layer >= LANDLOCK_MAX_NUM_LAYERS)) + return 0; + + access_weight = hweight_long(all_existing_optional_access & + GENMASK(access_bit, 0)); + if (WARN_ON_ONCE(access_weight < 1)) + return 0; + + return layer + << ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1)); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_get_layer_deny_mask(struct kunit *const test) +{ + const unsigned long truncate = BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE); + const unsigned long ioctl_dev = BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV); + + KUNIT_EXPECT_EQ(test, 0, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + truncate, 0)); + KUNIT_EXPECT_EQ(test, 0x3, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + truncate, 3)); + + KUNIT_EXPECT_EQ(test, 0, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + ioctl_dev, 0)); + KUNIT_EXPECT_EQ(test, 0xf0, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + ioctl_dev, 15)); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +deny_masks_t +landlock_get_deny_masks(const access_mask_t all_existing_optional_access, + const access_mask_t optional_access, + const struct layer_access_masks *const masks) +{ + const unsigned long access_opt = optional_access; + unsigned long access_bit; + deny_masks_t deny_masks = 0; + access_mask_t all_denied = 0; + + /* This may require change with new object types. */ + WARN_ON_ONCE(!access_mask_subset(optional_access, + all_existing_optional_access)); + + if (WARN_ON_ONCE(!masks)) + return 0; + + if (WARN_ON_ONCE(!access_opt)) + return 0; + + for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) { + const access_mask_t denied = masks->access[i] & optional_access; + const unsigned long newly_denied = denied & ~all_denied; + + if (!newly_denied) + continue; + + for_each_set_bit(access_bit, &newly_denied, + 8 * sizeof(access_mask_t)) { + deny_masks |= get_layer_deny_mask( + all_existing_optional_access, access_bit, i); + } + all_denied |= denied; + } + return deny_masks; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_landlock_get_deny_masks(struct kunit *const test) +{ + const struct layer_access_masks layers1 = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_IOCTL_DEV, + .access[1] = LANDLOCK_ACCESS_FS_TRUNCATE, + .access[2] = LANDLOCK_ACCESS_FS_IOCTL_DEV, + .access[9] = LANDLOCK_ACCESS_FS_EXECUTE, + }; + + KUNIT_EXPECT_EQ(test, 0x1, + landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL, + LANDLOCK_ACCESS_FS_TRUNCATE, + &layers1)); + KUNIT_EXPECT_EQ(test, 0x20, + landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL, + LANDLOCK_ACCESS_FS_IOCTL_DEV, + &layers1)); + KUNIT_EXPECT_EQ( + test, 0x21, + landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL, + LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_IOCTL_DEV, + &layers1)); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_get_layer_deny_mask), + KUNIT_CASE(test_landlock_get_deny_masks), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_domain", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +#endif /* CONFIG_AUDIT */ diff --git a/security/landlock/domain.h b/security/landlock/domain.h new file mode 100644 index 000000000000..a9d57db0120d --- /dev/null +++ b/security/landlock/domain.h @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Domain management + * + * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> + * Copyright © 2018-2020 ANSSI + * Copyright © 2024-2025 Microsoft Corporation + */ + +#ifndef _SECURITY_LANDLOCK_DOMAIN_H +#define _SECURITY_LANDLOCK_DOMAIN_H + +#include <linux/limits.h> +#include <linux/mm.h> +#include <linux/path.h> +#include <linux/pid.h> +#include <linux/refcount.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include "access.h" +#include "audit.h" + +enum landlock_log_status { + LANDLOCK_LOG_PENDING = 0, + LANDLOCK_LOG_RECORDED, + LANDLOCK_LOG_DISABLED, +}; + +/** + * struct landlock_details - Domain's creation information + * + * Rarely accessed, mainly when logging the first domain's denial. + * + * The contained pointers are initialized at the domain creation time and never + * changed again. Contrary to most other Landlock object types, this one is + * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the + * caller's control (e.g. unknown exe_path) and the data is not explicitly + * requested nor used by tasks. + */ +struct landlock_details { + /** + * @pid: PID of the task that initially restricted itself. It still + * identifies the same task. Keeping a reference to this PID ensures that + * it will not be recycled. + */ + struct pid *pid; + /** + * @uid: UID of the task that initially restricted itself, at creation time. + */ + uid_t uid; + /** + * @comm: Command line of the task that initially restricted itself, at + * creation time. Always NULL terminated. + */ + char comm[TASK_COMM_LEN]; + /** + * @exe_path: Executable path of the task that initially restricted + * itself, at creation time. Always NULL terminated, and never greater + * than LANDLOCK_PATH_MAX_SIZE. + */ + char exe_path[]; +}; + +/* Adds 11 extra characters for the potential " (deleted)" suffix. */ +#define LANDLOCK_PATH_MAX_SIZE (PATH_MAX + 11) + +/* Makes sure the greatest landlock_details can be allocated. */ +static_assert(struct_size_t(struct landlock_details, exe_path, + LANDLOCK_PATH_MAX_SIZE) <= KMALLOC_MAX_SIZE); + +/** + * struct landlock_hierarchy - Node in a domain hierarchy + */ +struct landlock_hierarchy { + /** + * @parent: Pointer to the parent node, or NULL if it is a root + * Landlock domain. + */ + struct landlock_hierarchy *parent; + /** + * @usage: Number of potential children domains plus their parent + * domain. + */ + refcount_t usage; + +#ifdef CONFIG_AUDIT + /** + * @log_status: Whether this domain should be logged or not. Because + * concurrent log entries may be created at the same time, it is still + * possible to have several domain records of the same domain. + */ + enum landlock_log_status log_status; + /** + * @num_denials: Number of access requests denied by this domain. + * Masked (i.e. never logged) denials are still counted. + */ + atomic64_t num_denials; + /** + * @id: Landlock domain ID, set once at domain creation time. + */ + u64 id; + /** + * @details: Information about the related domain. + */ + const struct landlock_details *details; + /** + * @log_same_exec: Set if the domain is *not* configured with + * %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF. Set to true by default. + */ + u32 log_same_exec : 1, + /** + * @log_new_exec: Set if the domain is configured with + * %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON. Set to false by default. + */ + log_new_exec : 1; +#endif /* CONFIG_AUDIT */ +}; + +#ifdef CONFIG_AUDIT + +deny_masks_t +landlock_get_deny_masks(const access_mask_t all_existing_optional_access, + const access_mask_t optional_access, + const struct layer_access_masks *const masks); + +int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy); + +static inline void +landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy) +{ + if (!hierarchy || !hierarchy->details) + return; + + put_pid(hierarchy->details->pid); + kfree(hierarchy->details); +} + +#else /* CONFIG_AUDIT */ + +static inline int +landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) +{ + return 0; +} + +static inline void +landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy) +{ +} + +#endif /* CONFIG_AUDIT */ + +static inline void +landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy) +{ + if (hierarchy) + refcount_inc(&hierarchy->usage); +} + +static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy) +{ + while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) { + const struct landlock_hierarchy *const freeme = hierarchy; + + landlock_log_drop_domain(hierarchy); + landlock_free_hierarchy_details(hierarchy); + hierarchy = hierarchy->parent; + kfree(freeme); + } +} + +#endif /* _SECURITY_LANDLOCK_DOMAIN_H */ diff --git a/security/landlock/errata.h b/security/landlock/errata.h new file mode 100644 index 000000000000..8e626accac10 --- /dev/null +++ b/security/landlock/errata.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Errata information + * + * Copyright © 2025 Microsoft Corporation + */ + +#ifndef _SECURITY_LANDLOCK_ERRATA_H +#define _SECURITY_LANDLOCK_ERRATA_H + +#include <linux/init.h> + +struct landlock_erratum { + const int abi; + const u8 number; +}; + +/* clang-format off */ +#define LANDLOCK_ERRATUM(NUMBER) \ + { \ + .abi = LANDLOCK_ERRATA_ABI, \ + .number = NUMBER, \ + }, +/* clang-format on */ + +/* + * Some fixes may require user space to check if they are applied on the running + * kernel before using a specific feature. For instance, this applies when a + * restriction was previously too restrictive and is now getting relaxed (for + * compatibility or semantic reasons). However, non-visible changes for + * legitimate use (e.g. security fixes) do not require an erratum. + */ +static const struct landlock_erratum landlock_errata_init[] __initconst = { + +/* + * Only Sparse may not implement __has_include. If a compiler does not + * implement __has_include, a warning will be printed at boot time (see + * setup.c). + */ +#ifdef __has_include + +#define LANDLOCK_ERRATA_ABI 1 +#if __has_include("errata/abi-1.h") +#include "errata/abi-1.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 2 +#if __has_include("errata/abi-2.h") +#include "errata/abi-2.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 3 +#if __has_include("errata/abi-3.h") +#include "errata/abi-3.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 4 +#if __has_include("errata/abi-4.h") +#include "errata/abi-4.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 5 +#if __has_include("errata/abi-5.h") +#include "errata/abi-5.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 6 +#if __has_include("errata/abi-6.h") +#include "errata/abi-6.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +/* + * For each new erratum, we need to include all the ABI files up to the impacted + * ABI to make all potential future intermediate errata easy to backport. + * + * If such change involves more than one ABI addition, then it must be in a + * dedicated commit with the same Fixes tag as used for the actual fix. + * + * Each commit creating a new security/landlock/errata/abi-*.h file must have a + * Depends-on tag to reference the commit that previously added the line to + * include this new file, except if the original Fixes tag is enough. + * + * Each erratum must be documented in its related ABI file, and a dedicated + * commit must update Documentation/userspace-api/landlock.rst to include this + * erratum. This commit will not be backported. + */ + +#endif + + {} +}; + +#endif /* _SECURITY_LANDLOCK_ERRATA_H */ diff --git a/security/landlock/errata/abi-1.h b/security/landlock/errata/abi-1.h new file mode 100644 index 000000000000..3f099555f059 --- /dev/null +++ b/security/landlock/errata/abi-1.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/** + * DOC: erratum_3 + * + * Erratum 3: Disconnected directory handling + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This fix addresses an issue with disconnected directories that occur when a + * directory is moved outside the scope of a bind mount. The change ensures + * that evaluated access rights include both those from the disconnected file + * hierarchy down to its filesystem root and those from the related mount point + * hierarchy. This prevents access right widening through rename or link + * actions. + * + * Impact: + * + * Without this fix, it was possible to widen access rights through rename or + * link actions involving disconnected directories, potentially bypassing + * ``LANDLOCK_ACCESS_FS_REFER`` restrictions. This could allow privilege + * escalation in complex mount scenarios where directories become disconnected + * from their original mount points. + */ +LANDLOCK_ERRATUM(3) diff --git a/security/landlock/errata/abi-4.h b/security/landlock/errata/abi-4.h new file mode 100644 index 000000000000..fe11ec7d7ddf --- /dev/null +++ b/security/landlock/errata/abi-4.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/** + * DOC: erratum_1 + * + * Erratum 1: TCP socket identification + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This fix addresses an issue where IPv4 and IPv6 stream sockets (e.g., SMC, + * MPTCP, or SCTP) were incorrectly restricted by TCP access rights during + * :manpage:`bind(2)` and :manpage:`connect(2)` operations. This change ensures + * that only TCP sockets are subject to TCP access rights, allowing other + * protocols to operate without unnecessary restrictions. + * + * Impact: + * + * In kernels without this fix, using ``LANDLOCK_ACCESS_NET_BIND_TCP`` or + * ``LANDLOCK_ACCESS_NET_CONNECT_TCP`` would incorrectly restrict non-TCP + * stream protocols (SMC, MPTCP, SCTP), potentially breaking applications + * that rely on these protocols while using Landlock network restrictions. + */ +LANDLOCK_ERRATUM(1) diff --git a/security/landlock/errata/abi-6.h b/security/landlock/errata/abi-6.h new file mode 100644 index 000000000000..5cb1475c7ea8 --- /dev/null +++ b/security/landlock/errata/abi-6.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/** + * DOC: erratum_2 + * + * Erratum 2: Scoped signal handling + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This fix addresses an issue where signal scoping was overly restrictive, + * preventing sandboxed threads from signaling other threads within the same + * process if they belonged to different domains. Because threads are not + * security boundaries, user space might assume that all threads within the same + * process can send signals between themselves (see :manpage:`nptl(7)` and + * :manpage:`libpsx(3)`). Consistent with :manpage:`ptrace(2)` behavior, direct + * interaction between threads of the same process should always be allowed. + * This change ensures that any thread is allowed to send signals to any other + * thread within the same process, regardless of their domain. + * + * Impact: + * + * This problem only manifests when the userspace process is itself using + * :manpage:`libpsx(3)` or an equivalent mechanism to enforce a Landlock policy + * on multiple already-running threads at once. Programs which enforce a + * Landlock policy at startup time and only then become multithreaded are not + * affected. Without this fix, signal scoping could break multi-threaded + * applications that expect threads within the same process to freely signal + * each other. + */ +LANDLOCK_ERRATUM(2) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 71b9dc331aae..c1ecfe239032 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -1,10 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Landlock LSM - Filesystem management and hooks + * Landlock - Filesystem management and hooks * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI - * Copyright © 2021-2022 Microsoft Corporation + * Copyright © 2021-2025 Microsoft Corporation * Copyright © 2022 Günther Noack <gnoack3000@gmail.com> * Copyright © 2023-2024 Google LLC */ @@ -23,22 +23,29 @@ #include <linux/kernel.h> #include <linux/limits.h> #include <linux/list.h> +#include <linux/lsm_audit.h> #include <linux/lsm_hooks.h> #include <linux/mount.h> #include <linux/namei.h> +#include <linux/net.h> #include <linux/path.h> +#include <linux/pid.h> #include <linux/rcupdate.h> +#include <linux/sched/signal.h> #include <linux/spinlock.h> #include <linux/stat.h> #include <linux/types.h> #include <linux/wait_bit.h> #include <linux/workqueue.h> +#include <net/af_unix.h> #include <uapi/linux/fiemap.h> #include <uapi/linux/landlock.h> #include "access.h" +#include "audit.h" #include "common.h" #include "cred.h" +#include "domain.h" #include "fs.h" #include "limits.h" #include "object.h" @@ -114,8 +121,8 @@ static const struct landlock_object_underops landlock_fs_underops = { * Any new IOCTL commands that are implemented in fs/ioctl.c's do_vfs_ioctl() * should be considered for inclusion here. * - * Returns: true if the IOCTL @cmd can not be restricted with Landlock for - * device files. + * Return: True if the IOCTL @cmd can not be restricted with Landlock for + * device files, false otherwise. */ static __attribute_const__ bool is_masked_device_ioctl(const unsigned int cmd) { @@ -309,7 +316,8 @@ retry: LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_READ_FILE | \ LANDLOCK_ACCESS_FS_TRUNCATE | \ - LANDLOCK_ACCESS_FS_IOCTL_DEV) + LANDLOCK_ACCESS_FS_IOCTL_DEV | \ + LANDLOCK_ACCESS_FS_RESOLVE_UNIX) /* clang-format on */ /* @@ -326,7 +334,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, /* Files only get access rights that make sense. */ if (!d_is_dir(path->dentry) && - (access_rights | ACCESS_FILE) != ACCESS_FILE) + !access_mask_subset(access_rights, ACCESS_FILE)) return -EINVAL; if (WARN_ON_ONCE(ruleset->num_layers != 1)) return -EINVAL; @@ -393,63 +401,55 @@ static const struct access_masks any_fs = { .fs = ~0, }; -static const struct landlock_ruleset *get_current_fs_domain(void) +/* + * Returns true iff the child file with the given src_child access rights under + * src_parent would result in having the same or fewer access rights if it were + * moved under new_parent. + */ +static bool may_refer(const struct layer_access_masks *const src_parent, + const struct layer_access_masks *const src_child, + const struct layer_access_masks *const new_parent, + const bool child_is_dir) { - return landlock_get_applicable_domain(landlock_get_current_domain(), - any_fs); + for (size_t i = 0; i < ARRAY_SIZE(new_parent->access); i++) { + access_mask_t child_access = src_parent->access[i] & + src_child->access[i]; + access_mask_t parent_access = new_parent->access[i]; + + if (!child_is_dir) { + child_access &= ACCESS_FILE; + parent_access &= ACCESS_FILE; + } + + if (!access_mask_subset(child_access, parent_access)) + return false; + } + return true; } /* * Check that a destination file hierarchy has more restrictions than a source * file hierarchy. This is only used for link and rename actions. * - * @layer_masks_child2: Optional child masks. + * Return: True if child1 may be moved from parent1 to parent2 without + * increasing its access rights (if child2 is set, an additional condition is + * that child2 may be used from parent2 to parent1 without increasing its access + * rights), false otherwise. */ -static bool no_more_access( - const layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS], - const layer_mask_t (*const layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS], - const bool child1_is_directory, - const layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS], - const layer_mask_t (*const layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS], - const bool child2_is_directory) +static bool no_more_access(const struct layer_access_masks *const parent1, + const struct layer_access_masks *const child1, + const bool child1_is_dir, + const struct layer_access_masks *const parent2, + const struct layer_access_masks *const child2, + const bool child2_is_dir) { - unsigned long access_bit; - - for (access_bit = 0; access_bit < ARRAY_SIZE(*layer_masks_parent2); - access_bit++) { - /* Ignores accesses that only make sense for directories. */ - const bool is_file_access = - !!(BIT_ULL(access_bit) & ACCESS_FILE); + if (!may_refer(parent1, child1, parent2, child1_is_dir)) + return false; - if (child1_is_directory || is_file_access) { - /* - * Checks if the destination restrictions are a - * superset of the source ones (i.e. inherited access - * rights without child exceptions): - * restrictions(parent2) >= restrictions(child1) - */ - if ((((*layer_masks_parent1)[access_bit] & - (*layer_masks_child1)[access_bit]) | - (*layer_masks_parent2)[access_bit]) != - (*layer_masks_parent2)[access_bit]) - return false; - } + if (!child2) + return true; - if (!layer_masks_child2) - continue; - if (child2_is_directory || is_file_access) { - /* - * Checks inverted restrictions for RENAME_EXCHANGE: - * restrictions(parent1) >= restrictions(child2) - */ - if ((((*layer_masks_parent2)[access_bit] & - (*layer_masks_child2)[access_bit]) | - (*layer_masks_parent1)[access_bit]) != - (*layer_masks_parent1)[access_bit]) - return false; - } - } - return true; + return may_refer(parent2, child2, parent1, child2_is_dir); } #define NMA_TRUE(...) KUNIT_EXPECT_TRUE(test, no_more_access(__VA_ARGS__)) @@ -459,25 +459,25 @@ static bool no_more_access( static void test_no_more_access(struct kunit *const test) { - const layer_mask_t rx0[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0), - [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT_ULL(0), + const struct layer_access_masks rx0 = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_READ_FILE, }; - const layer_mask_t mx0[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0), - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = BIT_ULL(0), + const struct layer_access_masks mx0 = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_MAKE_REG, }; - const layer_mask_t x0[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0), + const struct layer_access_masks x0 = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, }; - const layer_mask_t x1[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(1), + const struct layer_access_masks x1 = { + .access[1] = LANDLOCK_ACCESS_FS_EXECUTE, }; - const layer_mask_t x01[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) | - BIT_ULL(1), + const struct layer_access_masks x01 = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, + .access[1] = LANDLOCK_ACCESS_FS_EXECUTE, }; - const layer_mask_t allows_all[LANDLOCK_NUM_ACCESS_FS] = {}; + const struct layer_access_masks allows_all = {}; /* Checks without restriction. */ NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false); @@ -565,31 +565,30 @@ static void test_no_more_access(struct kunit *const test) #undef NMA_TRUE #undef NMA_FALSE -static bool is_layer_masks_allowed( - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +static bool is_layer_masks_allowed(const struct layer_access_masks *masks) { - return !memchr_inv(layer_masks, 0, sizeof(*layer_masks)); + return mem_is_zero(&masks->access, sizeof(masks->access)); } /* - * Removes @layer_masks accesses that are not requested. + * Removes @masks accesses that are not requested. * * Returns true if the request is allowed, false otherwise. */ -static bool -scope_to_request(const access_mask_t access_request, - layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS]) +static bool scope_to_request(const access_mask_t access_request, + struct layer_access_masks *masks) { - const unsigned long access_req = access_request; - unsigned long access_bit; + bool saw_unfulfilled_access = false; - if (WARN_ON_ONCE(!layer_masks)) + if (WARN_ON_ONCE(!masks)) return true; - for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks)) - (*layer_masks)[access_bit] = 0; - - return is_layer_masks_allowed(layer_masks); + for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) { + masks->access[i] &= access_request; + if (masks->access[i]) + saw_unfulfilled_access = true; + } + return !saw_unfulfilled_access; } #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST @@ -597,48 +596,41 @@ scope_to_request(const access_mask_t access_request, static void test_scope_to_request_with_exec_none(struct kunit *const test) { /* Allows everything. */ - layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + struct layer_access_masks masks = {}; /* Checks and scopes with execute. */ - KUNIT_EXPECT_TRUE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, - &layer_masks)); - KUNIT_EXPECT_EQ(test, 0, - layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]); - KUNIT_EXPECT_EQ(test, 0, - layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]); + KUNIT_EXPECT_TRUE(test, + scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, &masks)); + KUNIT_EXPECT_EQ(test, 0, masks.access[0]); } static void test_scope_to_request_with_exec_some(struct kunit *const test) { /* Denies execute and write. */ - layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0), - [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1), + struct layer_access_masks masks = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, + .access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE, }; /* Checks and scopes with execute. */ KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, - &layer_masks)); - KUNIT_EXPECT_EQ(test, BIT_ULL(0), - layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]); - KUNIT_EXPECT_EQ(test, 0, - layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]); + &masks)); + KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE, masks.access[0]); + KUNIT_EXPECT_EQ(test, 0, masks.access[1]); } static void test_scope_to_request_without_access(struct kunit *const test) { /* Denies execute and write. */ - layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0), - [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1), + struct layer_access_masks masks = { + .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, + .access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE, }; /* Checks and scopes without access request. */ - KUNIT_EXPECT_TRUE(test, scope_to_request(0, &layer_masks)); - KUNIT_EXPECT_EQ(test, 0, - layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]); - KUNIT_EXPECT_EQ(test, 0, - layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]); + KUNIT_EXPECT_TRUE(test, scope_to_request(0, &masks)); + KUNIT_EXPECT_EQ(test, 0, masks.access[0]); + KUNIT_EXPECT_EQ(test, 0, masks.access[1]); } #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ @@ -647,20 +639,16 @@ static void test_scope_to_request_without_access(struct kunit *const test) * Returns true if there is at least one access right different than * LANDLOCK_ACCESS_FS_REFER. */ -static bool -is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS], - const access_mask_t access_request) +static bool is_eacces(const struct layer_access_masks *masks, + const access_mask_t access_request) { - unsigned long access_bit; - /* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */ - const unsigned long access_check = access_request & - ~LANDLOCK_ACCESS_FS_REFER; - - if (!layer_masks) + if (!masks) return false; - for_each_set_bit(access_bit, &access_check, ARRAY_SIZE(*layer_masks)) { - if ((*layer_masks)[access_bit]) + for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) { + /* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */ + if (masks->access[i] & access_request & + ~LANDLOCK_ACCESS_FS_REFER) return true; } return false; @@ -673,37 +661,37 @@ is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS], static void test_is_eacces_with_none(struct kunit *const test) { - const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + const struct layer_access_masks masks = {}; - IE_FALSE(&layer_masks, 0); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE); + IE_FALSE(&masks, 0); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_EXECUTE); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_WRITE_FILE); } static void test_is_eacces_with_refer(struct kunit *const test) { - const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = BIT_ULL(0), + const struct layer_access_masks masks = { + .access[0] = LANDLOCK_ACCESS_FS_REFER, }; - IE_FALSE(&layer_masks, 0); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE); + IE_FALSE(&masks, 0); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_EXECUTE); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_WRITE_FILE); } static void test_is_eacces_with_write(struct kunit *const test) { - const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(0), + const struct layer_access_masks masks = { + .access[0] = LANDLOCK_ACCESS_FS_WRITE_FILE, }; - IE_FALSE(&layer_masks, 0); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER); - IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE); + IE_FALSE(&masks, 0); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER); + IE_FALSE(&masks, LANDLOCK_ACCESS_FS_EXECUTE); - IE_TRUE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE); + IE_TRUE(&masks, LANDLOCK_ACCESS_FS_WRITE_FILE); } #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ @@ -715,7 +703,8 @@ static void test_is_eacces_with_write(struct kunit *const test) * is_access_to_paths_allowed - Check accesses for requests with a common path * * @domain: Domain to check against. - * @path: File hierarchy to walk through. + * @path: File hierarchy to walk through. For refer checks, this would be + * the common mountpoint. * @access_request_parent1: Accesses to check, once @layer_masks_parent1 is * equal to @layer_masks_parent2 (if any). This is tied to the unique * requested path for most actions, or the source in case of a refer action @@ -728,6 +717,7 @@ static void test_is_eacces_with_write(struct kunit *const test) * those identified by @access_request_parent1). This matrix can * initially refer to domain layer masks and, when the accesses for the * destination and source are the same, to requested layer masks. + * @log_request_parent1: Audit request to fill if the related access is denied. * @dentry_child1: Dentry to the initial child of the parent1 path. This * pointer must be NULL for non-refer actions (i.e. not link nor rename). * @access_request_parent2: Similar to @access_request_parent1 but for a @@ -736,6 +726,7 @@ static void test_is_eacces_with_write(struct kunit *const test) * the source. Must be set to 0 when using a simple path request. * @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer * action. This must be NULL otherwise. + * @log_request_parent2: Audit request to fill if the related access is denied. * @dentry_child2: Dentry to the initial child of the parent2 path. This * pointer is only set for RENAME_EXCHANGE actions and must be NULL * otherwise. @@ -746,36 +737,38 @@ static void test_is_eacces_with_write(struct kunit *const test) * checks that the collected accesses and the remaining ones are enough to * allow the request. * - * Returns: - * - true if the access request is granted; - * - false otherwise. + * Return: True if the access request is granted, false otherwise. */ -static bool is_access_to_paths_allowed( - const struct landlock_ruleset *const domain, - const struct path *const path, - const access_mask_t access_request_parent1, - layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS], - const struct dentry *const dentry_child1, - const access_mask_t access_request_parent2, - layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS], - const struct dentry *const dentry_child2) +static bool +is_access_to_paths_allowed(const struct landlock_ruleset *const domain, + const struct path *const path, + const access_mask_t access_request_parent1, + struct layer_access_masks *layer_masks_parent1, + struct landlock_request *const log_request_parent1, + struct dentry *const dentry_child1, + const access_mask_t access_request_parent2, + struct layer_access_masks *layer_masks_parent2, + struct landlock_request *const log_request_parent2, + struct dentry *const dentry_child2) { bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check, child1_is_directory = true, child2_is_directory = true; struct path walker_path; access_mask_t access_masked_parent1, access_masked_parent2; - layer_mask_t _layer_masks_child1[LANDLOCK_NUM_ACCESS_FS], - _layer_masks_child2[LANDLOCK_NUM_ACCESS_FS]; - layer_mask_t(*layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS] = NULL, - (*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL; + struct layer_access_masks _layer_masks_child1, _layer_masks_child2; + struct layer_access_masks *layer_masks_child1 = NULL, + *layer_masks_child2 = NULL; if (!access_request_parent1 && !access_request_parent2) return true; - if (WARN_ON_ONCE(!domain || !path)) + + if (WARN_ON_ONCE(!path)) return true; + if (is_nouser_or_private(path->dentry)) return true; - if (WARN_ON_ONCE(domain->num_layers < 1 || !layer_masks_parent1)) + + if (WARN_ON_ONCE(!layer_masks_parent1)) return false; allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1); @@ -804,22 +797,20 @@ static bool is_access_to_paths_allowed( } if (unlikely(dentry_child1)) { - landlock_unmask_layers( - find_rule(domain, dentry_child1), - landlock_init_layer_masks( - domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child1, LANDLOCK_KEY_INODE), - &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1)); + if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child1, + LANDLOCK_KEY_INODE)) + landlock_unmask_layers(find_rule(domain, dentry_child1), + &_layer_masks_child1); layer_masks_child1 = &_layer_masks_child1; child1_is_directory = d_is_dir(dentry_child1); } if (unlikely(dentry_child2)) { - landlock_unmask_layers( - find_rule(domain, dentry_child2), - landlock_init_layer_masks( - domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child2, LANDLOCK_KEY_INODE), - &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2)); + if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child2, + LANDLOCK_KEY_INODE)) + landlock_unmask_layers(find_rule(domain, dentry_child2), + &_layer_masks_child2); layer_masks_child2 = &_layer_masks_child2; child2_is_directory = d_is_dir(dentry_child2); } @@ -831,7 +822,6 @@ static bool is_access_to_paths_allowed( * restriction. */ while (true) { - struct dentry *parent_dentry; const struct landlock_rule *rule; /* @@ -875,20 +865,17 @@ static bool is_access_to_paths_allowed( } rule = find_rule(domain, walker_path.dentry); - allowed_parent1 = allowed_parent1 || - landlock_unmask_layers( - rule, access_masked_parent1, - layer_masks_parent1, - ARRAY_SIZE(*layer_masks_parent1)); - allowed_parent2 = allowed_parent2 || - landlock_unmask_layers( - rule, access_masked_parent2, - layer_masks_parent2, - ARRAY_SIZE(*layer_masks_parent2)); + allowed_parent1 = + allowed_parent1 || + landlock_unmask_layers(rule, layer_masks_parent1); + allowed_parent2 = + allowed_parent2 || + landlock_unmask_layers(rule, layer_masks_parent2); /* Stops when a rule from each layer grants access. */ if (allowed_parent1 && allowed_parent2) break; + jump_up: if (walker_path.dentry == walker_path.mnt->mnt_root) { if (follow_up(&walker_path)) { @@ -902,42 +889,84 @@ jump_up: break; } } + if (unlikely(IS_ROOT(walker_path.dentry))) { - /* - * Stops at disconnected root directories. Only allows - * access to internal filesystems (e.g. nsfs, which is - * reachable through /proc/<pid>/ns/<namespace>). - */ - if (walker_path.mnt->mnt_flags & MNT_INTERNAL) { + if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) { + /* + * Stops and allows access when reaching disconnected root + * directories that are part of internal filesystems (e.g. nsfs, + * which is reachable through /proc/<pid>/ns/<namespace>). + */ allowed_parent1 = true; allowed_parent2 = true; + break; } - break; + + /* + * We reached a disconnected root directory from a bind mount. + * Let's continue the walk with the mount point we missed. + */ + dput(walker_path.dentry); + walker_path.dentry = walker_path.mnt->mnt_root; + dget(walker_path.dentry); + } else { + struct dentry *const parent_dentry = + dget_parent(walker_path.dentry); + + dput(walker_path.dentry); + walker_path.dentry = parent_dentry; } - parent_dentry = dget_parent(walker_path.dentry); - dput(walker_path.dentry); - walker_path.dentry = parent_dentry; } path_put(&walker_path); + /* + * Check CONFIG_AUDIT to enable elision of log_request_parent* and + * associated caller's stack variables thanks to dead code elimination. + */ +#ifdef CONFIG_AUDIT + if (!allowed_parent1 && log_request_parent1) { + log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS; + log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH; + log_request_parent1->audit.u.path = *path; + log_request_parent1->access = access_masked_parent1; + log_request_parent1->layer_masks = layer_masks_parent1; + } + + if (!allowed_parent2 && log_request_parent2) { + log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS; + log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH; + log_request_parent2->audit.u.path = *path; + log_request_parent2->access = access_masked_parent2; + log_request_parent2->layer_masks = layer_masks_parent2; + } +#endif /* CONFIG_AUDIT */ + return allowed_parent1 && allowed_parent2; } static int current_check_access_path(const struct path *const path, access_mask_t access_request) { - const struct landlock_ruleset *const dom = get_current_fs_domain(); - layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + const struct access_masks masks = { + .fs = access_request, + }; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), masks, NULL); + struct layer_access_masks layer_masks; + struct landlock_request request = {}; - if (!dom) + if (!subject) return 0; - access_request = landlock_init_layer_masks( - dom, access_request, &layer_masks, LANDLOCK_KEY_INODE); - if (is_access_to_paths_allowed(dom, path, access_request, &layer_masks, - NULL, 0, NULL, NULL)) + access_request = landlock_init_layer_masks(subject->domain, + access_request, &layer_masks, + LANDLOCK_KEY_INODE); + if (is_access_to_paths_allowed(subject->domain, path, access_request, + &layer_masks, &request, NULL, 0, NULL, + NULL, NULL)) return 0; + landlock_log_denial(subject, &request); return -EACCES; } @@ -987,20 +1016,21 @@ static access_mask_t maybe_remove(const struct dentry *const dentry) * file. While walking from @dir to @mnt_root, we record all the domain's * allowed accesses in @layer_masks_dom. * + * Because of disconnected directories, this walk may not reach @mnt_dir. In + * this case, the walk will continue to @mnt_dir after this call. + * * This is similar to is_access_to_paths_allowed() but much simpler because it * only handles walking on the same mount point and only checks one set of * accesses. * - * Returns: - * - true if all the domain access rights are allowed for @dir; - * - false if the walk reached @mnt_root. + * Return: True if all the domain access rights are allowed for @dir, false if + * the walk reached @mnt_root. */ -static bool collect_domain_accesses( - const struct landlock_ruleset *const domain, - const struct dentry *const mnt_root, struct dentry *dir, - layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS]) +static bool collect_domain_accesses(const struct landlock_ruleset *const domain, + const struct dentry *const mnt_root, + struct dentry *dir, + struct layer_access_masks *layer_masks_dom) { - unsigned long access_dom; bool ret = false; if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom)) @@ -1008,18 +1038,17 @@ static bool collect_domain_accesses( if (is_nouser_or_private(dir)) return true; - access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - layer_masks_dom, - LANDLOCK_KEY_INODE); + if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + layer_masks_dom, LANDLOCK_KEY_INODE)) + return true; dget(dir); while (true) { struct dentry *parent_dentry; /* Gets all layers allowing all domain accesses. */ - if (landlock_unmask_layers(find_rule(domain, dir), access_dom, - layer_masks_dom, - ARRAY_SIZE(*layer_masks_dom))) { + if (landlock_unmask_layers(find_rule(domain, dir), + layer_masks_dom)) { /* * Stops when all handled accesses are allowed by at * least one rule in each layer. @@ -1028,8 +1057,11 @@ static bool collect_domain_accesses( break; } - /* We should not reach a root other than @mnt_root. */ - if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir))) + /* + * Stops at the mount point or the filesystem root for a disconnected + * directory. + */ + if (dir == mnt_root || unlikely(IS_ROOT(dir))) break; parent_dentry = dget_parent(dir); @@ -1088,28 +1120,28 @@ static bool collect_domain_accesses( * ephemeral matrices take some space on the stack, which limits the number of * layers to a deemed reasonable number: 16. * - * Returns: - * - 0 if access is allowed; - * - -EXDEV if @old_dentry would inherit new access rights from @new_dir; - * - -EACCES if file removal or creation is denied. + * Return: 0 if access is allowed, -EXDEV if @old_dentry would inherit new + * access rights from @new_dir, or -EACCES if file removal or creation is + * denied. */ static int current_check_refer_path(struct dentry *const old_dentry, const struct path *const new_dir, struct dentry *const new_dentry, const bool removable, const bool exchange) { - const struct landlock_ruleset *const dom = get_current_fs_domain(); + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), any_fs, NULL); bool allow_parent1, allow_parent2; access_mask_t access_request_parent1, access_request_parent2; struct path mnt_dir; struct dentry *old_parent; - layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS] = {}, - layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS] = {}; + struct layer_access_masks layer_masks_parent1 = {}, + layer_masks_parent2 = {}; + struct landlock_request request1 = {}, request2 = {}; - if (!dom) + if (!subject) return 0; - if (WARN_ON_ONCE(dom->num_layers < 1)) - return -EACCES; + if (unlikely(d_is_negative(old_dentry))) return -ENOENT; if (exchange) { @@ -1134,12 +1166,16 @@ static int current_check_refer_path(struct dentry *const old_dentry, * for same-directory referer (i.e. no reparenting). */ access_request_parent1 = landlock_init_layer_masks( - dom, access_request_parent1 | access_request_parent2, + subject->domain, + access_request_parent1 | access_request_parent2, &layer_masks_parent1, LANDLOCK_KEY_INODE); - if (is_access_to_paths_allowed( - dom, new_dir, access_request_parent1, - &layer_masks_parent1, NULL, 0, NULL, NULL)) + if (is_access_to_paths_allowed(subject->domain, new_dir, + access_request_parent1, + &layer_masks_parent1, &request1, + NULL, 0, NULL, NULL, NULL)) return 0; + + landlock_log_denial(subject, &request1); return -EACCES; } @@ -1160,10 +1196,12 @@ static int current_check_refer_path(struct dentry *const old_dentry, old_dentry->d_parent; /* new_dir->dentry is equal to new_dentry->d_parent */ - allow_parent1 = collect_domain_accesses(dom, mnt_dir.dentry, old_parent, + allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry, + old_parent, &layer_masks_parent1); - allow_parent2 = collect_domain_accesses( - dom, mnt_dir.dentry, new_dir->dentry, &layer_masks_parent2); + allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry, + new_dir->dentry, + &layer_masks_parent2); if (allow_parent1 && allow_parent2) return 0; @@ -1175,11 +1213,21 @@ static int current_check_refer_path(struct dentry *const old_dentry, * destination parent access rights. */ if (is_access_to_paths_allowed( - dom, &mnt_dir, access_request_parent1, &layer_masks_parent1, - old_dentry, access_request_parent2, &layer_masks_parent2, + subject->domain, &mnt_dir, access_request_parent1, + &layer_masks_parent1, &request1, old_dentry, + access_request_parent2, &layer_masks_parent2, &request2, exchange ? new_dentry : NULL)) return 0; + if (request1.access) { + request1.audit.u.path.dentry = old_parent; + landlock_log_denial(subject, &request1); + } + if (request2.access) { + request2.audit.u.path.dentry = new_dir->dentry; + landlock_log_denial(subject, &request2); + } + /* * This prioritizes EACCES over EXDEV for all actions, including * renames with RENAME_EXCHANGE. @@ -1216,7 +1264,7 @@ static void hook_inode_free_security_rcu(void *inode_security) /* * Release the inodes used in a security policy. * - * Cf. fsnotify_unmount_inodes() and invalidate_inodes() + * Cf. fsnotify_unmount_inodes() and evict_inodes() */ static void hook_sb_delete(struct super_block *const sb) { @@ -1230,7 +1278,7 @@ static void hook_sb_delete(struct super_block *const sb) struct landlock_object *object; /* Only handles referenced inodes. */ - if (!atomic_read(&inode->i_count)) + if (!icount_read(inode)) continue; /* @@ -1245,7 +1293,8 @@ static void hook_sb_delete(struct super_block *const sb) * second call to iput() for the same Landlock object. Also * checks I_NEW because such inode cannot be tied to an object. */ - if (inode->i_state & (I_FREEING | I_WILL_FREE | I_NEW)) { + if (inode_state_read(inode) & + (I_FREEING | I_WILL_FREE | I_NEW)) { spin_unlock(&inode->i_lock); continue; } @@ -1284,11 +1333,10 @@ static void hook_sb_delete(struct super_block *const sb) * At this point, we own the ihold() reference that was * originally set up by get_inode_object() and the * __iget() reference that we just set in this loop - * walk. Therefore the following call to iput() will - * not sleep nor drop the inode because there is now at - * least two references to it. + * walk. Therefore there are at least two references + * on the inode. */ - iput(inode); + iput_not_last(inode); } else { spin_unlock(&object->lock); rcu_read_unlock(); @@ -1322,6 +1370,34 @@ static void hook_sb_delete(struct super_block *const sb) !atomic_long_read(&landlock_superblock(sb)->inode_refs)); } +static void +log_fs_change_topology_path(const struct landlock_cred_security *const subject, + size_t handle_layer, const struct path *const path) +{ + landlock_log_denial(subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, + .audit = { + .type = LSM_AUDIT_DATA_PATH, + .u.path = *path, + }, + .layer_plus_one = handle_layer + 1, + }); +} + +static void log_fs_change_topology_dentry( + const struct landlock_cred_security *const subject, size_t handle_layer, + struct dentry *const dentry) +{ + landlock_log_denial(subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, + .audit = { + .type = LSM_AUDIT_DATA_DENTRY, + .u.dentry = dentry, + }, + .layer_plus_one = handle_layer + 1, + }); +} + /* * Because a Landlock security policy is defined according to the filesystem * topology (i.e. the mount namespace), changing it may grant access to files @@ -1344,16 +1420,30 @@ static int hook_sb_mount(const char *const dev_name, const struct path *const path, const char *const type, const unsigned long flags, void *const data) { - if (!get_current_fs_domain()) + size_t handle_layer; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), any_fs, + &handle_layer); + + if (!subject) return 0; + + log_fs_change_topology_path(subject, handle_layer, path); return -EPERM; } static int hook_move_mount(const struct path *const from_path, const struct path *const to_path) { - if (!get_current_fs_domain()) + size_t handle_layer; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), any_fs, + &handle_layer); + + if (!subject) return 0; + + log_fs_change_topology_path(subject, handle_layer, to_path); return -EPERM; } @@ -1363,15 +1453,29 @@ static int hook_move_mount(const struct path *const from_path, */ static int hook_sb_umount(struct vfsmount *const mnt, const int flags) { - if (!get_current_fs_domain()) + size_t handle_layer; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), any_fs, + &handle_layer); + + if (!subject) return 0; + + log_fs_change_topology_dentry(subject, handle_layer, mnt->mnt_root); return -EPERM; } static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts) { - if (!get_current_fs_domain()) + size_t handle_layer; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), any_fs, + &handle_layer); + + if (!subject) return 0; + + log_fs_change_topology_dentry(subject, handle_layer, sb->s_root); return -EPERM; } @@ -1386,8 +1490,15 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts) static int hook_sb_pivotroot(const struct path *const old_path, const struct path *const new_path) { - if (!get_current_fs_domain()) + size_t handle_layer; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), any_fs, + &handle_layer); + + if (!subject) return 0; + + log_fs_change_topology_path(subject, handle_layer, new_path); return -EPERM; } @@ -1449,6 +1560,133 @@ static int hook_path_truncate(const struct path *const path) return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE); } +/** + * unmask_scoped_access - Remove access right bits in @masks in all layers + * where @client and @server have the same domain + * + * This does the same as domain_is_scoped(), but unmasks bits in @masks. + * It can not return early as domain_is_scoped() does. + * + * A scoped access for a given access right bit is allowed iff, for all layer + * depths where the access bit is set, the client and server domain are the + * same. This function clears the access rights @access in @masks at all layer + * depths where the client and server domain are the same, so that, when they + * are all cleared, the access is allowed. + * + * @client: Client domain + * @server: Server domain + * @masks: Layer access masks to unmask + * @access: Access bits that control scoping + */ +static void unmask_scoped_access(const struct landlock_ruleset *const client, + const struct landlock_ruleset *const server, + struct layer_access_masks *const masks, + const access_mask_t access) +{ + int client_layer, server_layer; + const struct landlock_hierarchy *client_walker, *server_walker; + + /* This should not happen. */ + if (WARN_ON_ONCE(!client)) + return; + + /* Server has no Landlock domain; nothing to clear. */ + if (!server) + return; + + /* + * client_layer must be able to represent all numbers from + * LANDLOCK_MAX_NUM_LAYERS - 1 to -1 for the loop below to terminate. + * (It must be large enough, and it must be signed.) + */ + BUILD_BUG_ON(!is_signed_type(typeof(client_layer))); + BUILD_BUG_ON(LANDLOCK_MAX_NUM_LAYERS - 1 > + type_max(typeof(client_layer))); + + client_layer = client->num_layers - 1; + client_walker = client->hierarchy; + server_layer = server->num_layers - 1; + server_walker = server->hierarchy; + + /* + * Clears the access bits at all layers where the client domain is the + * same as the server domain. We start the walk at min(client_layer, + * server_layer). The layer bits until there can not be cleared because + * either the client or the server domain is missing. + */ + for (; client_layer > server_layer; client_layer--) + client_walker = client_walker->parent; + + for (; server_layer > client_layer; server_layer--) + server_walker = server_walker->parent; + + for (; client_layer >= 0; client_layer--) { + if (masks->access[client_layer] & access && + client_walker == server_walker) + masks->access[client_layer] &= ~access; + + client_walker = client_walker->parent; + server_walker = server_walker->parent; + } +} + +static int hook_unix_find(const struct path *const path, struct sock *other, + int flags) +{ + const struct landlock_ruleset *dom_other; + const struct landlock_cred_security *subject; + struct layer_access_masks layer_masks; + struct landlock_request request = {}; + static const struct access_masks fs_resolve_unix = { + .fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX, + }; + + /* Lookup for the purpose of saving coredumps is OK. */ + if (unlikely(flags & SOCK_COREDUMP)) + return 0; + + subject = landlock_get_applicable_subject(current_cred(), + fs_resolve_unix, NULL); + + if (!subject) + return 0; + + /* + * Ignoring return value: that the domains apply was already checked in + * landlock_get_applicable_subject() above. + */ + landlock_init_layer_masks(subject->domain, fs_resolve_unix.fs, + &layer_masks, LANDLOCK_KEY_INODE); + + /* Checks the layers in which we are connecting within the same domain. */ + unix_state_lock(other); + if (unlikely(sock_flag(other, SOCK_DEAD) || !other->sk_socket || + !other->sk_socket->file)) { + unix_state_unlock(other); + /* + * We rely on the caller to catch the (non-reversible) SOCK_DEAD + * condition and retry the lookup. If we returned an error + * here, the lookup would not get retried. + */ + return 0; + } + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; + + /* Access to the same (or a lower) domain is always allowed. */ + unmask_scoped_access(subject->domain, dom_other, &layer_masks, + fs_resolve_unix.fs); + unix_state_unlock(other); + + /* Checks the connections to allow-listed paths. */ + if (is_access_to_paths_allowed(subject->domain, path, + fs_resolve_unix.fs, &layer_masks, + &request, NULL, 0, NULL, NULL, NULL)) + return 0; + + landlock_log_denial(subject, &request); + return -EACCES; +} + /* File hooks */ /** @@ -1456,7 +1694,7 @@ static int hook_path_truncate(const struct path *const path) * * @file: File being opened. * - * Returns the access rights that are required for opening the given file, + * Return: The access rights that are required for opening the given file, * depending on the file type and open mode. */ static access_mask_t @@ -1501,14 +1739,14 @@ static bool is_device(const struct file *const file) static int hook_file_open(struct file *const file) { - layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + struct layer_access_masks layer_masks = {}; access_mask_t open_access_request, full_access_request, allowed_access, optional_access; - const struct landlock_ruleset *const dom = - landlock_get_applicable_domain( - landlock_cred(file->f_cred)->domain, any_fs); + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(file->f_cred, any_fs, NULL); + struct landlock_request request = {}; - if (!dom) + if (!subject) return 0; /* @@ -1529,26 +1767,21 @@ static int hook_file_open(struct file *const file) full_access_request = open_access_request | optional_access; if (is_access_to_paths_allowed( - dom, &file->f_path, - landlock_init_layer_masks(dom, full_access_request, - &layer_masks, LANDLOCK_KEY_INODE), - &layer_masks, NULL, 0, NULL, NULL)) { + subject->domain, &file->f_path, + landlock_init_layer_masks(subject->domain, + full_access_request, &layer_masks, + LANDLOCK_KEY_INODE), + &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) { allowed_access = full_access_request; } else { - unsigned long access_bit; - const unsigned long access_req = full_access_request; - /* * Calculate the actual allowed access rights from layer_masks. - * Add each access right to allowed_access which has not been - * vetoed by any layer. + * Remove the access rights from the full access request which + * are still unfulfilled in any of the layers. */ - allowed_access = 0; - for_each_set_bit(access_bit, &access_req, - ARRAY_SIZE(layer_masks)) { - if (!layer_masks[access_bit]) - allowed_access |= BIT_ULL(access_bit); - } + allowed_access = full_access_request; + for (size_t i = 0; i < ARRAY_SIZE(layer_masks.access); i++) + allowed_access &= ~layer_masks.access[i]; } /* @@ -1558,10 +1791,17 @@ static int hook_file_open(struct file *const file) * file access rights in the opened struct file. */ landlock_file(file)->allowed_access = allowed_access; +#ifdef CONFIG_AUDIT + landlock_file(file)->deny_masks = landlock_get_deny_masks( + _LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks); +#endif /* CONFIG_AUDIT */ - if ((open_access_request & allowed_access) == open_access_request) + if (access_mask_subset(open_access_request, allowed_access)) return 0; + /* Sets access to reflect the actual request. */ + request.access = open_access_request; + landlock_log_denial(subject, &request); return -EACCES; } @@ -1579,11 +1819,24 @@ static int hook_file_truncate(struct file *const file) */ if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE) return 0; + + landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) { + .type = LANDLOCK_REQUEST_FS_ACCESS, + .audit = { + .type = LSM_AUDIT_DATA_FILE, + .u.file = file, + }, + .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL, + .access = LANDLOCK_ACCESS_FS_TRUNCATE, +#ifdef CONFIG_AUDIT + .deny_masks = landlock_file(file)->deny_masks, +#endif /* CONFIG_AUDIT */ + }); return -EACCES; } -static int hook_file_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +static int hook_file_ioctl_common(const struct file *const file, + const unsigned int cmd, const bool is_compat) { access_mask_t allowed_access = landlock_file(file)->allowed_access; @@ -1599,56 +1852,98 @@ static int hook_file_ioctl(struct file *file, unsigned int cmd, if (!is_device(file)) return 0; - if (is_masked_device_ioctl(cmd)) + if (unlikely(is_compat) ? is_masked_device_ioctl_compat(cmd) : + is_masked_device_ioctl(cmd)) return 0; + landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) { + .type = LANDLOCK_REQUEST_FS_ACCESS, + .audit = { + .type = LSM_AUDIT_DATA_IOCTL_OP, + .u.op = &(struct lsm_ioctlop_audit) { + .path = file->f_path, + .cmd = cmd, + }, + }, + .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL, + .access = LANDLOCK_ACCESS_FS_IOCTL_DEV, +#ifdef CONFIG_AUDIT + .deny_masks = landlock_file(file)->deny_masks, +#endif /* CONFIG_AUDIT */ + }); return -EACCES; } +static int hook_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return hook_file_ioctl_common(file, cmd, false); +} + static int hook_file_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) { - access_mask_t allowed_access = landlock_file(file)->allowed_access; + return hook_file_ioctl_common(file, cmd, true); +} + +/* + * Always allow sending signals between threads of the same process. This + * ensures consistency with hook_task_kill(). + */ +static bool control_current_fowner(struct fown_struct *const fown) +{ + struct task_struct *p; /* - * It is the access rights at the time of opening the file which - * determine whether IOCTL can be used on the opened file later. - * - * The access right is attached to the opened file in hook_file_open(). + * Lock already held by __f_setown(), see commit 26f204380a3c ("fs: Fix + * file_set_fowner LSM hook inconsistencies"). */ - if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV) - return 0; + lockdep_assert_held(&fown->lock); - if (!is_device(file)) - return 0; - - if (is_masked_device_ioctl_compat(cmd)) - return 0; + /* + * Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side + * critical section. + */ + guard(rcu)(); + p = pid_task(fown->pid, fown->pid_type); + if (!p) + return true; - return -EACCES; + return !same_thread_group(p, current); } static void hook_file_set_fowner(struct file *file) { - struct landlock_ruleset *new_dom, *prev_dom; + struct landlock_ruleset *prev_dom; + struct landlock_cred_security fown_subject = {}; + size_t fown_layer = 0; + + if (control_current_fowner(file_f_owner(file))) { + static const struct access_masks signal_scope = { + .scope = LANDLOCK_SCOPE_SIGNAL, + }; + const struct landlock_cred_security *new_subject = + landlock_get_applicable_subject( + current_cred(), signal_scope, &fown_layer); + if (new_subject) { + landlock_get_ruleset(new_subject->domain); + fown_subject = *new_subject; + } + } - /* - * Lock already held by __f_setown(), see commit 26f204380a3c ("fs: Fix - * file_set_fowner LSM hook inconsistencies"). - */ - lockdep_assert_held(&file_f_owner(file)->lock); - new_dom = landlock_get_current_domain(); - landlock_get_ruleset(new_dom); - prev_dom = landlock_file(file)->fown_domain; - landlock_file(file)->fown_domain = new_dom; + prev_dom = landlock_file(file)->fown_subject.domain; + landlock_file(file)->fown_subject = fown_subject; +#ifdef CONFIG_AUDIT + landlock_file(file)->fown_layer = fown_layer; +#endif /* CONFIG_AUDIT*/ - /* Called in an RCU read-side critical section. */ + /* May be called in an RCU read-side critical section. */ landlock_put_ruleset_deferred(prev_dom); } static void hook_file_free_security(struct file *file) { - landlock_put_ruleset_deferred(landlock_file(file)->fown_domain); + landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain); } static struct security_hook_list landlock_hooks[] __ro_after_init = { @@ -1669,6 +1964,7 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(path_unlink, hook_path_unlink), LSM_HOOK_INIT(path_rmdir, hook_path_rmdir), LSM_HOOK_INIT(path_truncate, hook_path_truncate), + LSM_HOOK_INIT(unix_find, hook_unix_find), LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security), LSM_HOOK_INIT(file_open, hook_file_open), diff --git a/security/landlock/fs.h b/security/landlock/fs.h index d445f411c26a..bf9948941f2f 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -1,19 +1,22 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Landlock LSM - Filesystem management and hooks + * Landlock - Filesystem management and hooks * * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2024-2025 Microsoft Corporation */ #ifndef _SECURITY_LANDLOCK_FS_H #define _SECURITY_LANDLOCK_FS_H +#include <linux/build_bug.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/rcupdate.h> #include "access.h" +#include "cred.h" #include "ruleset.h" #include "setup.h" @@ -53,15 +56,40 @@ struct landlock_file_security { * needed to authorize later operations on the open file. */ access_mask_t allowed_access; + +#ifdef CONFIG_AUDIT + /** + * @deny_masks: Domain layer levels that deny an optional access (see + * _LANDLOCK_ACCESS_FS_OPTIONAL). + */ + deny_masks_t deny_masks; /** - * @fown_domain: Domain of the task that set the PID that may receive a - * signal e.g., SIGURG when writing MSG_OOB to the related socket. - * This pointer is protected by the related file->f_owner->lock, as for - * fown_struct's members: pid, uid, and euid. + * @fown_layer: Layer level of @fown_subject->domain with + * LANDLOCK_SCOPE_SIGNAL. */ - struct landlock_ruleset *fown_domain; + u8 fown_layer; +#endif /* CONFIG_AUDIT */ + + /** + * @fown_subject: Landlock credential of the task that set the PID that + * may receive a signal e.g., SIGURG when writing MSG_OOB to the + * related socket. This pointer is protected by the related + * file->f_owner->lock, as for fown_struct's members: pid, uid, and + * euid. + */ + struct landlock_cred_security fown_subject; }; +#ifdef CONFIG_AUDIT + +/* Makes sure all layers can be identified. */ +/* clang-format off */ +static_assert((typeof_member(struct landlock_file_security, fown_layer))~0 >= + LANDLOCK_MAX_NUM_LAYERS); +/* clang-format off */ + +#endif /* CONFIG_AUDIT */ + /** * struct landlock_superblock_security - Superblock security blob * diff --git a/security/landlock/id.c b/security/landlock/id.c new file mode 100644 index 000000000000..6c8769777fdc --- /dev/null +++ b/security/landlock/id.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Unique identification number generator + * + * Copyright © 2024-2025 Microsoft Corporation + */ + +#include <kunit/test.h> +#include <linux/atomic.h> +#include <linux/bitops.h> +#include <linux/random.h> +#include <linux/spinlock.h> + +#include "common.h" +#include "id.h" + +#define COUNTER_PRE_INIT 0 + +static atomic64_t next_id = ATOMIC64_INIT(COUNTER_PRE_INIT); + +static void __init init_id(atomic64_t *const counter, const u32 random_32bits) +{ + u64 init; + + /* + * Ensures sure 64-bit values are always used by user space (or may + * fail with -EOVERFLOW), and makes this testable. + */ + init = BIT_ULL(32); + + /* + * Makes a large (2^32) boot-time value to limit ID collision in logs + * from different boots, and to limit info leak about the number of + * initially (relative to the reader) created elements (e.g. domains). + */ + init += random_32bits; + + /* Sets first or ignores. This will be the first ID. */ + atomic64_cmpxchg(counter, COUNTER_PRE_INIT, init); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void __init test_init_min(struct kunit *const test) +{ + atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT); + + init_id(&counter, 0); + KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1ULL + U32_MAX); +} + +static void __init test_init_max(struct kunit *const test) +{ + atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT); + + init_id(&counter, ~0); + KUNIT_EXPECT_EQ(test, atomic64_read(&counter), 1 + (2ULL * U32_MAX)); +} + +static void __init test_init_once(struct kunit *const test) +{ + const u64 first_init = 1ULL + U32_MAX; + atomic64_t counter = ATOMIC64_INIT(COUNTER_PRE_INIT); + + init_id(&counter, 0); + KUNIT_EXPECT_EQ(test, atomic64_read(&counter), first_init); + + init_id(&counter, ~0); + KUNIT_EXPECT_EQ_MSG( + test, atomic64_read(&counter), first_init, + "Should still have the same value after the subsequent init_id()"); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +void __init landlock_init_id(void) +{ + return init_id(&next_id, get_random_u32()); +} + +/* + * It's not worth it to try to hide the monotonic counter because it can still + * be inferred (with N counter ranges), and if we are allowed to read the inode + * number we should also be allowed to read the time creation anyway, and it + * can be handy to store and sort domain IDs for user space. + * + * Returns the value of next_id and increment it to let some space for the next + * one. + */ +static u64 get_id_range(size_t number_of_ids, atomic64_t *const counter, + u8 random_4bits) +{ + u64 id, step; + + /* + * We should return at least 1 ID, and we may need a set of consecutive + * ones (e.g. to generate a set of inodes). + */ + if (WARN_ON_ONCE(number_of_ids <= 0)) + number_of_ids = 1; + + /* + * Blurs the next ID guess with 1/16 ratio. We get 2^(64 - 4) - + * (2 * 2^32), so a bit less than 2^60 available IDs, which should be + * much more than enough considering the number of CPU cycles required + * to get a new ID (e.g. a full landlock_restrict_self() call), and the + * cost of draining all available IDs during the system's uptime. + */ + random_4bits &= 0b1111; + step = number_of_ids + random_4bits; + + /* It is safe to cast a signed atomic to an unsigned value. */ + id = atomic64_fetch_add(step, counter); + + /* Warns if landlock_init_id() was not called. */ + WARN_ON_ONCE(id == COUNTER_PRE_INIT); + return id; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static u8 get_random_u8_positive(void) +{ + /* max() evaluates its arguments once. */ + return max(1, get_random_u8()); +} + +static void test_range1_rand0(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 0), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 1); +} + +static void test_range1_rand1(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 1), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 2); +} + +static void test_range1_rand15(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 15), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 16); +} + +static void test_range1_rand16(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 16), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 1); +} + +static void test_range2_rand0(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 0), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 2); +} + +static void test_range2_rand1(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 1), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 3); +} + +static void test_range2_rand2(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 2), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 4); +} + +static void test_range2_rand15(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 15), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 17); +} + +static void test_range2_rand16(struct kunit *const test) +{ + atomic64_t counter; + u64 init; + + init = get_random_u32(); + atomic64_set(&counter, init); + KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 16), init); + KUNIT_EXPECT_EQ(test, + get_id_range(get_random_u8_positive(), &counter, + get_random_u8()), + init + 2); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +/** + * landlock_get_id_range - Get a range of unique IDs + * + * @number_of_ids: Number of IDs to hold. Must be greater than one. + * + * Return: The first ID in the range. + */ +u64 landlock_get_id_range(size_t number_of_ids) +{ + return get_id_range(number_of_ids, &next_id, get_random_u8()); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static struct kunit_case __refdata test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_init_min), + KUNIT_CASE(test_init_max), + KUNIT_CASE(test_init_once), + KUNIT_CASE(test_range1_rand0), + KUNIT_CASE(test_range1_rand1), + KUNIT_CASE(test_range1_rand15), + KUNIT_CASE(test_range1_rand16), + KUNIT_CASE(test_range2_rand0), + KUNIT_CASE(test_range2_rand1), + KUNIT_CASE(test_range2_rand2), + KUNIT_CASE(test_range2_rand15), + KUNIT_CASE(test_range2_rand16), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_id", + .test_cases = test_cases, +}; + +kunit_test_init_section_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/id.h b/security/landlock/id.h new file mode 100644 index 000000000000..45dcfb9e9a8b --- /dev/null +++ b/security/landlock/id.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Unique identification number generator + * + * Copyright © 2024-2025 Microsoft Corporation + */ + +#ifndef _SECURITY_LANDLOCK_ID_H +#define _SECURITY_LANDLOCK_ID_H + +#ifdef CONFIG_AUDIT + +void __init landlock_init_id(void); + +u64 landlock_get_id_range(size_t number_of_ids); + +#else /* CONFIG_AUDIT */ + +static inline void __init landlock_init_id(void) +{ +} + +#endif /* CONFIG_AUDIT */ + +#endif /* _SECURITY_LANDLOCK_ID_H */ diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 15f7606066c8..b454ad73b15e 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Landlock LSM - Limits for different components + * Landlock - Limits for different components * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2021-2025 Microsoft Corporation */ #ifndef _SECURITY_LANDLOCK_LIMITS_H @@ -18,7 +19,7 @@ #define LANDLOCK_MAX_NUM_LAYERS 16 #define LANDLOCK_MAX_NUM_RULES U32_MAX -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_IOCTL_DEV +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_RESOLVE_UNIX #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) @@ -29,6 +30,10 @@ #define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) + +#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC +#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1) + /* clang-format on */ #endif /* _SECURITY_LANDLOCK_LIMITS_H */ diff --git a/security/landlock/net.c b/security/landlock/net.c index d5dcc4407a19..c368649985c5 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -1,16 +1,18 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Landlock LSM - Network management and hooks + * Landlock - Network management and hooks * * Copyright © 2022-2023 Huawei Tech. Co., Ltd. - * Copyright © 2022-2023 Microsoft Corporation + * Copyright © 2022-2025 Microsoft Corporation */ #include <linux/in.h> +#include <linux/lsm_audit.h> #include <linux/net.h> #include <linux/socket.h> #include <net/ipv6.h> +#include "audit.h" #include "common.h" #include "cred.h" #include "limits.h" @@ -39,32 +41,25 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, return err; } -static const struct access_masks any_net = { - .net = ~0, -}; - static int current_check_access_socket(struct socket *const sock, struct sockaddr *const address, const int addrlen, access_mask_t access_request) { __be16 port; - layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {}; + struct layer_access_masks layer_masks = {}; const struct landlock_rule *rule; struct landlock_id id = { .type = LANDLOCK_KEY_NET_PORT, }; - const struct landlock_ruleset *const dom = - landlock_get_applicable_domain(landlock_get_current_domain(), - any_net); - - if (!dom) - return 0; - if (WARN_ON_ONCE(dom->num_layers < 1)) - return -EACCES; + const struct access_masks masks = { + .net = access_request, + }; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), masks, NULL); + struct lsm_network_audit audit_net = {}; - /* Checks if it's a (potential) TCP socket. */ - if (sock->type != SOCK_STREAM) + if (!subject) return 0; /* Checks for minimal header length to safely read sa_family. */ @@ -73,102 +68,174 @@ static int current_check_access_socket(struct socket *const sock, switch (address->sa_family) { case AF_UNSPEC: - case AF_INET: + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) { + /* + * Connecting to an address with AF_UNSPEC dissolves + * the TCP association, which have the same effect as + * closing the connection while retaining the socket + * object (i.e., the file descriptor). As for dropping + * privileges, closing connections is always allowed. + * + * For a TCP access control system, this request is + * legitimate. Let the network stack handle potential + * inconsistencies and return -EINVAL if needed. + */ + return 0; + } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + /* + * Binding to an AF_UNSPEC address is treated + * differently by IPv4 and IPv6 sockets. The socket's + * family may change under our feet due to + * setsockopt(IPV6_ADDRFORM), but that's ok: we either + * reject entirely or require + * %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so + * it cannot be used to bypass the policy. + * + * IPv4 sockets map AF_UNSPEC to AF_INET for + * retrocompatibility for bind accesses, only if the + * address is INADDR_ANY (cf. __inet_bind). IPv6 + * sockets always reject it. + * + * Checking the address is required to not wrongfully + * return -EACCES instead of -EAFNOSUPPORT or -EINVAL. + * We could return 0 and let the network stack handle + * these checks, but it is safer to return a proper + * error and test consistency thanks to kselftest. + */ + if (sock->sk->__sk_common.skc_family == AF_INET) { + const struct sockaddr_in *const sockaddr = + (struct sockaddr_in *)address; + + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + + if (sockaddr->sin_addr.s_addr != + htonl(INADDR_ANY)) + return -EAFNOSUPPORT; + } else { + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + else + return -EAFNOSUPPORT; + } + } else { + WARN_ON_ONCE(1); + } + /* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */ + fallthrough; + case AF_INET: { + const struct sockaddr_in *addr4; + if (addrlen < sizeof(struct sockaddr_in)) return -EINVAL; - port = ((struct sockaddr_in *)address)->sin_port; + + addr4 = (struct sockaddr_in *)address; + port = addr4->sin_port; + + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) { + audit_net.dport = port; + audit_net.v4info.daddr = addr4->sin_addr.s_addr; + } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + audit_net.sport = port; + audit_net.v4info.saddr = addr4->sin_addr.s_addr; + } else { + WARN_ON_ONCE(1); + } break; + } #if IS_ENABLED(CONFIG_IPV6) - case AF_INET6: + case AF_INET6: { + const struct sockaddr_in6 *addr6; + if (addrlen < SIN6_LEN_RFC2133) return -EINVAL; - port = ((struct sockaddr_in6 *)address)->sin6_port; + + addr6 = (struct sockaddr_in6 *)address; + port = addr6->sin6_port; + + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) { + audit_net.dport = port; + audit_net.v6info.daddr = addr6->sin6_addr; + } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + audit_net.sport = port; + audit_net.v6info.saddr = addr6->sin6_addr; + } else { + WARN_ON_ONCE(1); + } break; + } #endif /* IS_ENABLED(CONFIG_IPV6) */ default: return 0; } - /* Specific AF_UNSPEC handling. */ - if (address->sa_family == AF_UNSPEC) { - /* - * Connecting to an address with AF_UNSPEC dissolves the TCP - * association, which have the same effect as closing the - * connection while retaining the socket object (i.e., the file - * descriptor). As for dropping privileges, closing - * connections is always allowed. - * - * For a TCP access control system, this request is legitimate. - * Let the network stack handle potential inconsistencies and - * return -EINVAL if needed. - */ - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) - return 0; - - /* - * For compatibility reason, accept AF_UNSPEC for bind - * accesses (mapped to AF_INET) only if the address is - * INADDR_ANY (cf. __inet_bind). Checking the address is - * required to not wrongfully return -EACCES instead of - * -EAFNOSUPPORT. - * - * We could return 0 and let the network stack handle these - * checks, but it is safer to return a proper error and test - * consistency thanks to kselftest. - */ - if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { - /* addrlen has already been checked for AF_UNSPEC. */ - const struct sockaddr_in *const sockaddr = - (struct sockaddr_in *)address; - - if (sock->sk->__sk_common.skc_family != AF_INET) - return -EINVAL; - - if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY)) - return -EAFNOSUPPORT; - } - } else { - /* - * Checks sa_family consistency to not wrongfully return - * -EACCES instead of -EINVAL. Valid sa_family changes are - * only (from AF_INET or AF_INET6) to AF_UNSPEC. - * - * We could return 0 and let the network stack handle this - * check, but it is safer to return a proper error and test - * consistency thanks to kselftest. - */ - if (address->sa_family != sock->sk->__sk_common.skc_family) - return -EINVAL; - } + /* + * Checks sa_family consistency to not wrongfully return + * -EACCES instead of -EINVAL. Valid sa_family changes are + * only (from AF_INET or AF_INET6) to AF_UNSPEC. + * + * We could return 0 and let the network stack handle this + * check, but it is safer to return a proper error and test + * consistency thanks to kselftest. + */ + if (address->sa_family != sock->sk->__sk_common.skc_family && + address->sa_family != AF_UNSPEC) + return -EINVAL; id.key.data = (__force uintptr_t)port; BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); - rule = landlock_find_rule(dom, id); - access_request = landlock_init_layer_masks( - dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT); - if (landlock_unmask_layers(rule, access_request, &layer_masks, - ARRAY_SIZE(layer_masks))) + rule = landlock_find_rule(subject->domain, id); + access_request = landlock_init_layer_masks(subject->domain, + access_request, &layer_masks, + LANDLOCK_KEY_NET_PORT); + if (!access_request) + return 0; + + if (landlock_unmask_layers(rule, &layer_masks)) return 0; + audit_net.family = address->sa_family; + landlock_log_denial(subject, + &(struct landlock_request){ + .type = LANDLOCK_REQUEST_NET_ACCESS, + .audit.type = LSM_AUDIT_DATA_NET, + .audit.u.net = &audit_net, + .access = access_request, + .layer_masks = &layer_masks, + }); return -EACCES; } static int hook_socket_bind(struct socket *const sock, struct sockaddr *const address, const int addrlen) { + access_mask_t access_request; + + if (sk_is_tcp(sock->sk)) + access_request = LANDLOCK_ACCESS_NET_BIND_TCP; + else + return 0; + return current_check_access_socket(sock, address, addrlen, - LANDLOCK_ACCESS_NET_BIND_TCP); + access_request); } static int hook_socket_connect(struct socket *const sock, struct sockaddr *const address, const int addrlen) { + access_mask_t access_request; + + if (sk_is_tcp(sock->sk)) + access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP; + else + return 0; + return current_check_access_socket(sock, address, addrlen, - LANDLOCK_ACCESS_NET_CONNECT_TCP); + access_request); } static struct security_hook_list landlock_hooks[] __ro_after_init = { diff --git a/security/landlock/object.c b/security/landlock/object.c index 1f50612f0185..0d6e159ef8b5 100644 --- a/security/landlock/object.c +++ b/security/landlock/object.c @@ -25,7 +25,7 @@ landlock_create_object(const struct landlock_object_underops *const underops, if (WARN_ON_ONCE(!underops || !underobj)) return ERR_PTR(-ENOENT); - new_object = kzalloc(sizeof(*new_object), GFP_KERNEL_ACCOUNT); + new_object = kzalloc_obj(*new_object, GFP_KERNEL_ACCOUNT); if (!new_object) return ERR_PTR(-ENOMEM); refcount_set(&new_object->usage, 1); diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 241ce44375b6..181df7736bb9 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -23,6 +23,7 @@ #include <linux/workqueue.h> #include "access.h" +#include "domain.h" #include "limits.h" #include "object.h" #include "ruleset.h" @@ -31,9 +32,8 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) { struct landlock_ruleset *new_ruleset; - new_ruleset = - kzalloc(struct_size(new_ruleset, access_masks, num_layers), - GFP_KERNEL_ACCOUNT); + new_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers, + GFP_KERNEL_ACCOUNT); if (!new_ruleset) return ERR_PTR(-ENOMEM); refcount_set(&new_ruleset->usage, 1); @@ -81,6 +81,10 @@ static void build_check_rule(void) .num_layers = ~0, }; + /* + * Checks that .num_layers is large enough for at least + * LANDLOCK_MAX_NUM_LAYERS layers. + */ BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS); } @@ -103,7 +107,7 @@ static bool is_object_pointer(const enum landlock_key_type key_type) static struct landlock_rule * create_rule(const struct landlock_id id, - const struct landlock_layer (*const layers)[], const u32 num_layers, + const struct landlock_layer (*layers)[], const u32 num_layers, const struct landlock_layer *const new_layer) { struct landlock_rule *new_rule; @@ -118,13 +122,13 @@ create_rule(const struct landlock_id id, } else { new_num_layers = num_layers; } - new_rule = kzalloc(struct_size(new_rule, layers, new_num_layers), - GFP_KERNEL_ACCOUNT); + new_rule = kzalloc_flex(*new_rule, layers, new_num_layers, + GFP_KERNEL_ACCOUNT); if (!new_rule) return ERR_PTR(-ENOMEM); RB_CLEAR_NODE(&new_rule->node); if (is_object_pointer(id.type)) { - /* This should be catched by insert_rule(). */ + /* This should have been caught by insert_rule(). */ WARN_ON_ONCE(!id.key.object); landlock_get_object(id.key.object); } @@ -197,10 +201,12 @@ static void build_check_ruleset(void) * When merging a ruleset in a domain, or copying a domain, @layers will be * added to @ruleset as new constraints, similarly to a boolean AND between * access rights. + * + * Return: 0 on success, -errno on failure. */ static int insert_rule(struct landlock_ruleset *const ruleset, const struct landlock_id id, - const struct landlock_layer (*const layers)[], + const struct landlock_layer (*layers)[], const size_t num_layers) { struct rb_node **walker_node; @@ -288,6 +294,10 @@ static void build_check_layer(void) .access = ~0, }; + /* + * Checks that .level and .access are large enough to contain their expected + * maximum values. + */ BUILD_BUG_ON(layer.level < LANDLOCK_MAX_NUM_LAYERS); BUILD_BUG_ON(layer.access < LANDLOCK_MASK_ACCESS_FS); } @@ -307,22 +317,6 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers)); } -static void get_hierarchy(struct landlock_hierarchy *const hierarchy) -{ - if (hierarchy) - refcount_inc(&hierarchy->usage); -} - -static void put_hierarchy(struct landlock_hierarchy *hierarchy) -{ - while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) { - const struct landlock_hierarchy *const freeme = hierarchy; - - hierarchy = hierarchy->parent; - kfree(freeme); - } -} - static int merge_tree(struct landlock_ruleset *const dst, struct landlock_ruleset *const src, const enum landlock_key_type key_type) @@ -477,7 +471,7 @@ static int inherit_ruleset(struct landlock_ruleset *const parent, err = -EINVAL; goto out_unlock; } - get_hierarchy(parent->hierarchy); + landlock_get_hierarchy(parent->hierarchy); child->hierarchy->parent = parent->hierarchy; out_unlock: @@ -501,7 +495,7 @@ static void free_ruleset(struct landlock_ruleset *const ruleset) free_rule(freeme, LANDLOCK_KEY_NET_PORT); #endif /* IS_ENABLED(CONFIG_INET) */ - put_hierarchy(ruleset->hierarchy); + landlock_put_hierarchy(ruleset->hierarchy); kfree(ruleset); } @@ -520,6 +514,7 @@ static void free_ruleset_work(struct work_struct *const work) free_ruleset(ruleset); } +/* Only called by hook_cred_free(). */ void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset) { if (ruleset && refcount_dec_and_test(&ruleset->usage)) { @@ -534,8 +529,11 @@ void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset) * @parent: Parent domain. * @ruleset: New ruleset to be merged. * - * Returns the intersection of @parent and @ruleset, or returns @parent if - * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty. + * The current task is requesting to be restricted. The subjective credentials + * must not be in an overridden state. cf. landlock_init_hierarchy_log(). + * + * Return: A new domain merging @parent and @ruleset on success, or ERR_PTR() + * on failure. If @parent is NULL, the new domain duplicates @ruleset. */ struct landlock_ruleset * landlock_merge_ruleset(struct landlock_ruleset *const parent, @@ -563,7 +561,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent, return new_dom; new_dom->hierarchy = - kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL_ACCOUNT); + kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT); if (!new_dom->hierarchy) return ERR_PTR(-ENOMEM); @@ -579,6 +577,10 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent, if (err) return ERR_PTR(err); + err = landlock_init_hierarchy_log(new_dom->hierarchy); + if (err) + return ERR_PTR(err); + return no_free_ptr(new_dom); } @@ -611,22 +613,24 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset, return NULL; } -/* - * @layer_masks is read and may be updated according to the access request and - * the matching rule. - * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks). +/** + * landlock_unmask_layers - Remove the access rights in @masks + * which are granted in @rule + * + * Updates the set of (per-layer) unfulfilled access rights @masks + * so that all the access rights granted in @rule are removed from it + * (because they are now fulfilled). * - * Returns true if the request is allowed (i.e. relevant layer masks for the - * request are empty). + * @rule: A rule that grants a set of access rights for each layer + * @masks: A matrix of unfulfilled access rights for each layer + * + * Return: True if the request is allowed (i.e. the access rights granted all + * remaining unfulfilled access rights and masks has no leftover set bits). */ bool landlock_unmask_layers(const struct landlock_rule *const rule, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[], - const size_t masks_array_size) + struct layer_access_masks *masks) { - size_t layer_level; - - if (!access_request || !layer_masks) + if (!masks) return true; if (!rule) return false; @@ -641,28 +645,18 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule, * by only one rule, but by the union (binary OR) of multiple rules. * E.g. /a/b <execute> + /a <read> => /a/b <execute + read> */ - for (layer_level = 0; layer_level < rule->num_layers; layer_level++) { - const struct landlock_layer *const layer = - &rule->layers[layer_level]; - const layer_mask_t layer_bit = BIT_ULL(layer->level - 1); - const unsigned long access_req = access_request; - unsigned long access_bit; - bool is_empty; + for (size_t i = 0; i < rule->num_layers; i++) { + const struct landlock_layer *const layer = &rule->layers[i]; - /* - * Records in @layer_masks which layer grants access to each - * requested access. - */ - is_empty = true; - for_each_set_bit(access_bit, &access_req, masks_array_size) { - if (layer->access & BIT_ULL(access_bit)) - (*layer_masks)[access_bit] &= ~layer_bit; - is_empty = is_empty && !(*layer_masks)[access_bit]; - } - if (is_empty) - return true; + /* Clear the bits where the layer in the rule grants access. */ + masks->access[layer->level - 1] &= ~layer->access; + } + + for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) { + if (masks->access[i]) + return false; } - return false; + return true; } typedef access_mask_t @@ -672,38 +666,34 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset, /** * landlock_init_layer_masks - Initialize layer masks from an access request * - * Populates @layer_masks such that for each access right in @access_request, + * Populates @masks such that for each access right in @access_request, * the bits for all the layers are set where this access right is handled. * * @domain: The domain that defines the current restrictions. * @access_request: The requested access rights to check. - * @layer_masks: It must contain %LANDLOCK_NUM_ACCESS_FS or - * %LANDLOCK_NUM_ACCESS_NET elements according to @key_type. + * @masks: Layer access masks to populate. * @key_type: The key type to switch between access masks of different types. * - * Returns: An access mask where each access right bit is set which is handled + * Return: An access mask where each access right bit is set which is handled * in any of the active layers in @domain. */ access_mask_t landlock_init_layer_masks(const struct landlock_ruleset *const domain, const access_mask_t access_request, - layer_mask_t (*const layer_masks)[], + struct layer_access_masks *const masks, const enum landlock_key_type key_type) { access_mask_t handled_accesses = 0; - size_t layer_level, num_access; get_access_mask_t *get_access_mask; switch (key_type) { case LANDLOCK_KEY_INODE: get_access_mask = landlock_get_fs_access_mask; - num_access = LANDLOCK_NUM_ACCESS_FS; break; #if IS_ENABLED(CONFIG_INET) case LANDLOCK_KEY_NET_PORT: get_access_mask = landlock_get_net_access_mask; - num_access = LANDLOCK_NUM_ACCESS_NET; break; #endif /* IS_ENABLED(CONFIG_INET) */ @@ -712,27 +702,18 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain, return 0; } - memset(layer_masks, 0, - array_size(sizeof((*layer_masks)[0]), num_access)); - /* An empty access request can happen because of O_WRONLY | O_RDWR. */ if (!access_request) return 0; - /* Saves all handled accesses per layer. */ - for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { - const unsigned long access_req = access_request; - const access_mask_t access_mask = - get_access_mask(domain, layer_level); - unsigned long access_bit; - - for_each_set_bit(access_bit, &access_req, num_access) { - if (BIT_ULL(access_bit) & access_mask) { - (*layer_masks)[access_bit] |= - BIT_ULL(layer_level); - handled_accesses |= BIT_ULL(access_bit); - } - } + for (size_t i = 0; i < domain->num_layers; i++) { + const access_mask_t handled = get_access_mask(domain, i); + + masks->access[i] = access_request & handled; + handled_accesses |= masks->access[i]; } + for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->access); i++) + masks->access[i] = 0; + return handled_accesses; } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 52f4f0af6ab0..889f4b30301a 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -20,12 +20,14 @@ #include "limits.h" #include "object.h" +struct landlock_hierarchy; + /** * struct landlock_layer - Access rights for a given layer */ struct landlock_layer { /** - * @level: Position of this layer in the layer stack. + * @level: Position of this layer in the layer stack. Starts from 1. */ u16 level; /** @@ -109,22 +111,6 @@ struct landlock_rule { }; /** - * struct landlock_hierarchy - Node in a ruleset hierarchy - */ -struct landlock_hierarchy { - /** - * @parent: Pointer to the parent node, or NULL if it is a root - * Landlock domain. - */ - struct landlock_hierarchy *parent; - /** - * @usage: Number of potential children domains plus their parent - * domain. - */ - refcount_t usage; -}; - -/** * struct landlock_ruleset - Landlock ruleset * * This data structure must contain unique entries, be updatable, and quick to @@ -238,7 +224,7 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) * * @domain: Landlock ruleset (used as a domain) * - * Returns: an access_masks result of the OR of all the domain's access masks. + * Return: An access_masks result of the OR of all the domain's access masks. */ static inline struct access_masks landlock_union_access_masks(const struct landlock_ruleset *const domain) @@ -257,36 +243,6 @@ landlock_union_access_masks(const struct landlock_ruleset *const domain) return matches.masks; } -/** - * landlock_get_applicable_domain - Return @domain if it applies to (handles) - * at least one of the access rights specified - * in @masks - * - * @domain: Landlock ruleset (used as a domain) - * @masks: access masks - * - * Returns: @domain if any access rights specified in @masks is handled, or - * NULL otherwise. - */ -static inline const struct landlock_ruleset * -landlock_get_applicable_domain(const struct landlock_ruleset *const domain, - const struct access_masks masks) -{ - const union access_masks_all masks_all = { - .masks = masks, - }; - union access_masks_all merge = {}; - - if (!domain) - return NULL; - - merge.masks = landlock_union_access_masks(domain); - if (merge.all & masks_all.all) - return domain; - - return NULL; -} - static inline void landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset, const access_mask_t fs_access_mask, @@ -346,14 +302,12 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset, } bool landlock_unmask_layers(const struct landlock_rule *const rule, - const access_mask_t access_request, - layer_mask_t (*const layer_masks)[], - const size_t masks_array_size); + struct layer_access_masks *masks); access_mask_t landlock_init_layer_masks(const struct landlock_ruleset *const domain, const access_mask_t access_request, - layer_mask_t (*const layer_masks)[], + struct layer_access_masks *masks, const enum landlock_key_type key_type); #endif /* _SECURITY_LANDLOCK_RULESET_H */ diff --git a/security/landlock/setup.c b/security/landlock/setup.c index 28519a45b11f..47dac1736f10 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -6,19 +6,27 @@ * Copyright © 2018-2020 ANSSI */ +#include <linux/bits.h> #include <linux/init.h> #include <linux/lsm_hooks.h> #include <uapi/linux/lsm.h> #include "common.h" #include "cred.h" +#include "errata.h" #include "fs.h" +#include "id.h" #include "net.h" #include "setup.h" #include "task.h" bool landlock_initialized __ro_after_init = false; +const struct lsm_id landlock_lsmid = { + .name = LANDLOCK_NAME, + .id = LSM_ID_LANDLOCK, +}; + struct lsm_blob_sizes landlock_blob_sizes __ro_after_init = { .lbs_cred = sizeof(struct landlock_cred_security), .lbs_file = sizeof(struct landlock_file_security), @@ -26,24 +34,48 @@ struct lsm_blob_sizes landlock_blob_sizes __ro_after_init = { .lbs_superblock = sizeof(struct landlock_superblock_security), }; -const struct lsm_id landlock_lsmid = { - .name = LANDLOCK_NAME, - .id = LSM_ID_LANDLOCK, -}; +int landlock_errata __ro_after_init; + +static void __init compute_errata(void) +{ + size_t i; + +#ifndef __has_include + /* + * This is a safeguard to make sure the compiler implements + * __has_include (see errata.h). + */ + WARN_ON_ONCE(1); + return; +#endif + + for (i = 0; landlock_errata_init[i].number; i++) { + const int prev_errata = landlock_errata; + + if (WARN_ON_ONCE(landlock_errata_init[i].abi > + landlock_abi_version)) + continue; + + landlock_errata |= BIT(landlock_errata_init[i].number - 1); + WARN_ON_ONCE(prev_errata == landlock_errata); + } +} static int __init landlock_init(void) { + compute_errata(); landlock_add_cred_hooks(); landlock_add_task_hooks(); landlock_add_fs_hooks(); landlock_add_net_hooks(); + landlock_init_id(); landlock_initialized = true; pr_info("Up and running.\n"); return 0; } DEFINE_LSM(LANDLOCK_NAME) = { - .name = LANDLOCK_NAME, + .id = &landlock_lsmid, .init = landlock_init, .blobs = &landlock_blob_sizes, }; diff --git a/security/landlock/setup.h b/security/landlock/setup.h index c4252d46d49d..fca307c35fee 100644 --- a/security/landlock/setup.h +++ b/security/landlock/setup.h @@ -11,7 +11,10 @@ #include <linux/lsm_hooks.h> +extern const int landlock_abi_version; + extern bool landlock_initialized; +extern int landlock_errata; extern struct lsm_blob_sizes landlock_blob_sizes; extern const struct lsm_id landlock_lsmid; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index a9760d252fc2..accfd2e5a0cd 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -1,13 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Landlock LSM - System call implementations and user space interfaces + * Landlock - System call implementations and user space interfaces * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2021-2025 Microsoft Corporation */ #include <asm/current.h> #include <linux/anon_inodes.h> +#include <linux/bitops.h> #include <linux/build_bug.h> #include <linux/capability.h> #include <linux/cleanup.h> @@ -28,11 +30,13 @@ #include <uapi/linux/landlock.h> #include "cred.h" +#include "domain.h" #include "fs.h" #include "limits.h" #include "net.h" #include "ruleset.h" #include "setup.h" +#include "tsync.h" static bool is_initialized(void) { @@ -56,6 +60,8 @@ static bool is_initialized(void) * @ksize_min: Minimal required size to be copied. * @src: User space pointer or NULL. * @usize: (Alleged) size of the data pointed to by @src. + * + * Return: 0 on success, -errno on failure. */ static __always_inline int copy_min_struct_from_user(void *const dst, const size_t ksize, @@ -151,7 +157,16 @@ static const struct file_operations ruleset_fops = { .write = fop_dummy_write, }; -#define LANDLOCK_ABI_VERSION 6 +/* + * The Landlock ABI version should be incremented for each new Landlock-related + * user space visible change (e.g. Landlock syscalls). This version should + * only be incremented once per Linux release. When incrementing, the date in + * Documentation/userspace-api/landlock.rst should be updated to reflect the + * UAPI change. + * If the change involves a fix that requires userspace awareness, also update + * the errata documentation in Documentation/userspace-api/landlock.rst . + */ +const int landlock_abi_version = 9; /** * sys_landlock_create_ruleset - Create a new ruleset @@ -160,22 +175,30 @@ static const struct file_operations ruleset_fops = { * the new ruleset. * @size: Size of the pointed &struct landlock_ruleset_attr (needed for * backward and forward compatibility). - * @flags: Supported value: %LANDLOCK_CREATE_RULESET_VERSION. + * @flags: Supported values: * - * This system call enables to create a new Landlock ruleset, and returns the - * related file descriptor on success. + * - %LANDLOCK_CREATE_RULESET_VERSION + * - %LANDLOCK_CREATE_RULESET_ERRATA * - * If @flags is %LANDLOCK_CREATE_RULESET_VERSION and @attr is NULL and @size is - * 0, then the returned value is the highest supported Landlock ABI version - * (starting at 1). + * This system call enables to create a new Landlock ruleset. * - * Possible returned errors are: + * If %LANDLOCK_CREATE_RULESET_VERSION or %LANDLOCK_CREATE_RULESET_ERRATA is + * set, then @attr must be NULL and @size must be 0. + * + * Return: The ruleset file descriptor on success, the Landlock ABI version if + * %LANDLOCK_CREATE_RULESET_VERSION is set, the errata value if + * %LANDLOCK_CREATE_RULESET_ERRATA is set, or -errno on failure. Possible + * returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; - * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small @size; + * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small + * @size; * - %E2BIG: @attr or @size inconsistencies; * - %EFAULT: @attr or @size inconsistencies; * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. + * + * .. kernel-doc:: include/uapi/linux/landlock.h + * :identifiers: landlock_create_ruleset_flags */ SYSCALL_DEFINE3(landlock_create_ruleset, const struct landlock_ruleset_attr __user *const, attr, @@ -192,9 +215,15 @@ SYSCALL_DEFINE3(landlock_create_ruleset, return -EOPNOTSUPP; if (flags) { - if ((flags == LANDLOCK_CREATE_RULESET_VERSION) && !attr && - !size) - return LANDLOCK_ABI_VERSION; + if (attr || size) + return -EINVAL; + + if (flags == LANDLOCK_CREATE_RULESET_VERSION) + return landlock_abi_version; + + if (flags == LANDLOCK_CREATE_RULESET_ERRATA) + return landlock_errata; + return -EINVAL; } @@ -282,7 +311,6 @@ static int get_path_from_fd(const s32 fd, struct path *const path) if ((fd_file(f)->f_op == &ruleset_fops) || (fd_file(f)->f_path.mnt->mnt_flags & MNT_INTERNAL) || (fd_file(f)->f_path.dentry->d_sb->s_flags & SB_NOUSER) || - d_is_negative(fd_file(f)->f_path.dentry) || IS_PRIVATE(d_backing_inode(fd_file(f)->f_path.dentry))) return -EBADFD; @@ -375,7 +403,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, * This system call enables to define a new rule and add it to an existing * ruleset. * - * Possible returned errors are: + * Return: 0 on success, or -errno on failure. Possible returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not @@ -429,17 +457,22 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, * sys_landlock_restrict_self - Enforce a ruleset on the calling thread * * @ruleset_fd: File descriptor tied to the ruleset to merge with the target. - * @flags: Must be 0. + * @flags: Supported values: * - * This system call enables to enforce a Landlock ruleset on the current - * thread. Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its + * - %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF + * - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON + * - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF + * - %LANDLOCK_RESTRICT_SELF_TSYNC + * + * This system call enforces a Landlock ruleset on the current thread. + * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its * namespace or is running with no_new_privs. This avoids scenarios where * unprivileged tasks can affect the behavior of privileged children. * - * Possible returned errors are: + * Return: 0 on success, or -errno on failure. Possible returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; - * - %EINVAL: @flags is not 0. + * - %EINVAL: @flags contains an unknown bit. * - %EBADF: @ruleset_fd is not a file descriptor for the current thread; * - %EBADFD: @ruleset_fd is not a ruleset file descriptor; * - %EPERM: @ruleset_fd has no read access to the underlying ruleset, or the @@ -447,14 +480,18 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, * %CAP_SYS_ADMIN in its namespace. * - %E2BIG: The maximum number of stacked rulesets is reached for the current * thread. + * + * .. kernel-doc:: include/uapi/linux/landlock.h + * :identifiers: landlock_restrict_self_flags */ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, flags) { - struct landlock_ruleset *new_dom, - *ruleset __free(landlock_put_ruleset) = NULL; + struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL; struct cred *new_cred; struct landlock_cred_security *new_llcred; + bool __maybe_unused log_same_exec, log_new_exec, log_subdomains, + prev_log_subdomains; if (!is_initialized()) return -EOPNOTSUPP; @@ -467,14 +504,31 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) return -EPERM; - /* No flag for now. */ - if (flags) + if ((flags | LANDLOCK_MASK_RESTRICT_SELF) != + LANDLOCK_MASK_RESTRICT_SELF) return -EINVAL; - /* Gets and checks the ruleset. */ - ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ); - if (IS_ERR(ruleset)) - return PTR_ERR(ruleset); + /* Translates "off" flag to boolean. */ + log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF); + /* Translates "on" flag to boolean. */ + log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON); + /* Translates "off" flag to boolean. */ + log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF); + + /* + * It is allowed to set LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with + * -1 as ruleset_fd, optionally combined with + * LANDLOCK_RESTRICT_SELF_TSYNC to propagate this configuration to all + * threads. No other flag must be set. + */ + if (!(ruleset_fd == -1 && + (flags & ~LANDLOCK_RESTRICT_SELF_TSYNC) == + LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) { + /* Gets and checks the ruleset. */ + ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ); + if (IS_ERR(ruleset)) + return PTR_ERR(ruleset); + } /* Prepares new credentials. */ new_cred = prepare_creds(); @@ -483,18 +537,56 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, new_llcred = landlock_cred(new_cred); +#ifdef CONFIG_AUDIT + prev_log_subdomains = !new_llcred->log_subdomains_off; + new_llcred->log_subdomains_off = !prev_log_subdomains || + !log_subdomains; +#endif /* CONFIG_AUDIT */ + /* - * There is no possible race condition while copying and manipulating - * the current credentials because they are dedicated per thread. + * The only case when a ruleset may not be set is if + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF is set (optionally with + * LANDLOCK_RESTRICT_SELF_TSYNC) and ruleset_fd is -1. We could + * optimize this case by not calling commit_creds() if this flag was + * already set, but it is not worth the complexity. */ - new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset); - if (IS_ERR(new_dom)) { - abort_creds(new_cred); - return PTR_ERR(new_dom); + if (ruleset) { + /* + * There is no possible race condition while copying and + * manipulating the current credentials because they are + * dedicated per thread. + */ + struct landlock_ruleset *const new_dom = + landlock_merge_ruleset(new_llcred->domain, ruleset); + if (IS_ERR(new_dom)) { + abort_creds(new_cred); + return PTR_ERR(new_dom); + } + +#ifdef CONFIG_AUDIT + new_dom->hierarchy->log_same_exec = log_same_exec; + new_dom->hierarchy->log_new_exec = log_new_exec; + if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) + new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED; +#endif /* CONFIG_AUDIT */ + + /* Replaces the old (prepared) domain. */ + landlock_put_ruleset(new_llcred->domain); + new_llcred->domain = new_dom; + +#ifdef CONFIG_AUDIT + new_llcred->domain_exec |= BIT(new_dom->num_layers - 1); +#endif /* CONFIG_AUDIT */ + } + + if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) { + const int err = landlock_restrict_sibling_threads( + current_cred(), new_cred); + if (err) { + abort_creds(new_cred); + return err; + } } - /* Replaces the old (prepared) domain. */ - landlock_put_ruleset(new_llcred->domain); - new_llcred->domain = new_dom; return commit_creds(new_cred); } diff --git a/security/landlock/task.c b/security/landlock/task.c index dc7dab78392e..6d46042132ce 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -1,23 +1,29 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Landlock LSM - Ptrace hooks + * Landlock - Ptrace and scope hooks * * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2019-2020 ANSSI + * Copyright © 2024-2025 Microsoft Corporation */ #include <asm/current.h> +#include <linux/cleanup.h> #include <linux/cred.h> #include <linux/errno.h> #include <linux/kernel.h> +#include <linux/lsm_audit.h> #include <linux/lsm_hooks.h> #include <linux/rcupdate.h> #include <linux/sched.h> +#include <linux/sched/signal.h> #include <net/af_unix.h> #include <net/sock.h> +#include "audit.h" #include "common.h" #include "cred.h" +#include "domain.h" #include "fs.h" #include "ruleset.h" #include "setup.h" @@ -31,47 +37,38 @@ * * Checks if the @parent domain is less or equal to (i.e. an ancestor, which * means a subset of) the @child domain. + * + * Return: True if @parent is an ancestor of or equal to @child, false + * otherwise. */ static bool domain_scope_le(const struct landlock_ruleset *const parent, const struct landlock_ruleset *const child) { const struct landlock_hierarchy *walker; + /* Quick return for non-landlocked tasks. */ if (!parent) return true; + if (!child) return false; + for (walker = child->hierarchy; walker; walker = walker->parent) { if (walker == parent->hierarchy) /* @parent is in the scoped hierarchy of @child. */ return true; } + /* There is no relationship between @parent and @child. */ return false; } -static bool task_is_scoped(const struct task_struct *const parent, - const struct task_struct *const child) +static int domain_ptrace(const struct landlock_ruleset *const parent, + const struct landlock_ruleset *const child) { - bool is_scoped; - const struct landlock_ruleset *dom_parent, *dom_child; - - rcu_read_lock(); - dom_parent = landlock_get_task_domain(parent); - dom_child = landlock_get_task_domain(child); - is_scoped = domain_scope_le(dom_parent, dom_child); - rcu_read_unlock(); - return is_scoped; -} - -static int task_ptrace(const struct task_struct *const parent, - const struct task_struct *const child) -{ - /* Quick return for non-landlocked tasks. */ - if (!landlocked(parent)) - return 0; - if (task_is_scoped(parent, child)) + if (domain_scope_le(parent, child)) return 0; + return -EPERM; } @@ -85,13 +82,44 @@ static int task_ptrace(const struct task_struct *const parent, * If the current task has Landlock rules, then the child must have at least * the same rules. Else denied. * - * Determines whether a process may access another, returning 0 if permission - * granted, -errno if denied. + * Return: 0 if permission is granted, -errno if denied. */ static int hook_ptrace_access_check(struct task_struct *const child, const unsigned int mode) { - return task_ptrace(current, child); + const struct landlock_cred_security *parent_subject; + int err; + + /* Quick return for non-landlocked tasks. */ + parent_subject = landlock_cred(current_cred()); + if (!parent_subject) + return 0; + + scoped_guard(rcu) + { + const struct landlock_ruleset *const child_dom = + landlock_get_task_domain(child); + err = domain_ptrace(parent_subject->domain, child_dom); + } + + if (!err) + return 0; + + /* + * For the ptrace_access_check case, we log the current/parent domain + * and the child task. + */ + if (!(mode & PTRACE_MODE_NOAUDIT)) + landlock_log_denial(parent_subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_PTRACE, + .audit = { + .type = LSM_AUDIT_DATA_TASK, + .u.tsk = child, + }, + .layer_plus_one = parent_subject->domain->num_layers, + }); + + return err; } /** @@ -103,31 +131,58 @@ static int hook_ptrace_access_check(struct task_struct *const child, * If the parent has Landlock rules, then the current task must have the same * or more rules. Else denied. * - * Determines whether the nominated task is permitted to trace the current - * process, returning 0 if permission is granted, -errno if denied. + * Return: 0 if permission is granted, -errno if denied. */ static int hook_ptrace_traceme(struct task_struct *const parent) { - return task_ptrace(parent, current); + const struct landlock_cred_security *parent_subject; + const struct landlock_ruleset *child_dom; + int err; + + child_dom = landlock_get_current_domain(); + + guard(rcu)(); + parent_subject = landlock_cred(__task_cred(parent)); + err = domain_ptrace(parent_subject->domain, child_dom); + + if (!err) + return 0; + + /* + * For the ptrace_traceme case, we log the domain which is the cause of + * the denial, which means the parent domain instead of the current + * domain. This may look unusual because the ptrace_traceme action is a + * request to be traced, but the semantic is consistent with + * hook_ptrace_access_check(). + */ + landlock_log_denial(parent_subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_PTRACE, + .audit = { + .type = LSM_AUDIT_DATA_TASK, + .u.tsk = current, + }, + .layer_plus_one = parent_subject->domain->num_layers, + }); + return err; } /** - * domain_is_scoped - Checks if the client domain is scoped in the same - * domain as the server. + * domain_is_scoped - Check if an interaction from a client/sender to a + * server/receiver should be restricted based on scope controls. * * @client: IPC sender domain. * @server: IPC receiver domain. * @scope: The scope restriction criteria. * - * Returns: True if the @client domain is scoped to access the @server, - * unless the @server is also scoped in the same domain as @client. + * Return: True if @server is in a different domain from @client and @client + * is scoped to access @server (i.e. access should be denied), false otherwise. */ static bool domain_is_scoped(const struct landlock_ruleset *const client, const struct landlock_ruleset *const server, access_mask_t scope) { int client_layer, server_layer; - struct landlock_hierarchy *client_walker, *server_walker; + const struct landlock_hierarchy *client_walker, *server_walker; /* Quick return if client has no domain */ if (WARN_ON_ONCE(!client)) @@ -136,10 +191,13 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client, client_layer = client->num_layers - 1; client_walker = client->hierarchy; /* - * client_layer must be a signed integer with greater capacity - * than client->num_layers to ensure the following loop stops. + * client_layer must be able to represent all numbers from + * LANDLOCK_MAX_NUM_LAYERS - 1 to -1 for the loop below to terminate. + * (It must be large enough, and it must be signed.) */ - BUILD_BUG_ON(sizeof(client_layer) > sizeof(client->num_layers)); + BUILD_BUG_ON(!is_signed_type(typeof(client_layer))); + BUILD_BUG_ON(LANDLOCK_MAX_NUM_LAYERS - 1 > + type_max(typeof(client_layer))); server_layer = server ? (server->num_layers - 1) : -1; server_walker = server ? server->hierarchy : NULL; @@ -212,28 +270,43 @@ static int hook_unix_stream_connect(struct sock *const sock, struct sock *const other, struct sock *const newsk) { - const struct landlock_ruleset *const dom = - landlock_get_applicable_domain(landlock_get_current_domain(), - unix_scope); + size_t handle_layer; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), unix_scope, + &handle_layer); /* Quick return for non-landlocked tasks. */ - if (!dom) + if (!subject) + return 0; + + if (!is_abstract_socket(other)) return 0; - if (is_abstract_socket(other) && sock_is_scoped(other, dom)) - return -EPERM; + if (!sock_is_scoped(other, subject->domain)) + return 0; - return 0; + landlock_log_denial(subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, + .audit = { + .type = LSM_AUDIT_DATA_NET, + .u.net = &(struct lsm_network_audit) { + .sk = other, + }, + }, + .layer_plus_one = handle_layer + 1, + }); + return -EPERM; } static int hook_unix_may_send(struct socket *const sock, struct socket *const other) { - const struct landlock_ruleset *const dom = - landlock_get_applicable_domain(landlock_get_current_domain(), - unix_scope); + size_t handle_layer; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), unix_scope, + &handle_layer); - if (!dom) + if (!subject) return 0; /* @@ -243,10 +316,23 @@ static int hook_unix_may_send(struct socket *const sock, if (unix_peer(sock->sk) == other->sk) return 0; - if (is_abstract_socket(other->sk) && sock_is_scoped(other->sk, dom)) - return -EPERM; + if (!is_abstract_socket(other->sk)) + return 0; + + if (!sock_is_scoped(other->sk, subject->domain)) + return 0; - return 0; + landlock_log_denial(subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, + .audit = { + .type = LSM_AUDIT_DATA_NET, + .u.net = &(struct lsm_network_audit) { + .sk = other->sk, + }, + }, + .layer_plus_one = handle_layer + 1, + }); + return -EPERM; } static const struct access_masks signal_scope = { @@ -255,56 +341,97 @@ static const struct access_masks signal_scope = { static int hook_task_kill(struct task_struct *const p, struct kernel_siginfo *const info, const int sig, - const struct cred *const cred) + const struct cred *cred) { bool is_scoped; - const struct landlock_ruleset *dom; - - if (cred) { - /* Dealing with USB IO. */ - dom = landlock_cred(cred)->domain; - } else { - dom = landlock_get_current_domain(); + size_t handle_layer; + const struct landlock_cred_security *subject; + + if (!cred) { + /* + * Always allow sending signals between threads of the same process. + * This is required for process credential changes by the Native POSIX + * Threads Library and implemented by the set*id(2) wrappers and + * libcap(3) with tgkill(2). See nptl(7) and libpsx(3). + * + * This exception is similar to the __ptrace_may_access() one. + */ + if (same_thread_group(p, current)) + return 0; + + /* Not dealing with USB IO. */ + cred = current_cred(); } - dom = landlock_get_applicable_domain(dom, signal_scope); + + subject = landlock_get_applicable_subject(cred, signal_scope, + &handle_layer); /* Quick return for non-landlocked tasks. */ - if (!dom) + if (!subject) return 0; - rcu_read_lock(); - is_scoped = domain_is_scoped(dom, landlock_get_task_domain(p), - LANDLOCK_SCOPE_SIGNAL); - rcu_read_unlock(); - if (is_scoped) - return -EPERM; + scoped_guard(rcu) + { + is_scoped = domain_is_scoped(subject->domain, + landlock_get_task_domain(p), + signal_scope.scope); + } + + if (!is_scoped) + return 0; - return 0; + landlock_log_denial(subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_SCOPE_SIGNAL, + .audit = { + .type = LSM_AUDIT_DATA_TASK, + .u.tsk = p, + }, + .layer_plus_one = handle_layer + 1, + }); + return -EPERM; } static int hook_file_send_sigiotask(struct task_struct *tsk, struct fown_struct *fown, int signum) { - const struct landlock_ruleset *dom; + const struct landlock_cred_security *subject; bool is_scoped = false; /* Lock already held by send_sigio() and send_sigurg(). */ lockdep_assert_held(&fown->lock); - dom = landlock_get_applicable_domain( - landlock_file(fown->file)->fown_domain, signal_scope); + subject = &landlock_file(fown->file)->fown_subject; - /* Quick return for unowned socket. */ - if (!dom) + /* + * Quick return for unowned socket. + * + * subject->domain has already been filtered when saved by + * hook_file_set_fowner(), so there is no need to call + * landlock_get_applicable_subject() here. + */ + if (!subject->domain) return 0; - rcu_read_lock(); - is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk), - LANDLOCK_SCOPE_SIGNAL); - rcu_read_unlock(); - if (is_scoped) - return -EPERM; + scoped_guard(rcu) + { + is_scoped = domain_is_scoped(subject->domain, + landlock_get_task_domain(tsk), + signal_scope.scope); + } + + if (!is_scoped) + return 0; - return 0; + landlock_log_denial(subject, &(struct landlock_request) { + .type = LANDLOCK_REQUEST_SCOPE_SIGNAL, + .audit = { + .type = LSM_AUDIT_DATA_TASK, + .u.tsk = tsk, + }, +#ifdef CONFIG_AUDIT + .layer_plus_one = landlock_file(fown->file)->fown_layer + 1, +#endif /* CONFIG_AUDIT */ + }); + return -EPERM; } static struct security_hook_list landlock_hooks[] __ro_after_init = { diff --git a/security/landlock/tsync.c b/security/landlock/tsync.c new file mode 100644 index 000000000000..c5730bbd9ed3 --- /dev/null +++ b/security/landlock/tsync.c @@ -0,0 +1,619 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Cross-thread ruleset enforcement + * + * Copyright © 2025 Google LLC + */ + +#include <linux/atomic.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/cred.h> +#include <linux/errno.h> +#include <linux/overflow.h> +#include <linux/rcupdate.h> +#include <linux/sched.h> +#include <linux/sched/signal.h> +#include <linux/sched/task.h> +#include <linux/slab.h> +#include <linux/task_work.h> + +#include "cred.h" +#include "tsync.h" + +/* + * Shared state between multiple threads which are enforcing Landlock rulesets + * in lockstep with each other. + */ +struct tsync_shared_context { + /* The old and tentative new creds of the calling thread. */ + const struct cred *old_cred; + const struct cred *new_cred; + + /* True if sibling tasks need to set the no_new_privs flag. */ + bool set_no_new_privs; + + /* An error encountered in preparation step, or 0. */ + atomic_t preparation_error; + + /* + * Barrier after preparation step in restrict_one_thread. + * The calling thread waits for completion. + * + * Re-initialized on every round of looking for newly spawned threads. + */ + atomic_t num_preparing; + struct completion all_prepared; + + /* Sibling threads wait for completion. */ + struct completion ready_to_commit; + + /* + * Barrier after commit step (used by syscall impl to wait for + * completion). + */ + atomic_t num_unfinished; + struct completion all_finished; +}; + +struct tsync_work { + struct callback_head work; + struct task_struct *task; + struct tsync_shared_context *shared_ctx; +}; + +/* + * restrict_one_thread - update a thread's Landlock domain in lockstep with the + * other threads in the same process + * + * When this is run, the same function gets run in all other threads in the same + * process (except for the calling thread which called landlock_restrict_self). + * The concurrently running invocations of restrict_one_thread coordinate + * through the shared ctx object to do their work in lockstep to implement + * all-or-nothing semantics for enforcing the new Landlock domain. + * + * Afterwards, depending on the presence of an error, all threads either commit + * or abort the prepared credentials. The commit operation can not fail any + * more. + */ +static void restrict_one_thread(struct tsync_shared_context *ctx) +{ + int err; + struct cred *cred = NULL; + + if (current_cred() == ctx->old_cred) { + /* + * Switch out old_cred with new_cred, if possible. + * + * In the common case, where all threads initially point to the + * same struct cred, this optimization avoids creating separate + * redundant credentials objects for each, which would all have + * the same contents. + * + * Note: We are intentionally dropping the const qualifier + * here, because it is required by commit_creds() and + * abort_creds(). + */ + cred = (struct cred *)get_cred(ctx->new_cred); + } else { + /* Else, prepare new creds and populate them. */ + cred = prepare_creds(); + + if (!cred) { + atomic_set(&ctx->preparation_error, -ENOMEM); + + /* + * Even on error, we need to adhere to the protocol and + * coordinate with concurrently running invocations. + */ + if (atomic_dec_return(&ctx->num_preparing) == 0) + complete_all(&ctx->all_prepared); + + goto out; + } + + landlock_cred_copy(landlock_cred(cred), + landlock_cred(ctx->new_cred)); + } + + /* + * Barrier: Wait until all threads are done preparing. + * After this point, we can have no more failures. + */ + if (atomic_dec_return(&ctx->num_preparing) == 0) + complete_all(&ctx->all_prepared); + + /* + * Wait for signal from calling thread that it's safe to read the + * preparation error now and we are ready to commit (or abort). + */ + wait_for_completion(&ctx->ready_to_commit); + + /* Abort the commit if any of the other threads had an error. */ + err = atomic_read(&ctx->preparation_error); + if (err) { + abort_creds(cred); + goto out; + } + + /* + * Make sure that all sibling tasks fulfill the no_new_privs + * prerequisite. (This is in line with Seccomp's + * SECCOMP_FILTER_FLAG_TSYNC logic in kernel/seccomp.c) + */ + if (ctx->set_no_new_privs) + task_set_no_new_privs(current); + + commit_creds(cred); + +out: + /* Notify the calling thread once all threads are done */ + if (atomic_dec_return(&ctx->num_unfinished) == 0) + complete_all(&ctx->all_finished); +} + +/* + * restrict_one_thread_callback - task_work callback for restricting a thread + * + * Calls restrict_one_thread with the struct landlock_shared_tsync_context. + */ +static void restrict_one_thread_callback(struct callback_head *work) +{ + struct tsync_work *ctx = container_of(work, struct tsync_work, work); + + restrict_one_thread(ctx->shared_ctx); +} + +/* + * struct tsync_works - a growable array of per-task contexts + * + * The zero-initialized struct represents the empty array. + */ +struct tsync_works { + struct tsync_work **works; + size_t size; + size_t capacity; +}; + +/* + * tsync_works_provide - provides a preallocated tsync_work for the given task + * + * This also stores a task pointer in the context and increments the reference + * count of the task. + * + * This function may fail in the case where we did not preallocate sufficient + * capacity. This can legitimately happen if new threads get started after we + * grew the capacity. + * + * Return: A pointer to the preallocated context struct with task filled in, or + * NULL if preallocated context structs ran out. + */ +static struct tsync_work *tsync_works_provide(struct tsync_works *s, + struct task_struct *task) +{ + struct tsync_work *ctx; + + if (s->size >= s->capacity) + return NULL; + + ctx = s->works[s->size]; + s->size++; + + ctx->task = get_task_struct(task); + return ctx; +} + +/** + * tsync_works_trim - Put the last tsync_work element + * + * @s: TSYNC works to trim. + * + * Put the last task and decrement the size of @s. + * + * This helper does not cancel a running task, but just reset the last element + * to zero. + */ +static void tsync_works_trim(struct tsync_works *s) +{ + struct tsync_work *ctx; + + if (WARN_ON_ONCE(s->size <= 0)) + return; + + ctx = s->works[s->size - 1]; + + /* + * For consistency, remove the task from ctx so that it does not look + * like we handed it a task_work. + */ + put_task_struct(ctx->task); + *ctx = (typeof(*ctx)){}; + + /* + * Cancel the tsync_works_provide() change to recycle the reserved + * memory for the next thread, if any. This also ensures that + * cancel_tsync_works() and tsync_works_release() do not see any NULL + * task pointers. + */ + s->size--; +} + +/* + * tsync_works_grow_by - preallocates space for n more contexts in s + * + * On a successful return, the subsequent n calls to tsync_works_provide() are + * guaranteed to succeed. (size + n <= capacity) + * + * Return: 0 if sufficient space for n more elements could be provided, -ENOMEM + * on allocation errors, -EOVERFLOW in case of integer overflow. + */ +static int tsync_works_grow_by(struct tsync_works *s, size_t n, gfp_t flags) +{ + size_t i; + size_t new_capacity; + struct tsync_work **works; + struct tsync_work *work; + + if (check_add_overflow(s->size, n, &new_capacity)) + return -EOVERFLOW; + + /* No need to reallocate if s already has sufficient capacity. */ + if (new_capacity <= s->capacity) + return 0; + + works = krealloc_array(s->works, new_capacity, sizeof(s->works[0]), + flags); + if (!works) + return -ENOMEM; + + s->works = works; + + for (i = s->capacity; i < new_capacity; i++) { + work = kzalloc_obj(*work, flags); + if (!work) { + /* + * Leave the object in a consistent state, + * but return an error. + */ + s->capacity = i; + return -ENOMEM; + } + s->works[i] = work; + } + s->capacity = new_capacity; + return 0; +} + +/* + * tsync_works_contains - checks for presence of task in s + */ +static bool tsync_works_contains_task(const struct tsync_works *s, + const struct task_struct *task) +{ + size_t i; + + for (i = 0; i < s->size; i++) + if (s->works[i]->task == task) + return true; + + return false; +} + +/* + * tsync_works_release - frees memory held by s and drops all task references + * + * This does not free s itself, only the data structures held by it. + */ +static void tsync_works_release(struct tsync_works *s) +{ + size_t i; + + for (i = 0; i < s->size; i++) { + if (WARN_ON_ONCE(!s->works[i]->task)) + continue; + + put_task_struct(s->works[i]->task); + } + + for (i = 0; i < s->capacity; i++) + kfree(s->works[i]); + + kfree(s->works); + s->works = NULL; + s->size = 0; + s->capacity = 0; +} + +/* + * count_additional_threads - counts the sibling threads that are not in works + */ +static size_t count_additional_threads(const struct tsync_works *works) +{ + const struct task_struct *caller, *thread; + size_t n = 0; + + caller = current; + + guard(rcu)(); + + for_each_thread(caller, thread) { + /* Skip current, since it is initiating the sync. */ + if (thread == caller) + continue; + + /* Skip exited threads. */ + if (thread->flags & PF_EXITING) + continue; + + /* Skip threads that we have already seen. */ + if (tsync_works_contains_task(works, thread)) + continue; + + n++; + } + return n; +} + +/* + * schedule_task_work - adds task_work for all eligible sibling threads + * which have not been scheduled yet + * + * For each added task_work, atomically increments shared_ctx->num_preparing and + * shared_ctx->num_unfinished. + * + * Return: True if at least one eligible sibling thread was found, false + * otherwise. + */ +static bool schedule_task_work(struct tsync_works *works, + struct tsync_shared_context *shared_ctx) +{ + int err; + const struct task_struct *caller; + struct task_struct *thread; + struct tsync_work *ctx; + bool found_more_threads = false; + + caller = current; + + guard(rcu)(); + + for_each_thread(caller, thread) { + /* Skip current, since it is initiating the sync. */ + if (thread == caller) + continue; + + /* Skip exited threads. */ + if (thread->flags & PF_EXITING) + continue; + + /* Skip threads that we already looked at. */ + if (tsync_works_contains_task(works, thread)) + continue; + + /* + * We found a sibling thread that is not doing its task_work + * yet, and which might spawn new threads before our task work + * runs, so we need at least one more round in the outer loop. + */ + found_more_threads = true; + + ctx = tsync_works_provide(works, thread); + if (!ctx) { + /* + * We ran out of preallocated contexts -- we need to + * try again with this thread at a later time! + * found_more_threads is already true at this point. + */ + break; + } + + ctx->shared_ctx = shared_ctx; + + atomic_inc(&shared_ctx->num_preparing); + atomic_inc(&shared_ctx->num_unfinished); + + init_task_work(&ctx->work, restrict_one_thread_callback); + err = task_work_add(thread, &ctx->work, TWA_SIGNAL); + if (unlikely(err)) { + /* + * task_work_add() only fails if the task is about to + * exit. We checked that earlier, but it can happen as + * a race. Resume without setting an error, as the + * task is probably gone in the next loop iteration. + */ + tsync_works_trim(works); + + atomic_dec(&shared_ctx->num_preparing); + atomic_dec(&shared_ctx->num_unfinished); + } + } + + return found_more_threads; +} + +/* + * cancel_tsync_works - cancel all task works where it is possible + * + * Task works can be canceled as long as they are still queued and have not + * started running. If they get canceled, we decrement + * shared_ctx->num_preparing and shared_ctx->num_unfished and mark the two + * completions if needed, as if the task was never scheduled. + */ +static void cancel_tsync_works(const struct tsync_works *works, + struct tsync_shared_context *shared_ctx) +{ + size_t i; + + for (i = 0; i < works->size; i++) { + if (WARN_ON_ONCE(!works->works[i]->task)) + continue; + + if (!task_work_cancel(works->works[i]->task, + &works->works[i]->work)) + continue; + + /* After dequeueing, act as if the task work had executed. */ + + if (atomic_dec_return(&shared_ctx->num_preparing) == 0) + complete_all(&shared_ctx->all_prepared); + + if (atomic_dec_return(&shared_ctx->num_unfinished) == 0) + complete_all(&shared_ctx->all_finished); + } +} + +/* + * restrict_sibling_threads - enables a Landlock policy for all sibling threads + */ +int landlock_restrict_sibling_threads(const struct cred *old_cred, + const struct cred *new_cred) +{ + int err; + struct tsync_shared_context shared_ctx; + struct tsync_works works = {}; + size_t newly_discovered_threads; + bool found_more_threads; + + atomic_set(&shared_ctx.preparation_error, 0); + init_completion(&shared_ctx.all_prepared); + init_completion(&shared_ctx.ready_to_commit); + atomic_set(&shared_ctx.num_unfinished, 1); + init_completion(&shared_ctx.all_finished); + shared_ctx.old_cred = old_cred; + shared_ctx.new_cred = new_cred; + shared_ctx.set_no_new_privs = task_no_new_privs(current); + + /* + * Serialize concurrent TSYNC operations to prevent deadlocks when + * multiple threads call landlock_restrict_self() simultaneously. + * If the lock is already held, we gracefully yield by restarting the + * syscall. This allows the current thread to process pending + * task_works before retrying. + */ + if (!down_write_trylock(¤t->signal->exec_update_lock)) + return restart_syscall(); + + /* + * We schedule a pseudo-signal task_work for each of the calling task's + * sibling threads. In the task work, each thread: + * + * 1) runs prepare_creds() and writes back the error to + * shared_ctx.preparation_error, if needed. + * + * 2) signals that it's done with prepare_creds() to the calling task. + * (completion "all_prepared"). + * + * 3) waits for the completion "ready_to_commit". This is sent by the + * calling task after ensuring that all sibling threads have done + * with the "preparation" stage. + * + * After this barrier is reached, it's safe to read + * shared_ctx.preparation_error. + * + * 4) reads shared_ctx.preparation_error and then either does + * commit_creds() or abort_creds(). + * + * 5) signals that it's done altogether (barrier synchronization + * "all_finished") + * + * Unlike seccomp, which modifies sibling tasks directly, we do not + * need to acquire the cred_guard_mutex and sighand->siglock: + * + * - As in our case, all threads are themselves exchanging their own + * struct cred through the credentials API, no locks are needed for + * that. + * - Our for_each_thread() loops are protected by RCU. + * - We do not acquire a lock to keep the list of sibling threads + * stable between our for_each_thread loops. If the list of + * available sibling threads changes between these for_each_thread + * loops, we make up for that by continuing to look for threads until + * they are all discovered and have entered their task_work, where + * they are unable to spawn new threads. + */ + do { + /* In RCU read-lock, count the threads we need. */ + newly_discovered_threads = count_additional_threads(&works); + + if (newly_discovered_threads == 0) + break; /* done */ + + err = tsync_works_grow_by(&works, newly_discovered_threads, + GFP_KERNEL_ACCOUNT); + if (err) { + atomic_set(&shared_ctx.preparation_error, err); + break; + } + + /* + * The "all_prepared" barrier is used locally to the loop body, + * this use of for_each_thread(). We can reset it on each loop + * iteration because all previous loop iterations are done with + * it already. + * + * num_preparing is initialized to 1 so that the counter can + * not go to 0 and mark the completion as done before all task + * works are registered. We decrement it at the end of the + * loop body. + */ + atomic_set(&shared_ctx.num_preparing, 1); + reinit_completion(&shared_ctx.all_prepared); + + /* + * In RCU read-lock, schedule task work on newly discovered + * sibling tasks. + */ + found_more_threads = schedule_task_work(&works, &shared_ctx); + + /* + * Decrement num_preparing for current, to undo that we + * initialized it to 1 a few lines above. + */ + if (atomic_dec_return(&shared_ctx.num_preparing) > 0) { + if (wait_for_completion_interruptible( + &shared_ctx.all_prepared)) { + /* + * In case of interruption, we need to retry + * the system call. + */ + atomic_set(&shared_ctx.preparation_error, + -ERESTARTNOINTR); + + /* + * Opportunistic improvement: try to cancel task + * works for tasks that did not start running + * yet. We do not have a guarantee that it + * cancels any of the enqueued task works + * because task_work_run() might already have + * dequeued them. + */ + cancel_tsync_works(&works, &shared_ctx); + + /* + * Break the loop with error. The cleanup code + * after the loop unblocks the remaining + * task_works. + */ + break; + } + } + } while (found_more_threads && + !atomic_read(&shared_ctx.preparation_error)); + + /* + * We now have either (a) all sibling threads blocking and in "prepared" + * state in the task work, or (b) the preparation error is set. Ask all + * threads to commit (or abort). + */ + complete_all(&shared_ctx.ready_to_commit); + + /* + * Decrement num_unfinished for current, to undo that we initialized it + * to 1 at the beginning. + */ + if (atomic_dec_return(&shared_ctx.num_unfinished) > 0) + wait_for_completion(&shared_ctx.all_finished); + + tsync_works_release(&works); + up_write(¤t->signal->exec_update_lock); + return atomic_read(&shared_ctx.preparation_error); +} diff --git a/security/landlock/tsync.h b/security/landlock/tsync.h new file mode 100644 index 000000000000..ef86bb61c2f6 --- /dev/null +++ b/security/landlock/tsync.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Cross-thread ruleset enforcement + * + * Copyright © 2025 Google LLC + */ + +#ifndef _SECURITY_LANDLOCK_TSYNC_H +#define _SECURITY_LANDLOCK_TSYNC_H + +#include <linux/cred.h> + +int landlock_restrict_sibling_threads(const struct cred *old_cred, + const struct cred *new_cred); + +#endif /* _SECURITY_LANDLOCK_TSYNC_H */ diff --git a/security/loadpin/Kconfig b/security/loadpin/Kconfig index 848f8b4a6019..aef63d3e30df 100644 --- a/security/loadpin/Kconfig +++ b/security/loadpin/Kconfig @@ -16,7 +16,7 @@ config SECURITY_LOADPIN_ENFORCE depends on SECURITY_LOADPIN # Module compression breaks LoadPin unless modules are decompressed in # the kernel. - depends on !MODULES || (MODULE_COMPRESS_NONE || MODULE_DECOMPRESS) + depends on !MODULE_COMPRESS || MODULE_DECOMPRESS help If selected, LoadPin will enforce pinning at boot. If not selected, it can be enabled at boot with the kernel parameter diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c index 68252452b66c..f71861f98e1a 100644 --- a/security/loadpin/loadpin.c +++ b/security/loadpin/loadpin.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <linux/fs.h> +#include <linux/hex.h> #include <linux/kernel_read_file.h> #include <linux/lsm_hooks.h> #include <linux/mount.h> @@ -52,32 +53,29 @@ static DEFINE_SPINLOCK(pinned_root_spinlock); static bool deny_reading_verity_digests; #endif +// initialized to false +static bool loadpin_root_writable; #ifdef CONFIG_SYSCTL -static struct ctl_table loadpin_sysctl_table[] = { + +static int proc_handler_loadpin(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos) +{ + if (!loadpin_root_writable && SYSCTL_USER_TO_KERN(dir)) + return -EINVAL; + return proc_dointvec_minmax(table, dir, buffer, lenp, ppos); +} + +static const struct ctl_table loadpin_sysctl_table[] = { { .procname = "enforce", .data = &enforce, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = proc_dointvec_minmax, - .extra1 = SYSCTL_ONE, + .proc_handler = proc_handler_loadpin, + .extra1 = SYSCTL_ZERO, .extra2 = SYSCTL_ONE, }, }; - -static void set_sysctl(bool is_writable) -{ - /* - * If load pinning is not enforced via a read-only block - * device, allow sysctl to change modes for testing. - */ - if (is_writable) - loadpin_sysctl_table[0].extra1 = SYSCTL_ZERO; - else - loadpin_sysctl_table[0].extra1 = SYSCTL_ONE; -} -#else -static inline void set_sysctl(bool is_writable) { } #endif static void report_writable(struct super_block *mnt_sb, bool writable) @@ -131,7 +129,6 @@ static int loadpin_check(struct file *file, enum kernel_read_file_id id) struct super_block *load_root; const char *origin = kernel_read_file_id_str(id); bool first_root_pin = false; - bool load_root_writable; /* If the file id is excluded, ignore the pinning. */ if ((unsigned int)id < ARRAY_SIZE(ignore_read_file_id) && @@ -152,7 +149,6 @@ static int loadpin_check(struct file *file, enum kernel_read_file_id id) } load_root = file->f_path.mnt->mnt_sb; - load_root_writable = sb_is_writable(load_root); /* First loaded module/firmware defines the root for all others. */ spin_lock(&pinned_root_spinlock); @@ -168,8 +164,8 @@ static int loadpin_check(struct file *file, enum kernel_read_file_id id) spin_unlock(&pinned_root_spinlock); if (first_root_pin) { - report_writable(pinned_root, load_root_writable); - set_sysctl(load_root_writable); + loadpin_root_writable = sb_is_writable(pinned_root); + report_writable(pinned_root, loadpin_root_writable); report_load(origin, file, "pinned"); } @@ -270,11 +266,6 @@ static int __init loadpin_init(void) return 0; } -DEFINE_LSM(loadpin) = { - .name = "loadpin", - .init = loadpin_init, -}; - #ifdef CONFIG_SECURITY_LOADPIN_VERITY enum loadpin_securityfs_interface_index { @@ -336,7 +327,7 @@ static int read_trusted_verity_root_digests(unsigned int fd) len /= 2; - trd = kzalloc(struct_size(trd, data, len), GFP_KERNEL); + trd = kzalloc_flex(*trd, data, len); if (!trd) { rc = -ENOMEM; goto err; @@ -434,9 +425,15 @@ static int __init init_loadpin_securityfs(void) return 0; } -fs_initcall(init_loadpin_securityfs); +#endif /* CONFIG_SECURITY_LOADPIN_VERITY */ +DEFINE_LSM(loadpin) = { + .id = &loadpin_lsmid, + .init = loadpin_init, +#ifdef CONFIG_SECURITY_LOADPIN_VERITY + .initcall_fs = init_loadpin_securityfs, #endif /* CONFIG_SECURITY_LOADPIN_VERITY */ +}; /* Should not be mutable after boot, so not listed in sysfs (perm == 0). */ module_param(enforce, int, 0); diff --git a/security/lockdown/lockdown.c b/security/lockdown/lockdown.c index cf83afa1d879..8d46886d2cca 100644 --- a/security/lockdown/lockdown.c +++ b/security/lockdown/lockdown.c @@ -161,13 +161,12 @@ static int __init lockdown_secfs_init(void) return PTR_ERR_OR_ZERO(dentry); } -core_initcall(lockdown_secfs_init); - #ifdef CONFIG_SECURITY_LOCKDOWN_LSM_EARLY DEFINE_EARLY_LSM(lockdown) = { #else DEFINE_LSM(lockdown) = { #endif - .name = "lockdown", + .id = &lockdown_lsmid, .init = lockdown_lsm_init, + .initcall_core = lockdown_secfs_init, }; diff --git a/security/lsm.h b/security/lsm.h new file mode 100644 index 000000000000..32f808ad4335 --- /dev/null +++ b/security/lsm.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LSM functions + */ + +#ifndef _LSM_H_ +#define _LSM_H_ + +#include <linux/printk.h> +#include <linux/lsm_hooks.h> +#include <linux/lsm_count.h> + +/* LSM debugging */ +extern bool lsm_debug; +#define lsm_pr(...) pr_info(__VA_ARGS__) +#define lsm_pr_cont(...) pr_cont(__VA_ARGS__) +#define lsm_pr_dbg(...) \ + do { \ + if (lsm_debug) \ + pr_info(__VA_ARGS__); \ + } while (0) + +/* List of configured LSMs */ +extern unsigned int lsm_active_cnt; +extern const struct lsm_id *lsm_idlist[]; + +/* LSM blob configuration */ +extern struct lsm_blob_sizes blob_sizes; + +/* LSM blob caches */ +extern struct kmem_cache *lsm_file_cache; +extern struct kmem_cache *lsm_backing_file_cache; +extern struct kmem_cache *lsm_inode_cache; + +/* LSM blob allocators */ +int lsm_cred_alloc(struct cred *cred, gfp_t gfp); +int lsm_task_alloc(struct task_struct *task); + +/* LSM framework initializers */ + +#ifdef CONFIG_SECURITYFS +int securityfs_init(void); +#else +static inline int securityfs_init(void) +{ + return 0; +} +#endif /* CONFIG_SECURITYFS */ + +#endif /* _LSM_H_ */ diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 52db886dbba8..737f5a263a8f 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -3,7 +3,7 @@ * common LSM auditing functions * * Based on code written for SELinux by : - * Stephen Smalley, <sds@tycho.nsa.gov> + * Stephen Smalley * James Morris <jmorris@redhat.com> * Author : Etienne Basset, <etienne.basset@ensta.org> */ @@ -24,7 +24,6 @@ #include <net/ipv6.h> #include <linux/tcp.h> #include <linux/udp.h> -#include <linux/dccp.h> #include <linux/sctp.h> #include <linux/lsm_audit.h> #include <linux/security.h> @@ -68,13 +67,6 @@ int ipv4_skb_to_auditdata(struct sk_buff *skb, ad->u.net->dport = uh->dest; break; } - case IPPROTO_DCCP: { - struct dccp_hdr *dh = dccp_hdr(skb); - - ad->u.net->sport = dh->dccph_sport; - ad->u.net->dport = dh->dccph_dport; - break; - } case IPPROTO_SCTP: { struct sctphdr *sh = sctp_hdr(skb); @@ -140,17 +132,6 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb, ad->u.net->dport = uh->dest; break; } - case IPPROTO_DCCP: { - struct dccp_hdr _dccph, *dh; - - dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); - if (dh == NULL) - break; - - ad->u.net->sport = dh->dccph_sport; - ad->u.net->dport = dh->dccph_dport; - break; - } case IPPROTO_SCTP: { struct sctphdr _sctph, *sh; @@ -189,16 +170,13 @@ static inline void print_ipv4_addr(struct audit_buffer *ab, __be32 addr, } /** - * dump_common_audit_data - helper to dump common audit data + * audit_log_lsm_data - helper to log common LSM audit data * @ab : the audit buffer * @a : common audit data - * */ -static void dump_common_audit_data(struct audit_buffer *ab, - struct common_audit_data *a) +void audit_log_lsm_data(struct audit_buffer *ab, + const struct common_audit_data *a) { - char comm[sizeof(current->comm)]; - /* * To keep stack sizes in check force programmers to notice if they * start making this union too large! See struct lsm_network_audit @@ -206,9 +184,6 @@ static void dump_common_audit_data(struct audit_buffer *ab, */ BUILD_BUG_ON(sizeof(a->u) > sizeof(void *)*2); - audit_log_format(ab, " pid=%d comm=", task_tgid_nr(current)); - audit_log_untrustedstring(ab, get_task_comm(comm, current)); - switch (a->type) { case LSM_AUDIT_DATA_NONE: return; @@ -227,7 +202,7 @@ static void dump_common_audit_data(struct audit_buffer *ab, if (inode) { audit_log_format(ab, " dev="); audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " ino=%llu", inode->i_ino); } break; } @@ -240,7 +215,7 @@ static void dump_common_audit_data(struct audit_buffer *ab, if (inode) { audit_log_format(ab, " dev="); audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " ino=%llu", inode->i_ino); } break; } @@ -253,7 +228,7 @@ static void dump_common_audit_data(struct audit_buffer *ab, if (inode) { audit_log_format(ab, " dev="); audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " ino=%llu", inode->i_ino); } audit_log_format(ab, " ioctlcmd=0x%hx", a->u.op->cmd); @@ -271,7 +246,7 @@ static void dump_common_audit_data(struct audit_buffer *ab, if (inode) { audit_log_format(ab, " dev="); audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " ino=%llu", inode->i_ino); } break; } @@ -290,7 +265,7 @@ static void dump_common_audit_data(struct audit_buffer *ab, } audit_log_format(ab, " dev="); audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); + audit_log_format(ab, " ino=%llu", inode->i_ino); rcu_read_unlock(); break; } @@ -432,6 +407,21 @@ static void dump_common_audit_data(struct audit_buffer *ab, } /** + * dump_common_audit_data - helper to dump common audit data + * @ab : the audit buffer + * @a : common audit data + */ +static void dump_common_audit_data(struct audit_buffer *ab, + const struct common_audit_data *a) +{ + char comm[sizeof(current->comm)]; + + audit_log_format(ab, " pid=%d comm=", task_tgid_nr(current)); + audit_log_untrustedstring(ab, get_task_comm(comm, current)); + audit_log_lsm_data(ab, a); +} + +/** * common_lsm_audit - generic LSM auditing function * @a: auxiliary audit data * @pre_audit: lsm-specific pre-audit callback diff --git a/security/lsm_init.c b/security/lsm_init.c new file mode 100644 index 000000000000..7c0fd17f1601 --- /dev/null +++ b/security/lsm_init.c @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LSM initialization functions + */ + +#define pr_fmt(fmt) "LSM: " fmt + +#include <linux/init.h> +#include <linux/lsm_hooks.h> + +#include "lsm.h" + +/* LSM enabled constants. */ +static __initdata int lsm_enabled_true = 1; +static __initdata int lsm_enabled_false = 0; + +/* Pointers to LSM sections defined in include/asm-generic/vmlinux.lds.h */ +extern struct lsm_info __start_lsm_info[], __end_lsm_info[]; +extern struct lsm_info __start_early_lsm_info[], __end_early_lsm_info[]; + +/* Number of "early" LSMs */ +static __initdata unsigned int lsm_count_early; + +/* Build and boot-time LSM ordering. */ +static __initconst const char *const lsm_order_builtin = CONFIG_LSM; +static __initdata const char *lsm_order_cmdline; +static __initdata const char *lsm_order_legacy; + +/* Ordered list of LSMs to initialize. */ +static __initdata struct lsm_info *lsm_order[MAX_LSM_COUNT + 1]; +static __initdata struct lsm_info *lsm_exclusive; + +#define lsm_order_for_each(iter) \ + for ((iter) = lsm_order; *(iter); (iter)++) +#define lsm_for_each_raw(iter) \ + for ((iter) = __start_lsm_info; \ + (iter) < __end_lsm_info; (iter)++) +#define lsm_early_for_each_raw(iter) \ + for ((iter) = __start_early_lsm_info; \ + (iter) < __end_early_lsm_info; (iter)++) + +#define lsm_initcall(level) \ + ({ \ + int _r, _rc = 0; \ + struct lsm_info **_lp, *_l; \ + lsm_order_for_each(_lp) { \ + _l = *_lp; \ + if (!_l->initcall_##level) \ + continue; \ + lsm_pr_dbg("running %s %s initcall", \ + _l->id->name, #level); \ + _r = _l->initcall_##level(); \ + if (_r) { \ + pr_warn("failed LSM %s %s initcall with errno %d\n", \ + _l->id->name, #level, _r); \ + if (!_rc) \ + _rc = _r; \ + } \ + } \ + _rc; \ + }) + +/** + * lsm_choose_security - Legacy "major" LSM selection + * @str: kernel command line parameter + */ +static int __init lsm_choose_security(char *str) +{ + lsm_order_legacy = str; + return 1; +} +__setup("security=", lsm_choose_security); + +/** + * lsm_choose_lsm - Modern LSM selection + * @str: kernel command line parameter + */ +static int __init lsm_choose_lsm(char *str) +{ + lsm_order_cmdline = str; + return 1; +} +__setup("lsm=", lsm_choose_lsm); + +/** + * lsm_debug_enable - Enable LSM framework debugging + * @str: kernel command line parameter + * + * Currently we only provide debug info during LSM initialization, but we may + * want to expand this in the future. + */ +static int __init lsm_debug_enable(char *str) +{ + lsm_debug = true; + return 1; +} +__setup("lsm.debug", lsm_debug_enable); + +/** + * lsm_enabled_set - Mark a LSM as enabled + * @lsm: LSM definition + * @enabled: enabled flag + */ +static void __init lsm_enabled_set(struct lsm_info *lsm, bool enabled) +{ + /* + * When an LSM hasn't configured an enable variable, we can use + * a hard-coded location for storing the default enabled state. + */ + if (!lsm->enabled || + lsm->enabled == &lsm_enabled_true || + lsm->enabled == &lsm_enabled_false) { + lsm->enabled = enabled ? &lsm_enabled_true : &lsm_enabled_false; + } else { + *lsm->enabled = enabled; + } +} + +/** + * lsm_is_enabled - Determine if a LSM is enabled + * @lsm: LSM definition + */ +static inline bool lsm_is_enabled(struct lsm_info *lsm) +{ + return (lsm->enabled ? *lsm->enabled : false); +} + +/** + * lsm_order_exists - Determine if a LSM exists in the ordered list + * @lsm: LSM definition + */ +static bool __init lsm_order_exists(struct lsm_info *lsm) +{ + struct lsm_info **check; + + lsm_order_for_each(check) { + if (*check == lsm) + return true; + } + + return false; +} + +/** + * lsm_order_append - Append a LSM to the ordered list + * @lsm: LSM definition + * @src: source of the addition + * + * Append @lsm to the enabled LSM array after ensuring that it hasn't been + * explicitly disabled, is a duplicate entry, or would run afoul of the + * LSM_FLAG_EXCLUSIVE logic. + */ +static void __init lsm_order_append(struct lsm_info *lsm, const char *src) +{ + /* Ignore duplicate selections. */ + if (lsm_order_exists(lsm)) + return; + + /* Skip explicitly disabled LSMs. */ + if (lsm->enabled && !lsm_is_enabled(lsm)) { + lsm_pr_dbg("skip previously disabled LSM %s:%s\n", + src, lsm->id->name); + return; + } + + if (lsm_active_cnt == MAX_LSM_COUNT) { + pr_warn("exceeded maximum LSM count on %s:%s\n", + src, lsm->id->name); + lsm_enabled_set(lsm, false); + return; + } + + if (lsm->flags & LSM_FLAG_EXCLUSIVE) { + if (lsm_exclusive) { + lsm_pr_dbg("skip exclusive LSM conflict %s:%s\n", + src, lsm->id->name); + lsm_enabled_set(lsm, false); + return; + } else { + lsm_pr_dbg("select exclusive LSM %s:%s\n", + src, lsm->id->name); + lsm_exclusive = lsm; + } + } + + lsm_enabled_set(lsm, true); + lsm_order[lsm_active_cnt] = lsm; + lsm_idlist[lsm_active_cnt++] = lsm->id; + + lsm_pr_dbg("enabling LSM %s:%s\n", src, lsm->id->name); +} + +/** + * lsm_order_parse - Parse the comma delimited LSM list + * @list: LSM list + * @src: source of the list + */ +static void __init lsm_order_parse(const char *list, const char *src) +{ + struct lsm_info *lsm; + char *sep, *name, *next; + + /* Handle any Legacy LSM exclusions if one was specified. */ + if (lsm_order_legacy) { + /* + * To match the original "security=" behavior, this explicitly + * does NOT fallback to another Legacy Major if the selected + * one was separately disabled: disable all non-matching + * Legacy Major LSMs. + */ + lsm_for_each_raw(lsm) { + if ((lsm->flags & LSM_FLAG_LEGACY_MAJOR) && + strcmp(lsm->id->name, lsm_order_legacy)) { + lsm_enabled_set(lsm, false); + lsm_pr_dbg("skip legacy LSM conflict %s:%s\n", + src, lsm->id->name); + } + } + } + + /* LSM_ORDER_FIRST */ + lsm_for_each_raw(lsm) { + if (lsm->order == LSM_ORDER_FIRST) + lsm_order_append(lsm, "first"); + } + + /* Normal or "mutable" LSMs */ + sep = kstrdup(list, GFP_KERNEL); + next = sep; + /* Walk the list, looking for matching LSMs. */ + while ((name = strsep(&next, ",")) != NULL) { + lsm_for_each_raw(lsm) { + if (!strcmp(lsm->id->name, name) && + lsm->order == LSM_ORDER_MUTABLE) + lsm_order_append(lsm, src); + } + } + kfree(sep); + + /* Legacy LSM if specified. */ + if (lsm_order_legacy) { + lsm_for_each_raw(lsm) { + if (!strcmp(lsm->id->name, lsm_order_legacy)) + lsm_order_append(lsm, src); + } + } + + /* LSM_ORDER_LAST */ + lsm_for_each_raw(lsm) { + if (lsm->order == LSM_ORDER_LAST) + lsm_order_append(lsm, "last"); + } + + /* Disable all LSMs not previously enabled. */ + lsm_for_each_raw(lsm) { + if (lsm_order_exists(lsm)) + continue; + lsm_enabled_set(lsm, false); + lsm_pr_dbg("skip disabled LSM %s:%s\n", src, lsm->id->name); + } +} + +/** + * lsm_blob_size_update - Update the LSM blob size and offset information + * @sz_req: the requested additional blob size + * @sz_cur: the existing blob size + */ +static void __init lsm_blob_size_update(unsigned int *sz_req, + unsigned int *sz_cur) +{ + unsigned int offset; + + if (*sz_req == 0) + return; + + offset = ALIGN(*sz_cur, sizeof(void *)); + *sz_cur = offset + *sz_req; + *sz_req = offset; +} + +/** + * lsm_prepare - Prepare the LSM framework for a new LSM + * @lsm: LSM definition + */ +static void __init lsm_prepare(struct lsm_info *lsm) +{ + struct lsm_blob_sizes *blobs = lsm->blobs; + + if (!blobs) + return; + + /* Register the LSM blob sizes. */ + blobs = lsm->blobs; + lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred); + lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file); + lsm_blob_size_update(&blobs->lbs_backing_file, + &blob_sizes.lbs_backing_file); + lsm_blob_size_update(&blobs->lbs_ib, &blob_sizes.lbs_ib); + /* inode blob gets an rcu_head in addition to LSM blobs. */ + if (blobs->lbs_inode && blob_sizes.lbs_inode == 0) + blob_sizes.lbs_inode = sizeof(struct rcu_head); + lsm_blob_size_update(&blobs->lbs_inode, &blob_sizes.lbs_inode); + lsm_blob_size_update(&blobs->lbs_ipc, &blob_sizes.lbs_ipc); + lsm_blob_size_update(&blobs->lbs_key, &blob_sizes.lbs_key); + lsm_blob_size_update(&blobs->lbs_msg_msg, &blob_sizes.lbs_msg_msg); + lsm_blob_size_update(&blobs->lbs_perf_event, + &blob_sizes.lbs_perf_event); + lsm_blob_size_update(&blobs->lbs_sock, &blob_sizes.lbs_sock); + lsm_blob_size_update(&blobs->lbs_superblock, + &blob_sizes.lbs_superblock); + lsm_blob_size_update(&blobs->lbs_task, &blob_sizes.lbs_task); + lsm_blob_size_update(&blobs->lbs_tun_dev, &blob_sizes.lbs_tun_dev); + lsm_blob_size_update(&blobs->lbs_xattr_count, + &blob_sizes.lbs_xattr_count); + lsm_blob_size_update(&blobs->lbs_bdev, &blob_sizes.lbs_bdev); + lsm_blob_size_update(&blobs->lbs_bpf_map, &blob_sizes.lbs_bpf_map); + lsm_blob_size_update(&blobs->lbs_bpf_prog, &blob_sizes.lbs_bpf_prog); + lsm_blob_size_update(&blobs->lbs_bpf_token, &blob_sizes.lbs_bpf_token); +} + +/** + * lsm_init_single - Initialize a given LSM + * @lsm: LSM definition + */ +static void __init lsm_init_single(struct lsm_info *lsm) +{ + int ret; + + if (!lsm_is_enabled(lsm)) + return; + + lsm_pr_dbg("initializing %s\n", lsm->id->name); + ret = lsm->init(); + WARN(ret, "%s failed to initialize: %d\n", lsm->id->name, ret); +} + +/** + * lsm_static_call_init - Initialize a LSM's static calls + * @hl: LSM hook list + */ +static int __init lsm_static_call_init(struct security_hook_list *hl) +{ + struct lsm_static_call *scall = hl->scalls; + int i; + + for (i = 0; i < MAX_LSM_COUNT; i++) { + /* Update the first static call that is not used yet */ + if (!scall->hl) { + __static_call_update(scall->key, scall->trampoline, + hl->hook.lsm_func_addr); + scall->hl = hl; + static_branch_enable(scall->active); + return 0; + } + scall++; + } + + return -ENOSPC; +} + +/** + * security_add_hooks - Add a LSM's hooks to the LSM framework's hook lists + * @hooks: LSM hooks to add + * @count: number of hooks to add + * @lsmid: identification information for the LSM + * + * Each LSM has to register its hooks with the LSM framework. + */ +void __init security_add_hooks(struct security_hook_list *hooks, int count, + const struct lsm_id *lsmid) +{ + int i; + + for (i = 0; i < count; i++) { + hooks[i].lsmid = lsmid; + if (lsm_static_call_init(&hooks[i])) + panic("exhausted LSM callback slots with LSM %s\n", + lsmid->name); + } +} + +/** + * early_security_init - Initialize the early LSMs + */ +int __init early_security_init(void) +{ + struct lsm_info *lsm; + + /* NOTE: lsm_pr_dbg() doesn't work here as lsm_debug is not yet set */ + + lsm_early_for_each_raw(lsm) { + lsm_enabled_set(lsm, true); + lsm_order_append(lsm, "early"); + lsm_prepare(lsm); + lsm_init_single(lsm); + lsm_count_early++; + } + + return 0; +} + +/** + * security_init - Initializes the LSM framework + * + * This should be called early in the kernel initialization sequence. + */ +int __init security_init(void) +{ + unsigned int cnt; + struct lsm_info **lsm; + + if (lsm_debug) { + struct lsm_info *i; + + cnt = 0; + lsm_pr("available LSMs: "); + lsm_early_for_each_raw(i) + lsm_pr_cont("%s%s(E)", (cnt++ ? "," : ""), i->id->name); + lsm_for_each_raw(i) + lsm_pr_cont("%s%s", (cnt++ ? "," : ""), i->id->name); + lsm_pr_cont("\n"); + + lsm_pr("built-in LSM config: %s\n", lsm_order_builtin); + + lsm_pr("legacy LSM parameter: %s\n", lsm_order_legacy); + lsm_pr("boot LSM parameter: %s\n", lsm_order_cmdline); + + /* see the note about lsm_pr_dbg() in early_security_init() */ + lsm_early_for_each_raw(i) + lsm_pr("enabled LSM early:%s\n", i->id->name); + } + + if (lsm_order_cmdline) { + if (lsm_order_legacy) + lsm_order_legacy = NULL; + lsm_order_parse(lsm_order_cmdline, "cmdline"); + } else + lsm_order_parse(lsm_order_builtin, "builtin"); + + lsm_order_for_each(lsm) + lsm_prepare(*lsm); + + if (lsm_debug) { + lsm_pr("blob(cred) size %d\n", blob_sizes.lbs_cred); + lsm_pr("blob(file) size %d\n", blob_sizes.lbs_file); + lsm_pr("blob(backing_file) size %d\n", + blob_sizes.lbs_backing_file); + lsm_pr("blob(ib) size %d\n", blob_sizes.lbs_ib); + lsm_pr("blob(inode) size %d\n", blob_sizes.lbs_inode); + lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc); + lsm_pr("blob(key) size %d\n", blob_sizes.lbs_key); + lsm_pr("blob(msg_msg)_size %d\n", blob_sizes.lbs_msg_msg); + lsm_pr("blob(sock) size %d\n", blob_sizes.lbs_sock); + lsm_pr("blob(superblock) size %d\n", blob_sizes.lbs_superblock); + lsm_pr("blob(perf_event) size %d\n", blob_sizes.lbs_perf_event); + lsm_pr("blob(task) size %d\n", blob_sizes.lbs_task); + lsm_pr("blob(tun_dev) size %d\n", blob_sizes.lbs_tun_dev); + lsm_pr("blob(xattr) count %d\n", blob_sizes.lbs_xattr_count); + lsm_pr("blob(bdev) size %d\n", blob_sizes.lbs_bdev); + lsm_pr("blob(bpf_map) size %d\n", blob_sizes.lbs_bpf_map); + lsm_pr("blob(bpf_prog) size %d\n", blob_sizes.lbs_bpf_prog); + lsm_pr("blob(bpf_token) size %d\n", blob_sizes.lbs_bpf_token); + } + + if (blob_sizes.lbs_file) + lsm_file_cache = kmem_cache_create("lsm_file_cache", + blob_sizes.lbs_file, 0, + SLAB_PANIC, NULL); + if (blob_sizes.lbs_backing_file) + lsm_backing_file_cache = kmem_cache_create( + "lsm_backing_file_cache", + blob_sizes.lbs_backing_file, + 0, SLAB_PANIC, NULL); + if (blob_sizes.lbs_inode) + lsm_inode_cache = kmem_cache_create("lsm_inode_cache", + blob_sizes.lbs_inode, 0, + SLAB_PANIC, NULL); + + if (lsm_cred_alloc((struct cred *)unrcu_pointer(current->cred), + GFP_KERNEL)) + panic("early LSM cred alloc failed\n"); + if (lsm_task_alloc(current)) + panic("early LSM task alloc failed\n"); + + cnt = 0; + lsm_order_for_each(lsm) { + /* skip the "early" LSMs as they have already been setup */ + if (cnt++ < lsm_count_early) + continue; + lsm_init_single(*lsm); + } + + return 0; +} + +/** + * security_initcall_pure - Run the LSM pure initcalls + */ +static int __init security_initcall_pure(void) +{ + return lsm_initcall(pure); +} +pure_initcall(security_initcall_pure); + +/** + * security_initcall_early - Run the LSM early initcalls + */ +static int __init security_initcall_early(void) +{ + return lsm_initcall(early); +} +early_initcall(security_initcall_early); + +/** + * security_initcall_core - Run the LSM core initcalls + */ +static int __init security_initcall_core(void) +{ + int rc_sfs, rc_lsm; + + rc_sfs = securityfs_init(); + rc_lsm = lsm_initcall(core); + + return (rc_sfs ? rc_sfs : rc_lsm); +} +core_initcall(security_initcall_core); + +/** + * security_initcall_subsys - Run the LSM subsys initcalls + */ +static int __init security_initcall_subsys(void) +{ + return lsm_initcall(subsys); +} +subsys_initcall(security_initcall_subsys); + +/** + * security_initcall_fs - Run the LSM fs initcalls + */ +static int __init security_initcall_fs(void) +{ + return lsm_initcall(fs); +} +fs_initcall(security_initcall_fs); + +/** + * security_initcall_device - Run the LSM device initcalls + */ +static int __init security_initcall_device(void) +{ + return lsm_initcall(device); +} +device_initcall(security_initcall_device); + +/** + * security_initcall_late - Run the LSM late initcalls + */ +static int __init security_initcall_late(void) +{ + int rc; + + rc = lsm_initcall(late); + lsm_pr_dbg("all enabled LSMs fully activated\n"); + call_blocking_lsm_notifier(LSM_STARTED_ALL, NULL); + + return rc; +} +late_initcall(security_initcall_late); diff --git a/security/lsm_notifier.c b/security/lsm_notifier.c new file mode 100644 index 000000000000..c92fad5d57d4 --- /dev/null +++ b/security/lsm_notifier.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LSM notifier functions + * + */ + +#include <linux/notifier.h> +#include <linux/security.h> + +static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain); + +int call_blocking_lsm_notifier(enum lsm_event event, void *data) +{ + return blocking_notifier_call_chain(&blocking_lsm_notifier_chain, + event, data); +} +EXPORT_SYMBOL(call_blocking_lsm_notifier); + +int register_blocking_lsm_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&blocking_lsm_notifier_chain, + nb); +} +EXPORT_SYMBOL(register_blocking_lsm_notifier); + +int unregister_blocking_lsm_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&blocking_lsm_notifier_chain, + nb); +} +EXPORT_SYMBOL(unregister_blocking_lsm_notifier); diff --git a/security/lsm_syscalls.c b/security/lsm_syscalls.c index 8440948a690c..08a017669c02 100644 --- a/security/lsm_syscalls.c +++ b/security/lsm_syscalls.c @@ -17,6 +17,8 @@ #include <linux/lsm_hooks.h> #include <uapi/linux/lsm.h> +#include "lsm.h" + /** * lsm_name_to_attr - map an LSM attribute name to its ID * @name: name of the attribute @@ -55,7 +57,14 @@ u64 lsm_name_to_attr(const char *name) SYSCALL_DEFINE4(lsm_set_self_attr, unsigned int, attr, struct lsm_ctx __user *, ctx, u32, size, u32, flags) { - return security_setselfattr(attr, ctx, size, flags); + int rc; + + rc = mutex_lock_interruptible(¤t->signal->cred_guard_mutex); + if (rc < 0) + return rc; + rc = security_setselfattr(attr, ctx, size, flags); + mutex_unlock(¤t->signal->cred_guard_mutex); + return rc; } /** diff --git a/security/min_addr.c b/security/min_addr.c index 0ce267c041ab..56e4f9d25929 100644 --- a/security/min_addr.c +++ b/security/min_addr.c @@ -3,6 +3,7 @@ #include <linux/mm.h> #include <linux/security.h> #include <linux/sysctl.h> +#include <linux/minmax.h> /* amount of vm to protect from userspace access by both DAC and the LSM*/ unsigned long mmap_min_addr; @@ -16,10 +17,7 @@ unsigned long dac_mmap_min_addr = CONFIG_DEFAULT_MMAP_MIN_ADDR; static void update_mmap_min_addr(void) { #ifdef CONFIG_LSM_MMAP_MIN_ADDR - if (dac_mmap_min_addr > CONFIG_LSM_MMAP_MIN_ADDR) - mmap_min_addr = dac_mmap_min_addr; - else - mmap_min_addr = CONFIG_LSM_MMAP_MIN_ADDR; + mmap_min_addr = umax(dac_mmap_min_addr, CONFIG_LSM_MMAP_MIN_ADDR); #else mmap_min_addr = dac_mmap_min_addr; #endif @@ -44,10 +42,21 @@ int mmap_min_addr_handler(const struct ctl_table *table, int write, return ret; } -static int __init init_mmap_min_addr(void) +static const struct ctl_table min_addr_sysctl_table[] = { + { + .procname = "mmap_min_addr", + .data = &dac_mmap_min_addr, + .maxlen = sizeof(unsigned long), + .mode = 0644, + .proc_handler = mmap_min_addr_handler, + }, +}; + +static int __init mmap_min_addr_init(void) { + register_sysctl_init("vm", min_addr_sysctl_table); update_mmap_min_addr(); return 0; } -pure_initcall(init_mmap_min_addr); +pure_initcall(mmap_min_addr_init); diff --git a/security/safesetid/lsm.c b/security/safesetid/lsm.c index 1ba564f097f5..d5fb949050dd 100644 --- a/security/safesetid/lsm.c +++ b/security/safesetid/lsm.c @@ -287,6 +287,7 @@ static int __init safesetid_security_init(void) } DEFINE_LSM(safesetid_security_init) = { + .id = &safesetid_lsmid, .init = safesetid_security_init, - .name = "safesetid", + .initcall_fs = safesetid_init_securityfs, }; diff --git a/security/safesetid/lsm.h b/security/safesetid/lsm.h index d346f4849cea..bf5172e2c3f7 100644 --- a/security/safesetid/lsm.h +++ b/security/safesetid/lsm.h @@ -70,4 +70,6 @@ enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy, extern struct setid_ruleset __rcu *safesetid_setuid_rules; extern struct setid_ruleset __rcu *safesetid_setgid_rules; +int safesetid_init_securityfs(void); + #endif /* _SAFESETID_H */ diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c index 8e1ffd70b18a..a71e548065a9 100644 --- a/security/safesetid/securityfs.c +++ b/security/safesetid/securityfs.c @@ -118,7 +118,7 @@ static int verify_ruleset(struct setid_ruleset *pol) res = -EINVAL; /* fix it up */ - nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL); + nrule = kmalloc_obj(struct setid_rule); if (!nrule) return -ENOMEM; if (pol->type == UID){ @@ -146,7 +146,7 @@ static ssize_t handle_policy_update(struct file *file, if (len >= KMALLOC_MAX_SIZE) return -EINVAL; - pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL); + pol = kmalloc_obj(struct setid_ruleset); if (!pol) return -ENOMEM; pol->policy_str = NULL; @@ -175,7 +175,7 @@ static ssize_t handle_policy_update(struct file *file, } *end = '\0'; - rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL); + rule = kmalloc_obj(struct setid_rule); if (!rule) { err = -ENOMEM; goto out_free_buf; @@ -308,7 +308,7 @@ static const struct file_operations safesetid_gid_file_fops = { .write = safesetid_gid_file_write, }; -static int __init safesetid_init_securityfs(void) +int __init safesetid_init_securityfs(void) { int ret; struct dentry *policy_dir; @@ -345,4 +345,3 @@ error: securityfs_remove(policy_dir); return ret; } -fs_initcall(safesetid_init_securityfs); diff --git a/security/security.c b/security/security.c index 143561ebc3e8..4e999f023651 100644 --- a/security/security.c +++ b/security/security.c @@ -32,24 +32,7 @@ #include <net/flow.h> #include <net/sock.h> -#define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX - -/* - * Identifier for the LSM static calls. - * HOOK is an LSM hook as defined in linux/lsm_hookdefs.h - * IDX is the index of the static call. 0 <= NUM < MAX_LSM_COUNT - */ -#define LSM_STATIC_CALL(HOOK, IDX) lsm_static_call_##HOOK##_##IDX - -/* - * Call the macro M for each LSM hook MAX_LSM_COUNT times. - */ -#define LSM_LOOP_UNROLL(M, ...) \ -do { \ - UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) \ -} while (0) - -#define LSM_DEFINE_UNROLL(M, ...) UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) +#include "lsm.h" /* * These are descriptions of the reasons that can be passed to the @@ -78,6 +61,7 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = { [LOCKDOWN_BPF_WRITE_USER] = "use of bpf to write user RAM", [LOCKDOWN_DBG_WRITE_KERNEL] = "use of kgdb/kdb to write kernel RAM", [LOCKDOWN_RTAS_ERROR_INJECTION] = "RTAS error injection", + [LOCKDOWN_XEN_USER_ACTIONS] = "Xen guest user action", [LOCKDOWN_INTEGRITY_MAX] = "integrity", [LOCKDOWN_KCORE] = "/proc/kcore access", [LOCKDOWN_KPROBES] = "use of kprobes", @@ -90,23 +74,35 @@ const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX + 1] = { [LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality", }; -static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain); +bool lsm_debug __ro_after_init; + +unsigned int lsm_active_cnt __ro_after_init; +const struct lsm_id *lsm_idlist[MAX_LSM_COUNT]; + +struct lsm_blob_sizes blob_sizes; -static struct kmem_cache *lsm_file_cache; -static struct kmem_cache *lsm_inode_cache; +struct kmem_cache *lsm_file_cache; +struct kmem_cache *lsm_backing_file_cache; +struct kmem_cache *lsm_inode_cache; -char *lsm_names; -static struct lsm_blob_sizes blob_sizes __ro_after_init; +#define SECURITY_HOOK_ACTIVE_KEY(HOOK, IDX) security_hook_active_##HOOK##_##IDX -/* Boot-time LSM user choice */ -static __initdata const char *chosen_lsm_order; -static __initdata const char *chosen_major_lsm; +/* + * Identifier for the LSM static calls. + * HOOK is an LSM hook as defined in linux/lsm_hookdefs.h + * IDX is the index of the static call. 0 <= NUM < MAX_LSM_COUNT + */ +#define LSM_STATIC_CALL(HOOK, IDX) lsm_static_call_##HOOK##_##IDX -static __initconst const char *const builtin_lsm_order = CONFIG_LSM; +/* + * Call the macro M for each LSM hook MAX_LSM_COUNT times. + */ +#define LSM_LOOP_UNROLL(M, ...) \ +do { \ + UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) \ +} while (0) -/* Ordered list of LSMs to initialize. */ -static __initdata struct lsm_info *ordered_lsms[MAX_LSM_COUNT + 1]; -static __initdata struct lsm_info *exclusive; +#define LSM_DEFINE_UNROLL(M, ...) UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__) #ifdef CONFIG_HAVE_STATIC_CALL #define LSM_HOOK_TRAMP(NAME, NUM) \ @@ -121,7 +117,7 @@ static __initdata struct lsm_info *exclusive; #define DEFINE_LSM_STATIC_CALL(NUM, NAME, RET, ...) \ DEFINE_STATIC_CALL_NULL(LSM_STATIC_CALL(NAME, NUM), \ *((RET(*)(__VA_ARGS__))NULL)); \ - DEFINE_STATIC_KEY_FALSE(SECURITY_HOOK_ACTIVE_KEY(NAME, NUM)); + static DEFINE_STATIC_KEY_FALSE(SECURITY_HOOK_ACTIVE_KEY(NAME, NUM)); #define LSM_HOOK(RET, DEFAULT, NAME, ...) \ LSM_DEFINE_UNROLL(DEFINE_LSM_STATIC_CALL, NAME, RET, __VA_ARGS__) @@ -157,512 +153,50 @@ struct lsm_static_calls_table #undef INIT_LSM_STATIC_CALL }; -static __initdata bool debug; -#define init_debug(...) \ - do { \ - if (debug) \ - pr_info(__VA_ARGS__); \ - } while (0) - -static bool __init is_enabled(struct lsm_info *lsm) -{ - if (!lsm->enabled) - return false; - - return *lsm->enabled; -} - -/* Mark an LSM's enabled flag. */ -static int lsm_enabled_true __initdata = 1; -static int lsm_enabled_false __initdata = 0; -static void __init set_enabled(struct lsm_info *lsm, bool enabled) -{ - /* - * When an LSM hasn't configured an enable variable, we can use - * a hard-coded location for storing the default enabled state. - */ - if (!lsm->enabled) { - if (enabled) - lsm->enabled = &lsm_enabled_true; - else - lsm->enabled = &lsm_enabled_false; - } else if (lsm->enabled == &lsm_enabled_true) { - if (!enabled) - lsm->enabled = &lsm_enabled_false; - } else if (lsm->enabled == &lsm_enabled_false) { - if (enabled) - lsm->enabled = &lsm_enabled_true; - } else { - *lsm->enabled = enabled; - } -} - -/* Is an LSM already listed in the ordered LSMs list? */ -static bool __init exists_ordered_lsm(struct lsm_info *lsm) -{ - struct lsm_info **check; - - for (check = ordered_lsms; *check; check++) - if (*check == lsm) - return true; - - return false; -} - -/* Append an LSM to the list of ordered LSMs to initialize. */ -static int last_lsm __initdata; -static void __init append_ordered_lsm(struct lsm_info *lsm, const char *from) -{ - /* Ignore duplicate selections. */ - if (exists_ordered_lsm(lsm)) - return; - - if (WARN(last_lsm == MAX_LSM_COUNT, "%s: out of LSM static calls!?\n", from)) - return; - - /* Enable this LSM, if it is not already set. */ - if (!lsm->enabled) - lsm->enabled = &lsm_enabled_true; - ordered_lsms[last_lsm++] = lsm; - - init_debug("%s ordered: %s (%s)\n", from, lsm->name, - is_enabled(lsm) ? "enabled" : "disabled"); -} - -/* Is an LSM allowed to be initialized? */ -static bool __init lsm_allowed(struct lsm_info *lsm) -{ - /* Skip if the LSM is disabled. */ - if (!is_enabled(lsm)) - return false; - - /* Not allowed if another exclusive LSM already initialized. */ - if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && exclusive) { - init_debug("exclusive disabled: %s\n", lsm->name); - return false; - } - - return true; -} - -static void __init lsm_set_blob_size(int *need, int *lbs) -{ - int offset; - - if (*need <= 0) - return; - - offset = ALIGN(*lbs, sizeof(void *)); - *lbs = offset + *need; - *need = offset; -} - -static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed) -{ - if (!needed) - return; - - lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred); - lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file); - lsm_set_blob_size(&needed->lbs_ib, &blob_sizes.lbs_ib); - /* - * The inode blob gets an rcu_head in addition to - * what the modules might need. - */ - if (needed->lbs_inode && blob_sizes.lbs_inode == 0) - blob_sizes.lbs_inode = sizeof(struct rcu_head); - lsm_set_blob_size(&needed->lbs_inode, &blob_sizes.lbs_inode); - lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc); - lsm_set_blob_size(&needed->lbs_key, &blob_sizes.lbs_key); - lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg); - lsm_set_blob_size(&needed->lbs_perf_event, &blob_sizes.lbs_perf_event); - lsm_set_blob_size(&needed->lbs_sock, &blob_sizes.lbs_sock); - lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock); - lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task); - lsm_set_blob_size(&needed->lbs_tun_dev, &blob_sizes.lbs_tun_dev); - lsm_set_blob_size(&needed->lbs_xattr_count, - &blob_sizes.lbs_xattr_count); - lsm_set_blob_size(&needed->lbs_bdev, &blob_sizes.lbs_bdev); -} - -/* Prepare LSM for initialization. */ -static void __init prepare_lsm(struct lsm_info *lsm) -{ - int enabled = lsm_allowed(lsm); - - /* Record enablement (to handle any following exclusive LSMs). */ - set_enabled(lsm, enabled); - - /* If enabled, do pre-initialization work. */ - if (enabled) { - if ((lsm->flags & LSM_FLAG_EXCLUSIVE) && !exclusive) { - exclusive = lsm; - init_debug("exclusive chosen: %s\n", lsm->name); - } - - lsm_set_blob_sizes(lsm->blobs); - } -} - -/* Initialize a given LSM, if it is enabled. */ -static void __init initialize_lsm(struct lsm_info *lsm) -{ - if (is_enabled(lsm)) { - int ret; - - init_debug("initializing %s\n", lsm->name); - ret = lsm->init(); - WARN(ret, "%s failed to initialize: %d\n", lsm->name, ret); - } -} - -/* - * Current index to use while initializing the lsm id list. - */ -u32 lsm_active_cnt __ro_after_init; -const struct lsm_id *lsm_idlist[MAX_LSM_COUNT]; - -/* Populate ordered LSMs list from comma-separated LSM name list. */ -static void __init ordered_lsm_parse(const char *order, const char *origin) -{ - struct lsm_info *lsm; - char *sep, *name, *next; - - /* LSM_ORDER_FIRST is always first. */ - for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) { - if (lsm->order == LSM_ORDER_FIRST) - append_ordered_lsm(lsm, " first"); - } - - /* Process "security=", if given. */ - if (chosen_major_lsm) { - struct lsm_info *major; - - /* - * To match the original "security=" behavior, this - * explicitly does NOT fallback to another Legacy Major - * if the selected one was separately disabled: disable - * all non-matching Legacy Major LSMs. - */ - for (major = __start_lsm_info; major < __end_lsm_info; - major++) { - if ((major->flags & LSM_FLAG_LEGACY_MAJOR) && - strcmp(major->name, chosen_major_lsm) != 0) { - set_enabled(major, false); - init_debug("security=%s disabled: %s (only one legacy major LSM)\n", - chosen_major_lsm, major->name); - } - } - } - - sep = kstrdup(order, GFP_KERNEL); - next = sep; - /* Walk the list, looking for matching LSMs. */ - while ((name = strsep(&next, ",")) != NULL) { - bool found = false; - - for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) { - if (strcmp(lsm->name, name) == 0) { - if (lsm->order == LSM_ORDER_MUTABLE) - append_ordered_lsm(lsm, origin); - found = true; - } - } - - if (!found) - init_debug("%s ignored: %s (not built into kernel)\n", - origin, name); - } - - /* Process "security=", if given. */ - if (chosen_major_lsm) { - for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) { - if (exists_ordered_lsm(lsm)) - continue; - if (strcmp(lsm->name, chosen_major_lsm) == 0) - append_ordered_lsm(lsm, "security="); - } - } - - /* LSM_ORDER_LAST is always last. */ - for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) { - if (lsm->order == LSM_ORDER_LAST) - append_ordered_lsm(lsm, " last"); - } - - /* Disable all LSMs not in the ordered list. */ - for (lsm = __start_lsm_info; lsm < __end_lsm_info; lsm++) { - if (exists_ordered_lsm(lsm)) - continue; - set_enabled(lsm, false); - init_debug("%s skipped: %s (not in requested order)\n", - origin, lsm->name); - } - - kfree(sep); -} - -static void __init lsm_static_call_init(struct security_hook_list *hl) -{ - struct lsm_static_call *scall = hl->scalls; - int i; - - for (i = 0; i < MAX_LSM_COUNT; i++) { - /* Update the first static call that is not used yet */ - if (!scall->hl) { - __static_call_update(scall->key, scall->trampoline, - hl->hook.lsm_func_addr); - scall->hl = hl; - static_branch_enable(scall->active); - return; - } - scall++; - } - panic("%s - Ran out of static slots.\n", __func__); -} - -static void __init lsm_early_cred(struct cred *cred); -static void __init lsm_early_task(struct task_struct *task); - -static int lsm_append(const char *new, char **result); - -static void __init report_lsm_order(void) -{ - struct lsm_info **lsm, *early; - int first = 0; - - pr_info("initializing lsm="); - - /* Report each enabled LSM name, comma separated. */ - for (early = __start_early_lsm_info; - early < __end_early_lsm_info; early++) - if (is_enabled(early)) - pr_cont("%s%s", first++ == 0 ? "" : ",", early->name); - for (lsm = ordered_lsms; *lsm; lsm++) - if (is_enabled(*lsm)) - pr_cont("%s%s", first++ == 0 ? "" : ",", (*lsm)->name); - - pr_cont("\n"); -} - -static void __init ordered_lsm_init(void) -{ - struct lsm_info **lsm; - - if (chosen_lsm_order) { - if (chosen_major_lsm) { - pr_warn("security=%s is ignored because it is superseded by lsm=%s\n", - chosen_major_lsm, chosen_lsm_order); - chosen_major_lsm = NULL; - } - ordered_lsm_parse(chosen_lsm_order, "cmdline"); - } else - ordered_lsm_parse(builtin_lsm_order, "builtin"); - - for (lsm = ordered_lsms; *lsm; lsm++) - prepare_lsm(*lsm); - - report_lsm_order(); - - init_debug("cred blob size = %d\n", blob_sizes.lbs_cred); - init_debug("file blob size = %d\n", blob_sizes.lbs_file); - init_debug("ib blob size = %d\n", blob_sizes.lbs_ib); - init_debug("inode blob size = %d\n", blob_sizes.lbs_inode); - init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc); -#ifdef CONFIG_KEYS - init_debug("key blob size = %d\n", blob_sizes.lbs_key); -#endif /* CONFIG_KEYS */ - init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg); - init_debug("sock blob size = %d\n", blob_sizes.lbs_sock); - init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock); - init_debug("perf event blob size = %d\n", blob_sizes.lbs_perf_event); - init_debug("task blob size = %d\n", blob_sizes.lbs_task); - init_debug("tun device blob size = %d\n", blob_sizes.lbs_tun_dev); - init_debug("xattr slots = %d\n", blob_sizes.lbs_xattr_count); - init_debug("bdev blob size = %d\n", blob_sizes.lbs_bdev); - - /* - * Create any kmem_caches needed for blobs - */ - if (blob_sizes.lbs_file) - lsm_file_cache = kmem_cache_create("lsm_file_cache", - blob_sizes.lbs_file, 0, - SLAB_PANIC, NULL); - if (blob_sizes.lbs_inode) - lsm_inode_cache = kmem_cache_create("lsm_inode_cache", - blob_sizes.lbs_inode, 0, - SLAB_PANIC, NULL); - - lsm_early_cred((struct cred *) current->cred); - lsm_early_task(current); - for (lsm = ordered_lsms; *lsm; lsm++) - initialize_lsm(*lsm); -} - -int __init early_security_init(void) -{ - struct lsm_info *lsm; - - for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) { - if (!lsm->enabled) - lsm->enabled = &lsm_enabled_true; - prepare_lsm(lsm); - initialize_lsm(lsm); - } - - return 0; -} - /** - * security_init - initializes the security framework + * lsm_file_alloc - allocate a composite file blob + * @file: the file that needs a blob * - * This should be called early in the kernel initialization sequence. + * Allocate the file blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. */ -int __init security_init(void) +static int lsm_file_alloc(struct file *file) { - struct lsm_info *lsm; - - init_debug("legacy security=%s\n", chosen_major_lsm ? : " *unspecified*"); - init_debug(" CONFIG_LSM=%s\n", builtin_lsm_order); - init_debug("boot arg lsm=%s\n", chosen_lsm_order ? : " *unspecified*"); - - /* - * Append the names of the early LSM modules now that kmalloc() is - * available - */ - for (lsm = __start_early_lsm_info; lsm < __end_early_lsm_info; lsm++) { - init_debug(" early started: %s (%s)\n", lsm->name, - is_enabled(lsm) ? "enabled" : "disabled"); - if (lsm->enabled) - lsm_append(lsm->name, &lsm_names); + if (!lsm_file_cache) { + file->f_security = NULL; + return 0; } - /* Load LSMs in specified order. */ - ordered_lsm_init(); - - return 0; -} - -/* Save user chosen LSM */ -static int __init choose_major_lsm(char *str) -{ - chosen_major_lsm = str; - return 1; -} -__setup("security=", choose_major_lsm); - -/* Explicitly choose LSM initialization order. */ -static int __init choose_lsm_order(char *str) -{ - chosen_lsm_order = str; - return 1; -} -__setup("lsm=", choose_lsm_order); - -/* Enable LSM order debugging. */ -static int __init enable_debug(char *str) -{ - debug = true; - return 1; -} -__setup("lsm.debug", enable_debug); - -static bool match_last_lsm(const char *list, const char *lsm) -{ - const char *last; - - if (WARN_ON(!list || !lsm)) - return false; - last = strrchr(list, ','); - if (last) - /* Pass the comma, strcmp() will check for '\0' */ - last++; - else - last = list; - return !strcmp(last, lsm); -} - -static int lsm_append(const char *new, char **result) -{ - char *cp; - - if (*result == NULL) { - *result = kstrdup(new, GFP_KERNEL); - if (*result == NULL) - return -ENOMEM; - } else { - /* Check if it is the last registered name */ - if (match_last_lsm(*result, new)) - return 0; - cp = kasprintf(GFP_KERNEL, "%s,%s", *result, new); - if (cp == NULL) - return -ENOMEM; - kfree(*result); - *result = cp; - } + file->f_security = kmem_cache_zalloc(lsm_file_cache, GFP_KERNEL); + if (file->f_security == NULL) + return -ENOMEM; return 0; } /** - * security_add_hooks - Add a modules hooks to the hook lists. - * @hooks: the hooks to add - * @count: the number of hooks to add - * @lsmid: the identification information for the security module + * lsm_backing_file_alloc - allocate a composite backing file blob + * @backing_file: the backing file + * + * Allocate the backing file blob for all the modules. * - * Each LSM has to register its hooks with the infrastructure. + * Returns 0, or -ENOMEM if memory can't be allocated. */ -void __init security_add_hooks(struct security_hook_list *hooks, int count, - const struct lsm_id *lsmid) +static int lsm_backing_file_alloc(struct file *backing_file) { - int i; - - /* - * A security module may call security_add_hooks() more - * than once during initialization, and LSM initialization - * is serialized. Landlock is one such case. - * Look at the previous entry, if there is one, for duplication. - */ - if (lsm_active_cnt == 0 || lsm_idlist[lsm_active_cnt - 1] != lsmid) { - if (lsm_active_cnt >= MAX_LSM_COUNT) - panic("%s Too many LSMs registered.\n", __func__); - lsm_idlist[lsm_active_cnt++] = lsmid; - } - - for (i = 0; i < count; i++) { - hooks[i].lsmid = lsmid; - lsm_static_call_init(&hooks[i]); - } + void *blob; - /* - * Don't try to append during early_security_init(), we'll come back - * and fix this up afterwards. - */ - if (slab_is_available()) { - if (lsm_append(lsmid->name, &lsm_names) < 0) - panic("%s - Cannot get early memory.\n", __func__); + if (!lsm_backing_file_cache) { + backing_file_set_security(backing_file, NULL); + return 0; } -} -int call_blocking_lsm_notifier(enum lsm_event event, void *data) -{ - return blocking_notifier_call_chain(&blocking_lsm_notifier_chain, - event, data); -} -EXPORT_SYMBOL(call_blocking_lsm_notifier); - -int register_blocking_lsm_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_register(&blocking_lsm_notifier_chain, - nb); -} -EXPORT_SYMBOL(register_blocking_lsm_notifier); - -int unregister_blocking_lsm_notifier(struct notifier_block *nb) -{ - return blocking_notifier_chain_unregister(&blocking_lsm_notifier_chain, - nb); + blob = kmem_cache_zalloc(lsm_backing_file_cache, GFP_KERNEL); + backing_file_set_security(backing_file, blob); + if (!blob) + return -ENOMEM; + return 0; } -EXPORT_SYMBOL(unregister_blocking_lsm_notifier); /** * lsm_blob_alloc - allocate a composite blob @@ -696,47 +230,12 @@ static int lsm_blob_alloc(void **dest, size_t size, gfp_t gfp) * * Returns 0, or -ENOMEM if memory can't be allocated. */ -static int lsm_cred_alloc(struct cred *cred, gfp_t gfp) +int lsm_cred_alloc(struct cred *cred, gfp_t gfp) { return lsm_blob_alloc(&cred->security, blob_sizes.lbs_cred, gfp); } /** - * lsm_early_cred - during initialization allocate a composite cred blob - * @cred: the cred that needs a blob - * - * Allocate the cred blob for all the modules - */ -static void __init lsm_early_cred(struct cred *cred) -{ - int rc = lsm_cred_alloc(cred, GFP_KERNEL); - - if (rc) - panic("%s: Early cred alloc failed.\n", __func__); -} - -/** - * lsm_file_alloc - allocate a composite file blob - * @file: the file that needs a blob - * - * Allocate the file blob for all the modules - * - * Returns 0, or -ENOMEM if memory can't be allocated. - */ -static int lsm_file_alloc(struct file *file) -{ - if (!lsm_file_cache) { - file->f_security = NULL; - return 0; - } - - file->f_security = kmem_cache_zalloc(lsm_file_cache, GFP_KERNEL); - if (file->f_security == NULL) - return -ENOMEM; - return 0; -} - -/** * lsm_inode_alloc - allocate a composite inode blob * @inode: the inode that needs a blob * @gfp: allocation flags @@ -766,7 +265,7 @@ static int lsm_inode_alloc(struct inode *inode, gfp_t gfp) * * Returns 0, or -ENOMEM if memory can't be allocated. */ -static int lsm_task_alloc(struct task_struct *task) +int lsm_task_alloc(struct task_struct *task) { return lsm_blob_alloc(&task->security, blob_sizes.lbs_task, GFP_KERNEL); } @@ -823,31 +322,50 @@ static int lsm_msg_msg_alloc(struct msg_msg *mp) */ static int lsm_bdev_alloc(struct block_device *bdev) { - if (blob_sizes.lbs_bdev == 0) { - bdev->bd_security = NULL; - return 0; - } - - bdev->bd_security = kzalloc(blob_sizes.lbs_bdev, GFP_KERNEL); - if (!bdev->bd_security) - return -ENOMEM; + return lsm_blob_alloc(&bdev->bd_security, blob_sizes.lbs_bdev, + GFP_KERNEL); +} - return 0; +#ifdef CONFIG_BPF_SYSCALL +/** + * lsm_bpf_map_alloc - allocate a composite bpf_map blob + * @map: the bpf_map that needs a blob + * + * Allocate the bpf_map blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. + */ +static int lsm_bpf_map_alloc(struct bpf_map *map) +{ + return lsm_blob_alloc(&map->security, blob_sizes.lbs_bpf_map, GFP_KERNEL); } /** - * lsm_early_task - during initialization allocate a composite task blob - * @task: the task that needs a blob + * lsm_bpf_prog_alloc - allocate a composite bpf_prog blob + * @prog: the bpf_prog that needs a blob * - * Allocate the task blob for all the modules + * Allocate the bpf_prog blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. */ -static void __init lsm_early_task(struct task_struct *task) +static int lsm_bpf_prog_alloc(struct bpf_prog *prog) { - int rc = lsm_task_alloc(task); + return lsm_blob_alloc(&prog->aux->security, blob_sizes.lbs_bpf_prog, GFP_KERNEL); +} - if (rc) - panic("%s: Early task alloc failed.\n", __func__); +/** + * lsm_bpf_token_alloc - allocate a composite bpf_token blob + * @token: the bpf_token that needs a blob + * + * Allocate the bpf_token blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. + */ +static int lsm_bpf_token_alloc(struct bpf_token *token) +{ + return lsm_blob_alloc(&token->security, blob_sizes.lbs_bpf_token, GFP_KERNEL); } +#endif /* CONFIG_BPF_SYSCALL */ /** * lsm_superblock_alloc - allocate a composite superblock blob @@ -1775,7 +1293,7 @@ EXPORT_SYMBOL(security_dentry_init_security); * Return: Returns 0 on success, error on failure. */ int security_dentry_create_files_as(struct dentry *dentry, int mode, - struct qstr *name, + const struct qstr *name, const struct cred *old, struct cred *new) { return call_int_hook(dentry_create_files_as, dentry, mode, @@ -2181,7 +1699,7 @@ int security_inode_symlink(struct inode *dir, struct dentry *dentry, } /** - * security_inode_mkdir() - Check if creation a new director is allowed + * security_inode_mkdir() - Check if creating a new directory is allowed * @dir: parent directory * @dentry: new directory * @mode: new directory mode @@ -2623,6 +2141,36 @@ void security_inode_post_removexattr(struct dentry *dentry, const char *name) } /** + * security_inode_file_setattr() - check if setting fsxattr is allowed + * @dentry: file to set filesystem extended attributes on + * @fa: extended attributes to set on the inode + * + * Called when file_setattr() syscall or FS_IOC_FSSETXATTR ioctl() is called on + * inode + * + * Return: Returns 0 if permission is granted. + */ +int security_inode_file_setattr(struct dentry *dentry, struct file_kattr *fa) +{ + return call_int_hook(inode_file_setattr, dentry, fa); +} + +/** + * security_inode_file_getattr() - check if retrieving fsxattr is allowed + * @dentry: file to retrieve filesystem extended attributes from + * @fa: extended attributes to get + * + * Called when file_getattr() syscall or FS_IOC_FSGETXATTR ioctl() is called on + * inode + * + * Return: Returns 0 if permission is granted. + */ +int security_inode_file_getattr(struct dentry *dentry, struct file_kattr *fa) +{ + return call_int_hook(inode_file_getattr, dentry, fa); +} + +/** * security_inode_need_killpriv() - Check if security_inode_killpriv() required * @dentry: associated dentry * @@ -2896,6 +2444,57 @@ void security_file_free(struct file *file) } /** + * security_backing_file_alloc() - Allocate and setup a backing file blob + * @backing_file: the backing file + * @user_file: the associated user visible file + * + * Allocate a backing file LSM blob and perform any necessary initialization of + * the LSM blob. There will be some operations where the LSM will not have + * access to @user_file after this point, so any important state associated + * with @user_file that is important to the LSM should be captured in the + * backing file's LSM blob. + * + * LSM's should avoid taking a reference to @user_file in this hook as it will + * result in problems later when the system attempts to drop/put the file + * references due to a circular dependency. + * + * Return: Return 0 if the hook is successful, negative values otherwise. + */ +int security_backing_file_alloc(struct file *backing_file, + const struct file *user_file) +{ + int rc; + + rc = lsm_backing_file_alloc(backing_file); + if (rc) + return rc; + rc = call_int_hook(backing_file_alloc, backing_file, user_file); + if (unlikely(rc)) + security_backing_file_free(backing_file); + + return rc; +} + +/** + * security_backing_file_free() - Free a backing file blob + * @backing_file: the backing file + * + * Free any LSM state associate with a backing file's LSM blob, including the + * blob itself. + */ +void security_backing_file_free(struct file *backing_file) +{ + void *blob = backing_file_security(backing_file); + + call_void_hook(backing_file_free, backing_file); + + if (blob) { + backing_file_set_security(backing_file, NULL); + kmem_cache_free(lsm_backing_file_cache, blob); + } +} + +/** * security_file_ioctl() - Check if an ioctl is allowed * @file: associated file * @cmd: ioctl cmd @@ -2984,6 +2583,32 @@ int security_mmap_file(struct file *file, unsigned long prot, } /** + * security_mmap_backing_file - Check if mmap'ing a backing file is allowed + * @vma: the vm_area_struct for the mmap'd region + * @backing_file: the backing file being mmap'd + * @user_file: the user file being mmap'd + * + * Check permissions for a mmap operation on a stacked filesystem. This hook + * is called after the security_mmap_file() and is responsible for authorizing + * the mmap on @backing_file. It is important to note that the mmap operation + * on @user_file has already been authorized and the @vma->vm_file has been + * set to @backing_file. + * + * Return: Returns 0 if permission is granted. + */ +int security_mmap_backing_file(struct vm_area_struct *vma, + struct file *backing_file, + struct file *user_file) +{ + /* recommended by the stackable filesystem devs */ + if (WARN_ON_ONCE(!(backing_file->f_mode & FMODE_BACKING))) + return -EIO; + + return call_int_hook(mmap_backing_file, vma, backing_file, user_file); +} +EXPORT_SYMBOL_GPL(security_mmap_backing_file); + +/** * security_mmap_addr() - Check if mmap'ing an address is allowed * @addr: address * @@ -3155,7 +2780,7 @@ int security_file_truncate(struct file *file) * * Return: Returns a zero on success, negative values on failure. */ -int security_task_alloc(struct task_struct *task, unsigned long clone_flags) +int security_task_alloc(struct task_struct *task, u64 clone_flags) { int rc = lsm_task_alloc(task); @@ -4277,24 +3902,6 @@ int security_setprocattr(int lsmid, const char *name, void *value, size_t size) } /** - * security_netlink_send() - Save info and check if netlink sending is allowed - * @sk: sending socket - * @skb: netlink message - * - * Save security information for a netlink message so that permission checking - * can be performed when the message is processed. The security information - * can be saved using the eff_cap field of the netlink_skb_parms structure. - * Also may be used to provide fine grained control over message transmission. - * - * Return: Returns 0 if the information was successfully saved and message is - * allowed to be transmitted. - */ -int security_netlink_send(struct sock *sk, struct sk_buff *skb) -{ - return call_int_hook(netlink_send, sk, skb); -} - -/** * security_ismaclabel() - Check if the named attribute is a MAC label * @name: full extended attribute name * @@ -4330,17 +3937,31 @@ EXPORT_SYMBOL(security_secid_to_secctx); * security_lsmprop_to_secctx() - Convert a lsm_prop to a secctx * @prop: lsm specific information * @cp: the LSM context + * @lsmid: which security module to report * * Convert a @prop entry to security context. If @cp is NULL the * length of the result will be returned. This does mean that the * length could change between calls to check the length and the * next call which actually allocates and returns the @cp. * + * @lsmid identifies which LSM should supply the context. + * A value of LSM_ID_UNDEF indicates that the first LSM suppling + * the hook should be used. This is used in cases where the + * ID of the supplying LSM is unambiguous. + * * Return: Return length of data on success, error on failure. */ -int security_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp) +int security_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp, + int lsmid) { - return call_int_hook(lsmprop_to_secctx, prop, cp); + struct lsm_static_call *scall; + + lsm_for_each_hook(scall, lsmprop_to_secctx) { + if (lsmid != LSM_ID_UNDEF && lsmid != scall->hl->lsmid->id) + continue; + return scall->hl->hook.lsmprop_to_secctx(prop, cp); + } + return LSM_RET_DEFAULT(lsmprop_to_secctx); } EXPORT_SYMBOL(security_lsmprop_to_secctx); @@ -4484,6 +4105,24 @@ int security_watch_key(struct key *key) #ifdef CONFIG_SECURITY_NETWORK /** + * security_netlink_send() - Save info and check if netlink sending is allowed + * @sk: sending socket + * @skb: netlink message + * + * Save security information for a netlink message so that permission checking + * can be performed when the message is processed. The security information + * can be saved using the eff_cap field of the netlink_skb_parms structure. + * Also may be used to provide fine grained control over message transmission. + * + * Return: Returns 0 if the information was successfully saved and message is + * allowed to be transmitted. + */ +int security_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + return call_int_hook(netlink_send, sk, skb); +} + +/** * security_unix_stream_connect() - Check if a AF_UNIX stream is allowed * @sock: originating sock * @other: peer sock @@ -5195,6 +4834,26 @@ int security_mptcp_add_subflow(struct sock *sk, struct sock *ssk) #endif /* CONFIG_SECURITY_NETWORK */ +#if defined(CONFIG_SECURITY_NETWORK) && defined(CONFIG_SECURITY_PATH) +/** + * security_unix_find() - Check if a named AF_UNIX socket can connect + * @path: path of the socket being connected to + * @other: peer sock + * @flags: flags associated with the socket + * + * This hook is called to check permissions before connecting to a named + * AF_UNIX socket. The caller does not hold any locks on @other. + * + * Return: Returns 0 if permission is granted. + */ +int security_unix_find(const struct path *path, struct sock *other, int flags) +{ + return call_int_hook(unix_find, path, other, flags); +} +EXPORT_SYMBOL(security_unix_find); + +#endif /* CONFIG_SECURITY_NETWORK && CONFIG_SECURITY_PATH */ + #ifdef CONFIG_SECURITY_INFINIBAND /** * security_ib_pkey_access() - Check if access to an IB pkey is allowed @@ -5627,6 +5286,7 @@ int security_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, * @cmd: command * @attr: bpf attribute * @size: size + * @kernel: whether or not call originated from kernel * * Do a initial check for all bpf syscalls after the attribute is copied into * the kernel. The actual security module can implement their own rules to @@ -5634,9 +5294,9 @@ int security_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, * * Return: Returns 0 if permission is granted. */ -int security_bpf(int cmd, union bpf_attr *attr, unsigned int size) +int security_bpf(int cmd, union bpf_attr *attr, unsigned int size, bool kernel) { - return call_int_hook(bpf, cmd, attr, size); + return call_int_hook(bpf, cmd, attr, size, kernel); } /** @@ -5673,6 +5333,7 @@ int security_bpf_prog(struct bpf_prog *prog) * @map: BPF map object * @attr: BPF syscall attributes used to create BPF map * @token: BPF token used to grant user access + * @kernel: whether or not call originated from kernel * * Do a check when the kernel creates a new BPF map. This is also the * point where LSM blob is allocated for LSMs that need them. @@ -5680,9 +5341,18 @@ int security_bpf_prog(struct bpf_prog *prog) * Return: Returns 0 on success, error on failure. */ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, - struct bpf_token *token) + struct bpf_token *token, bool kernel) { - return call_int_hook(bpf_map_create, map, attr, token); + int rc; + + rc = lsm_bpf_map_alloc(map); + if (unlikely(rc)) + return rc; + + rc = call_int_hook(bpf_map_create, map, attr, token, kernel); + if (unlikely(rc)) + security_bpf_map_free(map); + return rc; } /** @@ -5690,6 +5360,7 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, * @prog: BPF program object * @attr: BPF syscall attributes used to create BPF program * @token: BPF token used to grant user access to BPF subsystem + * @kernel: whether or not call originated from kernel * * Perform an access control check when the kernel loads a BPF program and * allocates associated BPF program object. This hook is also responsible for @@ -5698,9 +5369,18 @@ int security_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, * Return: Returns 0 on success, error on failure. */ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, - struct bpf_token *token) + struct bpf_token *token, bool kernel) { - return call_int_hook(bpf_prog_load, prog, attr, token); + int rc; + + rc = lsm_bpf_prog_alloc(prog); + if (unlikely(rc)) + return rc; + + rc = call_int_hook(bpf_prog_load, prog, attr, token, kernel); + if (unlikely(rc)) + security_bpf_prog_free(prog); + return rc; } /** @@ -5717,7 +5397,16 @@ int security_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, int security_bpf_token_create(struct bpf_token *token, union bpf_attr *attr, const struct path *path) { - return call_int_hook(bpf_token_create, token, attr, path); + int rc; + + rc = lsm_bpf_token_alloc(token); + if (unlikely(rc)) + return rc; + + rc = call_int_hook(bpf_token_create, token, attr, path); + if (unlikely(rc)) + security_bpf_token_free(token); + return rc; } /** @@ -5761,6 +5450,8 @@ int security_bpf_token_capable(const struct bpf_token *token, int cap) void security_bpf_map_free(struct bpf_map *map) { call_void_hook(bpf_map_free, map); + kfree(map->security); + map->security = NULL; } /** @@ -5772,6 +5463,8 @@ void security_bpf_map_free(struct bpf_map *map) void security_bpf_prog_free(struct bpf_prog *prog) { call_void_hook(bpf_prog_free, prog); + kfree(prog->aux->security); + prog->aux->security = NULL; } /** @@ -5783,6 +5476,8 @@ void security_bpf_prog_free(struct bpf_prog *prog) void security_bpf_token_free(struct bpf_token *token) { call_void_hook(bpf_token_free, token); + kfree(token->security); + token->security = NULL; } #endif /* CONFIG_BPF_SYSCALL */ @@ -5883,16 +5578,15 @@ EXPORT_SYMBOL(security_bdev_setintegrity); #ifdef CONFIG_PERF_EVENTS /** * security_perf_event_open() - Check if a perf event open is allowed - * @attr: perf event attribute * @type: type of event * * Check whether the @type of perf_event_open syscall is allowed. * * Return: Returns 0 if permission is granted. */ -int security_perf_event_open(struct perf_event_attr *attr, int type) +int security_perf_event_open(int type) { - return call_int_hook(perf_event_open, attr, type); + return call_int_hook(perf_event_open, type); } /** @@ -5999,6 +5693,18 @@ int security_uring_cmd(struct io_uring_cmd *ioucmd) { return call_int_hook(uring_cmd, ioucmd); } + +/** + * security_uring_allowed() - Check if io_uring_setup() is allowed + * + * Check whether the current task is allowed to call io_uring_setup(). + * + * Return: Returns 0 if permission is granted. + */ +int security_uring_allowed(void) +{ + return call_int_hook(uring_allowed); +} #endif /* CONFIG_IO_URING */ /** diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig index 61abc1e094a8..5588c4d573f6 100644 --- a/security/selinux/Kconfig +++ b/security/selinux/Kconfig @@ -69,6 +69,17 @@ config SECURITY_SELINUX_SID2STR_CACHE_SIZE If unsure, keep the default value. +config SECURITY_SELINUX_AVC_HASH_BITS + int "SELinux avc hashtable size" + depends on SECURITY_SELINUX + range 9 14 + default 9 + help + This option sets the number of buckets used in the AVC hash table + to 2^SECURITY_SELINUX_AVC_HASH_BITS. A higher value helps maintain + shorter chain lengths especially when expanding AVC nodes via + /sys/fs/selinux/avc/cache_threshold. + config SECURITY_SELINUX_DEBUG bool "SELinux kernel debugging support" depends on SECURITY_SELINUX diff --git a/security/selinux/Makefile b/security/selinux/Makefile index 66e56e9011df..72d3baf7900c 100644 --- a/security/selinux/Makefile +++ b/security/selinux/Makefile @@ -15,7 +15,7 @@ ccflags-y := -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include ccflags-$(CONFIG_SECURITY_SELINUX_DEBUG) += -DDEBUG selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \ - netnode.o netport.o status.o \ + netnode.o netport.o status.o initcalls.o \ ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \ ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/context.o diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 1f2680bcc43a..813e82bcfc27 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -30,13 +30,14 @@ #include "avc.h" #include "avc_ss.h" #include "classmap.h" +#include "hash.h" #define CREATE_TRACE_POINTS #include <trace/events/avc.h> -#define AVC_CACHE_SLOTS 512 -#define AVC_DEF_CACHE_THRESHOLD 512 -#define AVC_CACHE_RECLAIM 16 +#define AVC_CACHE_SLOTS (1 << CONFIG_SECURITY_SELINUX_AVC_HASH_BITS) +#define AVC_DEF_CACHE_THRESHOLD AVC_CACHE_SLOTS +#define AVC_CACHE_RECLAIM 16 #ifdef CONFIG_SECURITY_SELINUX_AVC_STATS #define avc_cache_stats_incr(field) this_cpu_inc(avc_cache_stats.field) @@ -124,7 +125,7 @@ static struct kmem_cache *avc_xperms_cachep __ro_after_init; static inline u32 avc_hash(u32 ssid, u32 tsid, u16 tclass) { - return (ssid ^ (tsid<<2) ^ (tclass<<4)) & (AVC_CACHE_SLOTS - 1); + return av_hash(ssid, tsid, (u32)tclass, (u32)(AVC_CACHE_SLOTS - 1)); } /** @@ -292,27 +293,26 @@ static struct avc_xperms_decision_node struct avc_xperms_decision_node *xpd_node; struct extended_perms_decision *xpd; - xpd_node = kmem_cache_zalloc(avc_xperms_decision_cachep, - GFP_NOWAIT | __GFP_NOWARN); + xpd_node = kmem_cache_zalloc(avc_xperms_decision_cachep, GFP_NOWAIT); if (!xpd_node) return NULL; xpd = &xpd_node->xpd; if (which & XPERMS_ALLOWED) { xpd->allowed = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT | __GFP_NOWARN); + GFP_NOWAIT); if (!xpd->allowed) goto error; } if (which & XPERMS_AUDITALLOW) { xpd->auditallow = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT | __GFP_NOWARN); + GFP_NOWAIT); if (!xpd->auditallow) goto error; } if (which & XPERMS_DONTAUDIT) { xpd->dontaudit = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT | __GFP_NOWARN); + GFP_NOWAIT); if (!xpd->dontaudit) goto error; } @@ -340,7 +340,7 @@ static struct avc_xperms_node *avc_xperms_alloc(void) { struct avc_xperms_node *xp_node; - xp_node = kmem_cache_zalloc(avc_xperms_cachep, GFP_NOWAIT | __GFP_NOWARN); + xp_node = kmem_cache_zalloc(avc_xperms_cachep, GFP_NOWAIT); if (!xp_node) return xp_node; INIT_LIST_HEAD(&xp_node->xpd_head); @@ -495,7 +495,7 @@ static struct avc_node *avc_alloc_node(void) { struct avc_node *node; - node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT | __GFP_NOWARN); + node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT); if (!node) goto out; @@ -794,7 +794,7 @@ int __init avc_add_callback(int (*callback)(u32 event), u32 events) struct avc_callback_node *c; int rc = 0; - c = kmalloc(sizeof(*c), GFP_KERNEL); + c = kmalloc_obj(*c); if (!c) { rc = -ENOMEM; goto out; @@ -936,7 +936,7 @@ static void avc_flush(void) spin_lock_irqsave(lock, flag); /* - * With preemptable RCU, the outer spinlock does not + * With preemptible RCU, the outer spinlock does not * prevent RCU grace periods from ending. */ rcu_read_lock(); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 7b867dfec88b..0f704380a8c8 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -65,7 +65,6 @@ #include <net/netlink.h> #include <linux/tcp.h> #include <linux/udp.h> -#include <linux/dccp.h> #include <linux/sctp.h> #include <net/sctp/structs.h> #include <linux/quota.h> @@ -94,7 +93,9 @@ #include <linux/fanotify.h> #include <linux/io_uring/cmd.h> #include <uapi/linux/lsm.h> +#include <linux/memfd.h> +#include "initcalls.h" #include "avc.h" #include "objsec.h" #include "netif.h" @@ -211,10 +212,12 @@ static int selinux_lsm_notifier_avc_callback(u32 event) */ static void cred_init_security(void) { - struct task_security_struct *tsec; + struct cred_security_struct *crsec; + + /* NOTE: the lsm framework zeros out the buffer on allocation */ - tsec = selinux_cred(unrcu_pointer(current->real_cred)); - tsec->osid = tsec->sid = SECINITSID_KERNEL; + crsec = selinux_cred(unrcu_pointer(current->real_cred)); + crsec->osid = crsec->sid = SECINITSID_KERNEL; } /* @@ -222,10 +225,10 @@ static void cred_init_security(void) */ static inline u32 cred_sid(const struct cred *cred) { - const struct task_security_struct *tsec; + const struct cred_security_struct *crsec; - tsec = selinux_cred(cred); - return tsec->sid; + crsec = selinux_cred(cred); + return crsec->sid; } static void __ad_net_init(struct common_audit_data *ad, @@ -278,27 +281,21 @@ static int __inode_security_revalidate(struct inode *inode, struct dentry *dentry, bool may_sleep) { - struct inode_security_struct *isec = selinux_inode(inode); + if (!selinux_initialized()) + return 0; - might_sleep_if(may_sleep); + if (may_sleep) + might_sleep(); + else + return -ECHILD; /* - * The check of isec->initialized below is racy but - * inode_doinit_with_dentry() will recheck with - * isec->lock held. + * Check to ensure that an inode's SELinux state is valid and try + * reloading the inode security label if necessary. This will fail if + * @dentry is NULL and no dentry for this inode can be found; in that + * case, continue using the old label. */ - if (selinux_initialized() && - data_race(isec->initialized != LABEL_INITIALIZED)) { - if (!may_sleep) - return -ECHILD; - - /* - * Try reloading the inode security label. This will fail if - * @opt_dentry is NULL and no dentry for this inode can be - * found; in that case, continue using the old label. - */ - inode_doinit_with_dentry(inode, dentry); - } + inode_doinit_with_dentry(inode, dentry); return 0; } @@ -307,41 +304,53 @@ static struct inode_security_struct *inode_security_novalidate(struct inode *ino return selinux_inode(inode); } -static struct inode_security_struct *inode_security_rcu(struct inode *inode, bool rcu) +static inline struct inode_security_struct *inode_security_rcu(struct inode *inode, + bool rcu) { - int error; + int rc; + struct inode_security_struct *isec = selinux_inode(inode); - error = __inode_security_revalidate(inode, NULL, !rcu); - if (error) - return ERR_PTR(error); - return selinux_inode(inode); + /* check below is racy, but revalidate will recheck with lock held */ + if (data_race(likely(isec->initialized == LABEL_INITIALIZED))) + return isec; + rc = __inode_security_revalidate(inode, NULL, !rcu); + if (rc) + return ERR_PTR(rc); + return isec; } /* * Get the security label of an inode. */ -static struct inode_security_struct *inode_security(struct inode *inode) +static inline struct inode_security_struct *inode_security(struct inode *inode) { + struct inode_security_struct *isec = selinux_inode(inode); + + /* check below is racy, but revalidate will recheck with lock held */ + if (data_race(likely(isec->initialized == LABEL_INITIALIZED))) + return isec; __inode_security_revalidate(inode, NULL, true); - return selinux_inode(inode); + return isec; } -static struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry) +static inline struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry) { - struct inode *inode = d_backing_inode(dentry); - - return selinux_inode(inode); + return selinux_inode(d_backing_inode(dentry)); } /* * Get the security label of a dentry's backing inode. */ -static struct inode_security_struct *backing_inode_security(struct dentry *dentry) +static inline struct inode_security_struct *backing_inode_security(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); + struct inode_security_struct *isec = selinux_inode(inode); + /* check below is racy, but revalidate will recheck with lock held */ + if (data_race(likely(isec->initialized == LABEL_INITIALIZED))) + return isec; __inode_security_revalidate(inode, dentry, true); - return selinux_inode(inode); + return isec; } static void inode_free_security(struct inode *inode) @@ -430,15 +439,15 @@ static int may_context_mount_sb_relabel(u32 sid, struct superblock_security_struct *sbsec, const struct cred *cred) { - const struct task_security_struct *tsec = selinux_cred(cred); + const struct cred_security_struct *crsec = selinux_cred(cred); int rc; - rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + rc = avc_has_perm(crsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, FILESYSTEM__RELABELFROM, NULL); if (rc) return rc; - rc = avc_has_perm(tsec->sid, sid, SECCLASS_FILESYSTEM, + rc = avc_has_perm(crsec->sid, sid, SECCLASS_FILESYSTEM, FILESYSTEM__RELABELTO, NULL); return rc; } @@ -447,9 +456,9 @@ static int may_context_mount_inode_relabel(u32 sid, struct superblock_security_struct *sbsec, const struct cred *cred) { - const struct task_security_struct *tsec = selinux_cred(cred); + const struct cred_security_struct *crsec = selinux_cred(cred); int rc; - rc = avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + rc = avc_has_perm(crsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, FILESYSTEM__RELABELFROM, NULL); if (rc) return rc; @@ -469,7 +478,9 @@ static int selinux_is_genfs_special_handling(struct super_block *sb) !strcmp(sb->s_type->name, "rootfs") || (selinux_policycap_cgroupseclabel() && (!strcmp(sb->s_type->name, "cgroup") || - !strcmp(sb->s_type->name, "cgroup2"))); + !strcmp(sb->s_type->name, "cgroup2"))) || + (selinux_policycap_functionfs_seclabel() && + !strcmp(sb->s_type->name, "functionfs")); } static int selinux_is_sblabel_mnt(struct super_block *sb) @@ -726,6 +737,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, goto out; } + sbsec->creator_sid = current_sid(); + if (strcmp(sb->s_type->name, "proc") == 0) sbsec->flags |= SE_SBPROC | SE_SBGENFS; @@ -734,7 +747,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, !strcmp(sb->s_type->name, "binder") || !strcmp(sb->s_type->name, "bpf") || !strcmp(sb->s_type->name, "pstore") || - !strcmp(sb->s_type->name, "securityfs")) + !strcmp(sb->s_type->name, "securityfs") || + (selinux_policycap_functionfs_seclabel() && + !strcmp(sb->s_type->name, "functionfs"))) sbsec->flags |= SE_SBGENFS; if (!strcmp(sb->s_type->name, "sysfs") || @@ -895,6 +910,8 @@ static int selinux_cmp_sb_context(const struct super_block *oldsb, if (oldroot->sid != newroot->sid) goto mismatch; } + if (old->creator_sid != new->creator_sid) + goto mismatch; return 0; mismatch: pr_warn("SELinux: mount invalid. Same superblock, " @@ -954,6 +971,7 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, newsbsec->sid = oldsbsec->sid; newsbsec->def_sid = oldsbsec->def_sid; newsbsec->behavior = oldsbsec->behavior; + newsbsec->creator_sid = oldsbsec->creator_sid; if (newsbsec->behavior == SECURITY_FS_USE_NATIVE && !(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) { @@ -1012,7 +1030,7 @@ static int selinux_add_opt(int token, const char *s, void **mnt_opts) } if (!opts) { - opts = kzalloc(sizeof(*opts), GFP_KERNEL); + opts = kzalloc_obj(*opts); if (!opts) return -ENOMEM; *mnt_opts = opts; @@ -1191,8 +1209,6 @@ static inline u16 socket_type_to_security_class(int family, int type, int protoc return SECCLASS_ICMP_SOCKET; else return SECCLASS_RAWIP_SOCKET; - case SOCK_DCCP: - return SECCLASS_DCCP_SOCKET; default: return SECCLASS_RAWIP_SOCKET; } @@ -1384,7 +1400,7 @@ static int inode_doinit_use_xattr(struct inode *inode, struct dentry *dentry, if (rc < 0) { kfree(context); if (rc != -ENODATA) { - pr_warn("SELinux: %s: getxattr returned %d for dev=%s ino=%ld\n", + pr_warn("SELinux: %s: getxattr returned %d for dev=%s ino=%llu\n", __func__, -rc, inode->i_sb->s_id, inode->i_ino); return rc; } @@ -1396,13 +1412,13 @@ static int inode_doinit_use_xattr(struct inode *inode, struct dentry *dentry, def_sid, GFP_NOFS); if (rc) { char *dev = inode->i_sb->s_id; - unsigned long ino = inode->i_ino; + u64 ino = inode->i_ino; if (rc == -EINVAL) { - pr_notice_ratelimited("SELinux: inode=%lu on dev=%s was found to have an invalid context=%s. This indicates you may need to relabel the inode or the filesystem in question.\n", + pr_notice_ratelimited("SELinux: inode=%llu on dev=%s was found to have an invalid context=%s. This indicates you may need to relabel the inode or the filesystem in question.\n", ino, dev, context); } else { - pr_warn("SELinux: %s: context_to_sid(%s) returned %d for dev=%s ino=%ld\n", + pr_warn("SELinux: %s: context_to_sid(%s) returned %d for dev=%s ino=%llu\n", __func__, context, -rc, dev, ino); } } @@ -1420,7 +1436,8 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent struct dentry *dentry; int rc = 0; - if (isec->initialized == LABEL_INITIALIZED) + /* check below is racy, but we will recheck with lock held */ + if (data_race(isec->initialized == LABEL_INITIALIZED)) return 0; spin_lock(&isec->lock); @@ -1643,7 +1660,6 @@ static int cred_has_capability(const struct cred *cred, break; default: pr_err("SELinux: out of range capability %d\n", cap); - BUG(); return -EINVAL; } @@ -1683,12 +1699,15 @@ static inline int dentry_has_perm(const struct cred *cred, struct dentry *dentry, u32 av) { - struct inode *inode = d_backing_inode(dentry); struct common_audit_data ad; + struct inode *inode = d_backing_inode(dentry); + struct inode_security_struct *isec = selinux_inode(inode); ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; - __inode_security_revalidate(inode, dentry, true); + /* check below is racy, but revalidate will recheck with lock held */ + if (data_race(unlikely(isec->initialized != LABEL_INITIALIZED))) + __inode_security_revalidate(inode, dentry, true); return inode_has_perm(cred, inode, av, &ad); } @@ -1699,12 +1718,15 @@ static inline int path_has_perm(const struct cred *cred, const struct path *path, u32 av) { - struct inode *inode = d_backing_inode(path->dentry); struct common_audit_data ad; + struct inode *inode = d_backing_inode(path->dentry); + struct inode_security_struct *isec = selinux_inode(inode); ad.type = LSM_AUDIT_DATA_PATH; ad.u.path = *path; - __inode_security_revalidate(inode, path->dentry, true); + /* check below is racy, but revalidate will recheck with lock held */ + if (data_race(unlikely(isec->initialized != LABEL_INITIALIZED))) + __inode_security_revalidate(inode, path->dentry, true); return inode_has_perm(cred, inode, av, &ad); } @@ -1724,56 +1746,79 @@ static inline int file_path_has_perm(const struct cred *cred, static int bpf_fd_pass(const struct file *file, u32 sid); #endif -/* Check whether a task can use an open file descriptor to - access an inode in a given way. Check access to the - descriptor itself, and then use dentry_has_perm to - check a particular permission to the file. - Access to the descriptor is implicitly granted if it - has the same SID as the process. If av is zero, then - access to the file is not checked, e.g. for cases - where only the descriptor is affected like seek. */ -static int file_has_perm(const struct cred *cred, - struct file *file, - u32 av) +static int __file_has_perm(const struct cred *cred, const struct file *file, + u32 av, bool bf_user_file) + { - struct file_security_struct *fsec = selinux_file(file); - struct inode *inode = file_inode(file); struct common_audit_data ad; - u32 sid = cred_sid(cred); + struct inode *inode; + u32 ssid = cred_sid(cred); + u32 tsid_fd; int rc; - ad.type = LSM_AUDIT_DATA_FILE; - ad.u.file = file; + if (bf_user_file) { + struct backing_file_security_struct *bfsec; + const struct path *path; - if (sid != fsec->sid) { - rc = avc_has_perm(sid, fsec->sid, - SECCLASS_FD, - FD__USE, - &ad); + if (WARN_ON(!(file->f_mode & FMODE_BACKING))) + return -EIO; + + bfsec = selinux_backing_file(file); + path = backing_file_user_path(file); + tsid_fd = bfsec->uf_sid; + inode = d_inode(path->dentry); + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = *path; + } else { + struct file_security_struct *fsec = selinux_file(file); + + tsid_fd = fsec->sid; + inode = file_inode(file); + + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; + } + + if (ssid != tsid_fd) { + rc = avc_has_perm(ssid, tsid_fd, SECCLASS_FD, FD__USE, &ad); if (rc) - goto out; + return rc; } #ifdef CONFIG_BPF_SYSCALL - rc = bpf_fd_pass(file, cred_sid(cred)); + /* regardless of backing vs user file, use the underlying file here */ + rc = bpf_fd_pass(file, ssid); if (rc) return rc; #endif /* av is zero if only checking access to the descriptor. */ - rc = 0; if (av) - rc = inode_has_perm(cred, inode, av, &ad); + return inode_has_perm(cred, inode, av, &ad); -out: - return rc; + return 0; +} + +/* Check whether a task can use an open file descriptor to + access an inode in a given way. Check access to the + descriptor itself, and then use dentry_has_perm to + check a particular permission to the file. + Access to the descriptor is implicitly granted if it + has the same SID as the process. If av is zero, then + access to the file is not checked, e.g. for cases + where only the descriptor is affected like seek. */ +static inline int file_has_perm(const struct cred *cred, + const struct file *file, u32 av) +{ + return __file_has_perm(cred, file, av, false); } /* * Determine the label for an inode that might be unioned. */ static int -selinux_determine_inode_label(const struct task_security_struct *tsec, +selinux_determine_inode_label(const struct cred_security_struct *crsec, struct inode *dir, const struct qstr *name, u16 tclass, u32 *_new_isid) @@ -1785,11 +1830,11 @@ selinux_determine_inode_label(const struct task_security_struct *tsec, (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { *_new_isid = sbsec->mntpoint_sid; } else if ((sbsec->flags & SBLABEL_MNT) && - tsec->create_sid) { - *_new_isid = tsec->create_sid; + crsec->create_sid) { + *_new_isid = crsec->create_sid; } else { const struct inode_security_struct *dsec = inode_security(dir); - return security_transition_sid(tsec->sid, + return security_transition_sid(crsec->sid, dsec->sid, tclass, name, _new_isid); } @@ -1802,7 +1847,7 @@ static int may_create(struct inode *dir, struct dentry *dentry, u16 tclass) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct cred_security_struct *crsec = selinux_cred(current_cred()); struct inode_security_struct *dsec; struct superblock_security_struct *sbsec; u32 sid, newsid; @@ -1812,7 +1857,7 @@ static int may_create(struct inode *dir, dsec = inode_security(dir); sbsec = selinux_superblock(dir->i_sb); - sid = tsec->sid; + sid = crsec->sid; ad.type = LSM_AUDIT_DATA_DENTRY; ad.u.dentry = dentry; @@ -1823,7 +1868,7 @@ static int may_create(struct inode *dir, if (rc) return rc; - rc = selinux_determine_inode_label(tsec, dir, &dentry->d_name, tclass, + rc = selinux_determine_inode_label(crsec, dir, &dentry->d_name, tclass, &newsid); if (rc) return rc; @@ -2236,8 +2281,8 @@ static u32 ptrace_parent_sid(void) } static int check_nnp_nosuid(const struct linux_binprm *bprm, - const struct task_security_struct *old_tsec, - const struct task_security_struct *new_tsec) + const struct cred_security_struct *old_crsec, + const struct cred_security_struct *new_crsec) { int nnp = (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS); int nosuid = !mnt_may_suid(bprm->file->f_path.mnt); @@ -2247,7 +2292,7 @@ static int check_nnp_nosuid(const struct linux_binprm *bprm, if (!nnp && !nosuid) return 0; /* neither NNP nor nosuid */ - if (new_tsec->sid == old_tsec->sid) + if (new_crsec->sid == old_crsec->sid) return 0; /* No change in credentials */ /* @@ -2262,7 +2307,7 @@ static int check_nnp_nosuid(const struct linux_binprm *bprm, av |= PROCESS2__NNP_TRANSITION; if (nosuid) av |= PROCESS2__NOSUID_TRANSITION; - rc = avc_has_perm(old_tsec->sid, new_tsec->sid, + rc = avc_has_perm(old_crsec->sid, new_crsec->sid, SECCLASS_PROCESS2, av, NULL); if (!rc) return 0; @@ -2273,8 +2318,8 @@ static int check_nnp_nosuid(const struct linux_binprm *bprm, * i.e. SIDs that are guaranteed to only be allowed a subset * of the permissions of the current SID. */ - rc = security_bounded_transition(old_tsec->sid, - new_tsec->sid); + rc = security_bounded_transition(old_crsec->sid, + new_crsec->sid); if (!rc) return 0; @@ -2290,8 +2335,8 @@ static int check_nnp_nosuid(const struct linux_binprm *bprm, static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) { - const struct task_security_struct *old_tsec; - struct task_security_struct *new_tsec; + const struct cred_security_struct *old_crsec; + struct cred_security_struct *new_crsec; struct inode_security_struct *isec; struct common_audit_data ad; struct inode *inode = file_inode(bprm->file); @@ -2300,18 +2345,22 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) /* SELinux context only depends on initial program or script and not * the script interpreter */ - old_tsec = selinux_cred(current_cred()); - new_tsec = selinux_cred(bprm->cred); + old_crsec = selinux_cred(current_cred()); + new_crsec = selinux_cred(bprm->cred); isec = inode_security(inode); + if (WARN_ON(isec->sclass != SECCLASS_FILE && + isec->sclass != SECCLASS_MEMFD_FILE)) + return -EACCES; + /* Default to the current task SID. */ - new_tsec->sid = old_tsec->sid; - new_tsec->osid = old_tsec->sid; + new_crsec->sid = old_crsec->sid; + new_crsec->osid = old_crsec->sid; /* Reset fs, key, and sock SIDs on execve. */ - new_tsec->create_sid = 0; - new_tsec->keycreate_sid = 0; - new_tsec->sockcreate_sid = 0; + new_crsec->create_sid = 0; + new_crsec->keycreate_sid = 0; + new_crsec->sockcreate_sid = 0; /* * Before policy is loaded, label any task outside kernel space @@ -2320,26 +2369,26 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) * (if the policy chooses to set SECINITSID_INIT != SECINITSID_KERNEL). */ if (!selinux_initialized()) { - new_tsec->sid = SECINITSID_INIT; + new_crsec->sid = SECINITSID_INIT; /* also clear the exec_sid just in case */ - new_tsec->exec_sid = 0; + new_crsec->exec_sid = 0; return 0; } - if (old_tsec->exec_sid) { - new_tsec->sid = old_tsec->exec_sid; + if (old_crsec->exec_sid) { + new_crsec->sid = old_crsec->exec_sid; /* Reset exec SID on execve. */ - new_tsec->exec_sid = 0; + new_crsec->exec_sid = 0; /* Fail on NNP or nosuid if not an allowed transition. */ - rc = check_nnp_nosuid(bprm, old_tsec, new_tsec); + rc = check_nnp_nosuid(bprm, old_crsec, new_crsec); if (rc) return rc; } else { /* Check for a default transition on this program. */ - rc = security_transition_sid(old_tsec->sid, + rc = security_transition_sid(old_crsec->sid, isec->sid, SECCLASS_PROCESS, NULL, - &new_tsec->sid); + &new_crsec->sid); if (rc) return rc; @@ -2347,34 +2396,34 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) * Fallback to old SID on NNP or nosuid if not an allowed * transition. */ - rc = check_nnp_nosuid(bprm, old_tsec, new_tsec); + rc = check_nnp_nosuid(bprm, old_crsec, new_crsec); if (rc) - new_tsec->sid = old_tsec->sid; + new_crsec->sid = old_crsec->sid; } ad.type = LSM_AUDIT_DATA_FILE; ad.u.file = bprm->file; - if (new_tsec->sid == old_tsec->sid) { - rc = avc_has_perm(old_tsec->sid, isec->sid, - SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad); + if (new_crsec->sid == old_crsec->sid) { + rc = avc_has_perm(old_crsec->sid, isec->sid, isec->sclass, + FILE__EXECUTE_NO_TRANS, &ad); if (rc) return rc; } else { /* Check permissions for the transition. */ - rc = avc_has_perm(old_tsec->sid, new_tsec->sid, + rc = avc_has_perm(old_crsec->sid, new_crsec->sid, SECCLASS_PROCESS, PROCESS__TRANSITION, &ad); if (rc) return rc; - rc = avc_has_perm(new_tsec->sid, isec->sid, - SECCLASS_FILE, FILE__ENTRYPOINT, &ad); + rc = avc_has_perm(new_crsec->sid, isec->sid, isec->sclass, + FILE__ENTRYPOINT, &ad); if (rc) return rc; /* Check for shared state */ if (bprm->unsafe & LSM_UNSAFE_SHARE) { - rc = avc_has_perm(old_tsec->sid, new_tsec->sid, + rc = avc_has_perm(old_crsec->sid, new_crsec->sid, SECCLASS_PROCESS, PROCESS__SHARE, NULL); if (rc) @@ -2386,7 +2435,7 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) if (bprm->unsafe & LSM_UNSAFE_PTRACE) { u32 ptsid = ptrace_parent_sid(); if (ptsid != 0) { - rc = avc_has_perm(ptsid, new_tsec->sid, + rc = avc_has_perm(ptsid, new_crsec->sid, SECCLASS_PROCESS, PROCESS__PTRACE, NULL); if (rc) @@ -2400,7 +2449,7 @@ static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) /* Enable secure mode for SIDs transitions unless the noatsecure permission is granted between the two SIDs, i.e. ahp returns 0. */ - rc = avc_has_perm(old_tsec->sid, new_tsec->sid, + rc = avc_has_perm(old_crsec->sid, new_crsec->sid, SECCLASS_PROCESS, PROCESS__NOATSECURE, NULL); bprm->secureexec |= !!rc; @@ -2468,12 +2517,12 @@ static inline void flush_unauthorized_files(const struct cred *cred, */ static void selinux_bprm_committing_creds(const struct linux_binprm *bprm) { - struct task_security_struct *new_tsec; + struct cred_security_struct *new_crsec; struct rlimit *rlim, *initrlim; int rc, i; - new_tsec = selinux_cred(bprm->cred); - if (new_tsec->sid == new_tsec->osid) + new_crsec = selinux_cred(bprm->cred); + if (new_crsec->sid == new_crsec->osid) return; /* Close files for which the new task SID is not authorized. */ @@ -2492,7 +2541,7 @@ static void selinux_bprm_committing_creds(const struct linux_binprm *bprm) * higher than the default soft limit for cases where the default is * lower than the hard limit, e.g. RLIMIT_CORE or RLIMIT_STACK. */ - rc = avc_has_perm(new_tsec->osid, new_tsec->sid, SECCLASS_PROCESS, + rc = avc_has_perm(new_crsec->osid, new_crsec->sid, SECCLASS_PROCESS, PROCESS__RLIMITINH, NULL); if (rc) { /* protect against do_prlimit() */ @@ -2514,12 +2563,12 @@ static void selinux_bprm_committing_creds(const struct linux_binprm *bprm) */ static void selinux_bprm_committed_creds(const struct linux_binprm *bprm) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct cred_security_struct *crsec = selinux_cred(current_cred()); u32 osid, sid; int rc; - osid = tsec->osid; - sid = tsec->sid; + osid = crsec->osid; + sid = crsec->sid; if (sid == osid) return; @@ -2565,6 +2614,7 @@ static int selinux_sb_alloc_security(struct super_block *sb) sbsec->sid = SECINITSID_UNLABELED; sbsec->def_sid = SECINITSID_FILE; sbsec->mntpoint_sid = SECINITSID_UNLABELED; + sbsec->creator_sid = SECINITSID_UNLABELED; return 0; } @@ -2796,7 +2846,7 @@ static int selinux_fs_context_submount(struct fs_context *fc, if (!(sbsec->flags & (FSCONTEXT_MNT|CONTEXT_MNT|DEFCONTEXT_MNT))) return 0; - opts = kzalloc(sizeof(*opts), GFP_KERNEL); + opts = kzalloc_obj(*opts); if (!opts) return -ENOMEM; @@ -2890,13 +2940,13 @@ static int selinux_dentry_init_security(struct dentry *dentry, int mode, } static int selinux_dentry_create_files_as(struct dentry *dentry, int mode, - struct qstr *name, + const struct qstr *name, const struct cred *old, struct cred *new) { u32 newsid; int rc; - struct task_security_struct *tsec; + struct cred_security_struct *crsec; rc = selinux_determine_inode_label(selinux_cred(old), d_inode(dentry->d_parent), name, @@ -2905,8 +2955,8 @@ static int selinux_dentry_create_files_as(struct dentry *dentry, int mode, if (rc) return rc; - tsec = selinux_cred(new); - tsec->create_sid = newsid; + crsec = selinux_cred(new); + crsec->create_sid = newsid; return 0; } @@ -2914,9 +2964,9 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr, struct xattr *xattrs, int *xattr_count) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct cred_security_struct *crsec = selinux_cred(current_cred()); struct superblock_security_struct *sbsec; - struct xattr *xattr = lsm_get_xattr_slot(xattrs, xattr_count); + struct xattr *xattr; u32 newsid, clen; u16 newsclass; int rc; @@ -2924,9 +2974,9 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, sbsec = selinux_superblock(dir->i_sb); - newsid = tsec->create_sid; + newsid = crsec->create_sid; newsclass = inode_mode_to_security_class(inode->i_mode); - rc = selinux_determine_inode_label(tsec, dir, qstr, newsclass, &newsid); + rc = selinux_determine_inode_label(crsec, dir, qstr, newsclass, &newsid); if (rc) return rc; @@ -2942,6 +2992,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, !(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; + xattr = lsm_get_xattr_slot(xattrs, xattr_count); if (xattr) { rc = security_sid_to_context_force(newsid, &context, &clen); @@ -2963,10 +3014,18 @@ static int selinux_inode_init_security_anon(struct inode *inode, struct common_audit_data ad; struct inode_security_struct *isec; int rc; + bool is_memfd = false; if (unlikely(!selinux_initialized())) return 0; + if (name != NULL && name->name != NULL && + !strcmp(name->name, MEMFD_ANON_NAME)) { + if (!selinux_policycap_memfd_class()) + return 0; + is_memfd = true; + } + isec = selinux_inode(inode); /* @@ -2986,7 +3045,10 @@ static int selinux_inode_init_security_anon(struct inode *inode, isec->sclass = context_isec->sclass; isec->sid = context_isec->sid; } else { - isec->sclass = SECCLASS_ANON_INODE; + if (is_memfd) + isec->sclass = SECCLASS_MEMFD_FILE; + else + isec->sclass = SECCLASS_ANON_INODE; rc = security_transition_sid( sid, sid, isec->sclass, name, &isec->sid); @@ -3088,44 +3150,148 @@ static noinline int audit_inode_permission(struct inode *inode, audited, denied, result, &ad); } -static int selinux_inode_permission(struct inode *inode, int mask) +/** + * task_avdcache_reset - Reset the task's AVD cache + * @tsec: the task's security state + * + * Clear the task's AVD cache in @tsec and reset it to the current policy's + * and task's info. + */ +static inline void task_avdcache_reset(struct task_security_struct *tsec) { + memset(&tsec->avdcache.dir, 0, sizeof(tsec->avdcache.dir)); + tsec->avdcache.sid = current_sid(); + tsec->avdcache.seqno = avc_policy_seqno(); + tsec->avdcache.dir_spot = TSEC_AVDC_DIR_SIZE - 1; +} + +/** + * task_avdcache_search - Search the task's AVD cache + * @tsec: the task's security state + * @isec: the inode to search for in the cache + * @avdc: matching avd cache entry returned to the caller + * + * Search @tsec for a AVD cache entry that matches @isec and return it to the + * caller via @avdc. Returns 0 if a match is found, negative values otherwise. + */ +static inline int task_avdcache_search(struct task_security_struct *tsec, + struct inode_security_struct *isec, + struct avdc_entry **avdc) +{ + int orig, iter; + + /* focused on path walk optimization, only cache directories */ + if (isec->sclass != SECCLASS_DIR) + return -ENOENT; + + if (unlikely(current_sid() != tsec->avdcache.sid || + tsec->avdcache.seqno != avc_policy_seqno())) { + task_avdcache_reset(tsec); + return -ENOENT; + } + + orig = iter = tsec->avdcache.dir_spot; + do { + if (tsec->avdcache.dir[iter].isid == isec->sid) { + /* cache hit */ + tsec->avdcache.dir_spot = iter; + *avdc = &tsec->avdcache.dir[iter]; + return 0; + } + iter = (iter - 1) & (TSEC_AVDC_DIR_SIZE - 1); + } while (iter != orig); + + return -ENOENT; +} + +/** + * task_avdcache_update - Update the task's AVD cache + * @tsec: the task's security state + * @isec: the inode associated with the cache entry + * @avd: the AVD to cache + * + * Update the AVD cache in @tsec with the @avd info associated + * with @isec. + */ +static inline void task_avdcache_update(struct task_security_struct *tsec, + struct inode_security_struct *isec, + struct av_decision *avd) +{ + int spot; + + /* focused on path walk optimization, only cache directories */ + if (isec->sclass != SECCLASS_DIR) + return; + + /* update cache */ + spot = (tsec->avdcache.dir_spot + 1) & (TSEC_AVDC_DIR_SIZE - 1); + tsec->avdcache.dir_spot = spot; + tsec->avdcache.dir[spot].isid = isec->sid; + tsec->avdcache.dir[spot].avd = *avd; + tsec->avdcache.permissive_neveraudit = + (avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT)); +} + +/** + * selinux_inode_permission - Check if the current task can access an inode + * @inode: the inode that is being accessed + * @requested: the accesses being requested + * + * Check if the current task is allowed to access @inode according to + * @requested. Returns 0 if allowed, negative values otherwise. + */ +static int selinux_inode_permission(struct inode *inode, int requested) +{ + int mask; u32 perms; - bool from_access; - bool no_block = mask & MAY_NOT_BLOCK; - struct inode_security_struct *isec; u32 sid = current_sid(); - struct av_decision avd; + struct task_security_struct *tsec; + struct inode_security_struct *isec; + struct avdc_entry *avdc; + struct av_decision avd, *avdp = &avd; int rc, rc2; u32 audited, denied; - from_access = mask & MAY_ACCESS; - mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); + mask = requested & (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); /* No permission to check. Existence test. */ if (!mask) return 0; - if (unlikely(IS_PRIVATE(inode))) + tsec = selinux_task(current); + if (task_avdcache_permnoaudit(tsec, sid)) return 0; - perms = file_mask_to_av(inode->i_mode, mask); - - isec = inode_security_rcu(inode, no_block); + isec = inode_security_rcu(inode, requested & MAY_NOT_BLOCK); if (IS_ERR(isec)) return PTR_ERR(isec); + perms = file_mask_to_av(inode->i_mode, mask); - rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0, - &avd); - audited = avc_audit_required(perms, &avd, rc, - from_access ? FILE__AUDIT_ACCESS : 0, - &denied); + rc = task_avdcache_search(tsec, isec, &avdc); + if (likely(!rc)) { + /* Cache hit. */ + avdp = &avdc->avd; + denied = perms & ~avdp->allowed; + if (unlikely(denied) && enforcing_enabled() && + !(avdp->flags & AVD_FLAGS_PERMISSIVE)) + rc = -EACCES; + } else { + /* Cache miss. */ + rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, + perms, 0, avdp); + task_avdcache_update(tsec, isec, avdp); + } + + audited = avc_audit_required(perms, avdp, rc, + (requested & MAY_ACCESS) ? + FILE__AUDIT_ACCESS : 0, &denied); if (likely(!audited)) return rc; rc2 = audit_inode_permission(inode, perms, audited, denied, rc); if (rc2) return rc2; + return rc; } @@ -3160,6 +3326,13 @@ static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, static int selinux_inode_getattr(const struct path *path) { + struct task_security_struct *tsec; + + tsec = selinux_task(current); + + if (task_avdcache_permnoaudit(tsec, current_sid())) + return 0; + return path_has_perm(current_cred(), path, FILE__GETATTR); } @@ -3324,7 +3497,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, &newsid); if (rc) { pr_err("SELinux: unable to map context to SID" - "for (%s, %lu), rc=%d\n", + "for (%s, %llu), rc=%d\n", inode->i_sb->s_id, inode->i_ino, -rc); return; } @@ -3366,6 +3539,18 @@ static int selinux_inode_removexattr(struct mnt_idmap *idmap, return -EACCES; } +static int selinux_inode_file_setattr(struct dentry *dentry, + struct file_kattr *fa) +{ + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); +} + +static int selinux_inode_file_getattr(struct dentry *dentry, + struct file_kattr *fa) +{ + return dentry_has_perm(current_cred(), dentry, FILE__GETATTR); +} + static int selinux_path_notify(const struct path *path, u64 mask, unsigned int obj_type) { @@ -3395,6 +3580,9 @@ static int selinux_path_notify(const struct path *path, u64 mask, case FSNOTIFY_OBJ_TYPE_INODE: perm = FILE__WATCH; break; + case FSNOTIFY_OBJ_TYPE_MNTNS: + perm = FILE__WATCH_MOUNTNS; + break; default: return -EINVAL; } @@ -3514,7 +3702,7 @@ static void selinux_inode_getlsmprop(struct inode *inode, struct lsm_prop *prop) static int selinux_inode_copy_up(struct dentry *src, struct cred **new) { struct lsm_prop prop; - struct task_security_struct *tsec; + struct cred_security_struct *crsec; struct cred *new_creds = *new; if (new_creds == NULL) { @@ -3523,10 +3711,10 @@ static int selinux_inode_copy_up(struct dentry *src, struct cred **new) return -ENOMEM; } - tsec = selinux_cred(new_creds); + crsec = selinux_cred(new_creds); /* Get label from overlay inode and set it in create_sid */ selinux_inode_getlsmprop(d_inode(src), &prop); - tsec->create_sid = prop.selinux.secid; + crsec->create_sid = prop.selinux.secid; *new = new_creds; return 0; } @@ -3552,7 +3740,7 @@ static int selinux_inode_copy_up_xattr(struct dentry *dentry, const char *name) static int selinux_kernfs_init_security(struct kernfs_node *kn_dir, struct kernfs_node *kn) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct cred_security_struct *crsec = selinux_cred(current_cred()); u32 parent_sid, newsid, clen; int rc; char *context; @@ -3580,16 +3768,19 @@ static int selinux_kernfs_init_security(struct kernfs_node *kn_dir, if (rc) return rc; - if (tsec->create_sid) { - newsid = tsec->create_sid; + if (crsec->create_sid) { + newsid = crsec->create_sid; } else { u16 secclass = inode_mode_to_security_class(kn->mode); + const char *kn_name; struct qstr q; - q.name = kn->name; - q.hash_len = hashlen_string(kn_dir, kn->name); + /* kn is fresh, can't be renamed, name goes not away */ + kn_name = rcu_dereference_check(kn->name, true); + q.name = kn_name; + q.hash_len = hashlen_string(kn_dir, kn_name); - rc = security_transition_sid(tsec->sid, + rc = security_transition_sid(crsec->sid, parent_sid, secclass, &q, &newsid); if (rc) @@ -3654,6 +3845,17 @@ static int selinux_file_alloc_security(struct file *file) return 0; } +static int selinux_backing_file_alloc(struct file *backing_file, + const struct file *user_file) +{ + struct backing_file_security_struct *bfsec; + + bfsec = selinux_backing_file(backing_file); + bfsec->uf_sid = selinux_file(user_file)->sid; + + return 0; +} + /* * Check whether a task has the ioctl permission and cmd * operation to an inode. @@ -3771,42 +3973,55 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, static int default_noexec __ro_after_init; -static int file_map_prot_check(struct file *file, unsigned long prot, int shared) +static int __file_map_prot_check(const struct cred *cred, + const struct file *file, unsigned long prot, + bool shared, bool bf_user_file) { - const struct cred *cred = current_cred(); - u32 sid = cred_sid(cred); - int rc = 0; + struct inode *inode = NULL; + bool prot_exec = prot & PROT_EXEC; + bool prot_write = prot & PROT_WRITE; + + if (file) { + if (bf_user_file) + inode = d_inode(backing_file_user_path(file)->dentry); + else + inode = file_inode(file); + } + + if (default_noexec && prot_exec && + (!file || IS_PRIVATE(inode) || (!shared && prot_write))) { + int rc; + u32 sid = cred_sid(cred); - if (default_noexec && - (prot & PROT_EXEC) && (!file || IS_PRIVATE(file_inode(file)) || - (!shared && (prot & PROT_WRITE)))) { /* - * We are making executable an anonymous mapping or a - * private file mapping that will also be writable. - * This has an additional check. + * We are making executable an anonymous mapping or a private + * file mapping that will also be writable. */ - rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, - PROCESS__EXECMEM, NULL); + rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM, + NULL); if (rc) - goto error; + return rc; } if (file) { - /* read access is always possible with a mapping */ + /* "read" always possible, "write" only if shared */ u32 av = FILE__READ; - - /* write access only matters if the mapping is shared */ - if (shared && (prot & PROT_WRITE)) + if (shared && prot_write) av |= FILE__WRITE; - - if (prot & PROT_EXEC) + if (prot_exec) av |= FILE__EXECUTE; - return file_has_perm(cred, file, av); + return __file_has_perm(cred, file, av, bf_user_file); } -error: - return rc; + return 0; +} + +static inline int file_map_prot_check(const struct cred *cred, + const struct file *file, + unsigned long prot, bool shared) +{ + return __file_map_prot_check(cred, file, prot, shared, false); } static int selinux_mmap_addr(unsigned long addr) @@ -3822,36 +4037,80 @@ static int selinux_mmap_addr(unsigned long addr) return rc; } -static int selinux_mmap_file(struct file *file, - unsigned long reqprot __always_unused, - unsigned long prot, unsigned long flags) +static int selinux_mmap_file_common(const struct cred *cred, struct file *file, + unsigned long prot, bool shared) { - struct common_audit_data ad; - int rc; - if (file) { + int rc; + struct common_audit_data ad; + ad.type = LSM_AUDIT_DATA_FILE; ad.u.file = file; - rc = inode_has_perm(current_cred(), file_inode(file), - FILE__MAP, &ad); + rc = inode_has_perm(cred, file_inode(file), FILE__MAP, &ad); if (rc) return rc; } - return file_map_prot_check(file, prot, - (flags & MAP_TYPE) == MAP_SHARED); + return file_map_prot_check(cred, file, prot, shared); +} + +static int selinux_mmap_file(struct file *file, + unsigned long reqprot __always_unused, + unsigned long prot, unsigned long flags) +{ + return selinux_mmap_file_common(current_cred(), file, prot, + (flags & MAP_TYPE) == MAP_SHARED); +} + +/** + * selinux_mmap_backing_file - Check mmap permissions on a backing file + * @vma: memory region + * @backing_file: stacked filesystem backing file + * @user_file: user visible file + * + * This is called after selinux_mmap_file() on stacked filesystems, and it + * is this function's responsibility to verify access to @backing_file and + * setup the SELinux state for possible later use in the mprotect() code path. + * + * By the time this function is called, mmap() access to @user_file has already + * been authorized and @vma->vm_file has been set to point to @backing_file. + * + * Return zero on success, negative values otherwise. + */ +static int selinux_mmap_backing_file(struct vm_area_struct *vma, + struct file *backing_file, + struct file *user_file __always_unused) +{ + unsigned long prot = 0; + + /* translate vma->vm_flags perms into PROT perms */ + if (vma->vm_flags & VM_READ) + prot |= PROT_READ; + if (vma->vm_flags & VM_WRITE) + prot |= PROT_WRITE; + if (vma->vm_flags & VM_EXEC) + prot |= PROT_EXEC; + + return selinux_mmap_file_common(backing_file->f_cred, backing_file, + prot, vma->vm_flags & VM_SHARED); } static int selinux_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot __always_unused, unsigned long prot) { + int rc; const struct cred *cred = current_cred(); u32 sid = cred_sid(cred); + const struct file *file = vma->vm_file; + bool backing_file; + bool shared = vma->vm_flags & VM_SHARED; + + /* check if we need to trigger the "backing files are awful" mode */ + backing_file = file && (file->f_mode & FMODE_BACKING); if (default_noexec && (prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) { - int rc = 0; /* * We don't use the vma_is_initial_heap() helper as it has * a history of problems and is currently broken on systems @@ -3865,11 +4124,15 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, vma->vm_end <= vma->vm_mm->brk) { rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__EXECHEAP, NULL); - } else if (!vma->vm_file && (vma_is_initial_stack(vma) || + if (rc) + return rc; + } else if (!file && (vma_is_initial_stack(vma) || vma_is_stack_for_current(vma))) { rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__EXECSTACK, NULL); - } else if (vma->vm_file && vma->anon_vma) { + if (rc) + return rc; + } else if (file && vma->anon_vma) { /* * We are making executable a file mapping that has * had some COW done. Since pages might have been @@ -3877,13 +4140,29 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, * modified content. This typically should only * occur for text relocations. */ - rc = file_has_perm(cred, vma->vm_file, FILE__EXECMOD); + rc = __file_has_perm(cred, file, FILE__EXECMOD, + backing_file); + if (rc) + return rc; + if (backing_file) { + rc = file_has_perm(file->f_cred, file, + FILE__EXECMOD); + if (rc) + return rc; + } } + } + + rc = __file_map_prot_check(cred, file, prot, shared, backing_file); + if (rc) + return rc; + if (backing_file) { + rc = file_map_prot_check(file->f_cred, file, prot, shared); if (rc) return rc; } - return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED); + return 0; } static int selinux_file_lock(struct file *file, unsigned int cmd) @@ -4000,10 +4279,13 @@ static int selinux_file_open(struct file *file) /* task security operations */ static int selinux_task_alloc(struct task_struct *task, - unsigned long clone_flags) + u64 clone_flags) { u32 sid = current_sid(); + struct task_security_struct *old_tsec = selinux_task(current); + struct task_security_struct *new_tsec = selinux_task(task); + *new_tsec = *old_tsec; return avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__FORK, NULL); } @@ -4013,10 +4295,10 @@ static int selinux_task_alloc(struct task_struct *task, static int selinux_cred_prepare(struct cred *new, const struct cred *old, gfp_t gfp) { - const struct task_security_struct *old_tsec = selinux_cred(old); - struct task_security_struct *tsec = selinux_cred(new); + const struct cred_security_struct *old_crsec = selinux_cred(old); + struct cred_security_struct *crsec = selinux_cred(new); - *tsec = *old_tsec; + *crsec = *old_crsec; return 0; } @@ -4025,10 +4307,10 @@ static int selinux_cred_prepare(struct cred *new, const struct cred *old, */ static void selinux_cred_transfer(struct cred *new, const struct cred *old) { - const struct task_security_struct *old_tsec = selinux_cred(old); - struct task_security_struct *tsec = selinux_cred(new); + const struct cred_security_struct *old_crsec = selinux_cred(old); + struct cred_security_struct *crsec = selinux_cred(new); - *tsec = *old_tsec; + *crsec = *old_crsec; } static void selinux_cred_getsecid(const struct cred *c, u32 *secid) @@ -4047,7 +4329,7 @@ static void selinux_cred_getlsmprop(const struct cred *c, struct lsm_prop *prop) */ static int selinux_kernel_act_as(struct cred *new, u32 secid) { - struct task_security_struct *tsec = selinux_cred(new); + struct cred_security_struct *crsec = selinux_cred(new); u32 sid = current_sid(); int ret; @@ -4056,10 +4338,10 @@ static int selinux_kernel_act_as(struct cred *new, u32 secid) KERNEL_SERVICE__USE_AS_OVERRIDE, NULL); if (ret == 0) { - tsec->sid = secid; - tsec->create_sid = 0; - tsec->keycreate_sid = 0; - tsec->sockcreate_sid = 0; + crsec->sid = secid; + crsec->create_sid = 0; + crsec->keycreate_sid = 0; + crsec->sockcreate_sid = 0; } return ret; } @@ -4071,7 +4353,7 @@ static int selinux_kernel_act_as(struct cred *new, u32 secid) static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) { struct inode_security_struct *isec = inode_security(inode); - struct task_security_struct *tsec = selinux_cred(new); + struct cred_security_struct *crsec = selinux_cred(new); u32 sid = current_sid(); int ret; @@ -4081,7 +4363,7 @@ static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) NULL); if (ret == 0) - tsec->create_sid = isec->sid; + crsec->create_sid = isec->sid; return ret; } @@ -4096,7 +4378,7 @@ static int selinux_kernel_module_request(char *kmod_name) SYSTEM__MODULE_REQUEST, &ad); } -static int selinux_kernel_module_from_file(struct file *file) +static int selinux_kernel_load_from_file(struct file *file, u32 requested) { struct common_audit_data ad; struct inode_security_struct *isec; @@ -4104,12 +4386,8 @@ static int selinux_kernel_module_from_file(struct file *file) u32 sid = current_sid(); int rc; - /* init_module */ if (file == NULL) - return avc_has_perm(sid, sid, SECCLASS_SYSTEM, - SYSTEM__MODULE_LOAD, NULL); - - /* finit_module */ + return avc_has_perm(sid, sid, SECCLASS_SYSTEM, requested, NULL); ad.type = LSM_AUDIT_DATA_FILE; ad.u.file = file; @@ -4122,8 +4400,7 @@ static int selinux_kernel_module_from_file(struct file *file) } isec = inode_security(file_inode(file)); - return avc_has_perm(sid, isec->sid, SECCLASS_SYSTEM, - SYSTEM__MODULE_LOAD, &ad); + return avc_has_perm(sid, isec->sid, SECCLASS_SYSTEM, requested, &ad); } static int selinux_kernel_read_file(struct file *file, @@ -4132,9 +4409,31 @@ static int selinux_kernel_read_file(struct file *file, { int rc = 0; + BUILD_BUG_ON_MSG(READING_MAX_ID > 8, + "New kernel_read_file_id introduced; update SELinux!"); + switch (id) { + case READING_FIRMWARE: + rc = selinux_kernel_load_from_file(file, SYSTEM__FIRMWARE_LOAD); + break; case READING_MODULE: - rc = selinux_kernel_module_from_file(contents ? file : NULL); + case READING_MODULE_COMPRESSED: + rc = selinux_kernel_load_from_file(file, SYSTEM__MODULE_LOAD); + break; + case READING_KEXEC_IMAGE: + rc = selinux_kernel_load_from_file(file, + SYSTEM__KEXEC_IMAGE_LOAD); + break; + case READING_KEXEC_INITRAMFS: + rc = selinux_kernel_load_from_file(file, + SYSTEM__KEXEC_INITRAMFS_LOAD); + break; + case READING_POLICY: + rc = selinux_kernel_load_from_file(file, SYSTEM__POLICY_LOAD); + break; + case READING_X509_CERTIFICATE: + rc = selinux_kernel_load_from_file(file, + SYSTEM__X509_CERTIFICATE_LOAD); break; default: break; @@ -4147,9 +4446,31 @@ static int selinux_kernel_load_data(enum kernel_load_data_id id, bool contents) { int rc = 0; + BUILD_BUG_ON_MSG(LOADING_MAX_ID > 8, + "New kernel_load_data_id introduced; update SELinux!"); + switch (id) { + case LOADING_FIRMWARE: + rc = selinux_kernel_load_from_file(NULL, SYSTEM__FIRMWARE_LOAD); + break; case LOADING_MODULE: - rc = selinux_kernel_module_from_file(NULL); + rc = selinux_kernel_load_from_file(NULL, SYSTEM__MODULE_LOAD); + break; + case LOADING_KEXEC_IMAGE: + rc = selinux_kernel_load_from_file(NULL, + SYSTEM__KEXEC_IMAGE_LOAD); + break; + case LOADING_KEXEC_INITRAMFS: + rc = selinux_kernel_load_from_file(NULL, + SYSTEM__KEXEC_INITRAMFS_LOAD); + break; + case LOADING_POLICY: + rc = selinux_kernel_load_from_file(NULL, + SYSTEM__POLICY_LOAD); + break; + case LOADING_X509_CERTIFICATE: + rc = selinux_kernel_load_from_file(NULL, + SYSTEM__X509_CERTIFICATE_LOAD); break; default: break; @@ -4348,22 +4669,6 @@ static int selinux_parse_skb_ipv4(struct sk_buff *skb, break; } - case IPPROTO_DCCP: { - struct dccp_hdr _dccph, *dh; - - if (ntohs(ih->frag_off) & IP_OFFSET) - break; - - offset += ihlen; - dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); - if (dh == NULL) - break; - - ad->u.net->sport = dh->dccph_sport; - ad->u.net->dport = dh->dccph_dport; - break; - } - #if IS_ENABLED(CONFIG_IP_SCTP) case IPPROTO_SCTP: { struct sctphdr _sctph, *sh; @@ -4442,18 +4747,6 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb, break; } - case IPPROTO_DCCP: { - struct dccp_hdr _dccph, *dh; - - dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); - if (dh == NULL) - break; - - ad->u.net->sport = dh->dccph_sport; - ad->u.net->dport = dh->dccph_dport; - break; - } - #if IS_ENABLED(CONFIG_IP_SCTP) case IPPROTO_SCTP: { struct sctphdr _sctph, *sh; @@ -4586,15 +4879,15 @@ static int selinux_conn_sid(u32 sk_sid, u32 skb_sid, u32 *conn_sid) /* socket security operations */ -static int socket_sockcreate_sid(const struct task_security_struct *tsec, +static int socket_sockcreate_sid(const struct cred_security_struct *crsec, u16 secclass, u32 *socksid) { - if (tsec->sockcreate_sid > SECSID_NULL) { - *socksid = tsec->sockcreate_sid; + if (crsec->sockcreate_sid > SECSID_NULL) { + *socksid = crsec->sockcreate_sid; return 0; } - return security_transition_sid(tsec->sid, tsec->sid, + return security_transition_sid(crsec->sid, crsec->sid, secclass, NULL, socksid); } @@ -4623,7 +4916,7 @@ static bool sock_skip_has_perm(u32 sid) static int sock_has_perm(struct sock *sk, u32 perms) { - struct sk_security_struct *sksec = sk->sk_security; + struct sk_security_struct *sksec = selinux_sock(sk); struct common_audit_data ad; struct lsm_network_audit net; @@ -4639,7 +4932,7 @@ static int sock_has_perm(struct sock *sk, u32 perms) static int selinux_socket_create(int family, int type, int protocol, int kern) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct cred_security_struct *crsec = selinux_cred(current_cred()); u32 newsid; u16 secclass; int rc; @@ -4648,17 +4941,17 @@ static int selinux_socket_create(int family, int type, return 0; secclass = socket_type_to_security_class(family, type, protocol); - rc = socket_sockcreate_sid(tsec, secclass, &newsid); + rc = socket_sockcreate_sid(crsec, secclass, &newsid); if (rc) return rc; - return avc_has_perm(tsec->sid, newsid, secclass, SOCKET__CREATE, NULL); + return avc_has_perm(crsec->sid, newsid, secclass, SOCKET__CREATE, NULL); } static int selinux_socket_post_create(struct socket *sock, int family, int type, int protocol, int kern) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct cred_security_struct *crsec = selinux_cred(current_cred()); struct inode_security_struct *isec = inode_security_novalidate(SOCK_INODE(sock)); struct sk_security_struct *sksec; u16 sclass = socket_type_to_security_class(family, type, protocol); @@ -4666,7 +4959,7 @@ static int selinux_socket_post_create(struct socket *sock, int family, int err = 0; if (!kern) { - err = socket_sockcreate_sid(tsec, sclass, &sid); + err = socket_sockcreate_sid(crsec, sclass, &sid); if (err) return err; } @@ -4805,10 +5098,6 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in node_perm = UDP_SOCKET__NODE_BIND; break; - case SECCLASS_DCCP_SOCKET: - node_perm = DCCP_SOCKET__NODE_BIND; - break; - case SECCLASS_SCTP_SOCKET: node_perm = SCTP_SOCKET__NODE_BIND; break; @@ -4864,11 +5153,10 @@ static int selinux_socket_connect_helper(struct socket *sock, return 0; /* - * If a TCP, DCCP or SCTP socket, check name_connect permission + * If a TCP or SCTP socket, check name_connect permission * for the port. */ if (sksec->sclass == SECCLASS_TCP_SOCKET || - sksec->sclass == SECCLASS_DCCP_SOCKET || sksec->sclass == SECCLASS_SCTP_SOCKET) { struct common_audit_data ad; struct lsm_network_audit net = {0,}; @@ -4913,9 +5201,6 @@ static int selinux_socket_connect_helper(struct socket *sock, case SECCLASS_TCP_SOCKET: perm = TCP_SOCKET__NAME_CONNECT; break; - case SECCLASS_DCCP_SOCKET: - perm = DCCP_SOCKET__NAME_CONNECT; - break; case SECCLASS_SCTP_SOCKET: perm = SCTP_SOCKET__NAME_CONNECT; break; @@ -5739,7 +6024,7 @@ static unsigned int selinux_ip_output(void *priv, struct sk_buff *skb, /* we do this in the LOCAL_OUT path and not the POST_ROUTING path * because we want to make sure we apply the necessary labeling * before IPsec is applied so we can leverage AH protection */ - sk = sk_to_full_sk(skb->sk); + sk = skb_to_full_sk(skb); if (sk) { struct sk_security_struct *sksec; @@ -5938,7 +6223,7 @@ static unsigned int selinux_ip_postroute(void *priv, static int nlmsg_sock_has_extended_perms(struct sock *sk, u32 perms, u16 nlmsg_type) { - struct sk_security_struct *sksec = sk->sk_security; + struct sk_security_struct *sksec = selinux_sock(sk); struct common_audit_data ad; u8 driver; u8 xperm; @@ -6376,37 +6661,37 @@ static void selinux_d_instantiate(struct dentry *dentry, struct inode *inode) static int selinux_lsm_getattr(unsigned int attr, struct task_struct *p, char **value) { - const struct task_security_struct *tsec; + const struct cred_security_struct *crsec; int error; u32 sid; u32 len; rcu_read_lock(); - tsec = selinux_cred(__task_cred(p)); + crsec = selinux_cred(__task_cred(p)); if (p != current) { - error = avc_has_perm(current_sid(), tsec->sid, + error = avc_has_perm(current_sid(), crsec->sid, SECCLASS_PROCESS, PROCESS__GETATTR, NULL); if (error) goto err_unlock; } switch (attr) { case LSM_ATTR_CURRENT: - sid = tsec->sid; + sid = crsec->sid; break; case LSM_ATTR_PREV: - sid = tsec->osid; + sid = crsec->osid; break; case LSM_ATTR_EXEC: - sid = tsec->exec_sid; + sid = crsec->exec_sid; break; case LSM_ATTR_FSCREATE: - sid = tsec->create_sid; + sid = crsec->create_sid; break; case LSM_ATTR_KEYCREATE: - sid = tsec->keycreate_sid; + sid = crsec->keycreate_sid; break; case LSM_ATTR_SOCKCREATE: - sid = tsec->sockcreate_sid; + sid = crsec->sockcreate_sid; break; default: error = -EOPNOTSUPP; @@ -6431,7 +6716,7 @@ err_unlock: static int selinux_lsm_setattr(u64 attr, void *value, size_t size) { - struct task_security_struct *tsec; + struct cred_security_struct *crsec; struct cred *new; u32 mysid = current_sid(), sid = 0, ptsid; int error; @@ -6517,11 +6802,11 @@ static int selinux_lsm_setattr(u64 attr, void *value, size_t size) operation. See selinux_bprm_creds_for_exec for the execve checks and may_create for the file creation checks. The operation will then fail if the context is not permitted. */ - tsec = selinux_cred(new); + crsec = selinux_cred(new); if (attr == LSM_ATTR_EXEC) { - tsec->exec_sid = sid; + crsec->exec_sid = sid; } else if (attr == LSM_ATTR_FSCREATE) { - tsec->create_sid = sid; + crsec->create_sid = sid; } else if (attr == LSM_ATTR_KEYCREATE) { if (sid) { error = avc_has_perm(mysid, sid, @@ -6529,22 +6814,22 @@ static int selinux_lsm_setattr(u64 attr, void *value, size_t size) if (error) goto abort_change; } - tsec->keycreate_sid = sid; + crsec->keycreate_sid = sid; } else if (attr == LSM_ATTR_SOCKCREATE) { - tsec->sockcreate_sid = sid; + crsec->sockcreate_sid = sid; } else if (attr == LSM_ATTR_CURRENT) { error = -EINVAL; if (sid == 0) goto abort_change; if (!current_is_single_threaded()) { - error = security_bounded_transition(tsec->sid, sid); + error = security_bounded_transition(crsec->sid, sid); if (error) goto abort_change; } /* Check permissions for the transition. */ - error = avc_has_perm(tsec->sid, sid, SECCLASS_PROCESS, + error = avc_has_perm(crsec->sid, sid, SECCLASS_PROCESS, PROCESS__DYNTRANSITION, NULL); if (error) goto abort_change; @@ -6559,7 +6844,7 @@ static int selinux_lsm_setattr(u64 attr, void *value, size_t size) goto abort_change; } - tsec->sid = sid; + crsec->sid = sid; } else { error = -EINVAL; goto abort_change; @@ -6726,14 +7011,14 @@ static int selinux_inode_getsecctx(struct inode *inode, struct lsm_context *cp) static int selinux_key_alloc(struct key *k, const struct cred *cred, unsigned long flags) { - const struct task_security_struct *tsec; + const struct cred_security_struct *crsec; struct key_security_struct *ksec = selinux_key(k); - tsec = selinux_cred(cred); - if (tsec->keycreate_sid) - ksec->sid = tsec->keycreate_sid; + crsec = selinux_cred(cred); + if (crsec->keycreate_sid) + ksec->sid = crsec->keycreate_sid; else - ksec->sid = tsec->sid; + ksec->sid = crsec->sid; return 0; } @@ -6866,11 +7151,14 @@ static int selinux_ib_alloc_security(void *ib_sec) #ifdef CONFIG_BPF_SYSCALL static int selinux_bpf(int cmd, union bpf_attr *attr, - unsigned int size) + unsigned int size, bool kernel) { u32 sid = current_sid(); int ret; + if (selinux_policycap_bpf_token_perms()) + return 0; + switch (cmd) { case BPF_MAP_CREATE: ret = avc_has_perm(sid, sid, SECCLASS_BPF, BPF__MAP_CREATE, @@ -6916,14 +7204,14 @@ static int bpf_fd_pass(const struct file *file, u32 sid) if (file->f_op == &bpf_map_fops) { map = file->private_data; - bpfsec = map->security; + bpfsec = selinux_bpf_map_security(map); ret = avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, bpf_map_fmode_to_av(file->f_mode), NULL); if (ret) return ret; } else if (file->f_op == &bpf_prog_fops) { prog = file->private_data; - bpfsec = prog->aux->security; + bpfsec = selinux_bpf_prog_security(prog); ret = avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_RUN, NULL); if (ret) @@ -6937,7 +7225,7 @@ static int selinux_bpf_map(struct bpf_map *map, fmode_t fmode) u32 sid = current_sid(); struct bpf_security_struct *bpfsec; - bpfsec = map->security; + bpfsec = selinux_bpf_map_security(map); return avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, bpf_map_fmode_to_av(fmode), NULL); } @@ -6947,100 +7235,152 @@ static int selinux_bpf_prog(struct bpf_prog *prog) u32 sid = current_sid(); struct bpf_security_struct *bpfsec; - bpfsec = prog->aux->security; + bpfsec = selinux_bpf_prog_security(prog); return avc_has_perm(sid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_RUN, NULL); } -static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, - struct bpf_token *token) +static u32 selinux_bpffs_creator_sid(u32 fd) { - struct bpf_security_struct *bpfsec; + struct path path; + struct super_block *sb; + struct superblock_security_struct *sbsec; - bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL); - if (!bpfsec) - return -ENOMEM; + CLASS(fd, f)(fd); - bpfsec->sid = current_sid(); - map->security = bpfsec; + if (fd_empty(f)) + return SECSID_NULL; - return 0; + path = fd_file(f)->f_path; + sb = path.dentry->d_sb; + sbsec = selinux_superblock(sb); + + return sbsec->creator_sid; } -static void selinux_bpf_map_free(struct bpf_map *map) +static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, + struct bpf_token *token, bool kernel) { - struct bpf_security_struct *bpfsec = map->security; + struct bpf_security_struct *bpfsec; + u32 ssid; - map->security = NULL; - kfree(bpfsec); + bpfsec = selinux_bpf_map_security(map); + bpfsec->sid = current_sid(); + + if (!token) + ssid = bpfsec->sid; + else + ssid = selinux_bpffs_creator_sid(attr->map_token_fd); + + return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__MAP_CREATE, + NULL); } static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, - struct bpf_token *token) + struct bpf_token *token, bool kernel) { struct bpf_security_struct *bpfsec; + u32 ssid; - bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL); - if (!bpfsec) - return -ENOMEM; - + bpfsec = selinux_bpf_prog_security(prog); bpfsec->sid = current_sid(); - prog->aux->security = bpfsec; - return 0; + if (!token) + ssid = bpfsec->sid; + else + ssid = selinux_bpffs_creator_sid(attr->prog_token_fd); + + return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_LOAD, + NULL); } -static void selinux_bpf_prog_free(struct bpf_prog *prog) +#define bpf_token_cmd(T, C) \ + ((T)->allowed_cmds & (1ULL << (C))) + +static int selinux_bpf_token_create(struct bpf_token *token, + union bpf_attr *attr, + const struct path *path) { - struct bpf_security_struct *bpfsec = prog->aux->security; + struct bpf_security_struct *bpfsec; + u32 sid = selinux_bpffs_creator_sid(attr->token_create.bpffs_fd); + int err; + + bpfsec = selinux_bpf_token_security(token); + bpfsec->sid = current_sid(); + bpfsec->grantor_sid = sid; - prog->aux->security = NULL; - kfree(bpfsec); + bpfsec->perms = 0; + /** + * 'token->allowed_cmds' is a bit mask of allowed commands + * Convert the BPF command enum to a bitmask representing its position + * in the allowed_cmds bitmap. + */ + if (bpf_token_cmd(token, BPF_MAP_CREATE)) { + err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, + BPF__MAP_CREATE_AS, NULL); + if (err) + return err; + bpfsec->perms |= BPF__MAP_CREATE; + } + if (bpf_token_cmd(token, BPF_PROG_LOAD)) { + err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, + BPF__PROG_LOAD_AS, NULL); + if (err) + return err; + bpfsec->perms |= BPF__PROG_LOAD; + } + + return 0; } -static int selinux_bpf_token_create(struct bpf_token *token, union bpf_attr *attr, - const struct path *path) +static int selinux_bpf_token_cmd(const struct bpf_token *token, + enum bpf_cmd cmd) { struct bpf_security_struct *bpfsec; - bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL); - if (!bpfsec) - return -ENOMEM; - - bpfsec->sid = current_sid(); - token->security = bpfsec; + bpfsec = token->security; + switch (cmd) { + case BPF_MAP_CREATE: + if (!(bpfsec->perms & BPF__MAP_CREATE)) + return -EACCES; + break; + case BPF_PROG_LOAD: + if (!(bpfsec->perms & BPF__PROG_LOAD)) + return -EACCES; + break; + default: + break; + } return 0; } -static void selinux_bpf_token_free(struct bpf_token *token) +static int selinux_bpf_token_capable(const struct bpf_token *token, int cap) { + u16 sclass; struct bpf_security_struct *bpfsec = token->security; + bool initns = (token->userns == &init_user_ns); + u32 av = CAP_TO_MASK(cap); - token->security = NULL; - kfree(bpfsec); -} -#endif + switch (CAP_TO_INDEX(cap)) { + case 0: + sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP_USERNS; + break; + case 1: + sclass = initns ? SECCLASS_CAPABILITY2 : SECCLASS_CAP2_USERNS; + break; + default: + pr_err("SELinux: out of range capability %d\n", cap); + return -EINVAL; + } -struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = { - .lbs_cred = sizeof(struct task_security_struct), - .lbs_file = sizeof(struct file_security_struct), - .lbs_inode = sizeof(struct inode_security_struct), - .lbs_ipc = sizeof(struct ipc_security_struct), - .lbs_key = sizeof(struct key_security_struct), - .lbs_msg_msg = sizeof(struct msg_security_struct), -#ifdef CONFIG_PERF_EVENTS - .lbs_perf_event = sizeof(struct perf_event_security_struct), + return avc_has_perm(current_sid(), bpfsec->grantor_sid, sclass, av, + NULL); +} #endif - .lbs_sock = sizeof(struct sk_security_struct), - .lbs_superblock = sizeof(struct superblock_security_struct), - .lbs_xattr_count = SELINUX_INODE_INIT_XATTRS, - .lbs_tun_dev = sizeof(struct tun_security_struct), - .lbs_ib = sizeof(struct ib_security_struct), -}; #ifdef CONFIG_PERF_EVENTS -static int selinux_perf_event_open(struct perf_event_attr *attr, int type) +static int selinux_perf_event_open(int type) { u32 requested, sid = current_sid(); @@ -7137,6 +7477,19 @@ static int selinux_uring_cmd(struct io_uring_cmd *ioucmd) return avc_has_perm(current_sid(), isec->sid, SECCLASS_IO_URING, IO_URING__CMD, &ad); } + +/** + * selinux_uring_allowed - check if io_uring_setup() can be called + * + * Check to see if the current task is allowed to call io_uring_setup(). + */ +static int selinux_uring_allowed(void) +{ + u32 sid = current_sid(); + + return avc_has_perm(sid, sid, SECCLASS_IO_URING, IO_URING__ALLOWED, + NULL); +} #endif /* CONFIG_IO_URING */ static const struct lsm_id selinux_lsmid = { @@ -7144,6 +7497,28 @@ static const struct lsm_id selinux_lsmid = { .id = LSM_ID_SELINUX, }; +struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = { + .lbs_cred = sizeof(struct cred_security_struct), + .lbs_task = sizeof(struct task_security_struct), + .lbs_file = sizeof(struct file_security_struct), + .lbs_backing_file = sizeof(struct backing_file_security_struct), + .lbs_inode = sizeof(struct inode_security_struct), + .lbs_ipc = sizeof(struct ipc_security_struct), + .lbs_key = sizeof(struct key_security_struct), + .lbs_msg_msg = sizeof(struct msg_security_struct), +#ifdef CONFIG_PERF_EVENTS + .lbs_perf_event = sizeof(struct perf_event_security_struct), +#endif + .lbs_sock = sizeof(struct sk_security_struct), + .lbs_superblock = sizeof(struct superblock_security_struct), + .lbs_xattr_count = SELINUX_INODE_INIT_XATTRS, + .lbs_tun_dev = sizeof(struct tun_security_struct), + .lbs_ib = sizeof(struct ib_security_struct), + .lbs_bpf_map = sizeof(struct bpf_security_struct), + .lbs_bpf_prog = sizeof(struct bpf_security_struct), + .lbs_bpf_token = sizeof(struct bpf_security_struct), +}; + /* * IMPORTANT NOTE: When adding new hooks, please be careful to keep this order: * 1. any hooks that don't belong to (2.) or (3.) below, @@ -7215,6 +7590,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(inode_getxattr, selinux_inode_getxattr), LSM_HOOK_INIT(inode_listxattr, selinux_inode_listxattr), LSM_HOOK_INIT(inode_removexattr, selinux_inode_removexattr), + LSM_HOOK_INIT(inode_file_getattr, selinux_inode_file_getattr), + LSM_HOOK_INIT(inode_file_setattr, selinux_inode_file_setattr), LSM_HOOK_INIT(inode_set_acl, selinux_inode_set_acl), LSM_HOOK_INIT(inode_get_acl, selinux_inode_get_acl), LSM_HOOK_INIT(inode_remove_acl, selinux_inode_remove_acl), @@ -7230,9 +7607,11 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), + LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc), LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat), LSM_HOOK_INIT(mmap_file, selinux_mmap_file), + LSM_HOOK_INIT(mmap_backing_file, selinux_mmap_backing_file), LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr), LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect), LSM_HOOK_INIT(file_lock, selinux_file_lock), @@ -7375,9 +7754,6 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(bpf, selinux_bpf), LSM_HOOK_INIT(bpf_map, selinux_bpf_map), LSM_HOOK_INIT(bpf_prog, selinux_bpf_prog), - LSM_HOOK_INIT(bpf_map_free, selinux_bpf_map_free), - LSM_HOOK_INIT(bpf_prog_free, selinux_bpf_prog_free), - LSM_HOOK_INIT(bpf_token_free, selinux_bpf_token_free), #endif #ifdef CONFIG_PERF_EVENTS @@ -7390,6 +7766,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(uring_override_creds, selinux_uring_override_creds), LSM_HOOK_INIT(uring_sqpoll, selinux_uring_sqpoll), LSM_HOOK_INIT(uring_cmd, selinux_uring_cmd), + LSM_HOOK_INIT(uring_allowed, selinux_uring_allowed), #endif /* @@ -7437,6 +7814,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(bpf_map_create, selinux_bpf_map_create), LSM_HOOK_INIT(bpf_prog_load, selinux_bpf_prog_load), LSM_HOOK_INIT(bpf_token_create, selinux_bpf_token_create), + LSM_HOOK_INIT(bpf_token_cmd, selinux_bpf_token_cmd), + LSM_HOOK_INIT(bpf_token_capable, selinux_bpf_token_capable), #endif #ifdef CONFIG_PERF_EVENTS LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc), @@ -7445,6 +7824,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { static __init int selinux_init(void) { + vma_flags_t data_default_flags = VMA_DATA_DEFAULT_FLAGS; + pr_info("SELinux: Initializing.\n"); memset(&selinux_state, 0, sizeof(selinux_state)); @@ -7456,7 +7837,12 @@ static __init int selinux_init(void) /* Set the security state for the initial task. */ cred_init_security(); - default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC); + /* Inform the audit system that secctx is used */ + audit_cfg_lsm(&selinux_lsmid, + AUDIT_CFG_LSM_SECCTX_SUBJECT | + AUDIT_CFG_LSM_SECCTX_OBJECT); + + default_noexec = !vma_flags_test(&data_default_flags, VMA_EXEC_BIT); if (!default_noexec) pr_notice("SELinux: virtual memory is executable by default\n"); @@ -7477,6 +7863,10 @@ static __init int selinux_init(void) if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET)) panic("SELinux: Unable to register AVC LSM notifier callback\n"); + if (avc_add_callback(selinux_audit_rule_avc_callback, + AVC_CALLBACK_RESET)) + panic("SELinux: Unable to register AVC audit callback\n"); + if (selinux_enforcing_boot) pr_debug("SELinux: Starting in enforcing mode\n"); else @@ -7504,11 +7894,12 @@ void selinux_complete_init(void) /* SELinux requires early initialization in order to label all processes and objects when they are created. */ DEFINE_LSM(selinux) = { - .name = "selinux", + .id = &selinux_lsmid, .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE, .enabled = &selinux_enabled_boot, .blobs = &selinux_blob_sizes, .init = selinux_init, + .initcall_device = selinux_initcall, }; #if defined(CONFIG_NETFILTER) @@ -7570,7 +7961,7 @@ static struct pernet_operations selinux_net_ops = { .exit = selinux_nf_unregister, }; -static int __init selinux_nf_ip_init(void) +int __init selinux_nf_ip_init(void) { int err; @@ -7585,5 +7976,4 @@ static int __init selinux_nf_ip_init(void) return 0; } -__initcall(selinux_nf_ip_init); #endif /* CONFIG_NETFILTER */ diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c index 48f537b41c58..93a5637fbcd8 100644 --- a/security/selinux/ibpkey.c +++ b/security/selinux/ibpkey.c @@ -23,6 +23,7 @@ #include <linux/list.h> #include <linux/spinlock.h> +#include "initcalls.h" #include "ibpkey.h" #include "objsec.h" @@ -130,7 +131,7 @@ static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid) { int ret; struct sel_ib_pkey *pkey; - struct sel_ib_pkey *new = NULL; + struct sel_ib_pkey *new; unsigned long flags; spin_lock_irqsave(&sel_ib_pkey_lock, flags); @@ -146,12 +147,11 @@ static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid) if (ret) goto out; - /* If this memory allocation fails still return 0. The SID - * is valid, it just won't be added to the cache. - */ - new = kzalloc(sizeof(*new), GFP_ATOMIC); + new = kmalloc_obj(*new, GFP_ATOMIC); if (!new) { - ret = -ENOMEM; + /* If this memory allocation fails still return 0. The SID + * is valid, it just won't be added to the cache. + */ goto out; } @@ -184,7 +184,7 @@ int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid) rcu_read_lock(); pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); - if (pkey) { + if (likely(pkey)) { *sid = pkey->psec.sid; rcu_read_unlock(); return 0; @@ -219,7 +219,7 @@ void sel_ib_pkey_flush(void) spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); } -static __init int sel_ib_pkey_init(void) +int __init sel_ib_pkey_init(void) { int iter; @@ -233,5 +233,3 @@ static __init int sel_ib_pkey_init(void) return 0; } - -subsys_initcall(sel_ib_pkey_init); diff --git a/security/selinux/include/audit.h b/security/selinux/include/audit.h index d5b0425055e4..85a531ac737b 100644 --- a/security/selinux/include/audit.h +++ b/security/selinux/include/audit.h @@ -16,6 +16,15 @@ #include <linux/types.h> /** + * selinux_audit_rule_avc_callback - update the audit LSM rules on AVC events. + * @event: the AVC event + * + * Update any audit LSM rules based on the AVC event specified in @event. + * Returns 0 on success, negative values otherwise. + */ +int selinux_audit_rule_avc_callback(u32 event); + +/** * selinux_audit_rule_init - alloc/init an selinux audit rule structure. * @field: the field this rule refers to * @op: the operator the rule uses diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 281f40103663..01b5167fee1a 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -65,6 +65,10 @@ static inline u32 avc_audit_required(u32 requested, struct av_decision *avd, int result, u32 auditdeny, u32 *deniedp) { u32 denied, audited; + + if (avd->flags & AVD_FLAGS_NEVERAUDIT) + return 0; + denied = requested & ~avd->allowed; if (unlikely(denied)) { audited = denied & avd->auditdeny; diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 03e82477dce9..90cb61b16425 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -8,7 +8,7 @@ COMMON_FILE_SOCK_PERMS, "unlink", "link", "rename", "execute", \ "quotaon", "mounton", "audit_access", "open", "execmod", \ "watch", "watch_mount", "watch_sb", "watch_with_perm", \ - "watch_reads" + "watch_reads", "watch_mountns" #define COMMON_SOCK_PERMS \ COMMON_FILE_SOCK_PERMS, "bind", "connect", "listen", "accept", \ @@ -63,7 +63,9 @@ const struct security_class_mapping secclass_map[] = { { "process2", { "nnp_transition", "nosuid_transition", NULL } }, { "system", { "ipc_info", "syslog_read", "syslog_mod", "syslog_console", - "module_request", "module_load", NULL } }, + "module_request", "module_load", "firmware_load", + "kexec_image_load", "kexec_initramfs_load", "policy_load", + "x509_certificate_load", NULL } }, { "capability", { COMMON_CAP_PERMS, NULL } }, { "filesystem", { "mount", "remount", "unmount", "getattr", "relabelfrom", @@ -125,8 +127,6 @@ const struct security_class_mapping secclass_map[] = { { "key", { "view", "read", "write", "search", "link", "setattr", "create", NULL } }, - { "dccp_socket", - { COMMON_SOCK_PERMS, "node_bind", "name_connect", NULL } }, { "memprotect", { "mmap_zero", NULL } }, { "peer", { "recv", NULL } }, { "capability2", { COMMON_CAP2_PERMS, NULL } }, @@ -171,14 +171,16 @@ const struct security_class_mapping secclass_map[] = { { "infiniband_endport", { "manage_subnet", NULL } }, { "bpf", { "map_create", "map_read", "map_write", "prog_load", "prog_run", - NULL } }, + "map_create_as", "prog_load_as", NULL } }, { "xdp_socket", { COMMON_SOCK_PERMS, NULL } }, { "mctp_socket", { COMMON_SOCK_PERMS, NULL } }, { "perf_event", { "open", "cpu", "kernel", "tracepoint", "read", "write", NULL } }, { "anon_inode", { COMMON_FILE_PERMS, NULL } }, - { "io_uring", { "override_creds", "sqpoll", "cmd", NULL } }, + { "io_uring", { "override_creds", "sqpoll", "cmd", "allowed", NULL } }, { "user_namespace", { "create", NULL } }, + { "memfd_file", + { COMMON_FILE_PERMS, "execute_no_trans", "entrypoint", NULL } }, /* last one */ { NULL, {} } }; diff --git a/security/selinux/include/hash.h b/security/selinux/include/hash.h new file mode 100644 index 000000000000..18956dbef8ff --- /dev/null +++ b/security/selinux/include/hash.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _SELINUX_HASH_H_ +#define _SELINUX_HASH_H_ + +/* + * Based on MurmurHash3, written by Austin Appleby and placed in the + * public domain. + */ +static inline u32 av_hash(u32 key1, u32 key2, u32 key3, u32 mask) +{ + static const u32 c1 = 0xcc9e2d51; + static const u32 c2 = 0x1b873593; + static const u32 r1 = 15; + static const u32 r2 = 13; + static const u32 m = 5; + static const u32 n = 0xe6546b64; + + u32 hash = 0; + +#define mix(input) \ + do { \ + u32 v = input; \ + v *= c1; \ + v = (v << r1) | (v >> (32 - r1)); \ + v *= c2; \ + hash ^= v; \ + hash = (hash << r2) | (hash >> (32 - r2)); \ + hash = hash * m + n; \ + } while (0) + + mix(key1); + mix(key2); + mix(key3); + +#undef mix + + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + + return hash & mask; +} + +#endif /* _SELINUX_HASH_H_ */ diff --git a/security/selinux/include/initcalls.h b/security/selinux/include/initcalls.h new file mode 100644 index 000000000000..6674cf489473 --- /dev/null +++ b/security/selinux/include/initcalls.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SELinux initcalls + */ + +#ifndef _SELINUX_INITCALLS_H +#define _SELINUX_INITCALLS_H + +int init_sel_fs(void); +int sel_netport_init(void); +int sel_netnode_init(void); +int sel_netif_init(void); +int sel_netlink_init(void); +int sel_ib_pkey_init(void); +int selinux_nf_ip_init(void); + +int selinux_initcall(void); + +#endif diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h index 9b8b655a8cd3..e4dc904c3585 100644 --- a/security/selinux/include/netnode.h +++ b/security/selinux/include/netnode.h @@ -21,6 +21,6 @@ void sel_netnode_flush(void); -int sel_netnode_sid(void *addr, u16 family, u32 *sid); +int sel_netnode_sid(const void *addr, u16 family, u32 *sid); #endif diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index c88cae81ee4c..3c0a16ec978b 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -26,10 +26,16 @@ #include <linux/lsm_hooks.h> #include <linux/msg.h> #include <net/net_namespace.h> +#include <linux/bpf.h> #include "flask.h" #include "avc.h" -struct task_security_struct { +struct avdc_entry { + u32 isid; /* inode SID */ + struct av_decision avd; /* av decision */ +}; + +struct cred_security_struct { u32 osid; /* SID prior to last execve */ u32 sid; /* current SID */ u32 exec_sid; /* exec SID */ @@ -38,6 +44,25 @@ struct task_security_struct { u32 sockcreate_sid; /* fscreate SID */ } __randomize_layout; +struct task_security_struct { +#define TSEC_AVDC_DIR_SIZE (1 << 2) + struct { + u32 sid; /* current SID for cached entries */ + u32 seqno; /* AVC sequence number */ + unsigned int dir_spot; /* dir cache index to check first */ + struct avdc_entry dir[TSEC_AVDC_DIR_SIZE]; /* dir entries */ + bool permissive_neveraudit; /* permissive and neveraudit */ + } avdcache; +} __randomize_layout; + +static inline bool task_avdcache_permnoaudit(struct task_security_struct *tsec, + u32 sid) +{ + return (tsec->avdcache.permissive_neveraudit && + sid == tsec->avdcache.sid && + tsec->avdcache.seqno == avc_policy_seqno()); +} + enum label_initialized { LABEL_INVALID, /* invalid or not initialized */ LABEL_INITIALIZED, /* initialized */ @@ -61,10 +86,15 @@ struct file_security_struct { u32 pseqno; /* Policy seqno at the time of file open */ }; +struct backing_file_security_struct { + u32 uf_sid; /* associated user file fsec->sid */ +}; + struct superblock_security_struct { u32 sid; /* SID of file system superblock */ u32 def_sid; /* default SID for labeling */ u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ + u32 creator_sid; /* SID of privileged process */ unsigned short behavior; /* labeling behavior */ unsigned short flags; /* which mount options were specified */ struct mutex lock; @@ -82,7 +112,7 @@ struct ipc_security_struct { }; struct netif_security_struct { - struct net *ns; /* network namespace */ + const struct net *ns; /* network namespace */ int ifindex; /* device index */ u32 sid; /* SID for this interface */ }; @@ -142,6 +172,8 @@ struct pkey_security_struct { struct bpf_security_struct { u32 sid; /* SID of bpf obj creator */ + u32 perms; /* permissions for allowed bpf token commands */ + u32 grantor_sid; /* SID of token grantor */ }; struct perf_event_security_struct { @@ -149,16 +181,29 @@ struct perf_event_security_struct { }; extern struct lsm_blob_sizes selinux_blob_sizes; -static inline struct task_security_struct *selinux_cred(const struct cred *cred) +static inline struct cred_security_struct *selinux_cred(const struct cred *cred) { return cred->security + selinux_blob_sizes.lbs_cred; } +static inline struct task_security_struct * +selinux_task(const struct task_struct *task) +{ + return task->security + selinux_blob_sizes.lbs_task; +} + static inline struct file_security_struct *selinux_file(const struct file *file) { return file->f_security + selinux_blob_sizes.lbs_file; } +static inline struct backing_file_security_struct * +selinux_backing_file(const struct file *backing_file) +{ + void *blob = backing_file_security(backing_file); + return blob + selinux_blob_sizes.lbs_backing_file; +} + static inline struct inode_security_struct * selinux_inode(const struct inode *inode) { @@ -184,9 +229,9 @@ selinux_ipc(const struct kern_ipc_perm *ipc) */ static inline u32 current_sid(void) { - const struct task_security_struct *tsec = selinux_cred(current_cred()); + const struct cred_security_struct *crsec = selinux_cred(current_cred()); - return tsec->sid; + return crsec->sid; } static inline struct superblock_security_struct * @@ -223,4 +268,23 @@ selinux_perf_event(void *perf_event) return perf_event + selinux_blob_sizes.lbs_perf_event; } +#ifdef CONFIG_BPF_SYSCALL +static inline struct bpf_security_struct * +selinux_bpf_map_security(struct bpf_map *map) +{ + return map->security + selinux_blob_sizes.lbs_bpf_map; +} + +static inline struct bpf_security_struct * +selinux_bpf_prog_security(struct bpf_prog *prog) +{ + return prog->aux->security + selinux_blob_sizes.lbs_bpf_prog; +} + +static inline struct bpf_security_struct * +selinux_bpf_token_security(struct bpf_token *token) +{ + return token->security + selinux_blob_sizes.lbs_bpf_token; +} +#endif /* CONFIG_BPF_SYSCALL */ #endif /* _SELINUX_OBJSEC_H_ */ diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h index 079679fe7254..dbf39358ae6a 100644 --- a/security/selinux/include/policycap.h +++ b/security/selinux/include/policycap.h @@ -15,6 +15,11 @@ enum { POLICYDB_CAP_IOCTL_SKIP_CLOEXEC, POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT, POLICYDB_CAP_NETLINK_XPERM, + POLICYDB_CAP_NETIF_WILDCARD, + POLICYDB_CAP_GENFS_SECLABEL_WILDCARD, + POLICYDB_CAP_FUNCTIONFS_SECLABEL, + POLICYDB_CAP_MEMFD_CLASS, + POLICYDB_CAP_BPF_TOKEN_PERMS, __POLICYDB_CAP_MAX }; #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1) diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h index e080827408c4..6e2b808e12e8 100644 --- a/security/selinux/include/policycap_names.h +++ b/security/selinux/include/policycap_names.h @@ -18,6 +18,11 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = { "ioctl_skip_cloexec", "userspace_initial_context", "netlink_xperm", + "netif_wildcard", + "genfs_seclabel_wildcard", + "functionfs_seclabel", + "memfd_class", + "bpf_token_perms", }; /* clang-format on */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 8b4c2aa35839..0babb8992181 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -47,10 +47,11 @@ #define POLICYDB_VERSION_GLBLUB 32 #define POLICYDB_VERSION_COMP_FTRANS 33 /* compressed filename transitions */ #define POLICYDB_VERSION_COND_XPERMS 34 /* extended permissions in conditional policies */ +#define POLICYDB_VERSION_NEVERAUDIT 35 /* neveraudit types */ /* Range of policy versions we understand*/ #define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE -#define POLICYDB_VERSION_MAX POLICYDB_VERSION_COND_XPERMS +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_NEVERAUDIT /* Mask for just the mount related flags */ #define SE_MNTMASK 0x0f @@ -202,6 +203,23 @@ static inline bool selinux_policycap_netlink_xperm(void) selinux_state.policycap[POLICYDB_CAP_NETLINK_XPERM]); } +static inline bool selinux_policycap_functionfs_seclabel(void) +{ + return READ_ONCE( + selinux_state.policycap[POLICYDB_CAP_FUNCTIONFS_SECLABEL]); +} + +static inline bool selinux_policycap_memfd_class(void) +{ + return READ_ONCE(selinux_state.policycap[POLICYDB_CAP_MEMFD_CLASS]); +} + +static inline bool selinux_policycap_bpf_token_perms(void) +{ + return READ_ONCE( + selinux_state.policycap[POLICYDB_CAP_BPF_TOKEN_PERMS]); +} + struct selinux_policy_convert_data; struct selinux_load_state { @@ -254,6 +272,7 @@ struct extended_perms { /* definitions of av_decision.flags */ #define AVD_FLAGS_PERMISSIVE 0x0001 +#define AVD_FLAGS_NEVERAUDIT 0x0002 void security_compute_av(u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd, @@ -293,17 +312,15 @@ int security_context_to_sid_default(const char *scontext, u32 scontext_len, int security_context_to_sid_force(const char *scontext, u32 scontext_len, u32 *sid); -int security_get_user_sids(u32 fromsid, const char *username, u32 **sids, u32 *nel); - int security_port_sid(u8 protocol, u16 port, u32 *out_sid); int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid); int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid); -int security_netif_sid(char *name, u32 *if_sid); +int security_netif_sid(const char *name, u32 *if_sid); -int security_node_sid(u16 domain, void *addr, u32 addrlen, u32 *out_sid); +int security_node_sid(u16 domain, const void *addr, u32 addrlen, u32 *out_sid); int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, u16 tclass); diff --git a/security/selinux/initcalls.c b/security/selinux/initcalls.c new file mode 100644 index 000000000000..f6716a1d38c1 --- /dev/null +++ b/security/selinux/initcalls.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * SELinux initcalls + */ + +#include <linux/init.h> + +#include "initcalls.h" + +/** + * selinux_initcall - Perform the SELinux initcalls + * + * Used as a device initcall in the SELinux LSM definition. + */ +int __init selinux_initcall(void) +{ + int rc = 0, rc_tmp = 0; + + rc_tmp = init_sel_fs(); + if (!rc && rc_tmp) + rc = rc_tmp; + + rc_tmp = sel_netport_init(); + if (!rc && rc_tmp) + rc = rc_tmp; + + rc_tmp = sel_netnode_init(); + if (!rc && rc_tmp) + rc = rc_tmp; + + rc_tmp = sel_netif_init(); + if (!rc && rc_tmp) + rc = rc_tmp; + + rc_tmp = sel_netlink_init(); + if (!rc && rc_tmp) + rc = rc_tmp; + +#if defined(CONFIG_SECURITY_INFINIBAND) + rc_tmp = sel_ib_pkey_init(); + if (!rc && rc_tmp) + rc = rc_tmp; +#endif + +#if defined(CONFIG_NETFILTER) + rc_tmp = selinux_nf_ip_init(); + if (!rc && rc_tmp) + rc = rc_tmp; +#endif + + return rc; +} diff --git a/security/selinux/netif.c b/security/selinux/netif.c index 43a0d3594b72..fa6d24a37c39 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -22,6 +22,7 @@ #include <linux/rcupdate.h> #include <net/net_namespace.h> +#include "initcalls.h" #include "security.h" #include "objsec.h" #include "netif.h" @@ -156,7 +157,11 @@ static int sel_netif_sid_slow(struct net *ns, int ifindex, u32 *sid) ret = security_netif_sid(dev->name, sid); if (ret != 0) goto out; - new = kzalloc(sizeof(*new), GFP_ATOMIC); + + /* If this memory allocation fails still return 0. The SID + * is valid, it just won't be added to the cache. + */ + new = kmalloc_obj(*new, GFP_ATOMIC); if (new) { new->nsec.ns = ns; new->nsec.ifindex = ifindex; @@ -261,7 +266,7 @@ static struct notifier_block sel_netif_netdev_notifier = { .notifier_call = sel_netif_netdev_notifier_handler, }; -static __init int sel_netif_init(void) +int __init sel_netif_init(void) { int i; @@ -276,5 +281,3 @@ static __init int sel_netif_init(void) return 0; } -__initcall(sel_netif_init); - diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c index 1760aee712fd..eb40e4603475 100644 --- a/security/selinux/netlink.c +++ b/security/selinux/netlink.c @@ -17,6 +17,7 @@ #include <net/net_namespace.h> #include <net/netlink.h> +#include "initcalls.h" #include "security.h" static struct sock *selnl __ro_after_init; @@ -105,7 +106,7 @@ void selnl_notify_policyload(u32 seqno) selnl_notify(SELNL_MSG_POLICYLOAD, &seqno); } -static int __init selnl_init(void) +int __init sel_netlink_init(void) { struct netlink_kernel_cfg cfg = { .groups = SELNLGRP_MAX, @@ -117,5 +118,3 @@ static int __init selnl_init(void) panic("SELinux: Cannot create netlink socket."); return 0; } - -__initcall(selnl_init); diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index 5c8c77e50aad..adb93003b8c4 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -30,6 +30,7 @@ #include <net/ip.h> #include <net/ipv6.h> +#include "initcalls.h" #include "netnode.h" #include "objsec.h" @@ -187,7 +188,7 @@ static void sel_netnode_insert(struct sel_netnode *node) * failure. * */ -static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) +static int sel_netnode_sid_slow(const void *addr, u16 family, u32 *sid) { int ret; struct sel_netnode *node; @@ -201,19 +202,22 @@ static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) return 0; } - new = kzalloc(sizeof(*new), GFP_ATOMIC); + /* If this memory allocation fails still return 0. The SID + * is valid, it just won't be added to the cache. + */ + new = kmalloc_obj(*new, GFP_ATOMIC); switch (family) { case PF_INET: ret = security_node_sid(PF_INET, addr, sizeof(struct in_addr), sid); if (new) - new->nsec.addr.ipv4 = *(__be32 *)addr; + new->nsec.addr.ipv4 = *(const __be32 *)addr; break; case PF_INET6: ret = security_node_sid(PF_INET6, addr, sizeof(struct in6_addr), sid); if (new) - new->nsec.addr.ipv6 = *(struct in6_addr *)addr; + new->nsec.addr.ipv6 = *(const struct in6_addr *)addr; break; default: BUG(); @@ -247,13 +251,13 @@ static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) * on failure. * */ -int sel_netnode_sid(void *addr, u16 family, u32 *sid) +int sel_netnode_sid(const void *addr, u16 family, u32 *sid) { struct sel_netnode *node; rcu_read_lock(); node = sel_netnode_find(addr, family); - if (node != NULL) { + if (likely(node != NULL)) { *sid = node->nsec.sid; rcu_read_unlock(); return 0; @@ -287,7 +291,7 @@ void sel_netnode_flush(void) spin_unlock_bh(&sel_netnode_lock); } -static __init int sel_netnode_init(void) +int __init sel_netnode_init(void) { int iter; @@ -301,5 +305,3 @@ static __init int sel_netnode_init(void) return 0; } - -__initcall(sel_netnode_init); diff --git a/security/selinux/netport.c b/security/selinux/netport.c index 2e22ad9c2bd0..006a6ec71319 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -29,6 +29,7 @@ #include <net/ip.h> #include <net/ipv6.h> +#include "initcalls.h" #include "netport.h" #include "objsec.h" @@ -47,12 +48,6 @@ struct sel_netport { struct rcu_head rcu; }; -/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason - * for this is that I suspect most users will not make heavy use of both - * address families at the same time so one table will usually end up wasted, - * if this becomes a problem we can always add a hash table for each address - * family later */ - static DEFINE_SPINLOCK(sel_netport_lock); static struct sel_netport_bkt sel_netport_hash[SEL_NETPORT_HASH_SIZE]; @@ -151,7 +146,11 @@ static int sel_netport_sid_slow(u8 protocol, u16 pnum, u32 *sid) ret = security_port_sid(protocol, pnum, sid); if (ret != 0) goto out; - new = kzalloc(sizeof(*new), GFP_ATOMIC); + + /* If this memory allocation fails still return 0. The SID + * is valid, it just won't be added to the cache. + */ + new = kmalloc_obj(*new, GFP_ATOMIC); if (new) { new->psec.port = pnum; new->psec.protocol = protocol; @@ -186,7 +185,7 @@ int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid) rcu_read_lock(); port = sel_netport_find(protocol, pnum); - if (port != NULL) { + if (likely(port != NULL)) { *sid = port->psec.sid; rcu_read_unlock(); return 0; @@ -220,7 +219,7 @@ void sel_netport_flush(void) spin_unlock_bh(&sel_netport_lock); } -static __init int sel_netport_init(void) +int __init sel_netport_init(void) { int iter; @@ -234,5 +233,3 @@ static __init int sel_netport_init(void) return 0; } - -__initcall(sel_netport_init); diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 3a95986b134f..2c0b07f9fbbd 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -98,7 +98,6 @@ static const struct nlmsg_perm nlmsg_route_perms[] = { static const struct nlmsg_perm nlmsg_tcpdiag_perms[] = { { TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, - { DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, { SOCK_DIAG_BY_FAMILY, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, { SOCK_DESTROY, NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE }, }; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 47480eb2189b..25ca7d714014 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -18,6 +18,7 @@ #include <linux/vmalloc.h> #include <linux/fs.h> #include <linux/fs_context.h> +#include <linux/hex.h> #include <linux/mount.h> #include <linux/mutex.h> #include <linux/namei.h> @@ -35,6 +36,7 @@ /* selinuxfs pseudo filesystem for exporting the security policy API. Based on the proc code and the fs/nfsd/nfsctl.c code. */ +#include "initcalls.h" #include "flask.h" #include "avc.h" #include "avc_ss.h" @@ -74,8 +76,6 @@ struct selinux_fs_info { int *bool_pending_values; struct dentry *class_dir; unsigned long last_class_ino; - bool policy_opened; - struct dentry *policycap_dir; unsigned long last_ino; struct super_block *sb; }; @@ -84,7 +84,7 @@ static int selinux_fs_info_create(struct super_block *sb) { struct selinux_fs_info *fsi; - fsi = kzalloc(sizeof(*fsi), GFP_KERNEL); + fsi = kzalloc_obj(*fsi); if (!fsi) return -ENOMEM; @@ -117,7 +117,6 @@ static void selinux_fs_info_free(struct super_block *sb) #define BOOL_DIR_NAME "booleans" #define CLASS_DIR_NAME "class" -#define POLICYCAP_DIR_NAME "policy_capabilities" #define TMPBUFLEN 12 static ssize_t sel_read_enforce(struct file *filp, char __user *buf, @@ -272,35 +271,13 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char *page; - ssize_t length; - int new_value; - - if (count >= PAGE_SIZE) - return -ENOMEM; - - /* No partial writes. */ - if (*ppos != 0) - return -EINVAL; - - page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); - - if (sscanf(page, "%d", &new_value) != 1) { - length = -EINVAL; - goto out; - } - length = count; - - if (new_value) { - pr_err("SELinux: https://github.com/SELinuxProject/selinux-kernel/wiki/DEPRECATE-runtime-disable\n"); - pr_err("SELinux: Runtime disable is not supported, use selinux=0 on the kernel cmdline.\n"); - } - -out: - kfree(page); - return length; + /* + * Setting disable is no longer supported, see + * https://github.com/SELinuxProject/selinux-kernel/wiki/DEPRECATE-runtime-disable + */ + pr_err_once("SELinux: %s (%d) wrote to disable. This is no longer supported.\n", + current->comm, current->pid); + return count; } static const struct file_operations sel_disable_ops = { @@ -362,44 +339,31 @@ struct policy_load_memory { static int sel_open_policy(struct inode *inode, struct file *filp) { - struct selinux_fs_info *fsi = inode->i_sb->s_fs_info; struct policy_load_memory *plm = NULL; int rc; - BUG_ON(filp->private_data); - - mutex_lock(&selinux_state.policy_mutex); - rc = avc_has_perm(current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL); if (rc) - goto err; - - rc = -EBUSY; - if (fsi->policy_opened) - goto err; + return rc; - rc = -ENOMEM; - plm = kzalloc(sizeof(*plm), GFP_KERNEL); + plm = kzalloc_obj(*plm); if (!plm) - goto err; + return -ENOMEM; + mutex_lock(&selinux_state.policy_mutex); rc = security_read_policy(&plm->data, &plm->len); if (rc) goto err; - if ((size_t)i_size_read(inode) != plm->len) { inode_lock(inode); i_size_write(inode, plm->len); inode_unlock(inode); } - - fsi->policy_opened = 1; + mutex_unlock(&selinux_state.policy_mutex); filp->private_data = plm; - mutex_unlock(&selinux_state.policy_mutex); - return 0; err: mutex_unlock(&selinux_state.policy_mutex); @@ -412,13 +376,8 @@ err: static int sel_release_policy(struct inode *inode, struct file *filp) { - struct selinux_fs_info *fsi = inode->i_sb->s_fs_info; struct policy_load_memory *plm = filp->private_data; - BUG_ON(!plm); - - fsi->policy_opened = 0; - vfree(plm->data); kfree(plm); @@ -506,6 +465,7 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi, { int ret = 0; struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir; + struct renamedata rd = {}; unsigned int bool_num = 0; char **bool_names = NULL; int *bool_values = NULL; @@ -539,9 +499,14 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi, if (ret) goto out; - lock_rename(tmp_parent, fsi->sb->s_root); + rd.old_parent = tmp_parent; + rd.new_parent = fsi->sb->s_root; /* booleans */ + ret = start_renaming_two_dentries(&rd, tmp_bool_dir, fsi->bool_dir); + if (ret) + goto out; + d_exchange(tmp_bool_dir, fsi->bool_dir); swap(fsi->bool_num, bool_num); @@ -549,12 +514,17 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi, swap(fsi->bool_pending_values, bool_values); fsi->bool_dir = tmp_bool_dir; + end_renaming(&rd); /* classes */ + ret = start_renaming_two_dentries(&rd, tmp_class_dir, fsi->class_dir); + if (ret) + goto out; + d_exchange(tmp_class_dir, fsi->class_dir); fsi->class_dir = tmp_class_dir; - unlock_rename(tmp_parent, fsi->sb->s_root); + end_renaming(&rd); out: sel_remove_old_bool_data(bool_num, bool_names, bool_values); @@ -583,34 +553,31 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf, if (!count) return -EINVAL; - mutex_lock(&selinux_state.policy_mutex); - length = avc_has_perm(current_sid(), SECINITSID_SECURITY, SECCLASS_SECURITY, SECURITY__LOAD_POLICY, NULL); if (length) - goto out; + return length; data = vmalloc(count); - if (!data) { - length = -ENOMEM; - goto out; - } + if (!data) + return -ENOMEM; if (copy_from_user(data, buf, count) != 0) { length = -EFAULT; goto out; } + mutex_lock(&selinux_state.policy_mutex); length = security_load_policy(data, count, &load_state); if (length) { pr_warn_ratelimited("SELinux: failed to load policy\n"); - goto out; + goto out_unlock; } fsi = file_inode(file)->i_sb->s_fs_info; length = sel_make_policy_nodes(fsi, load_state.policy); if (length) { pr_warn_ratelimited("SELinux: failed to initialize selinuxfs\n"); selinux_policy_cancel(&load_state); - goto out; + goto out_unlock; } selinux_policy_commit(&load_state); @@ -620,8 +587,9 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf, from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); -out: +out_unlock: mutex_unlock(&selinux_state.policy_mutex); +out: vfree(data); return length; } @@ -678,46 +646,13 @@ static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - char *page; - ssize_t length; - unsigned int new_value; - - length = avc_has_perm(current_sid(), SECINITSID_SECURITY, - SECCLASS_SECURITY, SECURITY__SETCHECKREQPROT, - NULL); - if (length) - return length; - - if (count >= PAGE_SIZE) - return -ENOMEM; - - /* No partial writes. */ - if (*ppos != 0) - return -EINVAL; - - page = memdup_user_nul(buf, count); - if (IS_ERR(page)) - return PTR_ERR(page); - - if (sscanf(page, "%u", &new_value) != 1) { - length = -EINVAL; - goto out; - } - length = count; - - if (new_value) { - char comm[sizeof(current->comm)]; - - strscpy(comm, current->comm); - pr_err("SELinux: %s (%d) set checkreqprot to 1. This is no longer supported.\n", - comm, current->pid); - } - - selinux_ima_measure_state(); - -out: - kfree(page); - return length; + /* + * Setting checkreqprot is no longer supported, see + * https://github.com/SELinuxProject/selinux-kernel/wiki/DEPRECATE-checkreqprot + */ + pr_err_once("SELinux: %s (%d) wrote to checkreqprot. This is no longer supported.\n", + current->comm, current->pid); + return count; } static const struct file_operations sel_checkreqprot_ops = { .read = sel_read_checkreqprot, @@ -1062,68 +997,11 @@ out: static ssize_t sel_write_user(struct file *file, char *buf, size_t size) { - char *con = NULL, *user = NULL, *ptr; - u32 sid, *sids = NULL; - ssize_t length; - char *newcon; - int rc; - u32 i, len, nsids; - - pr_warn_ratelimited("SELinux: %s (%d) wrote to /sys/fs/selinux/user!" - " This will not be supported in the future; please update your" - " userspace.\n", current->comm, current->pid); - - length = avc_has_perm(current_sid(), SECINITSID_SECURITY, - SECCLASS_SECURITY, SECURITY__COMPUTE_USER, - NULL); - if (length) - goto out; - - length = -ENOMEM; - con = kzalloc(size + 1, GFP_KERNEL); - if (!con) - goto out; - - length = -ENOMEM; - user = kzalloc(size + 1, GFP_KERNEL); - if (!user) - goto out; - - length = -EINVAL; - if (sscanf(buf, "%s %s", con, user) != 2) - goto out; - - length = security_context_str_to_sid(con, &sid, GFP_KERNEL); - if (length) - goto out; - - length = security_get_user_sids(sid, user, &sids, &nsids); - if (length) - goto out; - - length = sprintf(buf, "%u", nsids) + 1; - ptr = buf + length; - for (i = 0; i < nsids; i++) { - rc = security_sid_to_context(sids[i], &newcon, &len); - if (rc) { - length = rc; - goto out; - } - if ((length + len) >= SIMPLE_TRANSACTION_LIMIT) { - kfree(newcon); - length = -ERANGE; - goto out; - } - memcpy(ptr, newcon, len); - kfree(newcon); - ptr += len; - length += len; - } -out: - kfree(sids); - kfree(user); - kfree(con); - return length; + pr_err_once("SELinux: %s (%d) wrote to user. This is no longer supported.\n", + current->comm, current->pid); + buf[0] = '0'; + buf[1] = 0; + return 2; } static ssize_t sel_write_member(struct file *file, char *buf, size_t size) @@ -1198,11 +1076,31 @@ static struct inode *sel_make_inode(struct super_block *sb, umode_t mode) return ret; } +static struct dentry *sel_attach(struct dentry *parent, const char *name, + struct inode *inode) +{ + struct dentry *dentry = d_alloc_name(parent, name); + if (unlikely(!dentry)) { + iput(inode); + return ERR_PTR(-ENOMEM); + } + d_make_persistent(dentry, inode); + dput(dentry); + return dentry; +} + +static int sel_attach_file(struct dentry *parent, const char *name, + struct inode *inode) +{ + struct dentry *dentry = sel_attach(parent, name, inode); + return PTR_ERR_OR_ZERO(dentry); +} + static ssize_t sel_read_bool(struct file *filep, char __user *buf, size_t count, loff_t *ppos) { struct selinux_fs_info *fsi = file_inode(filep)->i_sb->s_fs_info; - char *page = NULL; + char buffer[4]; ssize_t length; ssize_t ret; int cur_enforcing; @@ -1216,27 +1114,19 @@ static ssize_t sel_read_bool(struct file *filep, char __user *buf, fsi->bool_pending_names[index])) goto out_unlock; - ret = -ENOMEM; - page = (char *)get_zeroed_page(GFP_KERNEL); - if (!page) - goto out_unlock; - cur_enforcing = security_get_bool_value(index); if (cur_enforcing < 0) { ret = cur_enforcing; goto out_unlock; } - length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing, - fsi->bool_pending_values[index]); + length = scnprintf(buffer, sizeof(buffer), "%d %d", !!cur_enforcing, + !!fsi->bool_pending_values[index]); mutex_unlock(&selinux_state.policy_mutex); - ret = simple_read_from_buffer(buf, count, ppos, page, length); -out_free: - free_page((unsigned long)page); - return ret; + return simple_read_from_buffer(buf, count, ppos, buffer, length); out_unlock: mutex_unlock(&selinux_state.policy_mutex); - goto out_free; + return ret; } static ssize_t sel_write_bool(struct file *filep, const char __user *buf, @@ -1365,8 +1255,7 @@ static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_ *bool_num = num; *bool_pending_names = names; - for (i = 0; i < num; i++) { - struct dentry *dentry; + for (i = 0; !ret && i < num; i++) { struct inode *inode; struct inode_security_struct *isec; ssize_t len; @@ -1377,15 +1266,9 @@ static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_ ret = -ENAMETOOLONG; break; } - dentry = d_alloc_name(bool_dir, names[i]); - if (!dentry) { - ret = -ENOMEM; - break; - } inode = sel_make_inode(bool_dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR); if (!inode) { - dput(dentry); ret = -ENOMEM; break; } @@ -1403,7 +1286,8 @@ static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_ isec->initialized = LABEL_INITIALIZED; inode->i_fop = &sel_bool_ops; inode->i_ino = i|SEL_BOOL_INO_OFFSET; - d_add(dentry, inode); + + ret = sel_attach_file(bool_dir, names[i], inode); } out: free_page((unsigned long)page); @@ -1588,6 +1472,7 @@ static int sel_make_avc_files(struct dentry *dir) struct super_block *sb = dir->d_sb; struct selinux_fs_info *fsi = sb->s_fs_info; unsigned int i; + int err = 0; static const struct tree_descr files[] = { { "cache_threshold", &sel_avc_cache_threshold_ops, S_IRUGO|S_IWUSR }, @@ -1597,26 +1482,20 @@ static int sel_make_avc_files(struct dentry *dir) #endif }; - for (i = 0; i < ARRAY_SIZE(files); i++) { + for (i = 0; !err && i < ARRAY_SIZE(files); i++) { struct inode *inode; - struct dentry *dentry; - - dentry = d_alloc_name(dir, files[i].name); - if (!dentry) - return -ENOMEM; inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode); - if (!inode) { - dput(dentry); + if (!inode) return -ENOMEM; - } inode->i_fop = files[i].ops; inode->i_ino = ++fsi->last_ino; - d_add(dentry, inode); + + err = sel_attach_file(dir, files[i].name, inode); } - return 0; + return err; } static int sel_make_ss_files(struct dentry *dir) @@ -1624,30 +1503,25 @@ static int sel_make_ss_files(struct dentry *dir) struct super_block *sb = dir->d_sb; struct selinux_fs_info *fsi = sb->s_fs_info; unsigned int i; + int err = 0; static const struct tree_descr files[] = { { "sidtab_hash_stats", &sel_sidtab_hash_stats_ops, S_IRUGO }, }; - for (i = 0; i < ARRAY_SIZE(files); i++) { + for (i = 0; !err && i < ARRAY_SIZE(files); i++) { struct inode *inode; - struct dentry *dentry; - - dentry = d_alloc_name(dir, files[i].name); - if (!dentry) - return -ENOMEM; inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode); - if (!inode) { - dput(dentry); + if (!inode) return -ENOMEM; - } inode->i_fop = files[i].ops; inode->i_ino = ++fsi->last_ino; - d_add(dentry, inode); + + err = sel_attach_file(dir, files[i].name, inode); } - return 0; + return err; } static ssize_t sel_read_initcon(struct file *file, char __user *buf, @@ -1675,30 +1549,25 @@ static const struct file_operations sel_initcon_ops = { static int sel_make_initcon_files(struct dentry *dir) { unsigned int i; + int err = 0; - for (i = 1; i <= SECINITSID_NUM; i++) { - struct inode *inode; - struct dentry *dentry; + for (i = 1; !err && i <= SECINITSID_NUM; i++) { const char *s = security_get_initial_sid_context(i); + struct inode *inode; if (!s) continue; - dentry = d_alloc_name(dir, s); - if (!dentry) - return -ENOMEM; inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); - if (!inode) { - dput(dentry); + if (!inode) return -ENOMEM; - } inode->i_fop = &sel_initcon_ops; inode->i_ino = i|SEL_INITCON_INO_OFFSET; - d_add(dentry, inode); + err = sel_attach_file(dir, s, inode); } - return 0; + return err; } static inline unsigned long sel_class_to_ino(u16 class) @@ -1780,29 +1649,21 @@ static int sel_make_perm_files(struct selinux_policy *newpolicy, if (rc) return rc; - for (i = 0; i < nperms; i++) { + for (i = 0; !rc && i < nperms; i++) { struct inode *inode; - struct dentry *dentry; - rc = -ENOMEM; - dentry = d_alloc_name(dir, perms[i]); - if (!dentry) - goto out; - - rc = -ENOMEM; inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); if (!inode) { - dput(dentry); - goto out; + rc = -ENOMEM; + break; } inode->i_fop = &sel_perm_ops; /* i+1 since perm values are 1-indexed */ inode->i_ino = sel_perm_to_ino(classvalue, i + 1); - d_add(dentry, inode); + + rc = sel_attach_file(dir, perms[i], inode); } - rc = 0; -out: for (i = 0; i < nperms; i++) kfree(perms[i]); kfree(perms); @@ -1817,20 +1678,18 @@ static int sel_make_class_dir_entries(struct selinux_policy *newpolicy, struct selinux_fs_info *fsi = sb->s_fs_info; struct dentry *dentry = NULL; struct inode *inode = NULL; - - dentry = d_alloc_name(dir, "index"); - if (!dentry) - return -ENOMEM; + int err; inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); - if (!inode) { - dput(dentry); + if (!inode) return -ENOMEM; - } inode->i_fop = &sel_class_ops; inode->i_ino = sel_class_to_ino(index); - d_add(dentry, inode); + + err = sel_attach_file(dir, "index", inode); + if (err) + return err; dentry = sel_make_dir(dir, "perms", &fsi->last_class_ino); if (IS_ERR(dentry)) @@ -1878,61 +1737,51 @@ out: return rc; } -static int sel_make_policycap(struct selinux_fs_info *fsi) +static int sel_make_policycap(struct dentry *dir) { + struct super_block *sb = dir->d_sb; unsigned int iter; - struct dentry *dentry = NULL; struct inode *inode = NULL; + int err = 0; + + for (iter = 0; !err && iter <= POLICYDB_CAP_MAX; iter++) { + const char *name; - for (iter = 0; iter <= POLICYDB_CAP_MAX; iter++) { if (iter < ARRAY_SIZE(selinux_policycap_names)) - dentry = d_alloc_name(fsi->policycap_dir, - selinux_policycap_names[iter]); + name = selinux_policycap_names[iter]; else - dentry = d_alloc_name(fsi->policycap_dir, "unknown"); - - if (dentry == NULL) - return -ENOMEM; + name = "unknown"; - inode = sel_make_inode(fsi->sb, S_IFREG | 0444); - if (inode == NULL) { - dput(dentry); + inode = sel_make_inode(sb, S_IFREG | 0444); + if (!inode) return -ENOMEM; - } inode->i_fop = &sel_policycap_ops; inode->i_ino = iter | SEL_POLICYCAP_INO_OFFSET; - d_add(dentry, inode); + err = sel_attach_file(dir, name, inode); } - return 0; + return err; } static struct dentry *sel_make_dir(struct dentry *dir, const char *name, unsigned long *ino) { - struct dentry *dentry = d_alloc_name(dir, name); struct inode *inode; - if (!dentry) - return ERR_PTR(-ENOMEM); - inode = sel_make_inode(dir->d_sb, S_IFDIR | S_IRUGO | S_IXUGO); - if (!inode) { - dput(dentry); + if (!inode) return ERR_PTR(-ENOMEM); - } inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; inode->i_ino = ++(*ino); /* directory inodes start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); - d_add(dentry, inode); /* bump link count on parent directory, too */ inc_nlink(d_inode(dir)); - return dentry; + return sel_attach(dir, name, inode); } static int reject_all(struct mnt_idmap *idmap, struct inode *inode, int mask) @@ -1948,27 +1797,27 @@ static const struct inode_operations swapover_dir_inode_operations = { static struct dentry *sel_make_swapover_dir(struct super_block *sb, unsigned long *ino) { - struct dentry *dentry = d_alloc_name(sb->s_root, ".swapover"); + struct dentry *dentry; struct inode *inode; - if (!dentry) - return ERR_PTR(-ENOMEM); - inode = sel_make_inode(sb, S_IFDIR); - if (!inode) { - dput(dentry); + if (!inode) return ERR_PTR(-ENOMEM); + + dentry = simple_start_creating(sb->s_root, ".swapover"); + if (IS_ERR(dentry)) { + iput(inode); + return dentry; } inode->i_op = &swapover_dir_inode_operations; inode->i_ino = ++(*ino); /* directory inodes start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); - inode_lock(sb->s_root->d_inode); - d_add(dentry, inode); + d_make_persistent(dentry, inode); inc_nlink(sb->s_root->d_inode); - inode_unlock(sb->s_root->d_inode); - return dentry; + simple_done_creating(dentry); + return dentry; // borrowed } #define NULL_FILE_NAME "null" @@ -2021,16 +1870,9 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) } ret = -ENOMEM; - dentry = d_alloc_name(sb->s_root, NULL_FILE_NAME); - if (!dentry) - goto err; - - ret = -ENOMEM; inode = sel_make_inode(sb, S_IFCHR | S_IRUGO | S_IWUGO); - if (!inode) { - dput(dentry); + if (!inode) goto err; - } inode->i_ino = ++fsi->last_ino; isec = selinux_inode(inode); @@ -2039,7 +1881,9 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) isec->initialized = LABEL_INITIALIZED; init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, MKDEV(MEM_MAJOR, 3)); - d_add(dentry, inode); + ret = sel_attach_file(sb->s_root, NULL_FILE_NAME, inode); + if (ret) + goto err; dentry = sel_make_dir(sb->s_root, "avc", &fsi->last_ino); if (IS_ERR(dentry)) { @@ -2078,15 +1922,13 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) goto err; } - fsi->policycap_dir = sel_make_dir(sb->s_root, POLICYCAP_DIR_NAME, - &fsi->last_ino); - if (IS_ERR(fsi->policycap_dir)) { - ret = PTR_ERR(fsi->policycap_dir); - fsi->policycap_dir = NULL; + dentry = sel_make_dir(sb->s_root, "policy_capabilities", &fsi->last_ino); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); goto err; } - ret = sel_make_policycap(fsi); + ret = sel_make_policycap(dentry); if (ret) { pr_err("SELinux: failed to load policy capabilities\n"); goto err; @@ -2097,8 +1939,6 @@ err: pr_err("SELinux: %s: failed while creating inodes\n", __func__); - selinux_fs_info_free(sb); - return ret; } @@ -2120,7 +1960,7 @@ static int sel_init_fs_context(struct fs_context *fc) static void sel_kill_sb(struct super_block *sb) { selinux_fs_info_free(sb); - kill_litter_super(sb); + kill_anon_super(sb); } static struct file_system_type sel_fs_type = { @@ -2131,7 +1971,7 @@ static struct file_system_type sel_fs_type = { struct path selinux_null __ro_after_init; -static int __init init_sel_fs(void) +int __init init_sel_fs(void) { struct qstr null_name = QSTR_INIT(NULL_FILE_NAME, sizeof(NULL_FILE_NAME)-1); @@ -2158,8 +1998,8 @@ static int __init init_sel_fs(void) return err; } - selinux_null.dentry = d_hash_and_lookup(selinux_null.mnt->mnt_root, - &null_name); + selinux_null.dentry = try_lookup_noperm(&null_name, + selinux_null.mnt->mnt_root); if (IS_ERR(selinux_null.dentry)) { pr_err("selinuxfs: could not lookup null!\n"); err = PTR_ERR(selinux_null.dentry); @@ -2175,5 +2015,3 @@ static int __init init_sel_fs(void) return err; } - -__initcall(init_sel_fs); diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c index c2c31521cace..d12ca337e649 100644 --- a/security/selinux/ss/avtab.c +++ b/security/selinux/ss/avtab.c @@ -20,48 +20,15 @@ #include <linux/errno.h> #include "avtab.h" #include "policydb.h" +#include "hash.h" static struct kmem_cache *avtab_node_cachep __ro_after_init; static struct kmem_cache *avtab_xperms_cachep __ro_after_init; -/* Based on MurmurHash3, written by Austin Appleby and placed in the - * public domain. - */ static inline u32 avtab_hash(const struct avtab_key *keyp, u32 mask) { - static const u32 c1 = 0xcc9e2d51; - static const u32 c2 = 0x1b873593; - static const u32 r1 = 15; - static const u32 r2 = 13; - static const u32 m = 5; - static const u32 n = 0xe6546b64; - - u32 hash = 0; - -#define mix(input) \ - do { \ - u32 v = input; \ - v *= c1; \ - v = (v << r1) | (v >> (32 - r1)); \ - v *= c2; \ - hash ^= v; \ - hash = (hash << r2) | (hash >> (32 - r2)); \ - hash = hash * m + n; \ - } while (0) - - mix(keyp->target_class); - mix(keyp->target_type); - mix(keyp->source_type); - -#undef mix - - hash ^= hash >> 16; - hash *= 0x85ebca6b; - hash ^= hash >> 13; - hash *= 0xc2b2ae35; - hash ^= hash >> 16; - - return hash & mask; + return av_hash((u32)keyp->target_class, (u32)keyp->target_type, + (u32)keyp->source_type, mask); } static struct avtab_node *avtab_insert_node(struct avtab *h, diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index 1bebfcb9c6a1..824c3f896151 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -165,8 +165,8 @@ void cond_policydb_destroy(struct policydb *p) int cond_init_bool_indexes(struct policydb *p) { kfree(p->bool_val_to_struct); - p->bool_val_to_struct = kmalloc_array( - p->p_bools.nprim, sizeof(*p->bool_val_to_struct), GFP_KERNEL); + p->bool_val_to_struct = kmalloc_objs(*p->bool_val_to_struct, + p->p_bools.nprim); if (!p->bool_val_to_struct) return -ENOMEM; @@ -214,7 +214,7 @@ int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp) u32 len; int rc; - booldatum = kzalloc(sizeof(*booldatum), GFP_KERNEL); + booldatum = kzalloc_obj(*booldatum); if (!booldatum) return -ENOMEM; @@ -334,7 +334,7 @@ static int cond_read_av_list(struct policydb *p, struct policy_file *fp, if (len == 0) return 0; - list->nodes = kcalloc(len, sizeof(*list->nodes), GFP_KERNEL); + list->nodes = kzalloc_objs(*list->nodes, len); if (!list->nodes) return -ENOMEM; @@ -383,7 +383,7 @@ static int cond_read_node(struct policydb *p, struct cond_node *node, struct pol /* expr */ len = le32_to_cpu(buf[1]); - node->expr.nodes = kcalloc(len, sizeof(*node->expr.nodes), GFP_KERNEL); + node->expr.nodes = kzalloc_objs(*node->expr.nodes, len); if (!node->expr.nodes) return -ENOMEM; @@ -421,7 +421,7 @@ int cond_read_list(struct policydb *p, struct policy_file *fp) len = le32_to_cpu(buf[0]); - p->cond_list = kcalloc(len, sizeof(*p->cond_list), GFP_KERNEL); + p->cond_list = kzalloc_objs(*p->cond_list, len); if (!p->cond_list) return -ENOMEM; @@ -605,7 +605,7 @@ static int cond_dup_av_list(struct cond_av_list *new, memset(new, 0, sizeof(*new)); - new->nodes = kcalloc(orig->len, sizeof(*new->nodes), GFP_KERNEL); + new->nodes = kzalloc_objs(*new->nodes, orig->len); if (!new->nodes) return -ENOMEM; @@ -631,8 +631,7 @@ static int duplicate_policydb_cond_list(struct policydb *newp, return rc; newp->cond_list_len = 0; - newp->cond_list = kcalloc(origp->cond_list_len, - sizeof(*newp->cond_list), GFP_KERNEL); + newp->cond_list = kzalloc_objs(*newp->cond_list, origp->cond_list_len); if (!newp->cond_list) goto error; @@ -710,9 +709,8 @@ static int duplicate_policydb_bools(struct policydb *newdb, struct cond_bool_datum **cond_bool_array; int rc; - cond_bool_array = kmalloc_array(orig->p_bools.nprim, - sizeof(*orig->bool_val_to_struct), - GFP_KERNEL); + cond_bool_array = kmalloc_objs(*orig->bool_val_to_struct, + orig->p_bools.nprim); if (!cond_bool_array) return -ENOMEM; diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c index 383fd2d70878..9ffc8a371e23 100644 --- a/security/selinux/ss/hashtab.c +++ b/security/selinux/ss/hashtab.c @@ -40,7 +40,8 @@ int hashtab_init(struct hashtab *h, u32 nel_hint) h->htable = NULL; if (size) { - h->htable = kcalloc(size, sizeof(*h->htable), GFP_KERNEL); + h->htable = kzalloc_objs(*h->htable, size, + GFP_KERNEL | __GFP_NOWARN); if (!h->htable) return -ENOMEM; h->size = size; @@ -148,7 +149,7 @@ int hashtab_duplicate(struct hashtab *new, const struct hashtab *orig, memset(new, 0, sizeof(*new)); - new->htable = kcalloc(orig->size, sizeof(*new->htable), GFP_KERNEL); + new->htable = kzalloc_objs(*new->htable, orig->size); if (!new->htable) return -ENOMEM; diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 9ea971943713..738fd47f33e6 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -160,6 +160,11 @@ static const struct policydb_compat_info policydb_compat[] = { .sym_num = SYM_NUM, .ocon_num = OCON_NUM, }, + { + .version = POLICYDB_VERSION_NEVERAUDIT, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, }; static const struct policydb_compat_info * @@ -385,7 +390,7 @@ static int roles_init(struct policydb *p) int rc; struct role_datum *role; - role = kzalloc(sizeof(*role), GFP_KERNEL); + role = kzalloc_obj(*role); if (!role) return -ENOMEM; @@ -531,6 +536,7 @@ static void policydb_init(struct policydb *p) ebitmap_init(&p->filename_trans_ttypes); ebitmap_init(&p->policycaps); ebitmap_init(&p->permissive_map); + ebitmap_init(&p->neveraudit_map); } /* @@ -732,24 +738,23 @@ static int policydb_index(struct policydb *p) avtab_hash_eval(&p->te_avtab, "rules"); symtab_hash_eval(p->symtab); - p->class_val_to_struct = kcalloc(p->p_classes.nprim, - sizeof(*p->class_val_to_struct), - GFP_KERNEL); + p->class_val_to_struct = kzalloc_objs(*p->class_val_to_struct, + p->p_classes.nprim); if (!p->class_val_to_struct) return -ENOMEM; - p->role_val_to_struct = kcalloc( - p->p_roles.nprim, sizeof(*p->role_val_to_struct), GFP_KERNEL); + p->role_val_to_struct = kzalloc_objs(*p->role_val_to_struct, + p->p_roles.nprim); if (!p->role_val_to_struct) return -ENOMEM; - p->user_val_to_struct = kcalloc( - p->p_users.nprim, sizeof(*p->user_val_to_struct), GFP_KERNEL); + p->user_val_to_struct = kzalloc_objs(*p->user_val_to_struct, + p->p_users.nprim); if (!p->user_val_to_struct) return -ENOMEM; - p->type_val_to_struct = kvcalloc( - p->p_types.nprim, sizeof(*p->type_val_to_struct), GFP_KERNEL); + p->type_val_to_struct = kvzalloc_objs(*p->type_val_to_struct, + p->p_types.nprim); if (!p->type_val_to_struct) return -ENOMEM; @@ -852,6 +857,7 @@ void policydb_destroy(struct policydb *p) ebitmap_destroy(&p->filename_trans_ttypes); ebitmap_destroy(&p->policycaps); ebitmap_destroy(&p->permissive_map); + ebitmap_destroy(&p->neveraudit_map); } /* @@ -1124,7 +1130,7 @@ static int perm_read(struct policydb *p, struct symtab *s, struct policy_file *f __le32 buf[2]; u32 len; - perdatum = kzalloc(sizeof(*perdatum), GFP_KERNEL); + perdatum = kzalloc_obj(*perdatum); if (!perdatum) return -ENOMEM; @@ -1157,7 +1163,7 @@ static int common_read(struct policydb *p, struct symtab *s, struct policy_file u32 i, len, nel; int rc; - comdatum = kzalloc(sizeof(*comdatum), GFP_KERNEL); + comdatum = kzalloc_obj(*comdatum); if (!comdatum) return -ENOMEM; @@ -1230,7 +1236,7 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep, lc = NULL; for (i = 0; i < ncons; i++) { - c = kzalloc(sizeof(*c), GFP_KERNEL); + c = kzalloc_obj(*c); if (!c) return -ENOMEM; @@ -1247,7 +1253,7 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep, le = NULL; depth = -1; for (j = 0; j < nexpr; j++) { - e = kzalloc(sizeof(*e), GFP_KERNEL); + e = kzalloc_obj(*e); if (!e) return -ENOMEM; @@ -1290,9 +1296,7 @@ static int read_cons_helper(struct policydb *p, struct constraint_node **nodep, return rc; if (p->policyvers >= POLICYDB_VERSION_CONSTRAINT_NAMES) { - e->type_names = - kzalloc(sizeof(*e->type_names), - GFP_KERNEL); + e->type_names = kzalloc_obj(*e->type_names); if (!e->type_names) return -ENOMEM; type_set_init(e->type_names); @@ -1322,7 +1326,7 @@ static int class_read(struct policydb *p, struct symtab *s, struct policy_file * u32 i, len, len2, ncons, nel; int rc; - cladatum = kzalloc(sizeof(*cladatum), GFP_KERNEL); + cladatum = kzalloc_obj(*cladatum); if (!cladatum) return -ENOMEM; @@ -1420,7 +1424,7 @@ static int role_read(struct policydb *p, struct symtab *s, struct policy_file *f __le32 buf[3]; u32 len; - role = kzalloc(sizeof(*role), GFP_KERNEL); + role = kzalloc_obj(*role); if (!role) return -ENOMEM; @@ -1477,7 +1481,7 @@ static int type_read(struct policydb *p, struct symtab *s, struct policy_file *f __le32 buf[4]; u32 len; - typdatum = kzalloc(sizeof(*typdatum), GFP_KERNEL); + typdatum = kzalloc_obj(*typdatum); if (!typdatum) return -ENOMEM; @@ -1551,7 +1555,7 @@ static int user_read(struct policydb *p, struct symtab *s, struct policy_file *f __le32 buf[3]; u32 len; - usrdatum = kzalloc(sizeof(*usrdatum), GFP_KERNEL); + usrdatum = kzalloc_obj(*usrdatum); if (!usrdatum) return -ENOMEM; @@ -1601,7 +1605,7 @@ static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *f __le32 buf[2]; u32 len; - levdatum = kzalloc(sizeof(*levdatum), GFP_KERNEL); + levdatum = kzalloc_obj(*levdatum); if (!levdatum) return -ENOMEM; @@ -1637,7 +1641,7 @@ static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp __le32 buf[3]; u32 len; - catdatum = kzalloc(sizeof(*catdatum), GFP_KERNEL); + catdatum = kzalloc_obj(*catdatum); if (!catdatum) return -ENOMEM; @@ -1857,7 +1861,7 @@ static int range_read(struct policydb *p, struct policy_file *fp) for (i = 0; i < nel; i++) { rc = -ENOMEM; - rt = kzalloc(sizeof(*rt), GFP_KERNEL); + rt = kzalloc_obj(*rt); if (!rt) goto out; @@ -1882,7 +1886,7 @@ static int range_read(struct policydb *p, struct policy_file *fp) goto out; rc = -ENOMEM; - r = kzalloc(sizeof(*r), GFP_KERNEL); + r = kzalloc_obj(*r); if (!r) goto out; @@ -1958,7 +1962,7 @@ static int filename_trans_read_helper_compat(struct policydb *p, struct policy_f } if (!datum) { rc = -ENOMEM; - datum = kmalloc(sizeof(*datum), GFP_KERNEL); + datum = kmalloc_obj(*datum); if (!datum) goto out; @@ -2033,7 +2037,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp dst = &first; for (i = 0; i < ndatum; i++) { rc = -ENOMEM; - datum = kmalloc(sizeof(*datum), GFP_KERNEL); + datum = kmalloc_obj(*datum); if (!datum) goto out; @@ -2055,7 +2059,7 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp } rc = -ENOMEM; - ft = kmalloc(sizeof(*ft), GFP_KERNEL); + ft = kmalloc_obj(*ft); if (!ft) goto out; @@ -2148,7 +2152,7 @@ static int genfs_read(struct policydb *p, struct policy_file *fp) len = le32_to_cpu(buf[0]); rc = -ENOMEM; - newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL); + newgenfs = kzalloc_obj(*newgenfs); if (!newgenfs) goto out; @@ -2187,7 +2191,7 @@ static int genfs_read(struct policydb *p, struct policy_file *fp) len = le32_to_cpu(buf[0]); rc = -ENOMEM; - newc = kzalloc(sizeof(*newc), GFP_KERNEL); + newc = kzalloc_obj(*newc); if (!newc) goto out; @@ -2259,7 +2263,7 @@ static int ocontext_read(struct policydb *p, l = NULL; for (j = 0; j < nel; j++) { rc = -ENOMEM; - c = kzalloc(sizeof(*c), GFP_KERNEL); + c = kzalloc_obj(*c); if (!c) goto out; if (l) @@ -2538,6 +2542,12 @@ int policydb_read(struct policydb *p, struct policy_file *fp) goto bad; } + if (p->policyvers >= POLICYDB_VERSION_NEVERAUDIT) { + rc = ebitmap_read(&p->neveraudit_map, fp); + if (rc) + goto bad; + } + rc = -EINVAL; info = policydb_lookup_compat(p->policyvers); if (!info) { @@ -2610,12 +2620,12 @@ int policydb_read(struct policydb *p, struct policy_file *fp) goto bad; for (i = 0; i < nel; i++) { rc = -ENOMEM; - rtk = kmalloc(sizeof(*rtk), GFP_KERNEL); + rtk = kmalloc_obj(*rtk); if (!rtk) goto bad; rc = -ENOMEM; - rtd = kmalloc(sizeof(*rtd), GFP_KERNEL); + rtd = kmalloc_obj(*rtd); if (!rtd) goto bad; @@ -2658,7 +2668,7 @@ int policydb_read(struct policydb *p, struct policy_file *fp) lra = NULL; for (i = 0; i < nel; i++) { rc = -ENOMEM; - ra = kzalloc(sizeof(*ra), GFP_KERNEL); + ra = kzalloc_obj(*ra); if (!ra) goto bad; if (lra) @@ -2713,8 +2723,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp) goto bad; rc = -ENOMEM; - p->type_attr_map_array = kvcalloc( - p->p_types.nprim, sizeof(*p->type_attr_map_array), GFP_KERNEL); + p->type_attr_map_array = kvzalloc_objs(*p->type_attr_map_array, + p->p_types.nprim); if (!p->type_attr_map_array) goto bad; @@ -3723,6 +3733,12 @@ int policydb_write(struct policydb *p, struct policy_file *fp) return rc; } + if (p->policyvers >= POLICYDB_VERSION_NEVERAUDIT) { + rc = ebitmap_write(&p->neveraudit_map, fp); + if (rc) + return rc; + } + num_syms = info->sym_num; for (i = 0; i < num_syms; i++) { struct policy_data pd; diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index 25650224b6e7..89a180b1742f 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -300,6 +300,8 @@ struct policydb { struct ebitmap permissive_map; + struct ebitmap neveraudit_map; + /* length of this policy when it was loaded */ size_t len; diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 8478842fbf9e..143021c5e326 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -46,6 +46,7 @@ #include <linux/in.h> #include <linux/sched.h> #include <linux/audit.h> +#include <linux/parser.h> #include <linux/vmalloc.h> #include <linux/lsm_hooks.h> #include <net/netlabel.h> @@ -107,7 +108,7 @@ static int selinux_set_mapping(struct policydb *pol, i++; /* Allocate space for the class records, plus one for class zero */ - out_map->mapping = kcalloc(++i, sizeof(*out_map->mapping), GFP_ATOMIC); + out_map->mapping = kzalloc_objs(*out_map->mapping, ++i, GFP_ATOMIC); if (!out_map->mapping) return -ENOMEM; @@ -1152,6 +1153,14 @@ void security_compute_av(u32 ssid, if (ebitmap_get_bit(&policydb->permissive_map, scontext->type)) avd->flags |= AVD_FLAGS_PERMISSIVE; + /* neveraudit domain? */ + if (ebitmap_get_bit(&policydb->neveraudit_map, scontext->type)) + avd->flags |= AVD_FLAGS_NEVERAUDIT; + + /* both permissive and neveraudit => allow */ + if (avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT)) + goto allow; + tcontext = sidtab_search(sidtab, tsid); if (!tcontext) { pr_err("SELinux: %s: unrecognized SID %d\n", @@ -1171,6 +1180,8 @@ void security_compute_av(u32 ssid, policydb->allow_unknown); out: rcu_read_unlock(); + if (avd->flags & AVD_FLAGS_NEVERAUDIT) + avd->auditallow = avd->auditdeny = 0; return; allow: avd->allowed = 0xffffffff; @@ -1207,6 +1218,14 @@ void security_compute_av_user(u32 ssid, if (ebitmap_get_bit(&policydb->permissive_map, scontext->type)) avd->flags |= AVD_FLAGS_PERMISSIVE; + /* neveraudit domain? */ + if (ebitmap_get_bit(&policydb->neveraudit_map, scontext->type)) + avd->flags |= AVD_FLAGS_NEVERAUDIT; + + /* both permissive and neveraudit => allow */ + if (avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT)) + goto allow; + tcontext = sidtab_search(sidtab, tsid); if (!tcontext) { pr_err("SELinux: %s: unrecognized SID %d\n", @@ -1224,6 +1243,8 @@ void security_compute_av_user(u32 ssid, NULL); out: rcu_read_unlock(); + if (avd->flags & AVD_FLAGS_NEVERAUDIT) + avd->auditallow = avd->auditdeny = 0; return; allow: avd->allowed = 0xffffffff; @@ -1908,11 +1929,17 @@ retry: goto out_unlock; } /* Obtain the sid for the context. */ - rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid); - if (rc == -ESTALE) { - rcu_read_unlock(); - context_destroy(&newcontext); - goto retry; + if (context_equal(scontext, &newcontext)) + *out_sid = ssid; + else if (context_equal(tcontext, &newcontext)) + *out_sid = tsid; + else { + rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + context_destroy(&newcontext); + goto retry; + } } out_unlock: rcu_read_unlock(); @@ -2285,11 +2312,11 @@ int security_load_policy(void *data, size_t len, int rc = 0; struct policy_file file = { data, len }, *fp = &file; - newpolicy = kzalloc(sizeof(*newpolicy), GFP_KERNEL); + newpolicy = kzalloc_obj(*newpolicy); if (!newpolicy) return -ENOMEM; - newpolicy->sidtab = kzalloc(sizeof(*newpolicy->sidtab), GFP_KERNEL); + newpolicy->sidtab = kzalloc_obj(*newpolicy->sidtab); if (!newpolicy->sidtab) { rc = -ENOMEM; goto err_policy; @@ -2333,7 +2360,7 @@ int security_load_policy(void *data, size_t len, * in the new SID table. */ - convert_data = kmalloc(sizeof(*convert_data), GFP_KERNEL); + convert_data = kmalloc_obj(*convert_data); if (!convert_data) { rc = -ENOMEM; goto err_free_isids; @@ -2572,13 +2599,14 @@ out: * @name: interface name * @if_sid: interface SID */ -int security_netif_sid(char *name, u32 *if_sid) +int security_netif_sid(const char *name, u32 *if_sid) { struct selinux_policy *policy; struct policydb *policydb; struct sidtab *sidtab; int rc; struct ocontext *c; + bool wildcard_support; if (!selinux_initialized()) { *if_sid = SECINITSID_NETIF; @@ -2591,11 +2619,18 @@ retry: policy = rcu_dereference(selinux_state.policy); policydb = &policy->policydb; sidtab = policy->sidtab; + wildcard_support = ebitmap_get_bit(&policydb->policycaps, POLICYDB_CAP_NETIF_WILDCARD); c = policydb->ocontexts[OCON_NETIF]; while (c) { - if (strcmp(name, c->u.name) == 0) - break; + if (wildcard_support) { + if (match_wildcard(c->u.name, name)) + break; + } else { + if (strcmp(c->u.name, name) == 0) + break; + } + c = c->next; } @@ -2634,7 +2669,7 @@ static bool match_ipv6_addrmask(const u32 input[4], const u32 addr[4], const u32 * @out_sid: security identifier */ int security_node_sid(u16 domain, - void *addrp, + const void *addrp, u32 addrlen, u32 *out_sid) { @@ -2663,7 +2698,7 @@ retry: if (addrlen != sizeof(u32)) goto out; - addr = *((u32 *)addrp); + addr = *((const u32 *)addrp); c = policydb->ocontexts[OCON_NODE]; while (c) { @@ -2711,131 +2746,6 @@ out: return rc; } -#define SIDS_NEL 25 - -/** - * security_get_user_sids - Obtain reachable SIDs for a user. - * @fromsid: starting SID - * @username: username - * @sids: array of reachable SIDs for user - * @nel: number of elements in @sids - * - * Generate the set of SIDs for legal security contexts - * for a given user that can be reached by @fromsid. - * Set *@sids to point to a dynamically allocated - * array containing the set of SIDs. Set *@nel to the - * number of elements in the array. - */ - -int security_get_user_sids(u32 fromsid, - const char *username, - u32 **sids, - u32 *nel) -{ - struct selinux_policy *policy; - struct policydb *policydb; - struct sidtab *sidtab; - struct context *fromcon, usercon; - u32 *mysids = NULL, *mysids2, sid; - u32 i, j, mynel, maxnel = SIDS_NEL; - struct user_datum *user; - struct role_datum *role; - struct ebitmap_node *rnode, *tnode; - int rc; - - *sids = NULL; - *nel = 0; - - if (!selinux_initialized()) - return 0; - - mysids = kcalloc(maxnel, sizeof(*mysids), GFP_KERNEL); - if (!mysids) - return -ENOMEM; - -retry: - mynel = 0; - rcu_read_lock(); - policy = rcu_dereference(selinux_state.policy); - policydb = &policy->policydb; - sidtab = policy->sidtab; - - context_init(&usercon); - - rc = -EINVAL; - fromcon = sidtab_search(sidtab, fromsid); - if (!fromcon) - goto out_unlock; - - rc = -EINVAL; - user = symtab_search(&policydb->p_users, username); - if (!user) - goto out_unlock; - - usercon.user = user->value; - - ebitmap_for_each_positive_bit(&user->roles, rnode, i) { - role = policydb->role_val_to_struct[i]; - usercon.role = i + 1; - ebitmap_for_each_positive_bit(&role->types, tnode, j) { - usercon.type = j + 1; - - if (mls_setup_user_range(policydb, fromcon, user, - &usercon)) - continue; - - rc = sidtab_context_to_sid(sidtab, &usercon, &sid); - if (rc == -ESTALE) { - rcu_read_unlock(); - goto retry; - } - if (rc) - goto out_unlock; - if (mynel < maxnel) { - mysids[mynel++] = sid; - } else { - rc = -ENOMEM; - maxnel += SIDS_NEL; - mysids2 = kcalloc(maxnel, sizeof(*mysids2), GFP_ATOMIC); - if (!mysids2) - goto out_unlock; - memcpy(mysids2, mysids, mynel * sizeof(*mysids2)); - kfree(mysids); - mysids = mysids2; - mysids[mynel++] = sid; - } - } - } - rc = 0; -out_unlock: - rcu_read_unlock(); - if (rc || !mynel) { - kfree(mysids); - return rc; - } - - rc = -ENOMEM; - mysids2 = kcalloc(mynel, sizeof(*mysids2), GFP_KERNEL); - if (!mysids2) { - kfree(mysids); - return rc; - } - for (i = 0, j = 0; i < mynel; i++) { - struct av_decision dummy_avd; - rc = avc_has_perm_noaudit(fromsid, mysids[i], - SECCLASS_PROCESS, /* kernel value */ - PROCESS__TRANSITION, AVC_STRICT, - &dummy_avd); - if (!rc) - mysids2[j++] = mysids[i]; - cond_resched(); - } - kfree(mysids); - *sids = mysids2; - *nel = j; - return 0; -} - /** * __security_genfs_sid - Helper to obtain a SID for a file in a filesystem * @policy: policy @@ -2863,6 +2773,7 @@ static inline int __security_genfs_sid(struct selinux_policy *policy, struct genfs *genfs; struct ocontext *c; int cmp = 0; + bool wildcard; while (path[0] == '/' && path[1] == '/') path++; @@ -2879,11 +2790,20 @@ static inline int __security_genfs_sid(struct selinux_policy *policy, if (!genfs || cmp) return -ENOENT; + wildcard = ebitmap_get_bit(&policy->policydb.policycaps, + POLICYDB_CAP_GENFS_SECLABEL_WILDCARD); for (c = genfs->head; c; c = c->next) { - size_t len = strlen(c->u.name); - if ((!c->v.sclass || sclass == c->v.sclass) && - (strncmp(c->u.name, path, len) == 0)) - break; + if (!c->v.sclass || sclass == c->v.sclass) { + if (wildcard) { + if (match_wildcard(c->u.name, path)) + break; + } else { + size_t len = strlen(c->u.name); + + if ((strncmp(c->u.name, path, len)) == 0) + break; + } + } } if (!c) @@ -3020,7 +2940,7 @@ int security_get_bools(struct selinux_policy *policy, goto err; rc = -ENOMEM; - *values = kcalloc(*len, sizeof(int), GFP_ATOMIC); + *values = kzalloc_objs(int, *len, GFP_ATOMIC); if (!*values) goto err; @@ -3525,6 +3445,13 @@ struct selinux_audit_rule { struct context au_ctxt; }; +int selinux_audit_rule_avc_callback(u32 event) +{ + if (event == AVC_CALLBACK_RESET) + return audit_update_lsm_rules(); + return 0; +} + void selinux_audit_rule_free(void *vrule) { struct selinux_audit_rule *rule = vrule; @@ -3577,7 +3504,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, return -EINVAL; } - tmprule = kzalloc(sizeof(struct selinux_audit_rule), gfp); + tmprule = kzalloc_obj(struct selinux_audit_rule, gfp); if (!tmprule) return -ENOMEM; context_init(&tmprule->au_ctxt); @@ -3775,25 +3702,6 @@ out: return match; } -static int aurule_avc_callback(u32 event) -{ - if (event == AVC_CALLBACK_RESET) - return audit_update_lsm_rules(); - return 0; -} - -static int __init aurule_init(void) -{ - int err; - - err = avc_add_callback(aurule_avc_callback, AVC_CALLBACK_RESET); - if (err) - panic("avc_add_callback() failed, error %d\n", err); - - return err; -} -__initcall(aurule_init); - #ifdef CONFIG_NETLABEL /** * security_netlbl_cache_add - Add an entry to the NetLabel cache @@ -3811,7 +3719,7 @@ static void security_netlbl_cache_add(struct netlbl_lsm_secattr *secattr, { u32 *sid_cache; - sid_cache = kmalloc(sizeof(*sid_cache), GFP_ATOMIC); + sid_cache = kmalloc_obj(*sid_cache, GFP_ATOMIC); if (sid_cache == NULL) return; secattr->cache = netlbl_secattr_cache_alloc(GFP_ATOMIC); diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c index 59f8c09158ef..118af0aa2767 100644 --- a/security/selinux/ss/sidtab.c +++ b/security/selinux/ss/sidtab.c @@ -580,7 +580,7 @@ void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry, goto out_unlock; } - cache = kmalloc(struct_size(cache, str, str_len), GFP_ATOMIC); + cache = kmalloc_flex(*cache, str, str_len, GFP_ATOMIC); if (!cache) goto out_unlock; diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index 90ec4ef1b082..8e00b3306574 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -88,13 +88,13 @@ static int selinux_xfrm_alloc_user(struct xfrm_sec_ctx **ctxp, if (str_len >= PAGE_SIZE) return -ENOMEM; - ctx = kmalloc(struct_size(ctx, ctx_str, str_len + 1), gfp); + ctx = kmalloc_flex(*ctx, ctx_str, str_len + 1, gfp); if (!ctx) return -ENOMEM; ctx->ctx_doi = XFRM_SC_DOI_LSM; ctx->ctx_alg = XFRM_SC_ALG_SELINUX; - ctx->ctx_len = str_len; + ctx->ctx_len = str_len + 1; memcpy(ctx->ctx_str, &uctx[1], str_len); ctx->ctx_str[str_len] = '\0'; rc = security_context_to_sid(ctx->ctx_str, str_len, @@ -354,7 +354,7 @@ int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, if (rc) return rc; - ctx = kmalloc(struct_size(ctx, ctx_str, str_len), GFP_ATOMIC); + ctx = kmalloc_flex(*ctx, ctx_str, str_len, GFP_ATOMIC); if (!ctx) { rc = -ENOMEM; goto out; diff --git a/security/smack/smack.h b/security/smack/smack.h index bf6a6ed3946c..9b9eb262fe33 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -276,6 +276,20 @@ struct smk_audit_info { }; /* + * Initialization + */ +#if defined(CONFIG_SECURITY_SMACK_NETFILTER) +int smack_nf_ip_init(void); +#else +static inline int smack_nf_ip_init(void) +{ + return 0; +} +#endif +int init_smk_fs(void); +int smack_initcall(void); + +/* * These functions are in smack_access.c */ int smk_access_entry(char *, char *, struct list_head *); @@ -286,9 +300,12 @@ int smk_tskacc(struct task_smack *, struct smack_known *, int smk_curacc(struct smack_known *, u32, struct smk_audit_info *); int smack_str_from_perm(char *string, int access); struct smack_known *smack_from_secid(const u32); +int smk_parse_label_len(const char *string, int len); char *smk_parse_smack(const char *string, int len); int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int); struct smack_known *smk_import_entry(const char *, int); +struct smack_known *smk_import_valid_label(const char *label, int label_len, + gfp_t gfp); void smk_insert_entry(struct smack_known *skp); struct smack_known *smk_find_entry(const char *); bool smack_privileged(int cap); diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 2e4a0cb22782..350b88d582b3 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -392,7 +392,7 @@ void smack_log(char *subject_label, char *object_label, int request, } #else /* #ifdef CONFIG_AUDIT */ void smack_log(char *subject_label, char *object_label, int request, - int result, struct smk_audit_info *ad) + int result, struct smk_audit_info *ad) { } #endif @@ -443,19 +443,19 @@ struct smack_known *smk_find_entry(const char *string) } /** - * smk_parse_smack - parse smack label from a text string - * @string: a text string that might contain a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * smk_parse_label_len - calculate the length of the starting segment + * in the string that constitutes a valid smack label + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated * - * Returns a pointer to the clean label or an error code. + * Returns the length of the segment (0 < L < SMK_LONGLABEL) or an error code. */ -char *smk_parse_smack(const char *string, int len) +int smk_parse_label_len(const char *string, int len) { - char *smack; int i; - if (len <= 0) - len = strlen(string) + 1; + if (len <= 0 || len > SMK_LONGLABEL) + len = SMK_LONGLABEL; /* * Reserve a leading '-' as an indicator that @@ -463,7 +463,7 @@ char *smk_parse_smack(const char *string, int len) * including /smack/cipso and /smack/cipso2 */ if (string[0] == '-') - return ERR_PTR(-EINVAL); + return -EINVAL; for (i = 0; i < len; i++) if (string[i] > '~' || string[i] <= ' ' || string[i] == '/' || @@ -471,6 +471,25 @@ char *smk_parse_smack(const char *string, int len) break; if (i == 0 || i >= SMK_LONGLABEL) + return -EINVAL; + + return i; +} + +/** + * smk_parse_smack - copy the starting segment in the string + * that constitutes a valid smack label + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated + * + * Returns a pointer to the copy of the label or an error code. + */ +char *smk_parse_smack(const char *string, int len) +{ + char *smack; + int i = smk_parse_label_len(string, len); + + if (i < 0) return ERR_PTR(-EINVAL); smack = kstrndup(string, i, GFP_NOFS); @@ -554,31 +573,26 @@ int smack_populate_secattr(struct smack_known *skp) } /** - * smk_import_entry - import a label, return the list entry - * @string: a text string that might be a Smack label - * @len: the maximum size, or zero if it is NULL terminated. + * smk_import_valid_allocated_label - import a label, return the list entry + * @smack: a text string that is a valid Smack label and may be kfree()ed. + * It is consumed: either becomes a part of the entry or kfree'ed. + * @gfp: Allocation type * - * Returns a pointer to the entry in the label list that - * matches the passed string, adding it if necessary, - * or an error code. + * Returns: see description of smk_import_entry() */ -struct smack_known *smk_import_entry(const char *string, int len) +static struct smack_known * +smk_import_allocated_label(char *smack, gfp_t gfp) { struct smack_known *skp; - char *smack; int rc; - smack = smk_parse_smack(string, len); - if (IS_ERR(smack)) - return ERR_CAST(smack); - mutex_lock(&smack_known_lock); skp = smk_find_entry(smack); if (skp != NULL) goto freeout; - skp = kzalloc(sizeof(*skp), GFP_NOFS); + skp = kzalloc_obj(*skp, gfp); if (skp == NULL) { skp = ERR_PTR(-ENOMEM); goto freeout; @@ -609,6 +623,44 @@ unlockout: } /** + * smk_import_entry - import a label, return the list entry + * @string: a text string that might contain a Smack label at the beginning + * @len: the maximum size to look into, may be zero if string is null-terminated + * + * Returns a pointer to the entry in the label list that + * matches the passed string, adding it if necessary, + * or an error code. + */ +struct smack_known *smk_import_entry(const char *string, int len) +{ + char *smack = smk_parse_smack(string, len); + + if (IS_ERR(smack)) + return ERR_CAST(smack); + + return smk_import_allocated_label(smack, GFP_NOFS); +} + +/** + * smk_import_valid_label - import a label, return the list entry + * @label: a text string that is a valid Smack label, not null-terminated + * @label_len: the length of the text string in the @label + * @gfp: the GFP mask used for allocating memory for the @label text string copy + * + * Return: see description of smk_import_entry() + */ +struct smack_known * +smk_import_valid_label(const char *label, int label_len, gfp_t gfp) +{ + char *smack = kstrndup(label, label_len, gfp); + + if (!smack) + return ERR_PTR(-ENOMEM); + + return smk_import_allocated_label(smack, gfp); +} + +/** * smack_from_secid - find the Smack label associated with a secid * @secid: an integer that might be associated with a Smack label * diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 99833168604e..3f9ae05039a2 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -24,7 +24,6 @@ #include <linux/ip.h> #include <linux/tcp.h> #include <linux/udp.h> -#include <linux/dccp.h> #include <linux/icmpv6.h> #include <linux/slab.h> #include <linux/mutex.h> @@ -183,7 +182,7 @@ static int smk_bu_inode(struct inode *inode, int mode, int rc) char acc[SMK_NUM_ACCESS_TYPE + 1]; if (isp->smk_flags & SMK_INODE_IMPURE) - pr_info("Smack Unconfined Corruption: inode=(%s %ld) %s\n", + pr_info("Smack Unconfined Corruption: inode=(%s %llu) %s\n", inode->i_sb->s_id, inode->i_ino, current->comm); if (rc <= 0) @@ -196,7 +195,7 @@ static int smk_bu_inode(struct inode *inode, int mode, int rc) smk_bu_mode(mode, acc); - pr_info("Smack %s: (%s %s %s) inode=(%s %ld) %s\n", smk_bu_mess[rc], + pr_info("Smack %s: (%s %s %s) inode=(%s %llu) %s\n", smk_bu_mess[rc], tsp->smk_task->smk_known, isp->smk_inode->smk_known, acc, inode->i_sb->s_id, inode->i_ino, current->comm); return 0; @@ -215,7 +214,7 @@ static int smk_bu_file(struct file *file, int mode, int rc) char acc[SMK_NUM_ACCESS_TYPE + 1]; if (isp->smk_flags & SMK_INODE_IMPURE) - pr_info("Smack Unconfined Corruption: inode=(%s %ld) %s\n", + pr_info("Smack Unconfined Corruption: inode=(%s %llu) %s\n", inode->i_sb->s_id, inode->i_ino, current->comm); if (rc <= 0) @@ -224,7 +223,7 @@ static int smk_bu_file(struct file *file, int mode, int rc) rc = 0; smk_bu_mode(mode, acc); - pr_info("Smack %s: (%s %s %s) file=(%s %ld %pD) %s\n", smk_bu_mess[rc], + pr_info("Smack %s: (%s %s %s) file=(%s %llu %pD) %s\n", smk_bu_mess[rc], sskp->smk_known, smk_of_inode(inode)->smk_known, acc, inode->i_sb->s_id, inode->i_ino, file, current->comm); @@ -245,7 +244,7 @@ static int smk_bu_credfile(const struct cred *cred, struct file *file, char acc[SMK_NUM_ACCESS_TYPE + 1]; if (isp->smk_flags & SMK_INODE_IMPURE) - pr_info("Smack Unconfined Corruption: inode=(%s %ld) %s\n", + pr_info("Smack Unconfined Corruption: inode=(%s %llu) %s\n", inode->i_sb->s_id, inode->i_ino, current->comm); if (rc <= 0) @@ -254,7 +253,7 @@ static int smk_bu_credfile(const struct cred *cred, struct file *file, rc = 0; smk_bu_mode(mode, acc); - pr_info("Smack %s: (%s %s %s) file=(%s %ld %pD) %s\n", smk_bu_mess[rc], + pr_info("Smack %s: (%s %s %s) file=(%s %llu %pD) %s\n", smk_bu_mess[rc], sskp->smk_known, smk_of_inode(inode)->smk_known, acc, inode->i_sb->s_id, inode->i_ino, file, current->comm); @@ -373,7 +372,7 @@ static int smk_copy_relabel(struct list_head *nhead, struct list_head *ohead, struct smack_known_list_elem *oklep; list_for_each_entry(oklep, ohead, list) { - nklep = kzalloc(sizeof(struct smack_known_list_elem), gfp); + nklep = kzalloc_obj(struct smack_known_list_elem, gfp); if (nklep == NULL) { smk_destroy_label_list(nhead); return -ENOMEM; @@ -563,7 +562,7 @@ static int smack_add_opt(int token, const char *s, void **mnt_opts) struct smack_known *skp; if (!opts) { - opts = kzalloc(sizeof(struct smack_mnt_opts), GFP_KERNEL); + opts = kzalloc_obj(struct smack_mnt_opts); if (!opts) return -ENOMEM; *mnt_opts = opts; @@ -623,7 +622,7 @@ static int smack_fs_context_submount(struct fs_context *fc, struct smack_mnt_opts *ctx; struct inode_smack *isp; - ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + ctx = kzalloc_obj(*ctx); if (!ctx) return -ENOMEM; fc->security = ctx; @@ -674,7 +673,7 @@ static int smack_fs_context_dup(struct fs_context *fc, if (!src) return 0; - fc->security = kzalloc(sizeof(struct smack_mnt_opts), GFP_KERNEL); + fc->security = kzalloc_obj(struct smack_mnt_opts); if (!fc->security) return -ENOMEM; @@ -964,6 +963,42 @@ static int smack_inode_alloc_security(struct inode *inode) } /** + * smk_rule_transmutes - does access rule for (subject,object) contain 't'? + * @subject: a pointer to the subject's Smack label entry + * @object: a pointer to the object's Smack label entry + */ +static bool +smk_rule_transmutes(struct smack_known *subject, + const struct smack_known *object) +{ + int may; + + rcu_read_lock(); + may = smk_access_entry(subject->smk_known, object->smk_known, + &subject->smk_rules); + rcu_read_unlock(); + return (may > 0) && (may & MAY_TRANSMUTE); +} + +static int +xattr_dupval(struct xattr *xattrs, int *xattr_count, + const char *name, const void *value, unsigned int vallen) +{ + struct xattr * const xattr = lsm_get_xattr_slot(xattrs, xattr_count); + + if (!xattr) + return 0; + + xattr->value = kmemdup(value, vallen, GFP_NOFS); + if (!xattr->value) + return -ENOMEM; + + xattr->value_len = vallen; + xattr->name = name; + return 0; +} + +/** * smack_inode_init_security - copy out the smack from an inode * @inode: the newly created inode * @dir: containing directory object @@ -978,23 +1013,30 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, struct xattr *xattrs, int *xattr_count) { struct task_smack *tsp = smack_cred(current_cred()); - struct inode_smack *issp = smack_inode(inode); - struct smack_known *skp = smk_of_task(tsp); - struct smack_known *isp = smk_of_inode(inode); + struct inode_smack * const issp = smack_inode(inode); struct smack_known *dsp = smk_of_inode(dir); - struct xattr *xattr = lsm_get_xattr_slot(xattrs, xattr_count); - int may; + int rc = 0; + int transflag = 0; + bool trans_cred; + bool trans_rule; /* + * UNIX domain sockets use lower level socket data. Let + * UDS inode have fixed * label to keep smack_inode_permission() calm + * when called from unix_find_bsd() + */ + if (S_ISSOCK(inode->i_mode)) { + /* forced label, no need to save to xattrs */ + issp->smk_inode = &smack_known_star; + goto instant_inode; + } + /* * If equal, transmuting already occurred in * smack_dentry_create_files_as(). No need to check again. */ - if (tsp->smk_task != tsp->smk_transmuted) { - rcu_read_lock(); - may = smk_access_entry(skp->smk_known, dsp->smk_known, - &skp->smk_rules); - rcu_read_unlock(); - } + trans_cred = (tsp->smk_task == tsp->smk_transmuted); + if (!trans_cred) + trans_rule = smk_rule_transmutes(smk_of_task(tsp), dsp); /* * In addition to having smk_task equal to smk_transmuted, @@ -1002,47 +1044,38 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, * requests transmutation then by all means transmute. * Mark the inode as changed. */ - if ((tsp->smk_task == tsp->smk_transmuted) || - (may > 0 && ((may & MAY_TRANSMUTE) != 0) && - smk_inode_transmutable(dir))) { - struct xattr *xattr_transmute; - + if (trans_cred || (trans_rule && smk_inode_transmutable(dir))) { /* * The caller of smack_dentry_create_files_as() * should have overridden the current cred, so the * inode label was already set correctly in * smack_inode_alloc_security(). */ - if (tsp->smk_task != tsp->smk_transmuted) - isp = issp->smk_inode = dsp; - - issp->smk_flags |= SMK_INODE_TRANSMUTE; - xattr_transmute = lsm_get_xattr_slot(xattrs, - xattr_count); - if (xattr_transmute) { - xattr_transmute->value = kmemdup(TRANS_TRUE, - TRANS_TRUE_SIZE, - GFP_NOFS); - if (!xattr_transmute->value) - return -ENOMEM; + if (!trans_cred) + issp->smk_inode = dsp; - xattr_transmute->value_len = TRANS_TRUE_SIZE; - xattr_transmute->name = XATTR_SMACK_TRANSMUTE; + if (S_ISDIR(inode->i_mode)) { + transflag = SMK_INODE_TRANSMUTE; + + if (xattr_dupval(xattrs, xattr_count, + XATTR_SMACK_TRANSMUTE, + TRANS_TRUE, + TRANS_TRUE_SIZE + )) + rc = -ENOMEM; } } - issp->smk_flags |= SMK_INODE_INSTANT; - - if (xattr) { - xattr->value = kstrdup(isp->smk_known, GFP_NOFS); - if (!xattr->value) - return -ENOMEM; - - xattr->value_len = strlen(isp->smk_known); - xattr->name = XATTR_SMACK_SUFFIX; - } - - return 0; + if (rc == 0) + if (xattr_dupval(xattrs, xattr_count, + XATTR_SMACK_SUFFIX, + issp->smk_inode->smk_known, + strlen(issp->smk_inode->smk_known) + )) + rc = -ENOMEM; +instant_inode: + issp->smk_flags |= (SMK_INODE_INSTANT | transflag); + return rc; } /** @@ -1316,13 +1349,23 @@ static int smack_inode_setxattr(struct mnt_idmap *idmap, int check_import = 0; int check_star = 0; int rc = 0; + umode_t const i_mode = d_backing_inode(dentry)->i_mode; /* * Check label validity here so import won't fail in post_setxattr */ - if (strcmp(name, XATTR_NAME_SMACK) == 0 || - strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || - strcmp(name, XATTR_NAME_SMACKIPOUT) == 0) { + if (strcmp(name, XATTR_NAME_SMACK) == 0) { + /* + * UDS inode has fixed label + */ + if (S_ISSOCK(i_mode)) { + rc = -EINVAL; + } else { + check_priv = 1; + check_import = 1; + } + } else if (strcmp(name, XATTR_NAME_SMACKIPIN) == 0 || + strcmp(name, XATTR_NAME_SMACKIPOUT) == 0) { check_priv = 1; check_import = 1; } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0 || @@ -1332,7 +1375,7 @@ static int smack_inode_setxattr(struct mnt_idmap *idmap, check_star = 1; } else if (strcmp(name, XATTR_NAME_SMACKTRANSMUTE) == 0) { check_priv = 1; - if (!S_ISDIR(d_backing_inode(dentry)->i_mode) || + if (!S_ISDIR(i_mode) || size != TRANS_TRUE_SIZE || strncmp(value, TRANS_TRUE, TRANS_TRUE_SIZE) != 0) rc = -EINVAL; @@ -1463,12 +1506,15 @@ static int smack_inode_removexattr(struct mnt_idmap *idmap, * Don't do anything special for these. * XATTR_NAME_SMACKIPIN * XATTR_NAME_SMACKIPOUT + * XATTR_NAME_SMACK if S_ISSOCK (UDS inode has fixed label) */ if (strcmp(name, XATTR_NAME_SMACK) == 0) { - struct super_block *sbp = dentry->d_sb; - struct superblock_smack *sbsp = smack_superblock(sbp); + if (!S_ISSOCK(d_backing_inode(dentry)->i_mode)) { + struct super_block *sbp = dentry->d_sb; + struct superblock_smack *sbsp = smack_superblock(sbp); - isp->smk_inode = sbsp->smk_default; + isp->smk_inode = sbsp->smk_default; + } } else if (strcmp(name, XATTR_NAME_SMACKEXEC) == 0) isp->smk_task = NULL; else if (strcmp(name, XATTR_NAME_SMACKMMAP) == 0) @@ -2771,7 +2817,7 @@ static void smk_ipv6_port_label(struct socket *sock, struct sockaddr *address) /* * A new port entry is required. */ - spp = kzalloc(sizeof(*spp), GFP_KERNEL); + spp = kzalloc_obj(*spp); if (spp == NULL) return; @@ -3586,7 +3632,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ /* - * UNIX domain sockets use lower level socket data. + * UDS inode has fixed label (*) */ if (S_ISSOCK(inode->i_mode)) { final = &smack_known_star; @@ -3664,7 +3710,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * @attr: which attribute to fetch * @ctx: buffer to receive the result * @size: available size in, actual size out - * @flags: unused + * @flags: reserved, currently zero * * Fill the passed user space @ctx with the details of the requested * attribute. @@ -3725,47 +3771,55 @@ static int smack_getprocattr(struct task_struct *p, const char *name, char **val * Sets the Smack value of the task. Only setting self * is permitted and only with privilege * - * Returns the length of the smack label or an error code + * Returns zero on success or an error code */ -static int do_setattr(u64 attr, void *value, size_t size) +static int do_setattr(unsigned int attr, void *value, size_t size) { struct task_smack *tsp = smack_cred(current_cred()); struct cred *new; struct smack_known *skp; - struct smack_known_list_elem *sklep; - int rc; - - if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel)) - return -EPERM; + int label_len; + /* + * let unprivileged user validate input, check permissions later + */ if (value == NULL || size == 0 || size >= SMK_LONGLABEL) return -EINVAL; - if (attr != LSM_ATTR_CURRENT) - return -EOPNOTSUPP; - - skp = smk_import_entry(value, size); - if (IS_ERR(skp)) - return PTR_ERR(skp); + label_len = smk_parse_label_len(value, size); + if (label_len < 0 || label_len != size) + return -EINVAL; /* * No process is ever allowed the web ("@") label * and the star ("*") label. */ - if (skp == &smack_known_web || skp == &smack_known_star) - return -EINVAL; + if (label_len == 1 /* '@', '*' */) { + const char c = *(const char *)value; + + if (c == *smack_known_web.smk_known || + c == *smack_known_star.smk_known) + return -EPERM; + } if (!smack_privileged(CAP_MAC_ADMIN)) { - rc = -EPERM; - list_for_each_entry(sklep, &tsp->smk_relabel, list) - if (sklep->smk_label == skp) { - rc = 0; - break; - } - if (rc) - return rc; + const struct smack_known_list_elem *sklep; + list_for_each_entry(sklep, &tsp->smk_relabel, list) { + const char *cp = sklep->smk_label->smk_known; + + if (strlen(cp) == label_len && + strncmp(cp, value, label_len) == 0) + goto in_relabel; + } + return -EPERM; +in_relabel: + ; } + skp = smk_import_valid_label(value, label_len, GFP_KERNEL); + if (IS_ERR(skp)) + return PTR_ERR(skp); + new = prepare_creds(); if (new == NULL) return -ENOMEM; @@ -3778,7 +3832,7 @@ static int do_setattr(u64 attr, void *value, size_t size) smk_destroy_label_list(&tsp->smk_relabel); commit_creds(new); - return size; + return 0; } /** @@ -3786,7 +3840,7 @@ static int do_setattr(u64 attr, void *value, size_t size) * @attr: which attribute to set * @ctx: buffer containing the data * @size: size of @ctx - * @flags: unused + * @flags: reserved, must be zero * * Fill the passed user space @ctx with the details of the requested * attribute. @@ -3796,12 +3850,26 @@ static int do_setattr(u64 attr, void *value, size_t size) static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx, u32 size, u32 flags) { - int rc; + if (attr != LSM_ATTR_CURRENT) + return -EOPNOTSUPP; - rc = do_setattr(attr, ctx->ctx, ctx->ctx_len); - if (rc > 0) - return 0; - return rc; + if (ctx->flags) + return -EINVAL; + /* + * string must have \0 terminator, included in ctx->ctx + * (see description of struct lsm_ctx) + */ + if (ctx->ctx_len == 0) + return -EINVAL; + + if (ctx->ctx[ctx->ctx_len - 1] != '\0') + return -EINVAL; + /* + * other do_setattr() caller, smack_setprocattr(), + * does not count \0 into size, so + * decreasing length by 1 to accommodate the divergence. + */ + return do_setattr(attr, ctx->ctx, ctx->ctx_len - 1); } /** @@ -3813,15 +3881,39 @@ static int smack_setselfattr(unsigned int attr, struct lsm_ctx *ctx, * Sets the Smack value of the task. Only setting self * is permitted and only with privilege * - * Returns the length of the smack label or an error code + * Returns the size of the input value or an error code */ static int smack_setprocattr(const char *name, void *value, size_t size) { - int attr = lsm_name_to_attr(name); + size_t realsize = size; + unsigned int attr = lsm_name_to_attr(name); - if (attr != LSM_ATTR_UNDEF) - return do_setattr(attr, value, size); - return -EINVAL; + switch (attr) { + case LSM_ATTR_UNDEF: return -EINVAL; + default: return -EOPNOTSUPP; + case LSM_ATTR_CURRENT: + ; + } + + /* + * The value for the "current" attribute is the label + * followed by one of the 4 trailers: none, \0, \n, \n\0 + * + * I.e. following inputs are accepted as 3-characters long label "foo": + * + * "foo" (3 characters) + * "foo\0" (4 characters) + * "foo\n" (4 characters) + * "foo\n\0" (5 characters) + */ + + if (realsize && (((const char *)value)[realsize - 1] == '\0')) + --realsize; + + if (realsize && (((const char *)value)[realsize - 1] == '\n')) + --realsize; + + return do_setattr(attr, value, realsize) ? : size; } /** @@ -4061,7 +4153,6 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr_in6 *sip) __be16 frag_off; struct tcphdr _tcph, *th; struct udphdr _udph, *uh; - struct dccp_hdr _dccph, *dh; sip->sin6_port = 0; @@ -4085,16 +4176,10 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr_in6 *sip) sip->sin6_port = th->source; break; case IPPROTO_UDP: - case IPPROTO_UDPLITE: uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); if (uh != NULL) sip->sin6_port = uh->source; break; - case IPPROTO_DCCP: - dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); - if (dh != NULL) - sip->sin6_port = dh->dccph_sport; - break; } return proto; } @@ -4215,8 +4300,7 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) #if IS_ENABLED(CONFIG_IPV6) case PF_INET6: proto = smk_skb_to_addr_ipv6(skb, &sadd); - if (proto != IPPROTO_UDP && proto != IPPROTO_UDPLITE && - proto != IPPROTO_TCP && proto != IPPROTO_DCCP) + if (proto != IPPROTO_UDP && proto != IPPROTO_TCP) break; #ifdef SMACK_IPV6_SECMARK_LABELING skp = smack_from_skb(skb); @@ -4857,6 +4941,11 @@ static int smack_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) { + /* + * UDS inode has fixed label. Ignore nfs label. + */ + if (S_ISSOCK(inode->i_mode)) + return 0; return smack_inode_setsecurity(inode, XATTR_SMACK_SUFFIX, ctx, ctxlen, 0); } @@ -4915,14 +5004,13 @@ static int smack_inode_copy_up_xattr(struct dentry *src, const char *name) } static int smack_dentry_create_files_as(struct dentry *dentry, int mode, - struct qstr *name, + const struct qstr *name, const struct cred *old, struct cred *new) { struct task_smack *otsp = smack_cred(old); struct task_smack *ntsp = smack_cred(new); struct inode_smack *isp; - int may; /* * Use the process credential unless all of @@ -4936,18 +5024,12 @@ static int smack_dentry_create_files_as(struct dentry *dentry, int mode, isp = smack_inode(d_inode(dentry->d_parent)); if (isp->smk_flags & SMK_INODE_TRANSMUTE) { - rcu_read_lock(); - may = smk_access_entry(otsp->smk_task->smk_known, - isp->smk_inode->smk_known, - &otsp->smk_task->smk_rules); - rcu_read_unlock(); - /* * If the directory is transmuting and the rule * providing access is transmuting use the containing * directory label instead of the process label. */ - if (may > 0 && (may & MAY_TRANSMUTE)) { + if (smk_rule_transmutes(otsp->smk_task, isp->smk_inode)) { ntsp->smk_task = isp->smk_inode; ntsp->smk_transmuted = ntsp->smk_task; } @@ -5274,16 +5356,30 @@ static __init int smack_init(void) /* initialize the smack_known_list */ init_smack_known_list(); + /* Inform the audit system that secctx is used */ + audit_cfg_lsm(&smack_lsmid, + AUDIT_CFG_LSM_SECCTX_SUBJECT | + AUDIT_CFG_LSM_SECCTX_OBJECT); + return 0; } +int __init smack_initcall(void) +{ + int rc_fs = init_smk_fs(); + int rc_nf = smack_nf_ip_init(); + + return rc_fs ? rc_fs : rc_nf; +} + /* * Smack requires early initialization in order to label * all processes and objects when they are created. */ DEFINE_LSM(smack) = { - .name = "smack", + .id = &smack_lsmid, .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE, .blobs = &smack_blob_sizes, .init = smack_init, + .initcall_device = smack_initcall, }; diff --git a/security/smack/smack_netfilter.c b/security/smack/smack_netfilter.c index 8fd747b3653a..17ba578b1308 100644 --- a/security/smack/smack_netfilter.c +++ b/security/smack/smack_netfilter.c @@ -68,7 +68,7 @@ static struct pernet_operations smack_net_ops = { .exit = smack_nf_unregister, }; -static int __init smack_nf_ip_init(void) +int __init smack_nf_ip_init(void) { if (smack_enabled == 0) return 0; @@ -76,5 +76,3 @@ static int __init smack_nf_ip_init(void) printk(KERN_DEBUG "Smack: Registering netfilter hooks\n"); return register_pernet_subsys(&smack_net_ops); } - -__initcall(smack_nf_ip_init); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 90a67e410808..6e62dcb36f74 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -70,6 +70,7 @@ enum smk_inos { static DEFINE_MUTEX(smack_cipso_lock); static DEFINE_MUTEX(smack_ambient_lock); static DEFINE_MUTEX(smk_net4addr_lock); +static DEFINE_MUTEX(smk_cipso_doi_lock); #if IS_ENABLED(CONFIG_IPV6) static DEFINE_MUTEX(smk_net6addr_lock); #endif /* CONFIG_IPV6 */ @@ -141,7 +142,7 @@ struct smack_parsed_rule { int smk_access2; }; -static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; +static u32 smk_cipso_doi_value = CIPSO_V4_DOI_UNKNOWN; /* * Values for parsing cipso rules @@ -663,43 +664,60 @@ static const struct file_operations smk_load_ops = { }; /** - * smk_cipso_doi - initialize the CIPSO domain + * smk_cipso_doi - set netlabel maps + * @ndoi: new value for our CIPSO DOI + * @gfp_flags: kmalloc allocation context */ -static void smk_cipso_doi(void) +static int +smk_cipso_doi(u32 ndoi, gfp_t gfp_flags) { - int rc; + int rc = 0; struct cipso_v4_doi *doip; struct netlbl_audit nai; - smk_netlabel_audit_set(&nai); + mutex_lock(&smk_cipso_doi_lock); - rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); - if (rc != 0) - printk(KERN_WARNING "%s:%d remove rc = %d\n", - __func__, __LINE__, rc); + if (smk_cipso_doi_value == ndoi) + goto clr_doi_lock; + + smk_netlabel_audit_set(&nai); - doip = kmalloc(sizeof(struct cipso_v4_doi), GFP_KERNEL | __GFP_NOFAIL); + doip = kmalloc_obj(struct cipso_v4_doi, gfp_flags); + if (!doip) { + rc = -ENOMEM; + goto clr_doi_lock; + } doip->map.std = NULL; - doip->doi = smk_cipso_doi_value; + doip->doi = ndoi; doip->type = CIPSO_V4_MAP_PASS; doip->tags[0] = CIPSO_V4_TAG_RBITMAP; for (rc = 1; rc < CIPSO_V4_TAG_MAXCNT; rc++) doip->tags[rc] = CIPSO_V4_TAG_INVALID; rc = netlbl_cfg_cipsov4_add(doip, &nai); - if (rc != 0) { - printk(KERN_WARNING "%s:%d cipso add rc = %d\n", - __func__, __LINE__, rc); + if (rc) { kfree(doip); - return; + goto clr_doi_lock; } - rc = netlbl_cfg_cipsov4_map_add(doip->doi, NULL, NULL, NULL, &nai); - if (rc != 0) { - printk(KERN_WARNING "%s:%d map add rc = %d\n", - __func__, __LINE__, rc); - netlbl_cfg_cipsov4_del(doip->doi, &nai); - return; + + if (smk_cipso_doi_value != CIPSO_V4_DOI_UNKNOWN) { + rc = netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); + if (rc && rc != -ENOENT) + goto clr_ndoi_def; + + netlbl_cfg_cipsov4_del(smk_cipso_doi_value, &nai); } + + rc = netlbl_cfg_cipsov4_map_add(ndoi, NULL, NULL, NULL, &nai); + if (rc) { + smk_cipso_doi_value = CIPSO_V4_DOI_UNKNOWN; // no default map +clr_ndoi_def: netlbl_cfg_cipsov4_del(ndoi, &nai); + } else + smk_cipso_doi_value = ndoi; + +clr_doi_lock: + mutex_unlock(&smk_cipso_doi_lock); + return rc; } /** @@ -1077,13 +1095,12 @@ static int smk_open_net4addr(struct inode *inode, struct file *file) } /** - * smk_net4addr_insert + * smk_net4addr_insert - insert a new entry into the net4addrs list * @new : netlabel to insert * - * This helper insert netlabel in the smack_net4addrs list + * This helper inserts netlabel in the smack_net4addrs list * sorted by netmask length (longest to smallest) - * locked by &smk_net4addr_lock in smk_write_net4addr - * + * locked by &smk_net4addr_lock in smk_write_net4addr. */ static void smk_net4addr_insert(struct smk_net4addr *new) { @@ -1232,7 +1249,7 @@ static ssize_t smk_write_net4addr(struct file *file, const char __user *buf, smk_netlabel_audit_set(&audit_info); if (found == 0) { - snp = kzalloc(sizeof(*snp), GFP_KERNEL); + snp = kzalloc_obj(*snp); if (snp == NULL) rc = -ENOMEM; else { @@ -1340,13 +1357,12 @@ static int smk_open_net6addr(struct inode *inode, struct file *file) } /** - * smk_net6addr_insert + * smk_net6addr_insert - insert a new entry into the net6addrs list * @new : entry to insert * * This inserts an entry in the smack_net6addrs list * sorted by netmask length (longest to smallest) - * locked by &smk_net6addr_lock in smk_write_net6addr - * + * locked by &smk_net6addr_lock in smk_write_net6addr. */ static void smk_net6addr_insert(struct smk_net6addr *new) { @@ -1510,7 +1526,7 @@ static ssize_t smk_write_net6addr(struct file *file, const char __user *buf, break; } if (found == 0) { - snp = kzalloc(sizeof(*snp), GFP_KERNEL); + snp = kzalloc_obj(*snp); if (snp == NULL) rc = -ENOMEM; else { @@ -1564,7 +1580,7 @@ static ssize_t smk_read_doi(struct file *filp, char __user *buf, if (*ppos != 0) return 0; - sprintf(temp, "%d", smk_cipso_doi_value); + sprintf(temp, "%lu", (unsigned long)smk_cipso_doi_value); rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); return rc; @@ -1583,7 +1599,7 @@ static ssize_t smk_write_doi(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char temp[80]; - int i; + unsigned long u; if (!smack_privileged(CAP_MAC_ADMIN)) return -EPERM; @@ -1596,14 +1612,13 @@ static ssize_t smk_write_doi(struct file *file, const char __user *buf, temp[count] = '\0'; - if (sscanf(temp, "%d", &i) != 1) + if (kstrtoul(temp, 10, &u)) return -EINVAL; - smk_cipso_doi_value = i; - - smk_cipso_doi(); + if (u == CIPSO_V4_DOI_UNKNOWN || u > U32_MAX) + return -EINVAL; - return count; + return smk_cipso_doi(u, GFP_KERNEL) ? : count; } static const struct file_operations smk_doi_ops = { @@ -1955,7 +1970,7 @@ static int smk_parse_label_list(char *data, struct list_head *list) if (IS_ERR(skp)) return PTR_ERR(skp); - sklep = kzalloc(sizeof(*sklep), GFP_KERNEL); + sklep = kzalloc_obj(*sklep); if (sklep == NULL) return -ENOMEM; @@ -2962,7 +2977,7 @@ static int smk_init_fs_context(struct fs_context *fc) static struct file_system_type smk_fs_type = { .name = "smackfs", .init_fs_context = smk_init_fs_context, - .kill_sb = kill_litter_super, + .kill_sb = kill_anon_super, }; static struct vfsmount *smackfs_mount; @@ -2980,10 +2995,11 @@ static struct vfsmount *smackfs_mount; * Returns true if we were not chosen on boot or if * we were chosen and filesystem registration succeeded. */ -static int __init init_smk_fs(void) +int __init init_smk_fs(void) { int err; int rc; + struct netlbl_audit nai; if (smack_enabled == 0) return 0; @@ -3002,7 +3018,10 @@ static int __init init_smk_fs(void) } } - smk_cipso_doi(); + smk_netlabel_audit_set(&nai); + (void) netlbl_cfg_map_del(NULL, PF_INET, NULL, NULL, &nai); + (void) smk_cipso_doi(SMACK_CIPSO_DOI_DEFAULT, + GFP_KERNEL | __GFP_NOFAIL); smk_unlbl_ambient(NULL); rc = smack_populate_secattr(&smack_known_floor); @@ -3023,5 +3042,3 @@ static int __init init_smk_fs(void) return err; } - -__initcall(init_smk_fs); diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile index 55c67b9846a9..e3c0f853aa3b 100644 --- a/security/tomoyo/Makefile +++ b/security/tomoyo/Makefile @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 +CONTEXT_ANALYSIS := y + obj-y = audit.o common.o condition.o domain.o environ.o file.o gc.o group.o load_policy.o memory.o mount.o network.o realpath.o securityfs_if.o tomoyo.o util.o targets += builtin-policy.h diff --git a/security/tomoyo/audit.c b/security/tomoyo/audit.c index 610c1536cf70..1fba46aa357a 100644 --- a/security/tomoyo/audit.c +++ b/security/tomoyo/audit.c @@ -195,21 +195,19 @@ static char *tomoyo_print_header(struct tomoyo_request_info *r) if (i & 1) { pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, - " path%u.parent={ uid=%u gid=%u ino=%lu perm=0%o }", + " path%u.parent={ uid=%u gid=%u ino=%llu perm=0%o }", (i >> 1) + 1, from_kuid(&init_user_ns, stat->uid), from_kgid(&init_user_ns, stat->gid), - (unsigned long)stat->ino, - stat->mode & S_IALLUGO); + stat->ino, stat->mode & S_IALLUGO); continue; } pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, - " path%u={ uid=%u gid=%u ino=%lu major=%u minor=%u perm=0%o type=%s", + " path%u={ uid=%u gid=%u ino=%llu major=%u minor=%u perm=0%o type=%s", (i >> 1) + 1, from_kuid(&init_user_ns, stat->uid), from_kgid(&init_user_ns, stat->gid), - (unsigned long)stat->ino, - MAJOR(dev), MINOR(dev), + stat->ino, MAJOR(dev), MINOR(dev), mode & S_IALLUGO, tomoyo_filetype(mode)); if (S_ISCHR(mode) || S_ISBLK(mode)) { dev = stat->rdev; @@ -376,7 +374,7 @@ void tomoyo_write_log2(struct tomoyo_request_info *r, int len, const char *fmt, buf = tomoyo_init_log(r, len, fmt, args); if (!buf) goto out; - entry = kzalloc(sizeof(*entry), GFP_NOFS); + entry = kzalloc_obj(*entry, GFP_NOFS); if (!entry) { kfree(buf); goto out; diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index d9fa69632147..fdaeaff01fc1 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -268,6 +268,7 @@ static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, */ static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) + __must_hold(&head->io_sem) { va_list args; size_t len; @@ -416,8 +417,9 @@ static void tomoyo_print_name_union_quoted(struct tomoyo_io_buffer *head, * * Returns nothing. */ -static void tomoyo_print_number_union_nospace -(struct tomoyo_io_buffer *head, const struct tomoyo_number_union *ptr) +static void +tomoyo_print_number_union_nospace(struct tomoyo_io_buffer *head, const struct tomoyo_number_union *ptr) + __must_hold(&head->io_sem) { if (ptr->group) { tomoyo_set_string(head, "@"); @@ -466,6 +468,7 @@ static void tomoyo_print_number_union_nospace */ static void tomoyo_print_number_union(struct tomoyo_io_buffer *head, const struct tomoyo_number_union *ptr) + __must_hold(&head->io_sem) { tomoyo_set_space(head); tomoyo_print_number_union_nospace(head, ptr); @@ -490,7 +493,7 @@ static struct tomoyo_profile *tomoyo_assign_profile ptr = ns->profile_ptr[profile]; if (ptr) return ptr; - entry = kzalloc(sizeof(*entry), GFP_NOFS | __GFP_NOWARN); + entry = kzalloc_obj(*entry, GFP_NOFS | __GFP_NOWARN); if (mutex_lock_interruptible(&tomoyo_policy_lock)) goto out; ptr = ns->profile_ptr[profile]; @@ -664,6 +667,7 @@ static int tomoyo_set_mode(char *name, const char *value, * Returns 0 on success, negative value otherwise. */ static int tomoyo_write_profile(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { char *data = head->write_buf; unsigned int i; @@ -719,6 +723,7 @@ static int tomoyo_write_profile(struct tomoyo_io_buffer *head) * Caller prints functionality's name. */ static void tomoyo_print_config(struct tomoyo_io_buffer *head, const u8 config) + __must_hold(&head->io_sem) { tomoyo_io_printf(head, "={ mode=%s grant_log=%s reject_log=%s }\n", tomoyo_mode[config & 3], @@ -734,6 +739,7 @@ static void tomoyo_print_config(struct tomoyo_io_buffer *head, const u8 config) * Returns nothing. */ static void tomoyo_read_profile(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { u8 index; struct tomoyo_policy_namespace *ns = @@ -852,6 +858,7 @@ static bool tomoyo_same_manager(const struct tomoyo_acl_head *a, */ static int tomoyo_update_manager_entry(const char *manager, const bool is_delete) + __must_hold_shared(&tomoyo_ss) { struct tomoyo_manager e = { }; struct tomoyo_acl_param param = { @@ -883,6 +890,8 @@ static int tomoyo_update_manager_entry(const char *manager, * Caller holds tomoyo_read_lock(). */ static int tomoyo_write_manager(struct tomoyo_io_buffer *head) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { char *data = head->write_buf; @@ -901,6 +910,7 @@ static int tomoyo_write_manager(struct tomoyo_io_buffer *head) * Caller holds tomoyo_read_lock(). */ static void tomoyo_read_manager(struct tomoyo_io_buffer *head) + __must_hold_shared(&tomoyo_ss) { if (head->r.eof) return; @@ -927,6 +937,7 @@ static void tomoyo_read_manager(struct tomoyo_io_buffer *head) * Caller holds tomoyo_read_lock(). */ static bool tomoyo_manager(void) + __must_hold_shared(&tomoyo_ss) { struct tomoyo_manager *ptr; const char *exe; @@ -981,6 +992,8 @@ static struct tomoyo_domain_info *tomoyo_find_domain_by_qid */ static bool tomoyo_select_domain(struct tomoyo_io_buffer *head, const char *data) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { unsigned int pid; struct tomoyo_domain_info *domain = NULL; @@ -1051,6 +1064,7 @@ static bool tomoyo_same_task_acl(const struct tomoyo_acl_info *a, * Caller holds tomoyo_read_lock(). */ static int tomoyo_write_task(struct tomoyo_acl_param *param) + __must_hold_shared(&tomoyo_ss) { int error = -EINVAL; @@ -1079,6 +1093,7 @@ static int tomoyo_write_task(struct tomoyo_acl_param *param) * Caller holds tomoyo_read_lock(). */ static int tomoyo_delete_domain(char *domainname) + __must_hold_shared(&tomoyo_ss) { struct tomoyo_domain_info *domain; struct tomoyo_path_info name; @@ -1118,6 +1133,7 @@ static int tomoyo_delete_domain(char *domainname) static int tomoyo_write_domain2(struct tomoyo_policy_namespace *ns, struct list_head *list, char *data, const bool is_delete) + __must_hold_shared(&tomoyo_ss) { struct tomoyo_acl_param param = { .ns = ns, @@ -1162,6 +1178,8 @@ const char * const tomoyo_dif[TOMOYO_MAX_DOMAIN_INFO_FLAGS] = { * Caller holds tomoyo_read_lock(). */ static int tomoyo_write_domain(struct tomoyo_io_buffer *head) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { char *data = head->write_buf; struct tomoyo_policy_namespace *ns; @@ -1223,6 +1241,7 @@ static int tomoyo_write_domain(struct tomoyo_io_buffer *head) */ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head, const struct tomoyo_condition *cond) + __must_hold(&head->io_sem) { switch (head->r.cond_step) { case 0: @@ -1364,6 +1383,7 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head, */ static void tomoyo_set_group(struct tomoyo_io_buffer *head, const char *category) + __must_hold(&head->io_sem) { if (head->type == TOMOYO_EXCEPTIONPOLICY) { tomoyo_print_namespace(head); @@ -1383,6 +1403,7 @@ static void tomoyo_set_group(struct tomoyo_io_buffer *head, */ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head, struct tomoyo_acl_info *acl) + __must_hold(&head->io_sem) { const u8 acl_type = acl->type; bool first = true; @@ -1588,6 +1609,8 @@ print_cond_part: */ static bool tomoyo_read_domain2(struct tomoyo_io_buffer *head, struct list_head *list) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { list_for_each_cookie(head->r.acl, list) { struct tomoyo_acl_info *ptr = @@ -1608,6 +1631,8 @@ static bool tomoyo_read_domain2(struct tomoyo_io_buffer *head, * Caller holds tomoyo_read_lock(). */ static void tomoyo_read_domain(struct tomoyo_io_buffer *head) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { if (head->r.eof) return; @@ -1686,6 +1711,7 @@ static int tomoyo_write_pid(struct tomoyo_io_buffer *head) * using read()/write() interface rather than sysctl() interface. */ static void tomoyo_read_pid(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { char *buf = head->write_buf; bool global_pid = false; @@ -1746,6 +1772,8 @@ static const char *tomoyo_group_name[TOMOYO_MAX_GROUP] = { * Caller holds tomoyo_read_lock(). */ static int tomoyo_write_exception(struct tomoyo_io_buffer *head) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { const bool is_delete = head->w.is_delete; struct tomoyo_acl_param param = { @@ -1787,6 +1815,8 @@ static int tomoyo_write_exception(struct tomoyo_io_buffer *head) * Caller holds tomoyo_read_lock(). */ static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { struct tomoyo_policy_namespace *ns = container_of(head->r.ns, typeof(*ns), namespace_list); @@ -1846,6 +1876,7 @@ static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx) * Caller holds tomoyo_read_lock(). */ static bool tomoyo_read_policy(struct tomoyo_io_buffer *head, const int idx) + __must_hold_shared(&tomoyo_ss) { struct tomoyo_policy_namespace *ns = container_of(head->r.ns, typeof(*ns), namespace_list); @@ -1906,6 +1937,8 @@ static bool tomoyo_read_policy(struct tomoyo_io_buffer *head, const int idx) * Caller holds tomoyo_read_lock(). */ static void tomoyo_read_exception(struct tomoyo_io_buffer *head) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { struct tomoyo_policy_namespace *ns = container_of(head->r.ns, typeof(*ns), namespace_list); @@ -1981,6 +2014,114 @@ static int tomoyo_truncate(char *str) } /** + * tomoyo_numscan - sscanf() which stores the length of a decimal integer value. + * + * @str: String to scan. + * @head: Leading string that must start with. + * @width: Pointer to "int" for storing length of a decimal integer value after @head. + * @tail: Optional character that must match after a decimal integer value. + * + * Returns whether @str starts with @head and a decimal value follows @head. + */ +static bool tomoyo_numscan(const char *str, const char *head, int *width, const char tail) +{ + const char *cp; + const int n = strlen(head); + + if (!strncmp(str, head, n)) { + cp = str + n; + while (*cp && *cp >= '0' && *cp <= '9') + cp++; + if (*cp == tail || !tail) { + *width = cp - (str + n); + return *width != 0; + } + } + *width = 0; + return 0; +} + +/** + * tomoyo_patternize_path - Make patterns for file path. Used by learning mode. + * + * @buffer: Destination buffer. + * @len: Size of @buffer. + * @entry: Original line. + * + * Returns nothing. + */ +static void tomoyo_patternize_path(char *buffer, const int len, char *entry) +{ + int width; + char *cp = entry; + + /* Nothing to do if this line is not for "file" related entry. */ + if (strncmp(entry, "file ", 5)) + goto flush; + /* + * Nothing to do if there is no colon in this line, for this rewriting + * applies to only filesystems where numeric values in the path are volatile. + */ + cp = strchr(entry + 5, ':'); + if (!cp) { + cp = entry; + goto flush; + } + /* Flush e.g. "file ioctl" part. */ + while (*cp != ' ') + cp--; + *cp++ = '\0'; + tomoyo_addprintf(buffer, len, "%s ", entry); + /* e.g. file ioctl pipe:[$INO] $CMD */ + if (tomoyo_numscan(cp, "pipe:[", &width, ']')) { + cp += width + 7; + tomoyo_addprintf(buffer, len, "pipe:[\\$]"); + goto flush; + } + /* e.g. file ioctl socket:[$INO] $CMD */ + if (tomoyo_numscan(cp, "socket:[", &width, ']')) { + cp += width + 9; + tomoyo_addprintf(buffer, len, "socket:[\\$]"); + goto flush; + } + if (!strncmp(cp, "proc:/self", 10)) { + /* e.g. file read proc:/self/task/$TID/fdinfo/$FD */ + cp += 10; + tomoyo_addprintf(buffer, len, "proc:/self"); + } else if (tomoyo_numscan(cp, "proc:/", &width, 0)) { + /* e.g. file read proc:/$PID/task/$TID/fdinfo/$FD */ + /* + * Don't patternize $PID part if $PID == 1, for several + * programs access only files in /proc/1/ directory. + */ + cp += width + 6; + if (width == 1 && *(cp - 1) == '1') + tomoyo_addprintf(buffer, len, "proc:/1"); + else + tomoyo_addprintf(buffer, len, "proc:/\\$"); + } else { + goto flush; + } + /* Patternize $TID part if "/task/" follows. */ + if (tomoyo_numscan(cp, "/task/", &width, 0)) { + cp += width + 6; + tomoyo_addprintf(buffer, len, "/task/\\$"); + } + /* Patternize $FD part if "/fd/" or "/fdinfo/" follows. */ + if (tomoyo_numscan(cp, "/fd/", &width, 0)) { + cp += width + 4; + tomoyo_addprintf(buffer, len, "/fd/\\$"); + } else if (tomoyo_numscan(cp, "/fdinfo/", &width, 0)) { + cp += width + 8; + tomoyo_addprintf(buffer, len, "/fdinfo/\\$"); + } +flush: + /* Flush remaining part if any. */ + if (*cp) + tomoyo_addprintf(buffer, len, "%s", cp); +} + +/** * tomoyo_add_entry - Add an ACL to current thread's domain. Used by learning mode. * * @domain: Pointer to "struct tomoyo_domain_info". @@ -1989,6 +2130,7 @@ static int tomoyo_truncate(char *str) * Returns nothing. */ static void tomoyo_add_entry(struct tomoyo_domain_info *domain, char *header) + __must_hold_shared(&tomoyo_ss) { char *buffer; char *realpath = NULL; @@ -2003,7 +2145,8 @@ static void tomoyo_add_entry(struct tomoyo_domain_info *domain, char *header) if (!cp) return; *cp++ = '\0'; - len = strlen(cp) + 1; + /* Reserve some space for potentially using patterns. */ + len = strlen(cp) + 16; /* strstr() will return NULL if ordering is wrong. */ if (*cp == 'f') { argv0 = strstr(header, " argv[]={ \""); @@ -2020,40 +2163,10 @@ static void tomoyo_add_entry(struct tomoyo_domain_info *domain, char *header) if (symlink) len += tomoyo_truncate(symlink + 1) + 1; } - buffer = kmalloc(len, GFP_NOFS); + buffer = kmalloc(len, GFP_NOFS | __GFP_ZERO); if (!buffer) return; - snprintf(buffer, len - 1, "%s", cp); - if (*cp == 'f' && strchr(buffer, ':')) { - /* Automatically replace 2 or more digits with \$ pattern. */ - char *cp2; - - /* e.g. file read proc:/$PID/stat */ - cp = strstr(buffer, " proc:/"); - if (cp && simple_strtoul(cp + 7, &cp2, 10) >= 10 && *cp2 == '/') { - *(cp + 7) = '\\'; - *(cp + 8) = '$'; - memmove(cp + 9, cp2, strlen(cp2) + 1); - goto ok; - } - /* e.g. file ioctl pipe:[$INO] $CMD */ - cp = strstr(buffer, " pipe:["); - if (cp && simple_strtoul(cp + 7, &cp2, 10) >= 10 && *cp2 == ']') { - *(cp + 7) = '\\'; - *(cp + 8) = '$'; - memmove(cp + 9, cp2, strlen(cp2) + 1); - goto ok; - } - /* e.g. file ioctl socket:[$INO] $CMD */ - cp = strstr(buffer, " socket:["); - if (cp && simple_strtoul(cp + 9, &cp2, 10) >= 10 && *cp2 == ']') { - *(cp + 9) = '\\'; - *(cp + 10) = '$'; - memmove(cp + 11, cp2, strlen(cp2) + 1); - goto ok; - } - } -ok: + tomoyo_patternize_path(buffer, len, cp); if (realpath) tomoyo_addprintf(buffer, len, " exec.%s", realpath); if (argv0) @@ -2222,6 +2335,7 @@ static __poll_t tomoyo_poll_query(struct file *file, poll_table *wait) * @head: Pointer to "struct tomoyo_io_buffer". */ static void tomoyo_read_query(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { struct list_head *tmp; unsigned int pos = 0; @@ -2283,6 +2397,7 @@ static void tomoyo_read_query(struct tomoyo_io_buffer *head) * Returns 0 on success, -EINVAL otherwise. */ static int tomoyo_write_answer(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { char *data = head->write_buf; struct list_head *tmp; @@ -2322,6 +2437,7 @@ static int tomoyo_write_answer(struct tomoyo_io_buffer *head) * Returns version information. */ static void tomoyo_read_version(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { if (!head->r.eof) { tomoyo_io_printf(head, "2.6.0"); @@ -2370,6 +2486,7 @@ void tomoyo_update_stat(const u8 index) * Returns nothing. */ static void tomoyo_read_stat(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { u8 i; unsigned int total = 0; @@ -2414,6 +2531,7 @@ static void tomoyo_read_stat(struct tomoyo_io_buffer *head) * Returns 0. */ static int tomoyo_write_stat(struct tomoyo_io_buffer *head) + __must_hold(&head->io_sem) { char *data = head->write_buf; u8 i; @@ -2435,11 +2553,11 @@ static int tomoyo_write_stat(struct tomoyo_io_buffer *head) */ int tomoyo_open_control(const u8 type, struct file *file) { - struct tomoyo_io_buffer *head = kzalloc(sizeof(*head), GFP_NOFS); + struct tomoyo_io_buffer *head = kzalloc_obj(*head, GFP_NOFS); if (!head) return -ENOMEM; - mutex_init(&head->io_sem); + guard(mutex_init)(&head->io_sem); head->type = type; switch (type) { case TOMOYO_DOMAINPOLICY: @@ -2638,6 +2756,8 @@ ssize_t tomoyo_read_control(struct tomoyo_io_buffer *head, char __user *buffer, * Caller holds tomoyo_read_lock(). */ static int tomoyo_parse_policy(struct tomoyo_io_buffer *head, char *line) + __must_hold_shared(&tomoyo_ss) + __must_hold(&head->io_sem) { /* Delete request? */ head->w.is_delete = !strncmp(line, "delete ", 7); @@ -2890,8 +3010,11 @@ void __init tomoyo_load_builtin_policy(void) break; *end = '\0'; tomoyo_normalize_line(start); - head.write_buf = start; - tomoyo_parse_policy(&head, start); + /* head is stack-local and not shared. */ + context_unsafe( + head.write_buf = start; + tomoyo_parse_policy(&head, start); + ); start = end + 1; } } diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 0e8e2e959aef..d098cf8aae61 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -567,7 +567,7 @@ struct tomoyo_address_group { struct tomoyo_mini_stat { kuid_t uid; kgid_t gid; - ino_t ino; + u64 ino; umode_t mode; dev_t dev; dev_t rdev; @@ -827,13 +827,13 @@ struct tomoyo_io_buffer { bool is_delete; } w; /* Buffer for reading. */ - char *read_buf; + char *read_buf __guarded_by(&io_sem); /* Size of read buffer. */ - size_t readbuf_size; + size_t readbuf_size __guarded_by(&io_sem); /* Buffer for writing. */ - char *write_buf; + char *write_buf __guarded_by(&io_sem); /* Size of write buffer. */ - size_t writebuf_size; + size_t writebuf_size __guarded_by(&io_sem); /* Type of this interface. */ enum tomoyo_securityfs_interface_index type; /* Users counter protected by tomoyo_io_buffer_list_lock. */ @@ -922,8 +922,39 @@ struct tomoyo_task { struct tomoyo_domain_info *old_domain_info; }; +/********** External variable definitions. **********/ + +extern bool tomoyo_policy_loaded; +extern int tomoyo_enabled; +extern const char * const tomoyo_condition_keyword +[TOMOYO_MAX_CONDITION_KEYWORD]; +extern const char * const tomoyo_dif[TOMOYO_MAX_DOMAIN_INFO_FLAGS]; +extern const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX + + TOMOYO_MAX_MAC_CATEGORY_INDEX]; +extern const char * const tomoyo_mode[TOMOYO_CONFIG_MAX_MODE]; +extern const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION]; +extern const char * const tomoyo_proto_keyword[TOMOYO_SOCK_MAX]; +extern const char * const tomoyo_socket_keyword[TOMOYO_MAX_NETWORK_OPERATION]; +extern const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX]; +extern const u8 tomoyo_pn2mac[TOMOYO_MAX_PATH_NUMBER_OPERATION]; +extern const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION]; +extern const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION]; +extern struct list_head tomoyo_condition_list; +extern struct list_head tomoyo_domain_list; +extern struct list_head tomoyo_name_list[TOMOYO_MAX_HASH]; +extern struct list_head tomoyo_namespace_list; +extern struct mutex tomoyo_policy_lock; +extern struct srcu_struct tomoyo_ss; +extern struct tomoyo_domain_info tomoyo_kernel_domain; +extern struct tomoyo_policy_namespace tomoyo_kernel_namespace; +extern unsigned int tomoyo_memory_quota[TOMOYO_MAX_MEMORY_STAT]; +extern unsigned int tomoyo_memory_used[TOMOYO_MAX_MEMORY_STAT]; +extern struct lsm_blob_sizes tomoyo_blob_sizes; + /********** Function prototypes. **********/ +int tomoyo_interface_init(void); + bool tomoyo_address_matches_group(const bool is_ipv6, const __be32 *address, const struct tomoyo_group *group); bool tomoyo_compare_number_union(const unsigned long value, @@ -969,10 +1000,10 @@ const struct tomoyo_path_info *tomoyo_path_matches_group int tomoyo_check_open_permission(struct tomoyo_domain_info *domain, const struct path *path, const int flag); void tomoyo_close_control(struct tomoyo_io_buffer *head); -int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env); +int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env) __must_hold_shared(&tomoyo_ss); int tomoyo_execute_permission(struct tomoyo_request_info *r, - const struct tomoyo_path_info *filename); -int tomoyo_find_next_domain(struct linux_binprm *bprm); + const struct tomoyo_path_info *filename) __must_hold_shared(&tomoyo_ss); +int tomoyo_find_next_domain(struct linux_binprm *bprm) __must_hold_shared(&tomoyo_ss); int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile, const u8 index); int tomoyo_init_request_info(struct tomoyo_request_info *r, @@ -1000,6 +1031,7 @@ int tomoyo_socket_listen_permission(struct socket *sock); int tomoyo_socket_sendmsg_permission(struct socket *sock, struct msghdr *msg, int size); int tomoyo_supervisor(struct tomoyo_request_info *r, const char *fmt, ...) + __must_hold_shared(&tomoyo_ss) __printf(2, 3); int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size, struct tomoyo_acl_param *param, @@ -1059,7 +1091,7 @@ void tomoyo_print_ulong(char *buffer, const int buffer_len, const unsigned long value, const u8 type); void tomoyo_put_name_union(struct tomoyo_name_union *ptr); void tomoyo_put_number_union(struct tomoyo_number_union *ptr); -void tomoyo_read_log(struct tomoyo_io_buffer *head); +void tomoyo_read_log(struct tomoyo_io_buffer *head) __must_hold(&head->io_sem); void tomoyo_update_stat(const u8 index); void tomoyo_warn_oom(const char *function); void tomoyo_write_log(struct tomoyo_request_info *r, const char *fmt, ...) @@ -1067,35 +1099,6 @@ void tomoyo_write_log(struct tomoyo_request_info *r, const char *fmt, ...) void tomoyo_write_log2(struct tomoyo_request_info *r, int len, const char *fmt, va_list args) __printf(3, 0); -/********** External variable definitions. **********/ - -extern bool tomoyo_policy_loaded; -extern int tomoyo_enabled; -extern const char * const tomoyo_condition_keyword -[TOMOYO_MAX_CONDITION_KEYWORD]; -extern const char * const tomoyo_dif[TOMOYO_MAX_DOMAIN_INFO_FLAGS]; -extern const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX - + TOMOYO_MAX_MAC_CATEGORY_INDEX]; -extern const char * const tomoyo_mode[TOMOYO_CONFIG_MAX_MODE]; -extern const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION]; -extern const char * const tomoyo_proto_keyword[TOMOYO_SOCK_MAX]; -extern const char * const tomoyo_socket_keyword[TOMOYO_MAX_NETWORK_OPERATION]; -extern const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX]; -extern const u8 tomoyo_pn2mac[TOMOYO_MAX_PATH_NUMBER_OPERATION]; -extern const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION]; -extern const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION]; -extern struct list_head tomoyo_condition_list; -extern struct list_head tomoyo_domain_list; -extern struct list_head tomoyo_name_list[TOMOYO_MAX_HASH]; -extern struct list_head tomoyo_namespace_list; -extern struct mutex tomoyo_policy_lock; -extern struct srcu_struct tomoyo_ss; -extern struct tomoyo_domain_info tomoyo_kernel_domain; -extern struct tomoyo_policy_namespace tomoyo_kernel_namespace; -extern unsigned int tomoyo_memory_quota[TOMOYO_MAX_MEMORY_STAT]; -extern unsigned int tomoyo_memory_used[TOMOYO_MAX_MEMORY_STAT]; -extern struct lsm_blob_sizes tomoyo_blob_sizes; - /********** Inlined functions. **********/ /** @@ -1104,6 +1107,7 @@ extern struct lsm_blob_sizes tomoyo_blob_sizes; * Returns index number for tomoyo_read_unlock(). */ static inline int tomoyo_read_lock(void) + __acquires_shared(&tomoyo_ss) { return srcu_read_lock(&tomoyo_ss); } @@ -1116,6 +1120,7 @@ static inline int tomoyo_read_lock(void) * Returns nothing. */ static inline void tomoyo_read_unlock(int idx) + __releases_shared(&tomoyo_ss) { srcu_read_unlock(&tomoyo_ss, idx); } diff --git a/security/tomoyo/condition.c b/security/tomoyo/condition.c index f8bcc083bb0d..8b107b1ffdab 100644 --- a/security/tomoyo/condition.c +++ b/security/tomoyo/condition.c @@ -766,8 +766,8 @@ bool tomoyo_condition(struct tomoyo_request_info *r, const struct tomoyo_condition *cond) { u32 i; - unsigned long min_v[2] = { 0, 0 }; - unsigned long max_v[2] = { 0, 0 }; + u64 min_v[2] = { 0, 0 }; + u64 max_v[2] = { 0, 0 }; const struct tomoyo_condition_element *condp; const struct tomoyo_number_union *numbers_p; const struct tomoyo_name_union *names_p; @@ -834,7 +834,7 @@ bool tomoyo_condition(struct tomoyo_request_info *r, /* Check numeric or bit-op expressions. */ for (j = 0; j < 2; j++) { const u8 index = j ? right : left; - unsigned long value = 0; + u64 value = 0; switch (index) { case TOMOYO_TASK_UID: diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index 3a7b0874cf44..eeaad421f15b 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -611,6 +611,7 @@ out: * Returns 0 on success, negative value otherwise. */ static int tomoyo_environ(struct tomoyo_execve *ee) + __must_hold_shared(&tomoyo_ss) { struct tomoyo_request_info *r = &ee->r; struct linux_binprm *bprm = ee->bprm; @@ -707,7 +708,7 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm) bool reject_on_transition_failure = false; const struct tomoyo_path_info *candidate; struct tomoyo_path_info exename; - struct tomoyo_execve *ee = kzalloc(sizeof(*ee), GFP_NOFS); + struct tomoyo_execve *ee = kzalloc_obj(*ee, GFP_NOFS); if (!ee) return -ENOMEM; @@ -920,7 +921,7 @@ bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos, #ifdef CONFIG_MMU /* * This is called at execve() time in order to dig around - * in the argv/environment of the new proceess + * in the argv/environment of the new process * (represented by bprm). */ mmap_read_lock(bprm->mm); @@ -934,17 +935,12 @@ bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos, #endif if (page != dump->page) { const unsigned int offset = pos % PAGE_SIZE; - /* - * Maybe kmap()/kunmap() should be used here. - * But remove_arg_zero() uses kmap_atomic()/kunmap_atomic(). - * So do I. - */ - char *kaddr = kmap_atomic(page); + char *kaddr = kmap_local_page(page); dump->page = page; memcpy(dump->data + offset, kaddr + offset, PAGE_SIZE - offset); - kunmap_atomic(kaddr); + kunmap_local(kaddr); } /* Same with put_arg_page(page) in fs/exec.c */ #ifdef CONFIG_MMU diff --git a/security/tomoyo/environ.c b/security/tomoyo/environ.c index 7f0a471f19b2..bcb05910facc 100644 --- a/security/tomoyo/environ.c +++ b/security/tomoyo/environ.c @@ -32,6 +32,7 @@ static bool tomoyo_check_env_acl(struct tomoyo_request_info *r, * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_env_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { return tomoyo_supervisor(r, "misc env %s\n", r->param.environ.name->name); diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c index 8f3b90b6e03d..e9b67dbb38e7 100644 --- a/security/tomoyo/file.c +++ b/security/tomoyo/file.c @@ -164,6 +164,7 @@ static bool tomoyo_get_realpath(struct tomoyo_path_info *buf, const struct path * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_path_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { return tomoyo_supervisor(r, "file %s %s\n", tomoyo_path_keyword [r->param.path.operation], @@ -178,6 +179,7 @@ static int tomoyo_audit_path_log(struct tomoyo_request_info *r) * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_path2_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { return tomoyo_supervisor(r, "file %s %s %s\n", tomoyo_mac_keywords [tomoyo_pp2mac[r->param.path2.operation]], @@ -193,6 +195,7 @@ static int tomoyo_audit_path2_log(struct tomoyo_request_info *r) * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_mkdev_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { return tomoyo_supervisor(r, "file %s %s 0%o %u %u\n", tomoyo_mac_keywords @@ -210,6 +213,7 @@ static int tomoyo_audit_mkdev_log(struct tomoyo_request_info *r) * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_path_number_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { const u8 type = r->param.path_number.operation; u8 radix; @@ -572,6 +576,7 @@ static int tomoyo_update_path2_acl(const u8 perm, */ static int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, const struct tomoyo_path_info *filename) + __must_hold_shared(&tomoyo_ss) { int error; diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index 026e29ea3796..8e2008863af8 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -23,11 +23,10 @@ static inline void tomoyo_memory_free(void *ptr) tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= ksize(ptr); kfree(ptr); } - -/* The list for "struct tomoyo_io_buffer". */ -static LIST_HEAD(tomoyo_io_buffer_list); /* Lock for protecting tomoyo_io_buffer_list. */ static DEFINE_SPINLOCK(tomoyo_io_buffer_list_lock); +/* The list for "struct tomoyo_io_buffer". */ +static __guarded_by(&tomoyo_io_buffer_list_lock) LIST_HEAD(tomoyo_io_buffer_list); /** * tomoyo_struct_used_by_io_buffer - Check whether the list element is used by /sys/kernel/security/tomoyo/ users or not. @@ -385,6 +384,7 @@ static inline void tomoyo_del_number_group(struct list_head *element) */ static void tomoyo_try_to_gc(const enum tomoyo_policy_id type, struct list_head *element) + __must_hold(&tomoyo_policy_lock) { /* * __list_del_entry() guarantees that the list element became no longer @@ -484,6 +484,7 @@ reinject: */ static void tomoyo_collect_member(const enum tomoyo_policy_id id, struct list_head *member_list) + __must_hold(&tomoyo_policy_lock) { struct tomoyo_acl_head *member; struct tomoyo_acl_head *tmp; @@ -504,6 +505,7 @@ static void tomoyo_collect_member(const enum tomoyo_policy_id id, * Returns nothing. */ static void tomoyo_collect_acl(struct list_head *list) + __must_hold(&tomoyo_policy_lock) { struct tomoyo_acl_info *acl; struct tomoyo_acl_info *tmp; @@ -627,8 +629,11 @@ static int tomoyo_gc_thread(void *unused) if (head->users) continue; list_del(&head->list); - kfree(head->read_buf); - kfree(head->write_buf); + /* Safe destruction because no users are left. */ + context_unsafe( + kfree(head->read_buf); + kfree(head->write_buf); + ); kfree(head); } spin_unlock(&tomoyo_io_buffer_list_lock); @@ -656,11 +661,18 @@ void tomoyo_notify_gc(struct tomoyo_io_buffer *head, const bool is_register) head->users = 1; list_add(&head->list, &tomoyo_io_buffer_list); } else { - is_write = head->write_buf != NULL; + /* + * tomoyo_write_control() can concurrently update write_buf from + * a non-NULL to new non-NULL pointer with io_sem held. + */ + is_write = data_race(head->write_buf != NULL); if (!--head->users) { list_del(&head->list); - kfree(head->read_buf); - kfree(head->write_buf); + /* Safe destruction because no users are left. */ + context_unsafe( + kfree(head->read_buf); + kfree(head->write_buf); + ); kfree(head); } } diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c index 2755971f50df..322dfd188ada 100644 --- a/security/tomoyo/mount.c +++ b/security/tomoyo/mount.c @@ -28,6 +28,7 @@ static const char * const tomoyo_mounts[TOMOYO_MAX_SPECIAL_MOUNT] = { * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_mount_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { return tomoyo_supervisor(r, "file mount %s %s %s 0x%lX\n", r->param.mount.dev->name, @@ -78,6 +79,7 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r, const char *dev_name, const struct path *dir, const char *type, unsigned long flags) + __must_hold_shared(&tomoyo_ss) { struct tomoyo_obj_info obj = { }; struct path path; diff --git a/security/tomoyo/network.c b/security/tomoyo/network.c index 8dc61335f65e..cfc2a019de1e 100644 --- a/security/tomoyo/network.c +++ b/security/tomoyo/network.c @@ -363,6 +363,7 @@ int tomoyo_write_unix_network(struct tomoyo_acl_param *param) static int tomoyo_audit_net_log(struct tomoyo_request_info *r, const char *family, const u8 protocol, const u8 operation, const char *address) + __must_hold_shared(&tomoyo_ss) { return tomoyo_supervisor(r, "network %s %s %s %s\n", family, tomoyo_proto_keyword[protocol], @@ -377,6 +378,7 @@ static int tomoyo_audit_net_log(struct tomoyo_request_info *r, * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_inet_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { char buf[128]; int len; @@ -402,6 +404,7 @@ static int tomoyo_audit_inet_log(struct tomoyo_request_info *r) * Returns 0 on success, negative value otherwise. */ static int tomoyo_audit_unix_log(struct tomoyo_request_info *r) + __must_hold_shared(&tomoyo_ss) { return tomoyo_audit_net_log(r, "unix", r->param.unix_network.protocol, r->param.unix_network.operation, diff --git a/security/tomoyo/securityfs_if.c b/security/tomoyo/securityfs_if.c index a2705798476f..33933645f5b9 100644 --- a/security/tomoyo/securityfs_if.c +++ b/security/tomoyo/securityfs_if.c @@ -229,11 +229,11 @@ static void __init tomoyo_create_entry(const char *name, const umode_t mode, } /** - * tomoyo_initerface_init - Initialize /sys/kernel/security/tomoyo/ interface. + * tomoyo_interface_init - Initialize /sys/kernel/security/tomoyo/ interface. * * Returns 0. */ -static int __init tomoyo_initerface_init(void) +int __init tomoyo_interface_init(void) { struct tomoyo_domain_info *domain; struct dentry *tomoyo_dir; @@ -269,5 +269,3 @@ static int __init tomoyo_initerface_init(void) tomoyo_load_builtin_policy(); return 0; } - -fs_initcall(tomoyo_initerface_init); diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index 04a92c3d65d4..c66e02ed8ee3 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -514,7 +514,7 @@ struct lsm_blob_sizes tomoyo_blob_sizes __ro_after_init = { * Returns 0. */ static int tomoyo_task_alloc(struct task_struct *task, - unsigned long clone_flags) + u64 clone_flags) { struct tomoyo_task *old = tomoyo_task(current); struct tomoyo_task *new = tomoyo_task(task); @@ -549,10 +549,7 @@ static const struct lsm_id tomoyo_lsmid = { .id = LSM_ID_TOMOYO, }; -/* - * tomoyo_security_ops is a "struct security_operations" which is used for - * registering TOMOYO. - */ +/* tomoyo_hooks is used for registering TOMOYO. */ static struct security_hook_list tomoyo_hooks[] __ro_after_init = { LSM_HOOK_INIT(cred_prepare, tomoyo_cred_prepare), LSM_HOOK_INIT(bprm_committed_creds, tomoyo_bprm_committed_creds), @@ -615,9 +612,10 @@ static int __init tomoyo_init(void) } DEFINE_LSM(tomoyo) = { - .name = "tomoyo", + .id = &tomoyo_lsmid, .enabled = &tomoyo_enabled, .flags = LSM_FLAG_LEGACY_MAJOR, .blobs = &tomoyo_blob_sizes, .init = tomoyo_init, + .initcall_fs = tomoyo_interface_init, }; diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c index 1971710620c1..cef3776cf3b2 100644 --- a/security/yama/yama_lsm.c +++ b/security/yama/yama_lsm.c @@ -89,7 +89,7 @@ static void report_access(const char *access, struct task_struct *target, return; } - info = kmalloc(sizeof(*info), GFP_ATOMIC); + info = kmalloc_obj(*info, GFP_ATOMIC); if (!info) return; init_task_work(&info->work, __report_access); @@ -143,7 +143,7 @@ static int yama_ptracer_add(struct task_struct *tracer, { struct ptrace_relation *relation, *added; - added = kmalloc(sizeof(*added), GFP_KERNEL); + added = kmalloc_obj(*added); if (!added) return -ENOMEM; @@ -222,7 +222,7 @@ static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { int rc = -ENOSYS; - struct task_struct *myself = current; + struct task_struct *myself; switch (option) { case PR_SET_PTRACER: @@ -232,11 +232,7 @@ static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, * leader checking is handled later when walking the ancestry * at the time of PTRACE_ATTACH check. */ - rcu_read_lock(); - if (!thread_group_leader(myself)) - myself = rcu_dereference(myself->group_leader); - get_task_struct(myself); - rcu_read_unlock(); + myself = current->group_leader; if (arg2 == 0) { yama_ptracer_del(NULL, myself); @@ -255,7 +251,6 @@ static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, } } - put_task_struct(myself); break; } @@ -481,6 +476,6 @@ static int __init yama_init(void) } DEFINE_LSM(yama) = { - .name = "yama", + .id = &yama_lsmid, .init = yama_init, }; |
