summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2026-09-06 15:50:31 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-09-06 16:17:40 -0700
commit1b7415bf70be95b9a1e7e87d544867881065613f (patch)
tree546f3eae52bfdce557498530a6d999445cb02699 /security
parentb0ca562e0d05cf7afedcfa71c9aed180b72bcc97 (diff)
parentdf2908090cda368b01ff43709f51890076c56157 (diff)
Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf 7.3-rc2
Cross-merge BPF and other fixes after downstream PR. Conflicts: kernel/bpf/backtrack.c include/linux/bpf_verifier.h Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'security')
-rw-r--r--security/Kconfig.hardening2
-rw-r--r--security/apparmor/Kconfig14
-rw-r--r--security/apparmor/Makefile2
-rw-r--r--security/apparmor/af_inet.c565
-rw-r--r--security/apparmor/af_unix.c44
-rw-r--r--security/apparmor/apparmorfs.c281
-rw-r--r--security/apparmor/audit.c40
-rw-r--r--security/apparmor/capability.c2
-rw-r--r--security/apparmor/domain.c157
-rw-r--r--security/apparmor/file.c15
-rw-r--r--security/apparmor/include/af_inet.h36
-rw-r--r--security/apparmor/include/apparmor.h1
-rw-r--r--security/apparmor/include/apparmorfs.h3
-rw-r--r--security/apparmor/include/audit.h10
-rw-r--r--security/apparmor/include/capability.h3
-rw-r--r--security/apparmor/include/cred.h22
-rw-r--r--security/apparmor/include/file.h2
-rw-r--r--security/apparmor/include/label.h31
-rw-r--r--security/apparmor/include/lib.h6
-rw-r--r--security/apparmor/include/match.h18
-rw-r--r--security/apparmor/include/net.h9
-rw-r--r--security/apparmor/include/path.h3
-rw-r--r--security/apparmor/include/perms.h19
-rw-r--r--security/apparmor/include/policy.h17
-rw-r--r--security/apparmor/include/policy_unpack.h4
-rw-r--r--security/apparmor/include/procattr.h2
-rw-r--r--security/apparmor/include/task.h20
-rw-r--r--security/apparmor/label.c95
-rw-r--r--security/apparmor/lib.c57
-rw-r--r--security/apparmor/lsm.c282
-rw-r--r--security/apparmor/match.c35
-rw-r--r--security/apparmor/mount.c279
-rw-r--r--security/apparmor/net.c47
-rw-r--r--security/apparmor/policy.c30
-rw-r--r--security/apparmor/policy_compat.c14
-rw-r--r--security/apparmor/policy_unpack.c30
-rw-r--r--security/apparmor/task.c27
-rw-r--r--security/integrity/ima/Kconfig6
-rw-r--r--security/integrity/ima/ima_appraise.c2
-rw-r--r--security/integrity/ima/ima_policy.c7
-rw-r--r--security/integrity/ima/ima_queue.c3
-rw-r--r--security/keys/trusted-keys/trusted_tpm1.c47
-rw-r--r--security/keys/trusted-keys/trusted_tpm2.c166
-rw-r--r--security/landlock/Kconfig5
-rw-r--r--security/landlock/Makefile12
-rw-r--r--security/landlock/access.h6
-rw-r--r--security/landlock/audit.c641
-rw-r--r--security/landlock/audit.h57
-rw-r--r--security/landlock/cred.c14
-rw-r--r--security/landlock/cred.h29
-rw-r--r--security/landlock/domain.c472
-rw-r--r--security/landlock/domain.h163
-rw-r--r--security/landlock/errata/abi-1.h23
-rw-r--r--security/landlock/fs.c259
-rw-r--r--security/landlock/fs.h40
-rw-r--r--security/landlock/id.h6
-rw-r--r--security/landlock/limits.h2
-rw-r--r--security/landlock/log.c587
-rw-r--r--security/landlock/log.h86
-rw-r--r--security/landlock/net.c38
-rw-r--r--security/landlock/ruleset.c546
-rw-r--r--security/landlock/ruleset.h250
-rw-r--r--security/landlock/syscalls.c125
-rw-r--r--security/landlock/task.c87
-rw-r--r--security/landlock/trace.c185
-rw-r--r--security/landlock/trace.h44
-rw-r--r--security/landlock/tsync.c24
-rw-r--r--security/landlock/tsync.h4
-rw-r--r--security/selinux/hooks.c36
69 files changed, 4015 insertions, 2181 deletions
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening
index 6923036e1a2f..81c81ad983ad 100644
--- a/security/Kconfig.hardening
+++ b/security/Kconfig.hardening
@@ -278,7 +278,7 @@ config CC_HAS_RANDSTRUCT
choice
prompt "Randomize layout of sensitive kernel structures"
- default RANDSTRUCT_FULL if COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT)
+ default RANDSTRUCT_FULL if !(RUST_IS_AVAILABLE && HAVE_RUST) && COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT)
default RANDSTRUCT_NONE
help
If you enable this, the layouts of structures that are entirely
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 1e3bd44643da..a1f3749bdfd0 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -11,7 +11,7 @@ config SECURITY_APPARMOR
This enables the AppArmor security module.
Required userspace tools (if they are not included in your
distribution) and further information may be found at
- http://apparmor.wiki.kernel.org
+ https://apparmor.wiki.kernel.org
If you are unsure how to answer this question, answer N.
@@ -93,6 +93,18 @@ config SECURITY_APPARMOR_EXPORT_BINARY
also increases policy load time. This option is required for
checkpoint and restore support, and debugging of loaded policy.
+config SECURITY_APPARMOR_COMPRESSED_POLICY
+ bool "Allow loading policy in a compressed format"
+ depends on SECURITY_APPARMOR
+ select ZSTD_DECOMPRESS
+ default y
+ help
+ This option allows loading policy from userspace in a
+ compressed format. This allows for userspace to not have to
+ decrompress caches before loading policy, and also allows
+ for less kernel memory to be used when "exporting the raw
+ binary policy" is enabled.
+
config SECURITY_APPARMOR_PARANOID_LOAD
bool "Perform full verification of loaded policy"
depends on SECURITY_APPARMOR
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 12fb419714c0..8aed0d7ed6f5 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 af_unix.o
+ policy_compat.o af_unix.o af_inet.o
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o
diff --git a/security/apparmor/af_inet.c b/security/apparmor/af_inet.c
new file mode 100644
index 000000000000..516c9016537c
--- /dev/null
+++ b/security/apparmor/af_inet.c
@@ -0,0 +1,565 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor inet fine grained mediation
+ *
+ * Copyright 2024 Canonical Ltd.
+ *
+ */
+
+#include <net/tcp_states.h>
+
+#include "include/audit.h"
+#include "include/af_inet.h"
+#include "include/apparmor.h"
+#include "include/file.h"
+#include "include/label.h"
+#include "include/net.h"
+#include "include/path.h"
+#include "include/policy.h"
+#include "include/cred.h"
+
+
+static inline aa_state_t RULE_MEDIATES_SK(struct aa_ruleset *rules,
+ const struct sock *sk)
+{
+ return RULE_MEDIATES_NET(rules);
+}
+
+
+enum addr_type {
+ ADDR_LOCAL = 0,
+ ADDR_LOCAL_PRIV = 1,
+ ADDR_REMOTE = 2,
+};
+
+struct match_addr {
+ const char *addrp;
+ enum addr_type addrtype;
+ int len;
+ __be16 port;
+};
+
+struct stored_match_addr {
+ union {
+ struct sockaddr addr;
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+ };
+ int addrlen;
+ struct match_addr maddr;
+};
+
+static void set_ad_create(struct apparmor_audit_data *ad,
+ int family, int type, int protocol)
+{
+ ad->common.u.net->family = family;
+ ad->net.type = type;
+ ad->net.protocol = protocol;
+}
+
+static int set_ad_addr(struct apparmor_audit_data *ad,
+ u16 family, bool source, struct match_addr *maddr)
+{
+ ad->common.u.net->family = family;
+
+ if (source) {
+ ad->common.u.net->sport = maddr->port;
+ if (maddr->addrp) {
+ if (family == AF_INET)
+ /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */
+ ad->common.u.net->v4info.saddr = *(__be32 *)maddr->addrp;
+ else
+ /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */
+ ad->common.u.net->v6info.saddr = *(struct in6_addr *)maddr->addrp;
+ }
+ } else {
+ ad->common.u.net->dport = maddr->port;
+ if (maddr->addrp) {
+ if (family == AF_INET)
+ /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */
+ ad->common.u.net->v4info.daddr = *(__be32 *)maddr->addrp;
+ else
+ /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */
+ ad->common.u.net->v6info.daddr = *(struct in6_addr *)maddr->addrp;
+ }
+ }
+ return 0;
+}
+
+/* returns 0 on success
+* raw_port - if set raw_port (protocol) when SOCK_RAW */
+static int map_addr(struct sockaddr *addr, int addrlen, u16 raw_port,
+ enum addr_type addrtype, struct match_addr *maddr,
+ struct apparmor_audit_data *ad)
+{
+ struct sockaddr_in *addr4 = NULL;
+ struct sockaddr_in6 *addr6 = NULL;
+
+ AA_BUG(!addr);
+ AA_BUG(!maddr);
+
+ maddr->addrtype = addrtype;
+ if (!addr || addrlen < offsetofend(struct sockaddr, sa_family)) {
+ maddr->addrp = NULL;
+ maddr->port = 0;
+ maddr->len = 0;
+ return 0;
+ }
+
+ /*
+ * its possibly to have sk->sk_family == PF_INET6 and
+ * addr->sa_family == AF_INET. sk_family is used for socket
+ * mediation, sa_family for when we have address ...
+ */
+ switch (addr->sa_family) {
+ case AF_INET:
+ addr4 = (struct sockaddr_in *)addr;
+ if (addrlen < sizeof(struct sockaddr_in))
+ return -EINVAL;
+ maddr->port = addr4->sin_port;
+ maddr->addrp = (char *)&addr4->sin_addr.s_addr;
+ maddr->len = 4;
+ break;
+ case AF_INET6:
+ addr6 = (struct sockaddr_in6 *)addr;
+ if (addrlen < SIN6_LEN_RFC2133)
+ return -EINVAL;
+ maddr->port = addr6->sin6_port;
+ maddr->addrp = (char *)&addr6->sin6_addr.s6_addr;
+ maddr->len = 16;
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+ /* per ip spec, && sk->sk_type == SOCK_RAW*/
+ if (raw_port && addrtype != ADDR_REMOTE)
+ maddr->port = htons(raw_port);
+ if (ad)
+ set_ad_addr(ad, addr->sa_family, addrtype != ADDR_REMOTE, maddr);
+
+ return 0;
+}
+
+/* -ENOTCONN if not connected */
+static int map_sock_addr(struct socket *sock, enum addr_type addrtype,
+ struct stored_match_addr *maddr,
+ struct apparmor_audit_data *ad)
+{
+ /* do we need early bailout for !family ... */
+ maddr->addrlen = sock->ops->getname(sock, (struct sockaddr *) &maddr->addr, addrtype != ADDR_REMOTE ? 0 : 1);
+ if (maddr->addrlen == -ENOTCONN) {
+ maddr->addrlen = 0;
+ return map_addr(NULL, 0, 0, addrtype, &maddr->maddr, ad);
+ } else if (maddr->addrlen < 0)
+ return maddr->addrlen;
+ return map_addr(&maddr->addr, maddr->addrlen, 0, addrtype,
+ &maddr->maddr, ad);
+}
+
+/* TODO: combine with connect map addr */
+/* TODO: raw_port */
+static int bind_map_addr(const struct sock *sk, struct sockaddr *addr,
+ int addrlen,
+ struct match_addr *maddr,
+ struct apparmor_audit_data *ad)
+{
+ struct sockaddr_in *addr4 = NULL;
+ struct sockaddr_in6 *addr6 = NULL;
+ u16 family;
+
+ AA_BUG(!sk);
+ AA_BUG(!addr);
+ AA_BUG(!maddr);
+
+ if (addrlen < offsetofend(struct sockaddr, sa_family))
+ return -EINVAL;
+
+ maddr->addrtype = ADDR_LOCAL;
+ /*
+ * its possibly to have sk->sk_family == PF_INET6 and
+ * addr->sa_family == AF_INET. sk_family is used for socket
+ * mediation, sa_family for when we have address ...
+ */
+ family = addr->sa_family;
+ switch (addr->sa_family) {
+ case AF_UNSPEC:
+ if (sk->sk_family == PF_INET6) {
+ /* Length check from inet6_bind_sk() */
+ if (addrlen < SIN6_LEN_RFC2133)
+ return -EINVAL;
+ /* Family check from __inet6_bind() */
+ return -EAFNOSUPPORT;
+ }
+ /* see __inet_bind(), we only want to allow
+ * AF_UNSPEC if the address is INADDR_ANY
+ */
+ addr4 = (struct sockaddr_in *)addr;
+ if (addr4->sin_addr.s_addr != htonl(INADDR_ANY))
+ return -EAFNOSUPPORT;
+ family = AF_INET;
+ fallthrough;
+ case AF_INET:
+ addr4 = (struct sockaddr_in *)addr;
+ if (addrlen < sizeof(struct sockaddr_in))
+ return -EINVAL;
+ maddr->port = addr4->sin_port;
+ maddr->addrp = (char *)&addr4->sin_addr.s_addr;
+ maddr->len = 4;
+ break;
+ case AF_INET6:
+ addr6 = (struct sockaddr_in6 *)addr;
+ if (addrlen < SIN6_LEN_RFC2133)
+ return -EINVAL;
+ maddr->port = addr6->sin6_port;
+ maddr->addrp = (char *)&addr6->sin6_addr.s6_addr;
+ maddr->len = 16;
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+
+ if (ad)
+ set_ad_addr(ad, family, true, maddr);
+
+ return 0;
+}
+
+
+static inline int profile_sk_perm(struct aa_profile *profile, u32 request,
+ const struct sock *sk,
+ struct match_addr *maddr,
+ struct apparmor_audit_data *ad)
+{
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+
+ return aa_profile_af_sk_perm(profile, ad, request, sk);
+}
+
+/* no kernel_t bailout */
+static int profile_create_perm(struct aa_profile *profile, int family,
+ int type, int protocol,
+ struct apparmor_audit_data *ad)
+{
+ AA_BUG(!profile);
+
+ return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type,
+ protocol);
+}
+
+
+/* sendmsg/rcvmsg/connect */
+static int profile_remote_perm(struct aa_profile *profile,
+ const struct sock *sk,
+ u32 request, struct match_addr *raddr,
+ struct match_addr *laddr,
+ struct apparmor_audit_data *ad)
+{
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(!raddr);
+ AA_BUG(!laddr);
+ AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6,
+ "family=%d", sk->sk_family);
+
+ return aa_profile_af_sk_perm(profile, ad, request, sk);
+}
+
+static int profile_bind_perm(struct aa_profile *profile,
+ const struct sock *sk,
+ struct match_addr *maddr,
+ struct apparmor_audit_data *ad)
+{
+ return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk);
+
+}
+
+static int profile_listen_perm(struct aa_profile *profile,
+ const struct sock *sk,
+ struct match_addr *maddr, int backlog,
+ struct apparmor_audit_data *ad)
+{
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(!maddr);
+ AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6,
+ "family=%d", sk->sk_family);
+
+ return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk);
+}
+
+static inline int profile_accept_perm(struct aa_profile *profile,
+ const struct sock *sk,
+ struct match_addr *maddr,
+ const struct sock *newsk,
+ struct apparmor_audit_data *ad)
+{
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ /* AA_BUG(!newsk); newsk can be null here, since not using atm ... */
+ AA_BUG(!maddr);
+ AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6,
+ "family=%d", sk->sk_family);
+
+ return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk);
+}
+
+/* getopt/setopt */
+static int profile_opt_perm(struct aa_profile *profile, u32 request,
+ const struct sock *sk, struct match_addr *maddr,
+ int level, int optname,
+ struct apparmor_audit_data *ad)
+{
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(!maddr);
+ AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6,
+ "family=%d", sk->sk_family);
+
+ return aa_profile_af_sk_perm(profile, ad, request, sk);
+}
+
+/* ---------------------------------------------------------------------- */
+
+// TODO: cleanup init to use recursion, so we can have N init fns, in 1 macro
+// TODO: lift DEFINE_AUDIT out of macro into init fn???
+
+/* no kernel_t bailout */
+#define label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, YYYY, CALLBACKFN) \
+({ \
+ int __EERROR = 0; \
+ if (label_mediates(LABEL, AA_CLASS_NET)) { \
+ struct aa_profile *PROFILE; \
+ DEFINE_AUDIT_SK(AAD, OP, CRED, SOCKSK); \
+ (AAD).subj_cred = (CRED); \
+ (AAD).request = (REQUEST); \
+ __EERROR = (XXXX); \
+ if (__EERROR == 0) { \
+ __EERROR = (YYYY); \
+ if (__EERROR == 0) { \
+ __EERROR = fn_for_each(LABEL, PROFILE, \
+ (CALLBACKFN)); \
+ } \
+ } \
+ } \
+ __EERROR; \
+})
+
+/* no kernel_t bailout */
+#define label_sk_has_perm(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \
+ label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \
+ 0, 0, CALLBACKFN)
+
+/* no kernel_t bailout */
+#define label_sk_has_perm1(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, CALLBACKFN) \
+ label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \
+ XXXX, 0, CALLBACKFN)
+
+
+/* Early bailout for kernel_t - 2 init args before callback */
+#define sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN) \
+({ \
+ struct aa_label *__label; \
+ struct aa_sk_ctx *__ctx = aa_sock(SOCKSK); \
+ int __ERROR = 0; \
+ bool __needput; \
+ if (rcu_access_pointer(__ctx->label) != kernel_t) { \
+ \
+ __label = begin_current_label_crit_section(&__needput); \
+ __ERROR = label_sk_has_perm2(current_cred(), __label, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN); \
+ end_current_label_crit_section(__label, __needput); \
+ } \
+ __ERROR; \
+})
+
+/* Early bailout for kernel_t - no init args before callback */
+#define sk_has_perm(SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \
+ sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, 0, 0, CALLBACKFN)
+
+
+/* Early bailout for kernel_t - 1 init arg before callback */
+#define sk_has_perm1(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, CALLBACKFN) \
+ sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, 0, CALLBACKFN)
+
+
+
+/* no kernel_t early bailout */
+/* NOTE: already lifted label_mediates into lsm.c */
+int aa_inet_create_perm(struct aa_label *label, int family, int type,
+ int protocol)
+{
+ struct aa_profile *profile;
+ int error = 0;
+ DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family, type,
+ protocol);
+
+ ad.subj_cred = current_cred();
+ set_ad_create(&ad, family, type, protocol);
+ error = fn_for_each(label, profile,
+ profile_create_perm(profile, family, type,
+ protocol, &ad));
+
+ return error;
+}
+
+int aa_inet_bind_perm(struct socket *sock, struct sockaddr *addr,
+ int addrlen)
+{
+ struct match_addr maddr;
+
+ return sk_has_perm1(sock->sk, OP_BIND, AA_MAY_BIND, profile, ad,
+ bind_map_addr(sock->sk, addr, addrlen, &maddr,
+ &ad),
+ profile_bind_perm(profile, sock->sk, &maddr, &ad));
+}
+
+int aa_inet_connect_perm(struct socket *sock, struct sockaddr *addr,
+ int addrlen)
+{
+ struct stored_match_addr laddr;
+ struct match_addr raddr;
+
+ /* disconnect socket */
+ if (addrlen < offsetofend(struct sockaddr, sa_family))
+ return -EINVAL;
+ if (addr->sa_family == AF_UNSPEC)
+ return 0;
+
+ /* do we need early bailout for !family ... */
+ return sk_has_perm2(sock->sk, OP_CONNECT, AA_MAY_CONNECT, profile, ad,
+ map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad),
+ map_addr(addr, addrlen, 0, ADDR_REMOTE, &raddr,
+ &ad),
+ profile_remote_perm(profile, sock->sk,
+ AA_MAY_CONNECT, &raddr,
+ &laddr.maddr, &ad));
+}
+
+int aa_inet_listen_perm(struct socket *sock, int backlog)
+{
+ struct stored_match_addr maddr;
+
+ /* do we need early bailout for !family ... */
+ return sk_has_perm1(sock->sk, OP_LISTEN, AA_MAY_LISTEN, profile, ad,
+ map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad),
+ profile_listen_perm(profile, sock->sk, &maddr.maddr,
+ backlog, &ad));
+}
+
+/* ability of sock to connect, not peer address binding */
+int aa_inet_accept_perm(struct socket *sock, struct socket *newsock)
+{
+ struct stored_match_addr maddr;
+ int error;
+
+ error = sk_has_perm1(sock->sk, OP_ACCEPT, AA_MAY_ACCEPT, profile, ad,
+ map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad),
+ profile_accept_perm(profile, sock->sk,
+ &maddr.maddr,
+ newsock->sk, &ad));
+
+ /* selinux updates inode - need to investigate this more */
+ return error;
+}
+
+/* sendmsg, recvmsg. */
+int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ struct stored_match_addr laddr;
+ struct match_addr raddr;
+
+ /* do we need early bailout for !family ... */
+ return sk_has_perm2(sock->sk, op, request, profile, ad,
+ map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad),
+ map_addr(msg->msg_name, msg->msg_namelen, 0,
+ ADDR_REMOTE, &raddr, &ad),
+ profile_remote_perm(profile, sock->sk, request,
+ &raddr, &laddr.maddr, &ad));
+}
+
+/* getopt, setopt */
+int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock,
+ int level, int optname)
+{
+ struct stored_match_addr maddr;
+
+ return sk_has_perm1(sock->sk, op, request, profile, ad,
+ map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad),
+ profile_opt_perm(profile, request, sock->sk,
+ &maddr.maddr, level, optname, &ad));
+}
+
+static int inet_label_sock_perm(const struct cred *cred, struct aa_label *label,
+ const char *op, u32 request,
+ struct socket *sock)
+{
+ struct stored_match_addr maddr;
+
+ return label_sk_has_perm1(cred, label, sock->sk, op, request, profile,
+ ad,
+ map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad),
+ profile_sk_perm(profile, request, sock->sk,
+ &maddr.maddr, &ad));
+}
+
+/* revalidation, get/set attr/getsockname/peername */
+int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock)
+{
+ struct aa_sk_ctx *ctx = aa_sock(sock->sk);
+ struct aa_label *label;
+ bool needput;
+ int error;
+
+ if (rcu_access_pointer(ctx->label) == kernel_t)
+ return 0;
+
+ label = begin_current_label_crit_section(&needput);
+ error = inet_label_sock_perm(current_cred(), label, op, request, sock);
+ end_current_label_crit_section(label, needput);
+
+ return error;
+}
+
+int aa_inet_file_perm(const struct cred *subj_cred, struct aa_label *label,
+ const char *op, u32 request, struct socket *sock)
+{
+ u32 sk_req = request & ~NET_PEER_MASK;
+ struct stored_match_addr laddr;
+ const struct sock *sk = sock->sk;
+ int error = 0;
+
+ AA_BUG(!label);
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6,
+ "family=%d", sk->sk_family);
+
+ /* access to the local sock */
+ error = label_sk_has_perm1(subj_cred, label, sock->sk, op, request,
+ profile, ad,
+ map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad),
+ profile_sk_perm(profile, sk_req, sock->sk, &laddr.maddr,
+ &ad));
+
+ if (!error) {
+ struct stored_match_addr raddr;
+
+ /* TODO: have ad here: instead of in CB so we do have to redo */
+ error = map_sock_addr(sock, ADDR_REMOTE, &raddr, NULL);
+ if (!error && raddr.maddr.addrp) {
+ error = label_sk_has_perm1(subj_cred, label, sock->sk,
+ op, request, profile, ad,
+ set_ad_addr(&ad, raddr.addr.sa_family,
+ false, &raddr.maddr),
+ profile_remote_perm(profile, sock->sk,
+ request,
+ &raddr.maddr,
+ &laddr.maddr, &ad));
+ }
+ }
+
+ return error;
+}
diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c
index b9b22edae202..b908e744818c 100644
--- a/security/apparmor/af_unix.c
+++ b/security/apparmor/af_unix.c
@@ -20,6 +20,7 @@
#include "include/apparmor.h"
#include "include/file.h"
#include "include/label.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/cred.h"
@@ -66,7 +67,7 @@ static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred,
#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,
+static aa_state_t match_addr(const struct aa_dfa *dfa, aa_state_t state,
struct sockaddr_un *addr, int addrlen)
{
if (addr)
@@ -172,7 +173,7 @@ static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t 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,
+ const struct aa_perms *p,
struct apparmor_audit_data *ad)
{
AA_BUG(!profile);
@@ -208,7 +209,7 @@ static int profile_create_perm(struct aa_profile *profile, int family,
AA_BUG(!profile);
AA_BUG(profile_unconfined(profile));
- state = RULE_MEDIATES_v9NET(rules);
+ state = RULE_MEDIATES_UNIX(rules);
if (state) {
state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE,
PF_UNIX, type, protocol, NULL,
@@ -234,7 +235,7 @@ static int profile_sk_perm(struct aa_profile *profile,
AA_BUG(!sk);
AA_BUG(profile_unconfined(profile));
- state = RULE_MEDIATES_v9NET(rules);
+ state = RULE_MEDIATES_UNIX(rules);
if (state) {
if (is_unix_fs(sk))
return unix_fs_perm(ad->op, request, ad->subj_cred,
@@ -263,7 +264,7 @@ static int profile_bind_perm(struct aa_profile *profile, struct sock *sk,
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
- state = RULE_MEDIATES_v9NET(rules);
+ state = RULE_MEDIATES_UNIX(rules);
if (state) {
if (is_unix_addr_fs(ad->net.addr, ad->net.addrlen))
/* under v7-9 fs hook handles bind */
@@ -294,7 +295,7 @@ static int profile_listen_perm(struct aa_profile *profile, struct sock *sk,
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
- state = RULE_MEDIATES_v9NET(rules);
+ state = RULE_MEDIATES_UNIX(rules);
if (state) {
__be16 b = cpu_to_be16(backlog);
@@ -331,7 +332,7 @@ static int profile_accept_perm(struct aa_profile *profile,
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
- state = RULE_MEDIATES_v9NET(rules);
+ state = RULE_MEDIATES_UNIX(rules);
if (state) {
if (is_unix_fs(sk))
return unix_fs_perm(ad->op, AA_MAY_ACCEPT,
@@ -361,7 +362,7 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request,
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
- state = RULE_MEDIATES_v9NET(rules);
+ state = RULE_MEDIATES_UNIX(rules);
if (state) {
__be16 b = cpu_to_be16(optname);
if (is_unix_fs(sk))
@@ -402,7 +403,7 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request,
AA_BUG(!peer_label);
AA_BUG(!ad);
- state = RULE_MEDIATES_v9NET(rules);
+ state = RULE_MEDIATES_UNIX(rules);
if (state) {
struct aa_profile *peerp;
@@ -462,13 +463,14 @@ static int aa_unix_label_sk_perm(const struct cred *subj_cred,
int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock)
{
struct aa_label *label;
+ bool needput;
int error;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
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);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -489,13 +491,14 @@ int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr,
{
struct aa_profile *profile;
struct aa_label *label;
+ bool needput;
int error = 0;
error = valid_addr(addr, addrlen);
if (error)
return error;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
/* fs bind is handled by mknod */
if (!unconfined(label)) {
DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk);
@@ -506,7 +509,7 @@ int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr,
error = fn_for_each_confined(label, profile,
profile_bind_perm(profile, sock->sk, &ad));
}
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -528,9 +531,10 @@ int aa_unix_listen_perm(struct socket *sock, int backlog)
{
struct aa_profile *profile;
struct aa_label *label;
+ bool needput;
int error = 0;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (!unconfined(label)) {
DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk);
@@ -538,7 +542,7 @@ int aa_unix_listen_perm(struct socket *sock, int backlog)
profile_listen_perm(profile, sock->sk,
backlog, &ad));
}
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -549,16 +553,17 @@ int aa_unix_accept_perm(struct socket *sock, struct socket *newsock)
{
struct aa_profile *profile;
struct aa_label *label;
+ bool needput;
int error = 0;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
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);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -582,9 +587,10 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock,
{
struct aa_profile *profile;
struct aa_label *label;
+ bool needput;
int error = 0;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (!unconfined(label)) {
DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk);
@@ -592,7 +598,7 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock,
profile_opt_perm(profile, request, sock->sk,
optname, &ad));
}
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 56155d7d5b2f..5b42140e12e8 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -23,6 +23,7 @@
#include <linux/fs_context.h>
#include <linux/poll.h>
#include <linux/zstd.h>
+#include <linux/string.h>
#include <uapi/linux/major.h>
#include <uapi/linux/magic.h>
@@ -33,6 +34,7 @@
#include "include/crypto.h"
#include "include/ipc.h"
#include "include/label.h"
+#include "include/net.h"
#include "include/lib.h"
#include "include/policy.h"
#include "include/policy_ns.h"
@@ -482,6 +484,126 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
return data;
}
+#if defined(CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY) || \
+ defined(CONFIG_SECURITY_APPARMOR_EXPORT_BINARY)
+static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen)
+{
+ if (slen < dlen) {
+ const size_t wksp_len = zstd_dctx_workspace_bound();
+ zstd_dctx *ctx;
+ void *wksp;
+ size_t out_len;
+ int ret = 0;
+
+ wksp = kvzalloc(wksp_len, GFP_KERNEL);
+ if (!wksp) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ ctx = zstd_init_dctx(wksp, wksp_len);
+ if (ctx == NULL) {
+ ret = -ENOMEM;
+ goto cleanup;
+ }
+ out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen);
+ if (zstd_is_error(out_len)) {
+ ret = -EINVAL;
+ goto cleanup;
+ }
+cleanup:
+ kvfree(wksp);
+ return ret;
+ }
+
+ if (dlen < slen)
+ return -EINVAL;
+ memcpy(dst, src, slen);
+ return 0;
+}
+#endif
+
+
+#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY
+/**
+ * aa_get_data_from_compressed - common routine for getting compressed policy
+ * from user and get both compressed and uncompressed version.
+ * @userbuf: user buffer to copy data from (NOT NULL)
+ * @buffer_size: size of user buffer
+ * @pos: position write is at in the file (NOT NULL)
+ * @compressed_data: Ptr on compressed data. *compressed_data is allocated there
+ *
+ * Returns: kernel buffer containing copy of user buffer data or an
+ * ERR_PTR on failure.
+ */
+static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf,
+ size_t buffer_size,
+ loff_t *pos,
+ char **compressed_data)
+{
+ struct aa_loaddata *data;
+ zstd_frame_header header;
+ int error;
+
+ if (!userbuf || !pos)
+ return ERR_PTR(-EINVAL);
+ if (*pos)
+ return ERR_PTR(-ESPIPE);
+
+ *compressed_data = kvmalloc(buffer_size, GFP_KERNEL);
+ if (!*compressed_data)
+ return ERR_PTR(-ENOMEM);
+ if (copy_from_user(*compressed_data, userbuf, buffer_size)) {
+ error = -EFAULT;
+ goto fail;
+ }
+ error = zstd_get_frame_header(&header, *compressed_data, buffer_size);
+ if (error || header.frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN ||
+ header.frameContentSize == ZSTD_CONTENTSIZE_ERROR) {
+ error = -EINVAL;
+ goto fail;
+ }
+
+ data = aa_loaddata_alloc(header.frameContentSize);
+ if (IS_ERR(data)) {
+ error = PTR_ERR(data);
+ goto fail;
+ }
+
+ // We then decompress the data
+ error = decompress_zstd(*compressed_data, buffer_size, data->data,
+ header.frameContentSize);
+ if (error)
+ goto fail_decompress;
+
+ data->size = header.frameContentSize;
+ return data;
+
+fail_decompress:
+ aa_put_i_loaddata(data);
+fail:
+ kvfree(*compressed_data);
+ return ERR_PTR(error);
+
+}
+#else
+static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf __always_unused,
+ size_t buffer_size __always_unused,
+ loff_t *pos __always_unused,
+ char **compressed_data __always_unused)
+{
+ return ERR_PTR(-EINVAL);
+}
+#endif /* CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY */
+
+struct aa_user_hdr {
+ uint8_t version;
+ uint8_t compress_level;
+ uint8_t padding[6]; /* force 8-byte alignment */
+} __packed __aligned(8);
+
+#define aa_hdr_magic "\x04\x08\x00\x76\x65\x72\x73\x69\x6f\x6e\x00\x02"
+#define aa_hdr_magic_size 12
+
static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
loff_t *pos, struct aa_ns *ns,
const struct cred *ocred)
@@ -489,8 +611,13 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
struct aa_loaddata *data;
struct aa_label *label;
ssize_t error;
+ char *compressed_data = NULL;
+ __le32 magic_le;
+ bool is_compressed;
+ u8 aahdr[aa_hdr_magic_size];
+ bool needput;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
/* high level check about policy management - fine grained in
* below after unpack
@@ -499,17 +626,59 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
if (error)
goto end_section;
- data = aa_simple_write_to_buffer(buf, size, size, pos);
- error = PTR_ERR(data);
+ /* If the policy is userspace compressed we start by decompressing it
+ * to make the required checks (computing hash, verifying profile, ...)
+ *
+ * Getting a userspace-compressed version then decompressing it in the
+ * kernel actually makes sense since zstd decompression is ~3.5x faster
+ * than compression. This also allow to increase the compression level.
+ */
+
+ if (size >= sizeof(__le32) &&
+ !copy_from_user(&magic_le, buf, sizeof(magic_le)) &&
+ le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) {
+ is_compressed = true;
+ } else if (size >= sizeof(struct aa_user_hdr) + sizeof(__le32) &&
+ !copy_from_user(&magic_le,
+ buf + sizeof(struct aa_user_hdr),
+ sizeof(magic_le)) &&
+ le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) {
+ is_compressed = true;
+ /* skip the userspace header if present */
+ buf += sizeof(struct aa_user_hdr);
+ size -= sizeof(struct aa_user_hdr);
+ } else if (size >= sizeof(struct aa_user_hdr) +
+ aa_hdr_magic_size &&
+ !copy_from_user(&aahdr,
+ buf + sizeof(struct aa_user_hdr),
+ aa_hdr_magic_size) &&
+ memcmp(&aahdr, aa_hdr_magic, aa_hdr_magic_size) == 0) {
+ /* uncompressed blob with user hdr */
+ buf += sizeof(struct aa_user_hdr);
+ size -= sizeof(struct aa_user_hdr);
+ is_compressed = false;
+ } else {
+ is_compressed = false;
+ }
+ if (is_compressed) {
+
+ data = aa_get_data_from_compressed(buf, size, pos, &compressed_data);
+ error = PTR_ERR(data);
+ } else {
+ data = aa_simple_write_to_buffer(buf, size, size, pos);
+ error = PTR_ERR(data);
+ }
+
if (!IS_ERR(data)) {
- error = aa_replace_profiles(ns, label, mask, data);
+ error = aa_replace_profiles(ns, label, mask, data,
+ compressed_data, size);
/* 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);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -549,6 +718,7 @@ static const struct file_operations aa_fs_profile_replace = {
.llseek = default_llseek,
};
+
/* .remove file hook fn to remove loaded policy */
static ssize_t profile_remove(struct file *f, const char __user *buf,
size_t size, loff_t *pos)
@@ -557,8 +727,9 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
struct aa_label *label;
ssize_t error;
struct aa_ns *ns = get_ns_common_ref(f->f_inode->i_private);
+ bool needput;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
/* high level check about policy management - fine grained in
* below after unpack
*/
@@ -580,7 +751,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
aa_put_profile_loaddata(data);
}
out:
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
aa_put_ns(ns);
return error;
}
@@ -685,7 +856,8 @@ static const struct file_operations aa_fs_ns_revision_fops = {
.release = ns_revision_release,
};
-static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
+static void profile_query_cb(const struct aa_profile *profile,
+ struct aa_perms *perms,
const char *match_str, size_t match_len)
{
struct aa_ruleset *rules = profile->label.rules[0];
@@ -755,6 +927,7 @@ static ssize_t query_data(char *buf, size_t buf_len,
struct aa_data *data;
u32 bytes, blocks;
__le32 outle32;
+ bool needput;
if (!query_len)
return -EINVAL; /* need a query */
@@ -768,9 +941,9 @@ static ssize_t query_data(char *buf, size_t buf_len,
if (buf_len < sizeof(bytes) + sizeof(blocks))
return -EINVAL; /* not enough space */
- curr = begin_current_label_crit_section();
+ curr = begin_current_label_crit_section(&needput);
label = aa_label_parse(curr, query, GFP_KERNEL, false, false);
- end_current_label_crit_section(curr);
+ end_current_label_crit_section(curr, needput);
if (IS_ERR(label))
return PTR_ERR(label);
@@ -846,6 +1019,7 @@ static ssize_t query_label(char *buf, size_t buf_len,
size_t label_name_len, match_len;
struct aa_perms perms;
struct label_it i;
+ bool needput;
if (!query_len)
return -EINVAL;
@@ -864,9 +1038,9 @@ static ssize_t query_label(char *buf, size_t buf_len,
match_str = label_name + label_name_len + 1;
match_len = query_len - label_name_len - 1;
- curr = begin_current_label_crit_section();
+ curr = begin_current_label_crit_section(&needput);
label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false);
- end_current_label_crit_section(curr);
+ end_current_label_crit_section(curr, needput);
if (IS_ERR(label))
return PTR_ERR(label);
@@ -1234,10 +1408,11 @@ static const struct file_operations seq_ns_ ##NAME ##_fops = { \
static int seq_ns_stacked_show(struct seq_file *seq, void *v)
{
struct aa_label *label;
+ bool needput;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
seq_printf(seq, "%s\n", str_yes_no(label->size > 1));
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return 0;
}
@@ -1248,8 +1423,9 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v)
struct aa_profile *profile;
struct label_it it;
int count = 1;
+ bool needput;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (label->size > 1) {
label_for_each(it, label, profile)
@@ -1260,7 +1436,7 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v)
}
seq_printf(seq, "%s\n", str_yes_no(count > 1));
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return 0;
}
@@ -1268,23 +1444,32 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v)
static int seq_ns_level_show(struct seq_file *seq, void *v)
{
struct aa_label *label;
+ bool needput;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
seq_printf(seq, "%d\n", labels_ns(label)->level);
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return 0;
}
static int seq_ns_name_show(struct seq_file *seq, void *v)
{
- struct aa_label *label = begin_current_label_crit_section();
+ bool needput;
+ struct aa_label *label = begin_current_label_crit_section(&needput);
+
seq_printf(seq, "%s\n", labels_ns(label)->base.name);
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return 0;
}
+SEQ_NS_FOPS(stacked);
+SEQ_NS_FOPS(nsstacked);
+SEQ_NS_FOPS(level);
+SEQ_NS_FOPS(name);
+
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
static int seq_ns_compress_min_show(struct seq_file *seq, void *v)
{
seq_printf(seq, "%d\n", AA_MIN_CLEVEL);
@@ -1297,16 +1482,11 @@ static int seq_ns_compress_max_show(struct seq_file *seq, void *v)
return 0;
}
-SEQ_NS_FOPS(stacked);
-SEQ_NS_FOPS(nsstacked);
-SEQ_NS_FOPS(level);
-SEQ_NS_FOPS(name);
SEQ_NS_FOPS(compress_min);
SEQ_NS_FOPS(compress_max);
/* policy/raw_data/ * file ops */
-#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
#define SEQ_RAWDATA_FOPS(NAME) \
static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\
{ \
@@ -1396,41 +1576,6 @@ SEQ_RAWDATA_FOPS(revision);
SEQ_RAWDATA_FOPS(hash);
SEQ_RAWDATA_FOPS(compressed_size);
-static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen)
-{
- if (slen < dlen) {
- const size_t wksp_len = zstd_dctx_workspace_bound();
- zstd_dctx *ctx;
- void *wksp;
- size_t out_len;
- int ret = 0;
-
- wksp = kvzalloc(wksp_len, GFP_KERNEL);
- if (!wksp) {
- ret = -ENOMEM;
- goto cleanup;
- }
- ctx = zstd_init_dctx(wksp, wksp_len);
- if (ctx == NULL) {
- ret = -ENOMEM;
- goto cleanup;
- }
- out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen);
- if (zstd_is_error(out_len)) {
- ret = -EINVAL;
- goto cleanup;
- }
-cleanup:
- kvfree(wksp);
- return ret;
- }
-
- if (dlen < slen)
- return -EINVAL;
- memcpy(dst, src, slen);
- return 0;
-}
-
static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size,
loff_t *ppos)
{
@@ -1935,11 +2080,12 @@ static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir,
/* TODO: improve permission check */
struct aa_label *label;
int error;
+ bool needput;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
error = aa_may_manage_policy(current_cred(), label, NULL, NULL,
AA_MAY_LOAD_POLICY);
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
if (error)
return ERR_PTR(error);
@@ -1984,12 +2130,13 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
struct aa_ns *ns, *parent;
/* TODO: improve permission check */
struct aa_label *label;
+ bool needput;
int error;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
error = aa_may_manage_policy(current_cred(), label, NULL, NULL,
AA_MAY_LOAD_POLICY);
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
if (error)
return error;
@@ -2483,6 +2630,10 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = {
AA_SFS_FILE_STRING("permstable32", PERMS32STR),
AA_SFS_FILE_U64("state32", 1),
AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined),
+#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY
+ AA_SFS_FILE_BOOLEAN("compressed_load", 1),
+ AA_SFS_FILE_BOOLEAN("extended_policy_header", 1),
+#endif
{ }
};
@@ -2547,8 +2698,10 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = {
AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops),
AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops),
AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops),
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops),
AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops),
+#endif
AA_SFS_DIR("features", aa_sfs_entry_features),
{ }
};
diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c
index 4a60b6fda75f..5752bb93aaaa 100644
--- a/security/apparmor/audit.c
+++ b/security/apparmor/audit.c
@@ -20,6 +20,7 @@
const char *const audit_mode_names[] = {
"normal",
"quiet_denied",
+ "quiet.allowed",
"quiet",
"noquiet",
"all"
@@ -52,7 +53,7 @@ static const char *const aa_class_names[] = {
"unknown",
"unknown",
"net",
- "unknown",
+ "netv9",
"label",
"posix_mqueue",
"io_uring",
@@ -67,7 +68,7 @@ static const char *const aa_class_names[] = {
"unknown",
"unknown",
"unknown",
- "unknown",
+ "netv9_packet",
"X",
"dbus",
};
@@ -139,6 +140,17 @@ static void audit_pre(struct audit_buffer *ab, void *va)
}
}
+int aa_select_audit_type(u32 denied, const struct aa_perms *perms)
+{
+ if (likely(!denied))
+ return AUDIT_APPARMOR_AUDIT;
+ else if (denied & perms->kill)
+ return AUDIT_APPARMOR_KILL;
+ else if (denied == (denied & perms->complain))
+ return AUDIT_APPARMOR_ALLOWED;
+ return AUDIT_APPARMOR_DENIED;
+}
+
/**
* aa_audit_msg - Log a message to the audit subsystem
* @type: audit type for the message
@@ -152,6 +164,28 @@ void aa_audit_msg(int type, struct apparmor_audit_data *ad,
common_lsm_audit(&ad->common, audit_pre, cb);
}
+int aa_audit_perm_error(struct aa_label *label, u32 request, int error,
+ struct apparmor_audit_data *ad,
+ void (*cb)(struct audit_buffer *, void *))
+{
+ int type = aa_select_audit_type(request, &nullperms);
+
+ if (ad) {
+ struct aa_profile *profile;
+ struct label_it i;
+
+ ad->request = request;
+ ad->denied = request;
+ ad->error = error;
+ label_for_each_confined(i, label, profile) {
+ ad->subj_label = &profile->label;
+ aa_audit_msg(type, ad, cb);
+ }
+ }
+
+ return error;
+}
+
/**
* aa_audit - Log a profile based audit event to the audit subsystem
* @type: audit type for the message
@@ -192,7 +226,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(profile->signal, NULL,
+ send_sig_info(profile->signal, SEND_SIG_NOINFO,
ad->common.type == LSM_AUDIT_DATA_TASK &&
ad->common.u.tsk ? ad->common.u.tsk : current);
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
index b9ea6bc45c1a..2f3c6c84358d 100644
--- a/security/apparmor/capability.c
+++ b/security/apparmor/capability.c
@@ -191,7 +191,7 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label,
return error;
}
-kernel_cap_t aa_profile_capget(struct aa_profile *profile)
+kernel_cap_t aa_profile_capget(const struct aa_profile *profile)
{
struct aa_ruleset *rules = profile->label.rules[0];
aa_state_t state;
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index d6958eb00e30..af9e8431e3ea 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -24,6 +24,7 @@
#include "include/domain.h"
#include "include/file.h"
#include "include/ipc.h"
+#include "include/lib.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
@@ -90,8 +91,8 @@ out:
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
-static inline aa_state_t match_component(struct aa_profile *profile,
- struct aa_profile *tp,
+static inline aa_state_t match_component(const struct aa_profile *profile,
+ const struct aa_profile *tp,
bool stack, aa_state_t state)
{
struct aa_ruleset *rules = profile->label.rules[0];
@@ -126,7 +127,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_compound_match(struct aa_profile *profile,
+static int label_compound_match(const struct aa_profile *profile,
struct aa_label *label, bool stack,
aa_state_t state, bool inview, u32 request,
struct aa_perms *perms)
@@ -188,7 +189,7 @@ fail:
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_components_match(struct aa_profile *profile,
+static int label_components_match(const struct aa_profile *profile,
struct aa_label *label, bool stack,
aa_state_t start, bool inview, u32 request,
struct aa_perms *perms)
@@ -252,7 +253,7 @@ fail:
*
* 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,
+static int label_match(const struct aa_profile *profile, struct aa_label *label,
bool stack, aa_state_t state, bool inview, u32 request,
struct aa_perms *perms)
{
@@ -286,7 +287,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label,
* currently only matches full label A//&B//&C or individual components A, B, C
* not arbitrary combinations. Eg. A//&B, C
*/
-static int change_profile_perms(struct aa_profile *profile,
+static int change_profile_perms(const struct aa_profile *profile,
struct aa_label *target, bool stack,
u32 request, aa_state_t start,
struct aa_perms *perms)
@@ -303,28 +304,31 @@ static int change_profile_perms(struct aa_profile *profile,
/**
* aa_xattrs_match - check whether a file matches the xattrs defined in profile
- * @bprm: binprm struct for the process to validate
+ * @path: path for file being matched (NOT NULL)
* @profile: profile to match against (NOT NULL)
* @state: state to start match in
*
* Returns: number of extended attributes that matched, or < 0 on error
*/
-static int aa_xattrs_match(const struct linux_binprm *bprm,
- struct aa_profile *profile, aa_state_t state)
+static int aa_xattrs_match(const struct path *path,
+ const struct aa_profile *profile, aa_state_t state)
{
+ AA_BUG(!path);
+ AA_BUG(!profile);
+
int i;
struct dentry *d;
char *value = NULL;
- struct aa_attachment *attach = &profile->attach;
+ const struct aa_attachment *attach = &profile->attach;
int size, value_size = 0, ret = attach->xattr_count;
- if (!bprm || !attach->xattr_count)
+ if (!attach->xattr_count)
return 0;
might_sleep();
/* transition from exec match to xattr set */
state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);
- d = bprm->file->f_path.dentry;
+ d = path->dentry;
for (i = 0; i < attach->xattr_count; i++) {
size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],
@@ -372,7 +376,7 @@ out:
/**
* find_attach - do attachment search for unconfined processes
- * @bprm: binprm structure of transitioning task
+ * @path: path of file in question (NOT NULL)
* @ns: the current namespace (NOT NULL)
* @head: profile list to walk (NOT NULL)
* @name: to match against (NOT NULL)
@@ -387,7 +391,7 @@ out:
*
* Returns: label or NULL if no match found
*/
-static struct aa_label *find_attach(const struct linux_binprm *bprm,
+static struct aa_label *find_attach(const struct path *path,
struct aa_ns *ns, struct list_head *head,
const char *name, const char **info)
{
@@ -395,6 +399,7 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm,
bool conflict = false;
struct aa_profile *profile, *candidate = NULL;
+ AA_BUG(!path);
AA_BUG(!name);
AA_BUG(!head);
@@ -434,13 +439,13 @@ restart:
if (count < candidate_len)
continue;
- if (bprm && attach->xattr_count) {
+ if (attach->xattr_count) {
long rev = READ_ONCE(ns->revision);
if (!aa_get_profile_not0(profile))
goto restart;
rcu_read_unlock();
- ret = aa_xattrs_match(bprm, profile,
+ ret = aa_xattrs_match(path, profile,
state);
rcu_read_lock();
aa_put_profile(profile);
@@ -556,7 +561,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
/**
* x_to_label - get target label for a given xindex
* @profile: current profile (NOT NULL)
- * @bprm: binprm structure of transitioning task
+ * @path: path of file in question
* @name: name to lookup (NOT NULL)
* @xindex: index into x transition table
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
@@ -567,7 +572,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
* Returns: refcounted label or NULL if not found available
*/
static struct aa_label *x_to_label(struct aa_profile *profile,
- const struct linux_binprm *bprm,
+ const struct path *path,
const char *name, u32 xindex,
const char **lookupname,
const char **info)
@@ -598,11 +603,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
case AA_X_NAME:
if (xindex & AA_X_CHILD)
/* released by caller */
- new = find_attach(bprm, ns, &profile->base.profiles,
+ new = find_attach(path, ns, &profile->base.profiles,
name, info);
else
/* released by caller */
- new = find_attach(bprm, ns, &ns->base.profiles,
+ new = find_attach(path, ns, &ns->base.profiles,
name, info);
*lookupname = name;
break;
@@ -690,7 +695,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
}
if (profile_unconfined(profile)) {
- new = find_attach(bprm, profile->ns,
+ new = find_attach(&bprm->file->f_path, profile->ns,
&profile->ns->base.profiles, name, &info);
/* info set -> something unusual that we should report
* Currently this is only conflicting attachments, but other
@@ -720,8 +725,8 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
state = aa_str_perms(rules->file, state, name, cond, &perms);
if (perms.allow & MAY_EXEC) {
/* exec permission determine how to transition */
- new = x_to_label(profile, bprm, name, perms.xindex, &target,
- &info);
+ new = x_to_label(profile, &bprm->file->f_path, 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
@@ -874,6 +879,52 @@ static struct aa_label *label_merge_wrap(struct aa_label *a, struct aa_label *b,
return label;
}
+static bool is_profile_priv_restricted_to_stack(const struct cred *subj_cred,
+ struct aa_profile *profile)
+{
+ if (profile_unconfined(profile) && profile == profile->ns->unconfined &&
+ aa_unprivileged_unconfined_restricted &&
+ /* cap_capable returns false (0) if true, hence true here means
+ * doesn't have capability and the stack will be restricted
+ */
+ cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE,
+ CAP_OPT_NOAUDIT))
+ return true;
+ return false;
+}
+
+static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking";
+
+static struct aa_label *priv_restricted_transition(const struct cred *subj_cred,
+ struct aa_profile *profile,
+ const char *op, u32 request,
+ const char *name,
+ struct aa_label *transition,
+ gfp_t gfp)
+{
+ if (!is_profile_priv_restricted_to_stack(subj_cred, profile))
+ return aa_get_newest_label(transition);
+
+ /* transition allowed but only via stack */
+ struct aa_label *target = label_merge_wrap(&profile->label,
+ transition, gfp);
+ if (IS_ERR_OR_NULL(target))
+ return target;
+
+ /* doing this here is less than optimal but good enough until the
+ * fs mediation rework lands
+ */
+ struct aa_perms perms = {
+ .allow = request,
+ .audit = request,
+ };
+ aa_audit_file(subj_cred, profile, &perms, op,
+ request, name, NULL, target,
+ subj_cred->euid, stack_msg, 0);
+
+ return target;
+}
+
static struct aa_label *handle_onexec(const struct cred *subj_cred,
struct aa_label *label,
struct aa_label *onexec, bool stack,
@@ -902,7 +953,10 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
stack ? label_merge_wrap(&profile->label, onexec,
GFP_KERNEL)
- : aa_get_newest_label(onexec),
+ : priv_restricted_transition(subj_cred, profile,
+ OP_CHANGE_ONEXEC, AA_MAY_ONEXEC,
+ bprm->filename, onexec,
+ GFP_KERNEL),
profile_transition(subj_cred, profile, bprm,
buffer, cond, unsafe));
AA_BUG(!new);
@@ -1084,8 +1138,8 @@ static struct aa_label *build_change_hat(const struct cred *subj_cred,
if (!hat) {
error = -ENOENT;
if (COMPLAIN_MODE(profile)) {
- hat = aa_new_learning_profile(profile, true, name,
- GFP_KERNEL);
+ hat = __aa_new_learning_profile(profile, true, name,
+ GFP_KERNEL);
if (!hat) {
info = "failed null profile create";
error = -ENOMEM;
@@ -1123,6 +1177,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
bool sibling = false;
const char *name, *info = NULL;
int i, error;
+ bool needput = false;
AA_BUG(!label);
AA_BUG(!hats);
@@ -1135,7 +1190,6 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
* the profiles and label, we can rely on the namespaces being live
* and avoid incrementing their refcounts while grabbing the lock.
*/
- label = aa_get_label(label);
ns = labels_ns(label);
retry:
@@ -1143,15 +1197,19 @@ retry:
if (label_is_stale(label)) {
new = aa_get_newest_label(label);
new_ns = labels_ns(new);
+
+ if (needput)
+ /* aa_put_label() is safe to call when under lock */
+ aa_put_label(label);
+ label = new;
+ needput = true;
+ /* check if replaced with label in parent ns, and lock there */
if (new_ns != ns) {
- aa_put_label(new);
mutex_unlock(&ns->lock);
ns = new_ns;
- label = new;
+ /* retry will bottom out at the root of the tree */
goto retry;
}
- aa_put_label(label);
- label = new;
}
if (PROFILE_IS_HAT(labels_profile(label)))
@@ -1162,7 +1220,8 @@ retry:
name = hats[i];
label_for_each_in_scope(it, labels_ns(label), label, profile) {
if (sibling && PROFILE_IS_HAT(profile)) {
- root = aa_get_profile(profile->parent);
+ root = aa_get_profile(rcu_dereference_protected(profile->parent,
+ mutex_is_locked(&ns->lock)));
} else if (!sibling && !PROFILE_IS_HAT(profile)) {
root = aa_get_profile(profile);
} else { /* conflicting change type */
@@ -1223,6 +1282,8 @@ fail:
}
}
mutex_unlock(&ns->lock);
+ if (needput)
+ aa_put_label(label);
return ERR_PTR(error);
build:
@@ -1233,7 +1294,8 @@ build:
mutex_unlock(&ns->lock);
AA_BUG(!new);
/* return new label or error ptr */
-
+ if (needput)
+ aa_put_label(label);
return new;
}
@@ -1406,8 +1468,6 @@ static int change_profile_perms_wrapper(const char *op, const char *name,
return error;
}
-static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking";
-
/**
* aa_change_profile - perform a one-way profile transition
* @fqname: name of profile may include namespace (NOT NULL)
@@ -1467,28 +1527,6 @@ int aa_change_profile(const char *fqname, int flags)
op = OP_CHANGE_PROFILE;
}
- /* This should move to a per profile test. Requires pushing build
- * into callback
- */
- if (!stack && unconfined(label) &&
- label == &labels_ns(label)->unconfined->label &&
- aa_unprivileged_unconfined_restricted &&
- /* TODO: refactor so this check is a fn */
- cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE,
- CAP_OPT_NOAUDIT)) {
- /* regardless of the request in this case apparmor
- * stacks against unconfined so admin set policy can't be
- * by-passed
- */
- stack = true;
- perms.audit = request;
- (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));
- perms.audit = 0;
- }
-
if (*fqname == '&') {
stack = true;
/* don't have label_parse() do stacking */
@@ -1559,7 +1597,10 @@ check:
/* stacking is always a subset, so only check the nonstack case */
if (!stack) {
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
- aa_get_label(target),
+ priv_restricted_transition(subj_cred, profile,
+ op, request,
+ auditname, target,
+ GFP_KERNEL),
aa_get_label(&profile->label));
AA_BUG(!new);
if (IS_ERR(new))
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index c9d55fe1086f..3e74b613db32 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -93,11 +93,13 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
* Returns: %0 or error on failure
*/
int aa_audit_file(const struct cred *subj_cred,
- struct aa_profile *profile, struct aa_perms *perms,
+ struct aa_profile *profile, const struct aa_perms *perms,
const char *op, u32 request, const char *name,
const char *target, struct aa_label *tlabel,
kuid_t ouid, const char *info, int error)
{
+ u32 quiet = perms->quiet;
+ u32 complain = perms->complain;
int type = AUDIT_APPARMOR_AUTO;
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op);
@@ -112,6 +114,8 @@ int aa_audit_file(const struct cred *subj_cred,
ad.error = error;
ad.common.u.tsk = NULL;
+ if (COMPLAIN_MODE(profile))
+ complain |= ~(perms->allow | perms->deny);
if (likely(!ad.error)) {
u32 mask = perms->audit;
@@ -132,11 +136,14 @@ int aa_audit_file(const struct cred *subj_cred,
if (ad.request & perms->kill)
type = AUDIT_APPARMOR_KILL;
+ if (AUDIT_MODE(profile) == AUDIT_QUIET_ALLOWED)
+ quiet |= complain | perms->allow;
+
/* quiet known rejects, assumes quiet and kill do not overlap */
- if ((ad.request & perms->quiet) &&
+ if ((ad.request & quiet) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL)
- ad.request &= ~perms->quiet;
+ ad.request &= ~quiet;
if (!ad.request)
return ad.error;
@@ -232,7 +239,7 @@ int __aa_path_perm(const char *op, const struct cred *subj_cred,
int e = 0;
if (profile_unconfined(profile) ||
- ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules)))
+ ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_UNIX(rules)))
return 0;
aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],
name, cond, perms);
diff --git a/security/apparmor/include/af_inet.h b/security/apparmor/include/af_inet.h
new file mode 100644
index 000000000000..601ab44bdfe1
--- /dev/null
+++ b/security/apparmor/include/af_inet.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor inet/inet6 fine grained mediation
+ *
+ * Copyright 2024 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_INET_H
+#define __AA_AF_INET_H
+
+#include "label.h"
+
+int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock);
+int aa_inet_create_perm(struct aa_label *label, int family, int type,
+ int protocol);
+int aa_inet_bind_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_inet_connect_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_inet_listen_perm(struct socket *sock, int backlog);
+int aa_inet_accept_perm(struct socket *sock, struct socket *newsock);
+int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock,
+ struct msghdr *msg, int size);
+int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock, int level,
+ int optname);
+int aa_inet_file_perm(const struct cred *subj_cred,
+ struct aa_label *label, const char *op, u32 request,
+ struct socket *sock);
+
+#endif /* __AA_AF_INET_H */
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
index cc6e3df1bc62..cb06b2f55d49 100644
--- a/security/apparmor/include/apparmor.h
+++ b/security/apparmor/include/apparmor.h
@@ -36,6 +36,7 @@
#define AA_CLASS_NS 21
#define AA_CLASS_IO_URING 22
+#define AA_CLASS_NETV9_SKB 30
#define AA_CLASS_X 31
#define AA_CLASS_DBUS 32
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index 33243d11fd10..c6c8fcde728f 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -11,6 +11,9 @@
#ifndef __AA_APPARMORFS_H
#define __AA_APPARMORFS_H
+#include <linux/init.h>
+#include <linux/types.h>
+
extern struct path aa_null;
enum aa_sfs_type {
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
index aa00b34404f9..987ed4441fd3 100644
--- a/security/apparmor/include/audit.h
+++ b/security/apparmor/include/audit.h
@@ -21,13 +21,14 @@
#include "label.h"
extern const char *const audit_mode_names[];
-#define AUDIT_MAX_INDEX 5
enum audit_mode {
AUDIT_NORMAL, /* follow normal auditing of accesses */
AUDIT_QUIET_DENIED, /* quiet all denied access messages */
+ AUDIT_QUIET_ALLOWED, /* quiet all allowed access messages */
AUDIT_QUIET, /* quiet all messages */
AUDIT_NOQUIET, /* do not quiet audit messages */
- AUDIT_ALL /* audit all accesses */
+ AUDIT_ALL, /* audit all accesses */
+ AUDIT_MODE_NAMES_COUNT /* Must be last entry */
};
enum audit_type {
@@ -183,6 +184,8 @@ struct apparmor_audit_data {
.common.apparmor_audit_data = &NAME, \
};
+int aa_select_audit_type(u32 denied, const struct aa_perms *perms);
+
void aa_audit_msg(int type, struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *));
int aa_audit(int type, struct aa_profile *profile,
@@ -196,6 +199,9 @@ int aa_audit(int type, struct aa_profile *profile,
(AD)->error; \
})
+int aa_audit_perm_error(struct aa_label *label, u32 request, int error,
+ struct apparmor_audit_data *ad,
+ void (*cb)(struct audit_buffer *, void *));
static inline int complain_error(int error)
{
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h
index 1ddcec2d1160..89a9c75d8f44 100644
--- a/security/apparmor/include/capability.h
+++ b/security/apparmor/include/capability.h
@@ -11,6 +11,7 @@
#ifndef __AA_CAPABILITY_H
#define __AA_CAPABILITY_H
+#include <linux/capability.h>
#include <linux/sched.h>
#include "apparmorfs.h"
@@ -36,7 +37,7 @@ struct aa_caps {
extern struct aa_sfs_entry aa_sfs_entry_caps[];
-kernel_cap_t aa_profile_capget(struct aa_profile *profile);
+kernel_cap_t aa_profile_capget(const 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 2b6098149b15..056e031b7b8a 100644
--- a/security/apparmor/include/cred.h
+++ b/security/apparmor/include/cred.h
@@ -177,12 +177,14 @@ static inline void __end_current_label_crit_section(struct aa_label *label,
/**
* end_current_label_crit_section - put a reference found with begin_current_label..
* @label: label reference to put
+ * @needput: output: bool set by __begin_current_label_crit_section
*
* Should only be used with a reference obtained with
* begin_current_label_crit_section and never used in situations where the
* task cred may be updated
*/
-static inline void end_current_label_crit_section(struct aa_label *label)
+static inline void end_current_label_crit_section(struct aa_label *label,
+ bool needput)
{
if (label != aa_current_raw_label())
aa_put_label(label);
@@ -208,28 +210,22 @@ static inline struct aa_label *__begin_current_label_crit_section(bool *needput)
/**
* begin_current_label_crit_section - current's confining label and update it
+ * @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)
*
- * Not safe to call inside locks
- *
* The returned reference must be put with end_current_label_crit_section()
- * This must NOT be used if the task cred could be updated within the
+ * This should NOT be used if the task cred could be updated within the
* 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();
- might_sleep();
-
- if (label_is_stale(label)) {
- label = aa_get_newest_label(label);
- if (aa_replace_current_label(label) == 0)
- /* task cred will keep the reference */
- aa_put_label(label);
- }
+ label = __begin_current_label_crit_section(needput);
+ if (*needput)
+ aa_schedule_stale_label_replacement();
return label;
}
diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h
index ef60f99bc5ae..1614c07fc53e 100644
--- a/security/apparmor/include/file.h
+++ b/security/apparmor/include/file.h
@@ -72,7 +72,7 @@ struct path_cond {
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
int aa_audit_file(const struct cred *cred,
- struct aa_profile *profile, struct aa_perms *perms,
+ struct aa_profile *profile, const struct aa_perms *perms,
const char *op, u32 request, const char *name,
const char *target, struct aa_label *tlabel, kuid_t ouid,
const char *info, int error);
diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h
index b5a722a47fd2..2c869b7dec97 100644
--- a/security/apparmor/include/label.h
+++ b/security/apparmor/include/label.h
@@ -23,7 +23,7 @@ struct aa_ruleset;
#define LOCAL_VEC_ENTRIES 8
#define DEFINE_VEC(T, V) \
- struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \
+ struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES + 1]; \
struct aa_ ## T **(V)
#define vec_setup(T, V, N, GFP) \
@@ -31,10 +31,10 @@ struct aa_ruleset;
if ((N) <= LOCAL_VEC_ENTRIES) { \
typeof(N) i; \
(V) = (_ ## V ## _localtmp); \
- for (i = 0; i < (N); i++) \
+ for (i = 0; i <= (N); i++) \
(V)[i] = NULL; \
} else \
- (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \
+ (V) = kzalloc_objs(struct aa_ ## T *, (N) + 1, (GFP)); \
(V) ? 0 : -ENOMEM; \
})
@@ -165,7 +165,7 @@ do { \
#define labels_profile(X) ((X)->vec[(X)->size - 1])
-int aa_label_next_confined(struct aa_label *l, int i);
+int aa_label_next_confined(const struct aa_label *l, int i);
/* for each profile in a label */
#define label_for_each(I, L, P) \
@@ -246,12 +246,12 @@ 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)
-static inline bool label_mediates(struct aa_label *L, unsigned char C)
+static inline bool label_mediates(const 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)
+static inline bool label_mediates_safe(const struct aa_label *L, unsigned char C)
{
if (C > AA_CLASS_LAST)
return false;
@@ -268,11 +268,12 @@ void aa_label_kref(struct kref *kref);
bool aa_label_init(struct aa_label *label, int size, gfp_t gfp);
struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp);
-bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub);
-bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub);
+bool aa_label_is_subset(const struct aa_label *set, const struct aa_label *sub);
+bool aa_label_is_unconfined_subset(const struct aa_label *set,
+ const struct aa_label *sub);
struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
- struct aa_label *set,
- struct aa_label *sub);
+ const struct aa_label *set,
+ const struct aa_label *sub);
bool aa_label_remove(struct aa_label *label);
struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l);
bool aa_label_replace(struct aa_label *old, struct aa_label *new);
@@ -280,8 +281,8 @@ bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
struct aa_label *new);
struct aa_profile *aa_label_next_in_merge(struct label_it *I,
- struct aa_label *a,
- struct aa_label *b);
+ const struct aa_label *a,
+ const struct aa_label *b);
struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b);
struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
gfp_t gfp);
@@ -342,7 +343,7 @@ static inline const char *aa_label_str_split(const char *str)
struct aa_perms;
struct aa_ruleset;
-int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
+int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules,
struct aa_label *label, aa_state_t state, bool subns,
u32 request, struct aa_perms *perms);
@@ -462,8 +463,8 @@ static inline void aa_put_label(struct aa_label *l)
}
/* 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)
+static inline bool __aa_subj_label_is_cached(const struct aa_label *subj_label,
+ const struct aa_label *obj_label)
{
return aa_label_is_subset(obj_label, subj_label);
}
diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h
index e3c8cb044a90..475dd71fbe40 100644
--- a/security/apparmor/include/lib.h
+++ b/security/apparmor/include/lib.h
@@ -136,7 +136,7 @@ static inline bool aa_strneq(const char *str, const char *sub, int len)
* character which is not used in standard matching and is only
* used to separate pairs.
*/
-static inline aa_state_t aa_dfa_null_transition(struct aa_dfa *dfa,
+static inline aa_state_t aa_dfa_null_transition(const struct aa_dfa *dfa,
aa_state_t start)
{
/* the null transition only needs the string's null terminator byte */
@@ -338,8 +338,8 @@ __do_cleanup: \
__new_ = (FN); \
} \
__done: \
- if (PTR_ERR(__new_)) \
- AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \
+ if (IS_ERR(__new_)) \
+ AA_DEBUG(DEBUG_LABEL, "label build failed %ld\n", PTR_ERR(__new_)); \
(__new_); \
})
diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h
index 7accb1c39849..f7bd7855f1bd 100644
--- a/security/apparmor/include/match.h
+++ b/security/apparmor/include/match.h
@@ -125,16 +125,18 @@ static inline size_t table_size(size_t len, size_t el_size)
#define aa_state_t unsigned int
-struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
-aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start,
+struct aa_dfa *aa_dfa_unpack(const void *blob, size_t size, int flags);
+aa_state_t aa_dfa_match_len(const struct aa_dfa *dfa, aa_state_t start,
const char *str, int len);
-aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start,
+aa_state_t aa_dfa_match(const struct aa_dfa *dfa, aa_state_t start,
const char *str);
-aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c);
-aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state);
-aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start,
+aa_state_t aa_dfa_next(const struct aa_dfa *dfa, aa_state_t state,
+ const char c);
+aa_state_t aa_dfa_outofband_transition(const struct aa_dfa *dfa,
+ aa_state_t state);
+aa_state_t aa_dfa_match_until(const struct aa_dfa *dfa, aa_state_t start,
const char *str, const char **retpos);
-aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start,
+aa_state_t aa_dfa_matchn_until(const struct aa_dfa *dfa, aa_state_t start,
const char *str, int n, const char **retpos);
void aa_dfa_free_kref(struct kref *kref);
@@ -152,7 +154,7 @@ struct match_workbuf N = { \
.len = 0, \
}
-aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start,
+aa_state_t aa_dfa_leftmatch(const struct aa_dfa *dfa, aa_state_t start,
const char *str, unsigned int *count);
/**
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
index 0d0b0ce42723..375341929cb6 100644
--- a/security/apparmor/include/net.h
+++ b/security/apparmor/include/net.h
@@ -86,7 +86,7 @@ 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,
+ aa_state_t state, u32 request, const 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,
@@ -102,13 +102,14 @@ int aa_af_perm(const struct cred *subj_cred, struct aa_label *label,
static inline int aa_profile_af_sk_perm(struct aa_profile *profile,
struct apparmor_audit_data *ad,
u32 request,
- struct sock *sk)
+ const struct sock *sk)
{
return aa_profile_af_perm(profile, ad, request, sk->sk_family,
sk->sk_type, sk->sk_protocol);
}
-int aa_sk_perm(const char *op, u32 request, struct sock *sk);
-
+int aa_sk_perm(const char *op, u32 request, const struct sock *sk);
+int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label,
+ const char *op, u32 request, const struct sock *sk);
int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request,
struct file *file);
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
index 8bb915d48dc7..250812a77e11 100644
--- a/security/apparmor/include/path.h
+++ b/security/apparmor/include/path.h
@@ -11,6 +11,9 @@
#ifndef __AA_PATH_H
#define __AA_PATH_H
+#include <linux/path.h>
+#include <linux/types.h>
+
enum path_flags {
PATH_IS_DIR = 0x1, /* path is a directory */
PATH_SOCK_COND = 0x2,
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h
index 37a3781b99a0..ee25eb8e78e4 100644
--- a/security/apparmor/include/perms.h
+++ b/security/apparmor/include/perms.h
@@ -96,8 +96,8 @@ struct aa_perms {
#define AA_INDEX_NONE 0
#define ALL_PERMS_MASK 0xffffffff
-extern struct aa_perms nullperms;
-extern struct aa_perms allperms;
+extern const struct aa_perms nullperms;
+extern const struct aa_perms allperms;
/**
* aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
@@ -105,7 +105,7 @@ extern struct aa_perms allperms;
* @addend: perms struct to add to @accum
*/
static inline void aa_perms_accum_raw(struct aa_perms *accum,
- struct aa_perms *addend)
+ const struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~addend->deny;
@@ -132,7 +132,7 @@ static inline void aa_perms_accum_raw(struct aa_perms *accum,
* @addend: perms struct to add to @accum
*/
static inline void aa_perms_accum(struct aa_perms *accum,
- struct aa_perms *addend)
+ const struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~accum->deny;
@@ -206,14 +206,15 @@ void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names,
u32 mask);
void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
u32 chrsmask, const char * const *names, u32 namesmask);
-void aa_apply_modes_to_perms(struct aa_profile *profile,
+void aa_apply_modes_to_perms(const struct aa_profile *profile,
struct aa_perms *perms);
-void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend);
-void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend);
-void aa_profile_match_label(struct aa_profile *profile,
+void aa_perms_accum(struct aa_perms *accum, const struct aa_perms *addend);
+void aa_perms_accum_raw(struct aa_perms *accum, const struct aa_perms *addend);
+void aa_profile_match_label(const struct aa_profile *profile,
struct aa_ruleset *rules, struct aa_label *label,
int type, u32 request, struct aa_perms *perms);
-int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms,
u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *));
#endif /* __AA_PERM_H */
+
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index 3895f8774a3f..0dcbf8cf1029 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -26,7 +26,6 @@
#include "file.h"
#include "lib.h"
#include "label.h"
-#include "net.h"
#include "perms.h"
#include "resource.h"
@@ -37,7 +36,6 @@ extern int unprivileged_userns_apparmor_policy;
extern int aa_unprivileged_unconfined_restricted;
extern const char *const aa_profile_mode_names[];
-#define APPARMOR_MODE_NAMES_MAX_INDEX 4
#define PROFILE_MODE(_profile, _mode) \
((aa_g_profile_mode == (_mode)) || \
@@ -76,6 +74,7 @@ enum profile_mode {
APPARMOR_KILL, /* kill task on access violation */
APPARMOR_UNCONFINED, /* profile set to unconfined */
APPARMOR_USER, /* modified complain mode to userspace */
+ PROFILE_MODE_NAMES_COUNT /* Must be last entry */
};
@@ -295,6 +294,9 @@ struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy,
gfp_t gfp);
struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,
gfp_t gfp);
+struct aa_profile *__aa_new_learning_profile(struct aa_profile *parent,
+ bool hat, const char *base,
+ gfp_t gfp);
struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp);
void aa_free_profile(struct aa_profile *profile);
@@ -305,7 +307,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n);
ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label,
- u32 mask, struct aa_loaddata *udata);
+ u32 mask, struct aa_loaddata *udata,
+ char *compressed_profile, size_t compressed_size);
ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label,
char *name, size_t size);
void __aa_profile_list_release(struct list_head *head);
@@ -351,10 +354,14 @@ static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules)
/* fallback and check v7/8 if v9 is NOT mediated */
if (!state)
state = RULE_MEDIATES(rules, AA_CLASS_NET);
-
return state;
}
+static inline aa_state_t RULE_MEDIATES_UNIX(struct aa_ruleset *rules)
+{
+ return RULE_MEDIATES_v9NET(rules);
+}
+
void aa_compute_profile_mediates(struct aa_profile *profile);
static inline bool profile_mediates(struct aa_profile *profile,
@@ -429,7 +436,7 @@ static inline void aa_put_profile(struct aa_profile *p)
kref_put(&p->label.count.count, aa_label_kref);
}
-static inline int AUDIT_MODE(struct aa_profile *profile)
+static inline int AUDIT_MODE(const struct aa_profile *profile)
{
if (aa_g_audit != AUDIT_NORMAL)
return aa_g_audit;
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
index 4ea9b6479a3e..c01f6885dbe3 100644
--- a/security/apparmor/include/policy_unpack.h
+++ b/security/apparmor/include/policy_unpack.h
@@ -16,6 +16,7 @@
#include <linux/dcache.h>
#include <linux/workqueue.h>
+#include "lib.h"
struct aa_load_ent {
struct list_head list;
@@ -128,7 +129,8 @@ struct aa_loaddata {
char *data;
};
-int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
+int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns,
+ char *compressed_data, size_t compressed_size);
/**
* aa_get_i_loaddata - get a reference count from a counted data reference
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h
index 03dbfdb2f2c0..56acd1bdb634 100644
--- a/security/apparmor/include/procattr.h
+++ b/security/apparmor/include/procattr.h
@@ -11,6 +11,8 @@
#ifndef __AA_PROCATTR_H
#define __AA_PROCATTR_H
+#include "label.h"
+
int aa_getprocattr(struct aa_label *label, char **string, bool newline);
int aa_setprocattr_changehat(char *args, size_t size, int flags);
diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h
index b1aaaf60fa8b..a8030ed78ff2 100644
--- a/security/apparmor/include/task.h
+++ b/security/apparmor/include/task.h
@@ -10,6 +10,11 @@
#ifndef __AA_TASK_H
#define __AA_TASK_H
+#include <linux/sched.h>
+
+#include "audit.h"
+#include "label.h"
+
static inline struct aa_task_ctx *task_ctx(struct task_struct *task)
{
return task->security + apparmor_blob_sizes.lbs_task;
@@ -21,15 +26,22 @@ static inline struct aa_task_ctx *task_ctx(struct task_struct *task)
* @onexec: profile to transition to on next exec (MAY BE NULL)
* @previous: profile the task may return to (MAY BE NULL)
* @token: magic value the task must know for returning to @previous_profile
+ * @label_replacement_tw: for aa_schedule_stale_label_replacement()
+ * @label_replacement_pending: is @label_replacement_tw pending?
+ *
+ * When changing this, check if aa_dup_task_ctx() needs to be updated.
*/
struct aa_task_ctx {
struct aa_label *nnp;
struct aa_label *onexec;
struct aa_label *previous;
u64 token;
+ struct callback_head label_replacement_tw;
+ bool label_replacement_pending;
};
int aa_replace_current_label(struct aa_label *label);
+void aa_schedule_stale_label_replacement(void);
void aa_set_current_onexec(struct aa_label *label, bool stack);
int aa_set_current_hat(struct aa_label *label, u64 token);
int aa_restore_previous_label(u64 cookie);
@@ -56,10 +68,10 @@ static inline void aa_free_task_ctx(struct aa_task_ctx *ctx)
static inline void aa_dup_task_ctx(struct aa_task_ctx *new,
const struct aa_task_ctx *old)
{
- *new = *old;
- aa_get_label(new->nnp);
- aa_get_label(new->previous);
- aa_get_label(new->onexec);
+ new->nnp = aa_get_label(old->nnp);
+ new->onexec = aa_get_label(old->onexec);
+ new->previous = aa_get_label(old->previous);
+ new->token = old->token;
}
/**
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
index 3fd384d8c41a..82742a471055 100644
--- a/security/apparmor/label.c
+++ b/security/apparmor/label.c
@@ -165,7 +165,8 @@ static int profile_cmp(struct aa_profile *a, struct aa_profile *b)
* ==0 if @a == @b
* >0 if @a > @b
*/
-static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn)
+static int vec_cmp(struct aa_profile * const *a, int an,
+ struct aa_profile * const *b, int bn)
{
int i;
@@ -473,7 +474,7 @@ fail:
* ==0 if a == b
* >0 if a > b
*/
-static int label_cmp(struct aa_label *a, struct aa_label *b)
+static int label_cmp(const struct aa_label *a, const struct aa_label *b)
{
AA_BUG(!b);
@@ -484,7 +485,7 @@ static int label_cmp(struct aa_label *a, struct aa_label *b)
}
/* helper fn for label_for_each_confined */
-int aa_label_next_confined(struct aa_label *label, int i)
+int aa_label_next_confined(const struct aa_label *label, int i)
{
AA_BUG(!label);
AA_BUG(i < 0);
@@ -507,8 +508,8 @@ int aa_label_next_confined(struct aa_label *label, int i)
* else NULL if @sub is a subset of @set
*/
struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
- struct aa_label *set,
- struct aa_label *sub)
+ const struct aa_label *set,
+ const struct aa_label *sub)
{
AA_BUG(!set);
AA_BUG(!I);
@@ -544,7 +545,7 @@ struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
* Returns: true if @sub is subset of @set
* else false
*/
-bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub)
+bool aa_label_is_subset(const struct aa_label *set, const struct aa_label *sub)
{
struct label_it i = { };
@@ -571,7 +572,8 @@ bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub)
* Returns: true if @sub is special_subset of @set
* else false
*/
-bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub)
+bool aa_label_is_unconfined_subset(const struct aa_label *set,
+ const struct aa_label *sub)
{
struct label_it i = { };
struct aa_profile *p;
@@ -796,6 +798,44 @@ bool aa_label_remove(struct aa_label *label)
return res;
}
+enum ls_lock_class {
+ AA_LS_LOCK_FIRST,
+ AA_LS_LOCK_SECOND,
+};
+
+#define write_lock_irqsave_nested(L, F, SC) write_lock_irqsave(L, F)
+
+static void ns_ls_double_lock(struct aa_ns *ns1, struct aa_ns *ns2,
+ unsigned long *flags)
+{
+ if (likely(ns1 == ns2)) {
+ write_lock_irqsave(&ns1->labels.lock, *flags);
+ return;
+ }
+
+ /* ordered by namespace hierarchy (walked in nesting order in
+ * labels_update. If at the same level by address order
+ */
+ if ((ns1->level > ns2->level) ||
+ (ns1->level == ns2->level && ns1 > ns2))
+ swap(ns1, ns2);
+
+ write_lock_irqsave_nested(&ns1->labels.lock, *flags, AA_LS_LOCK_FIRST);
+ write_lock_nested(&ns2->labels.lock, AA_LS_LOCK_SECOND);
+}
+
+static void ns_ls_double_unlock(struct aa_ns *ns1, struct aa_ns *ns2,
+ unsigned long flags)
+{
+ if (likely(ns1 == ns2)) {
+ write_unlock_irqrestore(&ns1->labels.lock, flags);
+ return;
+ }
+ /* order doesn't matter on unlock, except flags restore must be last */
+ write_unlock(&ns2->labels.lock);
+ write_unlock_irqrestore(&ns1->labels.lock, flags);
+}
+
/**
* aa_label_replace - replace a label @old with a new version @new
* @old: label to replace
@@ -803,36 +843,34 @@ bool aa_label_remove(struct aa_label *label)
*
* Returns: true if @old was in tree and replaced
* else @old was not in tree, and @new was not inserted
+ *
+ * replacement can involve two different labelsets so has to be
+ * handled very careful, as a double lock may be required.
*/
bool aa_label_replace(struct aa_label *old, struct aa_label *new)
{
+ struct aa_ns *ons = labels_ns(old);
+ struct aa_ns *nns = labels_ns(new);
unsigned long flags;
bool res;
- if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) {
- write_lock_irqsave(&labels_set(old)->lock, flags);
+ ns_ls_double_lock(ons, nns, &flags);
+ if (ons == nns && name_is_shared(old, new)) {
if (old->proxy != new->proxy)
__proxy_share(old, new);
else
__aa_proxy_redirect(old, new);
res = __label_replace(old, new);
- write_unlock_irqrestore(&labels_set(old)->lock, flags);
} else {
struct aa_label *l;
- struct aa_labelset *ls = labels_set(old);
- write_lock_irqsave(&ls->lock, flags);
+ /* will redirect old proxy to new */
res = __label_remove(old, new);
- if (labels_ns(old) != labels_ns(new)) {
- write_unlock_irqrestore(&ls->lock, flags);
- ls = labels_set(new);
- write_lock_irqsave(&ls->lock, flags);
- }
- l = __label_insert(ls, new, true);
+ l = __label_insert(&nns->labels, new, true);
res = (l == new);
- write_unlock_irqrestore(&ls->lock, flags);
aa_put_label(l);
}
+ ns_ls_double_unlock(ons, nns, flags);
return res;
}
@@ -955,8 +993,8 @@ struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label)
* else null if no more profiles
*/
struct aa_profile *aa_label_next_in_merge(struct label_it *I,
- struct aa_label *a,
- struct aa_label *b)
+ const struct aa_label *a,
+ const struct aa_label *b)
{
AA_BUG(!a);
AA_BUG(!b);
@@ -1254,9 +1292,9 @@ out:
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
-static inline aa_state_t match_component(struct aa_profile *profile,
+static inline aa_state_t match_component(const struct aa_profile *profile,
struct aa_ruleset *rules,
- struct aa_profile *tp,
+ const struct aa_profile *tp,
aa_state_t state)
{
const char *ns_name;
@@ -1288,7 +1326,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_compound_match(struct aa_profile *profile,
+static int label_compound_match(const struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label,
aa_state_t state, bool inview, u32 request,
@@ -1344,7 +1382,7 @@ fail:
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_components_match(struct aa_profile *profile,
+static int label_components_match(const struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label, aa_state_t start,
bool inview, u32 request,
@@ -1403,7 +1441,7 @@ fail:
*
* 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,
+int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules,
struct aa_label *label, aa_state_t state, bool inview,
u32 request, struct aa_perms *perms)
{
@@ -1743,10 +1781,7 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
str = (char *) label->hname;
len = strlen(str);
}
- if (audit_string_contains_control(str, len))
- audit_log_n_hex(ab, str, len);
- else
- audit_log_n_string(ab, str, len);
+ audit_log_n_untrustedstring(ab, str, len);
kfree(name);
}
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index e41ff57798b2..73b88bde40bb 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -20,8 +20,8 @@
#include "include/perms.h"
#include "include/policy.h"
-struct aa_perms nullperms;
-struct aa_perms allperms = { .allow = ALL_PERMS_MASK,
+const struct aa_perms nullperms;
+const struct aa_perms allperms = { .allow = ALL_PERMS_MASK,
.quiet = ALL_PERMS_MASK,
.hide = ALL_PERMS_MASK };
@@ -30,7 +30,7 @@ struct val_table_ent {
int value;
};
-static struct val_table_ent debug_values_table[] = {
+static const struct val_table_ent debug_values_table[] = {
{ "N", DEBUG_NONE },
{ "none", DEBUG_NONE },
{ "n", DEBUG_NONE },
@@ -49,10 +49,11 @@ static struct val_table_ent debug_values_table[] = {
{ NULL, 0 }
};
-static struct val_table_ent *val_table_find_ent(struct val_table_ent *table,
- const char *name, size_t len)
+static const struct val_table_ent *
+val_table_find_ent(const struct val_table_ent *table,
+ const char *name, size_t len)
{
- struct val_table_ent *entry;
+ const struct val_table_ent *entry;
for (entry = table; entry->str != NULL; entry++) {
if (strncmp(entry->str, name, len) == 0 &&
@@ -64,7 +65,7 @@ static struct val_table_ent *val_table_find_ent(struct val_table_ent *table,
int aa_parse_debug_params(const char *str)
{
- struct val_table_ent *ent;
+ const struct val_table_ent *ent;
const char *next;
int val = 0;
@@ -360,8 +361,16 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
*
* TODO: split into profile and ns based flags for when accumulating perms
*/
-void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
+void aa_apply_modes_to_perms(const struct aa_profile *profile,
+ struct aa_perms *perms)
{
+ if (KILL_MODE(profile))
+ perms->kill = ~perms->allow;
+ else if (COMPLAIN_MODE(profile))
+ perms->complain |= ~(perms->allow | perms->deny);
+ else if (USER_MODE(profile))
+ perms->prompt |= ~(perms->allow | perms->deny);
+
switch (AUDIT_MODE(profile)) {
case AUDIT_ALL:
perms->audit = ALL_PERMS_MASK;
@@ -373,19 +382,15 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
perms->audit = 0;
fallthrough;
case AUDIT_QUIET_DENIED:
- perms->quiet = ALL_PERMS_MASK;
+ perms->quiet |= ~perms->allow;
+ break;
+ case AUDIT_QUIET_ALLOWED:
+ perms->quiet |= perms->complain | perms->allow;
break;
}
-
- if (KILL_MODE(profile))
- perms->kill = ALL_PERMS_MASK;
- else if (COMPLAIN_MODE(profile))
- perms->complain = ALL_PERMS_MASK;
- else if (USER_MODE(profile))
- perms->prompt = ALL_PERMS_MASK;
}
-void aa_profile_match_label(struct aa_profile *profile,
+void aa_profile_match_label(const struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label,
int type, u32 request, struct aa_perms *perms)
@@ -417,11 +422,11 @@ void aa_profile_match_label(struct aa_profile *profile,
* error code will indicate whether there was an explicit deny
* with a positive value.
*/
-int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms,
u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *))
{
- int type, error;
+ int error;
u32 denied = request & (~perms->allow | perms->deny);
if (likely(!denied)) {
@@ -430,18 +435,10 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
if (!request || !ad)
return 0;
- type = AUDIT_APPARMOR_AUDIT;
error = 0;
} else {
error = -EACCES;
- if (denied & perms->kill)
- type = AUDIT_APPARMOR_KILL;
- else if (denied == (denied & perms->complain))
- type = AUDIT_APPARMOR_ALLOWED;
- else
- type = AUDIT_APPARMOR_DENIED;
-
if (denied == (denied & perms->hide))
error = -ENOENT;
@@ -450,6 +447,8 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
return error;
}
+ int type = aa_select_audit_type(denied, perms);
+
if (ad) {
ad->subj_label = &profile->label;
ad->request = request;
@@ -482,6 +481,8 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix,
char *hname;
size_t hname_sz;
+ INIT_LIST_HEAD(&policy->list);
+ INIT_LIST_HEAD(&policy->profiles);
hname_sz = (prefix ? strlen(prefix) + 2 : 0) + strlen(name) + 1;
/* freed by policy_free */
hname = aa_str_alloc(hname_sz, gfp);
@@ -494,8 +495,6 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix,
policy->hname = hname;
/* base.name is a substring of fqname */
policy->name = basename(policy->hname);
- INIT_LIST_HEAD(&policy->list);
- INIT_LIST_HEAD(&policy->profiles);
return true;
}
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 88d12e89d115..d502ad0ac26f 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -28,6 +28,7 @@
#include <uapi/linux/lsm.h>
#include "include/af_unix.h"
+#include "include/af_inet.h"
#include "include/apparmor.h"
#include "include/apparmorfs.h"
#include "include/audit.h"
@@ -367,15 +368,16 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_
{
struct aa_label *label;
int error = 0;
+ bool needput;
if (!path_mediated_fs(old_dentry))
return 0;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (!unconfined(label))
error = aa_path_link(current_cred(), label, old_dentry, new_dir,
new_dentry);
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -386,13 +388,14 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d
{
struct aa_label *label;
int error = 0;
+ bool needput;
if (!path_mediated_fs(old_dentry))
return 0;
if ((flags & RENAME_EXCHANGE) && !path_mediated_fs(new_dentry))
return 0;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (!unconfined(label)) {
struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt);
vfsuid_t vfsuid;
@@ -438,7 +441,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d
AA_MAY_CREATE, &cond);
}
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -505,11 +508,12 @@ static int apparmor_file_open(struct file *file)
static int apparmor_file_alloc_security(struct file *file)
{
struct aa_file_ctx *ctx = file_ctx(file);
- struct aa_label *label = begin_current_label_crit_section();
+ bool needput;
+ struct aa_label *label = begin_current_label_crit_section(&needput);
spin_lock_init(&ctx->lock);
rcu_assign_pointer(ctx->label, aa_get_label(label));
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return 0;
}
@@ -524,11 +528,12 @@ static void apparmor_file_free_security(struct file *file)
static int common_file_perm(const char *op, struct file *file, u32 mask)
{
struct aa_label *label;
+ bool needput;
int error = 0;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
error = aa_file_perm(op, current_cred(), label, file, mask, false);
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -849,6 +854,7 @@ static int do_setattr(u64 attr, void *value, size_t size)
char *command, *largs = NULL, *args = value;
size_t arg_size;
int error;
+ bool needput;
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE,
OP_SETPROCATTR);
@@ -906,7 +912,7 @@ out:
return error;
fail:
- ad.subj_label = begin_current_label_crit_section();
+ ad.subj_label = begin_current_label_crit_section(&needput);
if (attr == LSM_ATTR_CURRENT)
ad.info = "current";
else if (attr == LSM_ATTR_EXEC)
@@ -915,7 +921,7 @@ fail:
ad.info = "invalid";
ad.error = error = -EINVAL;
aa_audit_msg(AUDIT_APPARMOR_DENIED, &ad, NULL);
- end_current_label_crit_section(ad.subj_label);
+ end_current_label_crit_section(ad.subj_label, needput);
goto out;
}
@@ -1046,18 +1052,19 @@ static int apparmor_userns_create(const struct cred *cred)
struct aa_label *label;
struct aa_profile *profile;
int error = 0;
+ bool needput;
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_NS,
OP_USERNS_CREATE);
ad.subj_cred = current_cred();
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (!unconfined(label)) {
error = fn_for_each(label, profile,
aa_profile_ns_perm(profile, &ad,
AA_USERNS_CREATE));
}
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -1251,23 +1258,32 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern)
{
struct aa_label *label;
int error = 0;
+ bool needput;
AA_BUG(in_interrupt());
if (kern)
return 0;
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (!unconfined(label)) {
- if (family == PF_UNIX)
+ switch (family) {
+ case PF_UNIX:
error = aa_unix_create_perm(label, family, type,
protocol);
- else
+ break;
+ case PF_INET:
+ case PF_INET6:
+ error = aa_inet_create_perm(label, family, type,
+ protocol);
+ break;
+ default:
error = aa_af_perm(current_cred(), label, OP_CREATE,
AA_MAY_CREATE, family, type,
protocol);
+ }
}
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -1314,9 +1330,10 @@ static int apparmor_socket_socketpair(struct socket *socka,
struct aa_sk_ctx *a_ctx = aa_sock(socka->sk);
struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk);
struct aa_label *label;
+ bool needput;
/* socks not live yet - initial values set in sk_alloc */
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
if (rcu_access_pointer(a_ctx->label) != label) {
AA_BUG("a_ctx != label");
aa_put_label(rcu_dereference_protected(a_ctx->label, true));
@@ -1332,7 +1349,7 @@ static int apparmor_socket_socketpair(struct socket *socka,
/* unix socket pairs by-pass unix_stream_connect */
unix_connect_peers(a_ctx, b_ctx);
}
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return 0;
}
@@ -1359,8 +1376,13 @@ static int apparmor_socket_bind(struct socket *sock,
AA_BUG(!address);
AA_BUG(in_interrupt());
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return aa_unix_bind_perm(sock, address, addrlen);
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_bind_perm(sock, address, addrlen);
+ }
return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk);
}
@@ -1373,8 +1395,13 @@ static int apparmor_socket_connect(struct socket *sock,
AA_BUG(in_interrupt());
/* PF_UNIX goes through unix_stream_connect && unix_may_send */
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return 0;
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_connect_perm(sock, address, addrlen);
+ }
return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk);
}
@@ -1384,8 +1411,13 @@ static int apparmor_socket_listen(struct socket *sock, int backlog)
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return aa_unix_listen_perm(sock, backlog);
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_listen_perm(sock, backlog);
+ }
return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk);
}
@@ -1400,8 +1432,13 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
AA_BUG(!newsock);
AA_BUG(in_interrupt());
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return aa_unix_accept_perm(sock, newsock);
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_accept_perm(sock, newsock);
+ }
return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk);
}
@@ -1414,8 +1451,14 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
AA_BUG(in_interrupt());
/* PF_UNIX goes through unix_may_send */
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return 0;
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_msg_perm(op, request, sock, msg, size);
+ }
+
return aa_sk_perm(op, request, sock->sk);
}
@@ -1434,7 +1477,8 @@ static int apparmor_socket_sendmsg(struct socket *sock,
(sk_is_tcp(sock->sk) ||
(sk_is_inet(sock->sk) && sock->sk->sk_type == SOCK_STREAM &&
sock->sk->sk_protocol == IPPROTO_MPTCP)))
- error = aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk);
+ error = aa_sock_msg_perm(OP_CONNECT, AA_MAY_CONNECT, sock,
+ msg, size);
return error;
}
@@ -1452,8 +1496,13 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock)
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return aa_unix_sock_perm(op, request, sock);
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_sock_perm(op, request, sock);
+ }
return aa_sk_perm(op, request, sock->sk);
}
@@ -1475,8 +1524,13 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock,
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return aa_unix_opt_perm(op, request, sock, level, optname);
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_opt_perm(op, request, sock, level, optname);
+ }
return aa_sk_perm(op, request, sock->sk);
}
@@ -1499,42 +1553,6 @@ static int apparmor_socket_shutdown(struct socket *sock, int how)
return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock);
}
-#ifdef CONFIG_NETWORK_SECMARK
-/**
- * apparmor_socket_sock_rcv_skb - check perms before associating skb to sk
- * @sk: sk to associate @skb with
- * @skb: skb to check for perms
- *
- * Note: can not sleep may be called with locks held
- *
- * don't want protocol specific in __skb_recv_datagram()
- * to deny an incoming connection socket_sock_rcv_skb()
- */
-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;
-
- /*
- * If reach here before socket_post_create hook is called, in which
- * case label is null, drop the packet.
- */
- if (!rcu_access_pointer(ctx->label))
- return -EACCES;
-
- 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_get_label(struct sock *sk)
{
struct aa_sk_ctx *ctx = aa_sock(sk);
@@ -1563,13 +1581,14 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,
int slen, error = 0;
struct aa_label *label;
struct aa_label *peer;
+ bool needput;
peer = sk_peer_get_label(sock->sk);
if (IS_ERR(peer)) {
error = PTR_ERR(peer);
goto done;
}
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
slen = aa_label_asxprint(&name, labels_ns(label), peer,
FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
FLAG_HIDDEN_UNCONFINED, GFP_KERNEL);
@@ -1590,7 +1609,7 @@ done_len:
error = -EFAULT;
done_put:
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
aa_put_label(peer);
done:
kfree(name);
@@ -1634,6 +1653,39 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent)
}
#ifdef CONFIG_NETWORK_SECMARK
+/**
+ * apparmor_socket_sock_rcv_skb - check perms before associating skb to sk
+ * @sk: sk to associate @skb with
+ * @skb: skb to check for perms
+ *
+ * Note: can not sleep may be called with locks held
+ *
+ * don't want protocol specific in __skb_recv_datagram()
+ * to deny an incoming connection socket_sock_rcv_skb()
+ */
+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;
+
+ /*
+ * If reach here before socket_post_create hook is called, in which
+ * case label is null, drop the packet.
+ */
+ if (!rcu_access_pointer(ctx->label))
+ return -EACCES;
+
+ rcu_read_lock();
+ error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG,
+ AA_MAY_RECEIVE, skb->secmark, sk);
+ rcu_read_unlock();
+
+ return error;
+}
+
static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb,
struct request_sock *req)
{
@@ -1652,6 +1704,51 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb
}
#endif
+#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK)
+static unsigned int apparmor_ip_postroute(void *priv,
+ struct sk_buff *skb,
+ const struct nf_hook_state *state)
+{
+ struct aa_sk_ctx *ctx;
+ struct sock *sk;
+ int error;
+
+ if (!skb->secmark)
+ return NF_ACCEPT;
+
+ sk = skb_to_full_sk(skb);
+ if (sk == NULL)
+ return NF_ACCEPT;
+
+ ctx = aa_sock(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);
+}
+
+static const struct nf_hook_ops apparmor_nf_ops[] = {
+ {
+ .hook = apparmor_ip_postroute,
+ .pf = NFPROTO_IPV4,
+ .hooknum = NF_INET_POST_ROUTING,
+ .priority = NF_IP_PRI_SELINUX_FIRST,
+ },
+#if IS_ENABLED(CONFIG_IPV6)
+ {
+ .hook = apparmor_ip_postroute,
+ .pf = NFPROTO_IPV6,
+ .hooknum = NF_INET_POST_ROUTING,
+ .priority = NF_IP6_PRI_SELINUX_FIRST,
+ },
+#endif
+};
+#endif
+
/*
* The cred blob is a pointer to, not an instance of, an aa_label.
*/
@@ -2093,7 +2190,7 @@ static int param_set_audit(const char *val, const struct kernel_param *kp)
if (apparmor_initialized && !aa_current_policy_admin_capable(NULL))
return -EPERM;
- i = match_string(audit_mode_names, AUDIT_MAX_INDEX, val);
+ i = match_string(audit_mode_names, AUDIT_MODE_NAMES_COUNT, val);
if (i < 0)
return -EINVAL;
@@ -2121,7 +2218,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
if (apparmor_initialized && !aa_current_policy_admin_capable(NULL))
return -EPERM;
- i = match_string(aa_profile_mode_names, APPARMOR_MODE_NAMES_MAX_INDEX,
+ i = match_string(aa_profile_mode_names, PROFILE_MODE_NAMES_COUNT,
val);
if (i < 0)
return -EINVAL;
@@ -2369,51 +2466,8 @@ static inline int apparmor_init_sysctl(void)
}
#endif /* CONFIG_SYSCTL */
-#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK)
-static unsigned int apparmor_ip_postroute(void *priv,
- struct sk_buff *skb,
- const struct nf_hook_state *state)
-{
- struct aa_sk_ctx *ctx;
- struct sock *sk;
- int error;
-
- if (!skb->secmark)
- return NF_ACCEPT;
-
- sk = skb_to_full_sk(skb);
- if (sk == NULL)
- return NF_ACCEPT;
-
- ctx = aa_sock(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);
-
-}
-
-static const struct nf_hook_ops apparmor_nf_ops[] = {
- {
- .hook = apparmor_ip_postroute,
- .pf = NFPROTO_IPV4,
- .hooknum = NF_INET_POST_ROUTING,
- .priority = NF_IP_PRI_SELINUX_FIRST,
- },
-#if IS_ENABLED(CONFIG_IPV6)
- {
- .hook = apparmor_ip_postroute,
- .pf = NFPROTO_IPV6,
- .hooknum = NF_INET_POST_ROUTING,
- .priority = NF_IP6_PRI_SELINUX_FIRST,
- },
-#endif
-};
+#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK)
static int __net_init apparmor_nf_register(struct net *net)
{
return nf_register_net_hooks(net, apparmor_nf_ops,
@@ -2446,16 +2500,16 @@ static int __init apparmor_nf_ip_init(void)
}
#endif
-static char nulldfa_src[] __aligned(8) = {
+static const char nulldfa_src[] __aligned(8) = {
#include "nulldfa.in"
};
-static struct aa_dfa *nulldfa;
+static struct aa_dfa *nulldfa __ro_after_init;
static char stacksplitdfa_src[] __aligned(8) = {
#include "stacksplitdfa.in"
};
-struct aa_dfa *stacksplitdfa;
-struct aa_policydb *nullpdb;
+struct aa_dfa *stacksplitdfa __ro_after_init;
+struct aa_policydb *nullpdb __ro_after_init;
static int __init aa_setup_dfa_engine(void)
{
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
index d43ff34d705c..7713484f6a36 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -31,7 +31,7 @@
*
* NOTE: must be freed by kvfree (not kfree)
*/
-static struct table_header *unpack_table(char *blob, size_t bsize)
+static struct table_header *unpack_table(const char *blob, size_t bsize)
{
struct table_header *table = ERR_PTR(-EPROTO);
struct table_header th;
@@ -151,7 +151,7 @@ out:
*
* Returns: %0 else error code on failure to verify
*/
-static int verify_dfa(struct aa_dfa *dfa)
+static int verify_dfa(const struct aa_dfa *dfa)
{
size_t i, state_count, trans_count;
int error = -EPROTO;
@@ -312,11 +312,11 @@ static struct table_header *remap_data16_to_data32(struct table_header *old)
*
* Returns: an unpacked dfa ready for matching or ERR_PTR on failure
*/
-struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
+struct aa_dfa *aa_dfa_unpack(const void *blob, size_t size, int flags)
{
int hsize;
int error = -ENOMEM;
- char *data = blob;
+ const char *data = blob;
struct table_header *table = NULL;
struct aa_dfa *dfa = kzalloc_obj(struct aa_dfa);
if (!dfa)
@@ -467,7 +467,7 @@ do { \
*
* Returns: final state reached after input is consumed
*/
-aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start,
+aa_state_t aa_dfa_match_len(const struct aa_dfa *dfa, aa_state_t start,
const char *str, int len)
{
u32 *def = DEFAULT_TABLE(dfa);
@@ -512,7 +512,8 @@ aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start,
*
* Returns: final state reached after input is consumed
*/
-aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str)
+aa_state_t aa_dfa_match(const struct aa_dfa *dfa, aa_state_t start,
+ const char *str)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -555,7 +556,8 @@ aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str)
*
* Returns: state reach after input @c
*/
-aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c)
+aa_state_t aa_dfa_next(const struct aa_dfa *dfa, aa_state_t state,
+ const char c)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -573,7 +575,8 @@ aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c)
return state;
}
-aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state)
+aa_state_t aa_dfa_outofband_transition(const struct aa_dfa *dfa,
+ aa_state_t state)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -603,8 +606,8 @@ aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state)
*
* Returns: final state reached after input is consumed
*/
-aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start,
- const char *str, const char **retpos)
+aa_state_t aa_dfa_match_until(const struct aa_dfa *dfa, aa_state_t start,
+ const char *str, const char **retpos)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -664,8 +667,8 @@ aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start,
*
* Returns: final state reached after input is consumed
*/
-aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start,
- const char *str, int n, const char **retpos)
+aa_state_t aa_dfa_matchn_until(const struct aa_dfa *dfa, aa_state_t start,
+ const char *str, int n, const char **retpos)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -740,9 +743,9 @@ static bool is_loop(struct match_workbuf *wb, aa_state_t state,
return false;
}
-static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start,
- const char *str, struct match_workbuf *wb,
- unsigned int *count)
+static aa_state_t leftmatch_fb(const struct aa_dfa *dfa, aa_state_t start,
+ const char *str, struct match_workbuf *wb,
+ unsigned int *count)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@@ -821,7 +824,7 @@ out:
*
* Returns: final state reached after input is consumed
*/
-aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start,
+aa_state_t aa_dfa_leftmatch(const struct aa_dfa *dfa, aa_state_t start,
const char *str, unsigned int *count)
{
DEFINE_MATCH_WB(wb);
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
index 2f5d918832c1..4ed7b9136beb 100644
--- a/security/apparmor/mount.c
+++ b/security/apparmor/mount.c
@@ -24,6 +24,10 @@
#include "include/policy.h"
+#define DEFINE_AUDIT_MOUNT(NAME, OP, CRED) \
+ DEFINE_AUDIT_DATA(NAME, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, OP);\
+ NAME.subj_cred = (CRED)
+
static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags)
{
if (flags & MS_RDONLY)
@@ -114,77 +118,6 @@ static void audit_cb(struct audit_buffer *ab, void *va)
}
/**
- * audit_mount - handle the auditing of mount operations
- * @subj_cred: cred of the subject
- * @profile: the profile being enforced (NOT NULL)
- * @op: operation being mediated (NOT NULL)
- * @name: name of object being mediated (MAYBE NULL)
- * @src_name: src_name of object being mediated (MAYBE_NULL)
- * @type: type of filesystem (MAYBE_NULL)
- * @trans: name of trans (MAYBE NULL)
- * @flags: filesystem independent mount flags
- * @data: filesystem mount flags
- * @request: permissions requested
- * @perms: the permissions computed for the request (NOT NULL)
- * @info: extra information message (MAYBE NULL)
- * @error: 0 if operation allowed else failure error code
- *
- * Returns: %0 or error on failure
- */
-static int audit_mount(const struct cred *subj_cred,
- struct aa_profile *profile, const char *op,
- const char *name, const char *src_name,
- const char *type, const char *trans,
- unsigned long flags, const void *data, u32 request,
- struct aa_perms *perms, const char *info, int error)
-{
- int audit_type = AUDIT_APPARMOR_AUTO;
- DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, op);
-
- if (likely(!error)) {
- u32 mask = perms->audit;
-
- if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
- mask = 0xffff;
-
- /* mask off perms that are not being force audited */
- request &= mask;
-
- if (likely(!request))
- return 0;
- audit_type = AUDIT_APPARMOR_AUDIT;
- } else {
- /* only report permissions that were denied */
- request = request & ~perms->allow;
-
- if (request & perms->kill)
- audit_type = AUDIT_APPARMOR_KILL;
-
- /* quiet known rejects, assumes quiet and kill do not overlap */
- if ((request & perms->quiet) &&
- AUDIT_MODE(profile) != AUDIT_NOQUIET &&
- AUDIT_MODE(profile) != AUDIT_ALL)
- request &= ~perms->quiet;
-
- if (!request)
- return error;
- }
-
- ad.subj_cred = subj_cred;
- ad.name = name;
- ad.mnt.src_name = src_name;
- ad.mnt.type = type;
- ad.mnt.trans = trans;
- ad.mnt.flags = flags;
- if (data && (perms->audit & AA_AUDIT_DATA))
- ad.mnt.data = data;
- ad.info = info;
- ad.error = error;
-
- return aa_audit(audit_type, profile, &ad, audit_cb);
-}
-
-/**
* match_mnt_flags - Do an ordered match on mount flags
* @dfa: dfa to match against
* @state: state to start in
@@ -196,7 +129,7 @@ static int audit_mount(const struct cred *subj_cred,
*
* Returns: next state after flags match
*/
-static aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t state,
+static aa_state_t match_mnt_flags(const struct aa_dfa *dfa, aa_state_t state,
unsigned long flags)
{
unsigned int i;
@@ -278,7 +211,7 @@ static int do_match_mnt(struct aa_policydb *policy, aa_state_t start,
}
-static int path_flags(struct aa_profile *profile, const struct path *path)
+static int path_flags(const struct aa_profile *profile, const struct path *path)
{
AA_BUG(!profile);
AA_BUG(!path);
@@ -289,7 +222,6 @@ static int path_flags(struct aa_profile *profile, const struct path *path)
/**
* match_mnt_path_str - handle path matching for mount
- * @subj_cred: cred of confined subject
* @profile: the confining profile
* @mntpath: for the mntpnt (NOT NULL)
* @buffer: buffer to be used to lookup mntpath
@@ -299,18 +231,19 @@ static int path_flags(struct aa_profile *profile, const struct path *path)
* @data: fs mount data (MAYBE NULL)
* @binary: whether @data is binary
* @devinfo: error str if (IS_ERR(@devname))
+ * @ad: apparmor audit data structure
*
* Returns: 0 on success else error
*/
-static int match_mnt_path_str(const struct cred *subj_cred,
- struct aa_profile *profile,
+static int match_mnt_path_str(struct aa_profile *profile,
const struct path *mntpath, char *buffer,
const char *devname, const char *type,
unsigned long flags, void *data, bool binary,
- const char *devinfo)
+ const char *devinfo,
+ struct apparmor_audit_data *ad)
{
struct aa_perms perms = { };
- const char *mntpnt = NULL, *info = NULL;
+ const char *mntpnt = NULL;
struct aa_ruleset *rules = profile->label.rules[0];
int pos, error;
@@ -321,36 +254,37 @@ static int match_mnt_path_str(const struct cred *subj_cred,
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
+ ad->mnt.type = type;
+
error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer,
- &mntpnt, &info, profile->disconnected);
+ &mntpnt, &ad->info, profile->disconnected);
if (error)
- goto audit;
+ return aa_audit_perm_error(&profile->label, AA_MAY_MOUNT,
+ error, ad, audit_cb);
+ ad->name = mntpnt;
+
if (IS_ERR(devname)) {
error = PTR_ERR(devname);
- devname = NULL;
- info = devinfo;
- goto audit;
+ ad->info = devinfo;
+ return aa_audit_perm_error(&profile->label, AA_MAY_MOUNT,
+ error, ad, audit_cb);
}
+ ad->mnt.src_name = devname;
- error = -EACCES;
pos = do_match_mnt(rules->policy,
rules->policy->start[AA_CLASS_MOUNT],
mntpnt, devname, type, flags, data, binary, &perms);
- if (pos) {
- info = mnt_info_table[pos];
- goto audit;
- }
- error = 0;
+ if (pos)
+ ad->info = mnt_info_table[pos];
-audit:
- return audit_mount(subj_cred, profile, OP_MOUNT, mntpnt, devname,
- type, NULL,
- flags, data, AA_MAY_MOUNT, &perms, info, error);
+ aa_apply_modes_to_perms(profile, &perms);
+ if (data && !binary && (perms.audit & AA_AUDIT_DATA))
+ ad->mnt.data = data;
+ return aa_check_perms(profile, &perms, AA_MAY_MOUNT, ad, audit_cb);
}
/**
* match_mnt - handle path matching for mount
- * @subj_cred: cred of the subject
* @profile: the confining profile
* @path: for the mntpnt (NOT NULL)
* @buffer: buffer to be used to lookup mntpath
@@ -360,14 +294,14 @@ audit:
* @flags: mount flags to match
* @data: fs mount data (MAYBE NULL)
* @binary: whether @data is binary
+ * @ad: apparmor audit data structure
*
* Returns: 0 on success else error
*/
-static int match_mnt(const struct cred *subj_cred,
- struct aa_profile *profile, const struct path *path,
+static int match_mnt(struct aa_profile *profile, const struct path *path,
char *buffer, const struct path *devpath, char *devbuffer,
const char *type, unsigned long flags, void *data,
- bool binary)
+ bool binary, struct apparmor_audit_data *ad)
{
const char *devname = NULL, *info = NULL;
struct aa_ruleset *rules = profile->label.rules[0];
@@ -387,8 +321,8 @@ static int match_mnt(const struct cred *subj_cred,
devname = ERR_PTR(error);
}
- return match_mnt_path_str(subj_cred, profile, path, buffer, devname,
- type, flags, data, binary, info);
+ return match_mnt_path_str(profile, path, buffer, devname,
+ type, flags, data, binary, info, ad);
}
int aa_remount(const struct cred *subj_cred,
@@ -399,6 +333,8 @@ int aa_remount(const struct cred *subj_cred,
char *buffer = NULL;
bool binary;
int error;
+ DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred);
+ ad.mnt.flags = flags;
AA_BUG(!label);
AA_BUG(!path);
@@ -408,10 +344,9 @@ int aa_remount(const struct cred *subj_cred,
buffer = aa_get_buffer(false);
if (!buffer)
return -ENOMEM;
- error = fn_for_each_confined(label, profile,
- match_mnt(subj_cred, profile, path, buffer, NULL,
- NULL, NULL,
- flags, data, binary));
+ error = fn_for_each(label, profile,
+ match_mnt(profile, path, buffer, NULL, NULL, NULL,
+ flags, data, binary, &ad));
aa_put_buffer(buffer);
return error;
@@ -425,6 +360,7 @@ int aa_bind_mount(const struct cred *subj_cred,
char *buffer = NULL, *old_buffer = NULL;
struct path old_path;
int error;
+ DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred);
AA_BUG(!label);
AA_BUG(!path);
@@ -433,10 +369,12 @@ int aa_bind_mount(const struct cred *subj_cred,
return -EINVAL;
flags &= MS_REC | MS_BIND;
+ ad.mnt.flags = flags;
error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
if (error)
- return error;
+ return aa_audit_perm_error(label, AA_MAY_MOUNT, error, &ad,
+ audit_cb);
buffer = aa_get_buffer(false);
old_buffer = aa_get_buffer(false);
@@ -444,9 +382,9 @@ int aa_bind_mount(const struct cred *subj_cred,
if (!buffer || !old_buffer)
goto out;
- error = fn_for_each_confined(label, profile,
- match_mnt(subj_cred, profile, path, buffer, &old_path,
- old_buffer, NULL, flags, NULL, false));
+ error = fn_for_each(label, profile,
+ match_mnt(profile, path, buffer, &old_path,
+ old_buffer, NULL, flags, NULL, false, &ad));
out:
aa_put_buffer(buffer);
aa_put_buffer(old_buffer);
@@ -462,6 +400,7 @@ int aa_mount_change_type(const struct cred *subj_cred,
struct aa_profile *profile;
char *buffer = NULL;
int error;
+ DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred);
AA_BUG(!label);
AA_BUG(!path);
@@ -469,14 +408,14 @@ int aa_mount_change_type(const struct cred *subj_cred,
/* These are the flags allowed by do_change_type() */
flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE |
MS_UNBINDABLE);
+ ad.mnt.flags = flags;
buffer = aa_get_buffer(false);
if (!buffer)
return -ENOMEM;
- error = fn_for_each_confined(label, profile,
- match_mnt(subj_cred, profile, path, buffer, NULL,
- NULL, NULL,
- flags, NULL, false));
+ error = fn_for_each(label, profile,
+ match_mnt(profile, path, buffer, NULL, NULL, NULL,
+ flags, NULL, false, &ad));
aa_put_buffer(buffer);
return error;
@@ -489,6 +428,8 @@ int aa_move_mount(const struct cred *subj_cred,
struct aa_profile *profile;
char *to_buffer = NULL, *from_buffer = NULL;
int error;
+ DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred);
+ ad.mnt.flags = MS_MOVE;
AA_BUG(!label);
AA_BUG(!from_path);
@@ -503,10 +444,10 @@ int aa_move_mount(const struct cred *subj_cred,
if (!our_mnt(from_path->mnt))
/* moving a mount detached from the namespace */
from_path = NULL;
- error = fn_for_each_confined(label, profile,
- match_mnt(subj_cred, profile, to_path, to_buffer,
- from_path, from_buffer,
- NULL, MS_MOVE, NULL, false));
+ error = fn_for_each(label, profile,
+ match_mnt(profile, to_path, to_buffer, from_path,
+ from_buffer, NULL, MS_MOVE, NULL, false,
+ &ad));
out:
aa_put_buffer(to_buffer);
aa_put_buffer(from_buffer);
@@ -542,6 +483,8 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label,
int error;
int requires_dev = 0;
struct path tmp_path, *dev_path = NULL;
+ DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred);
+ ad.mnt.flags = flags;
AA_BUG(!label);
AA_BUG(!path);
@@ -578,15 +521,15 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label,
error = -ENOMEM;
goto out;
}
- error = fn_for_each_confined(label, profile,
- match_mnt(subj_cred, profile, path, buffer,
- dev_path, dev_buffer,
- type, flags, data, binary));
+ error = fn_for_each(label, profile,
+ match_mnt(profile, path, buffer, dev_path,
+ dev_buffer, type, flags, data,
+ binary, &ad));
} else {
- error = fn_for_each_confined(label, profile,
- match_mnt_path_str(subj_cred, profile, path,
- buffer, dev_name,
- type, flags, data, binary, NULL));
+ error = fn_for_each(label, profile,
+ match_mnt_path_str(profile, path, buffer,
+ dev_name, type, flags, data,
+ binary, NULL, &ad));
}
out:
@@ -598,13 +541,12 @@ out:
return error;
}
-static int profile_umount(const struct cred *subj_cred,
- struct aa_profile *profile, const struct path *path,
- char *buffer)
+static int profile_umount(struct aa_profile *profile, const struct path *path,
+ char *buffer, struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms perms = { };
- const char *name = NULL, *info = NULL;
+ const char *name = NULL;
aa_state_t state;
int error;
@@ -614,22 +556,23 @@ static int profile_umount(const struct cred *subj_cred,
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
+ /* TODO: lift path_name, need to separate profile path_flags from
+ * the lookup
+ */
error = aa_path_name(path, path_flags(profile, path), buffer, &name,
- &info, profile->disconnected);
+ &ad->info, profile->disconnected);
if (error)
- goto audit;
+ return aa_audit_perm_error(&profile->label, AA_MAY_UMOUNT,
+ error, ad, audit_cb);
+ ad->name = name;
state = aa_dfa_match(rules->policy->dfa,
rules->policy->start[AA_CLASS_MOUNT],
name);
perms = *aa_lookup_perms(rules->policy, state);
- if (AA_MAY_UMOUNT & ~perms.allow)
- error = -EACCES;
-audit:
- return audit_mount(subj_cred, profile, OP_UMOUNT, name, NULL, NULL,
- NULL, 0, NULL,
- AA_MAY_UMOUNT, &perms, info, error);
+ aa_apply_modes_to_perms(profile, &perms);
+ return aa_check_perms(profile, &perms, AA_MAY_UMOUNT, ad, audit_cb);
}
int aa_umount(const struct cred *subj_cred, struct aa_label *label,
@@ -639,6 +582,7 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label,
char *buffer = NULL;
int error;
struct path path = { .mnt = mnt, .dentry = mnt->mnt_root };
+ DEFINE_AUDIT_MOUNT(ad, OP_UMOUNT, subj_cred);
AA_BUG(!label);
AA_BUG(!mnt);
@@ -647,8 +591,8 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label,
if (!buffer)
return -ENOMEM;
- error = fn_for_each_confined(label, profile,
- profile_umount(subj_cred, profile, &path, buffer));
+ error = fn_for_each(label, profile,
+ profile_umount(profile, &path, buffer, &ad));
aa_put_buffer(buffer);
return error;
@@ -658,16 +602,15 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label,
*
* Returns: label for transition or ERR_PTR. Does not return NULL
*/
-static struct aa_label *build_pivotroot(const struct cred *subj_cred,
- struct aa_profile *profile,
+static struct aa_label *build_pivotroot(struct aa_profile *profile,
const struct path *new_path,
char *new_buffer,
const struct path *old_path,
- char *old_buffer)
+ char *old_buffer,
+ struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
- const char *old_name, *new_name = NULL, *info = NULL;
- const char *trans_name = NULL;
+ const char *old_name, *new_name = NULL;
struct aa_perms perms = { };
aa_state_t state;
int error;
@@ -681,36 +624,40 @@ static struct aa_label *build_pivotroot(const struct cred *subj_cred,
return aa_get_newest_label(&profile->label);
error = aa_path_name(old_path, path_flags(profile, old_path),
- old_buffer, &old_name, &info,
+ old_buffer, &old_name, &ad->info,
profile->disconnected);
if (error)
- goto audit;
+ goto err;
+ ad->mnt.src_name = old_name;
error = aa_path_name(new_path, path_flags(profile, new_path),
- new_buffer, &new_name, &info,
+ new_buffer, &new_name, &ad->info,
profile->disconnected);
if (error)
- goto audit;
+ goto err;
+ ad->name = new_name;
- error = -EACCES;
state = aa_dfa_match(rules->policy->dfa,
rules->policy->start[AA_CLASS_MOUNT],
new_name);
state = aa_dfa_null_transition(rules->policy->dfa, state);
state = aa_dfa_match(rules->policy->dfa, state, old_name);
perms = *aa_lookup_perms(rules->policy, state);
+ /* todo: allow pivotroot to specify a transition other than profile */
+ ad->mnt.trans = profile->label.hname;
- if (AA_MAY_PIVOTROOT & perms.allow)
- error = 0;
+ aa_apply_modes_to_perms(profile, &perms);
+ error = aa_check_perms(profile, &perms, AA_MAY_PIVOTROOT, ad, audit_cb);
-audit:
- error = audit_mount(subj_cred, profile, OP_PIVOTROOT, new_name,
- old_name,
- NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT,
- &perms, info, error);
+out:
if (error)
return ERR_PTR(error);
return aa_get_newest_label(&profile->label);
+
+err:
+ error = aa_audit_perm_error(&profile->label, AA_MAY_PIVOTROOT, error,
+ ad, audit_cb);
+ goto out;
}
int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label,
@@ -719,8 +666,9 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label,
{
struct aa_profile *profile;
struct aa_label *target = NULL;
- char *old_buffer = NULL, *new_buffer = NULL, *info = NULL;
+ char *old_buffer = NULL, *new_buffer = NULL;
int error;
+ DEFINE_AUDIT_MOUNT(ad, OP_PIVOTROOT, subj_cred);
AA_BUG(!label);
AA_BUG(!old_path);
@@ -732,9 +680,8 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label,
if (!old_buffer || !new_buffer)
goto out;
target = fn_label_build(label, profile, GFP_KERNEL,
- build_pivotroot(subj_cred, profile, new_path,
- new_buffer,
- old_path, old_buffer));
+ build_pivotroot(profile, new_path, new_buffer,
+ old_path, old_buffer, &ad));
AA_BUG(!target);
if (!IS_ERR(target)) {
error = aa_replace_current_label(target);
@@ -742,7 +689,7 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label,
goto fail;
aa_put_label(target);
} else
- /* already audited error */
+ /* already audited error in build_pivotroot */
error = PTR_ERR(target);
out:
aa_put_buffer(old_buffer);
@@ -751,14 +698,12 @@ out:
return error;
fail:
- /* TODO: add back in auditing of new_name and old_name */
- error = fn_for_each(label, profile,
- audit_mount(subj_cred, profile, OP_PIVOTROOT,
- NULL /*new_name */,
- NULL /* old_name */,
- NULL, NULL,
- 0, target->hname, AA_MAY_PIVOTROOT, &nullperms, info,
- error));
+ /* TODO: add back in auditing of new_name and old_name,
+ * needs lifting of name lookup out of profile cb
+ */
+ ad.mnt.trans = target->hname;
+ error = aa_audit_perm_error(label, AA_MAY_PIVOTROOT, error, &ad,
+ audit_cb);
aa_put_label(target);
goto out;
}
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
index cf590dd08540..a333e6aff926 100644
--- a/security/apparmor/net.c
+++ b/security/apparmor/net.c
@@ -9,6 +9,7 @@
*/
#include "include/af_unix.h"
+#include "include/af_inet.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
@@ -133,12 +134,12 @@ void audit_net_cb(struct audit_buffer *ab, void *va)
audit_log_format(ab, " protocol=%d", ad->net.protocol);
if (ad->request & NET_PERMS_MASK) {
- audit_log_format(ab, " requested_mask=");
+ audit_log_format(ab, " requested=");
aa_audit_perm_mask(ab, ad->request, NULL, 0,
net_mask_names, NET_PERMS_MASK);
if (ad->denied & NET_PERMS_MASK) {
- audit_log_format(ab, " denied_mask=");
+ audit_log_format(ab, " denied=");
aa_audit_perm_mask(ab, ad->denied, NULL, 0,
net_mask_names, NET_PERMS_MASK);
}
@@ -166,7 +167,7 @@ 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)
+ const struct aa_perms *p, struct apparmor_audit_data *ad)
{
struct aa_perms perms;
@@ -198,7 +199,7 @@ static struct aa_perms *early_match(struct aa_policydb *policy,
return p;
}
-static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state,
+static aa_state_t aa_dfa_match_be16(const struct aa_dfa *dfa, aa_state_t state,
u16 data)
{
__be16 buffer = cpu_to_be16(data);
@@ -261,14 +262,15 @@ int aa_profile_af_perm(struct aa_profile *profile,
AA_BUG(type < 0 || type >= SOCK_MAX);
AA_BUG(profile_unconfined(profile));
- if (profile_unconfined(profile))
- return 0;
state = RULE_MEDIATES_NET(rules);
- if (!state)
- return 0;
- 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);
+ if (state) {
+ 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);
+ } /* else */
+
+ return 0;
}
int aa_af_perm(const struct cred *subj_cred, struct aa_label *label,
@@ -282,10 +284,8 @@ int aa_af_perm(const struct cred *subj_cred, struct aa_label *label,
type, protocol));
}
-static int aa_label_sk_perm(const struct cred *subj_cred,
- struct aa_label *label,
- const char *op, u32 request,
- struct sock *sk)
+int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label,
+ const char *op, u32 request, const struct sock *sk)
{
struct aa_sk_ctx *ctx = aa_sock(sk);
int error = 0;
@@ -299,24 +299,26 @@ static int aa_label_sk_perm(const struct cred *subj_cred,
ad.subj_cred = subj_cred;
error = fn_for_each_confined(label, profile,
- aa_profile_af_sk_perm(profile, &ad, request, sk));
+ aa_profile_af_perm(profile, &ad, request, sk->sk_family,
+ sk->sk_type, sk->sk_protocol));
}
return error;
}
-int aa_sk_perm(const char *op, u32 request, struct sock *sk)
+int aa_sk_perm(const char *op, u32 request, const struct sock *sk)
{
struct aa_label *label;
+ bool needput;
int error;
AA_BUG(!sk);
AA_BUG(in_interrupt());
/* TODO: switch to begin_current_label ???? */
- label = begin_current_label_crit_section();
+ label = begin_current_label_crit_section(&needput);
error = aa_label_sk_perm(current_cred(), label, op, request, sk);
- end_current_label_crit_section(label);
+ end_current_label_crit_section(label, needput);
return error;
}
@@ -333,8 +335,13 @@ int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label,
if (!sock || !sock->sk)
return 0;
- if (sock->sk->sk_family == PF_UNIX)
+ switch (sock->sk->sk_family) {
+ case PF_UNIX:
return aa_unix_file_perm(subj_cred, label, op, request, file);
+ case PF_INET:
+ case PF_INET6:
+ return aa_inet_file_perm(subj_cred, label, op, request, sock);
+ }
return aa_label_sk_perm(subj_cred, label, op, request, sock->sk);
}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 94b4a7e727cc..397f9ff78aeb 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -81,6 +81,7 @@
#include "include/file.h"
#include "include/ipc.h"
#include "include/match.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_ns.h"
@@ -719,7 +720,7 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,
}
/**
- * aa_new_learning_profile - create or find a null-X learning profile
+ * __aa_new_learning_profile - create or find a null-X learning profile
* @parent: profile that caused this profile to be created (NOT NULL)
* @hat: true if the null- learning profile is a hat
* @base: name to base the null profile off of
@@ -736,8 +737,9 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,
*
* Returns: new refcounted profile else NULL on failure
*/
-struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
- const char *base, gfp_t gfp)
+struct aa_profile *__aa_new_learning_profile(struct aa_profile *parent,
+ bool hat, const char *base,
+ gfp_t gfp)
{
struct aa_profile *p, *profile;
const char *bname;
@@ -745,6 +747,7 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
size_t name_sz;
AA_BUG(!parent);
+ AA_BUG(!mutex_is_locked(&parent->ns->lock));
if (base) {
name_sz = strlen(parent->base.hname) + 8 + strlen(base);
@@ -778,7 +781,6 @@ name:
if (hat)
profile->label.flags |= FLAG_HAT;
- mutex_lock_nested(&profile->ns->lock, profile->ns->level);
p = __find_child(&parent->base.profiles, bname);
if (p) {
aa_free_profile(profile);
@@ -786,7 +788,6 @@ name:
} else {
__add_profile(&parent->base.profiles, profile);
}
- mutex_unlock(&profile->ns->lock);
/* refcount released by caller */
out:
@@ -800,6 +801,18 @@ fail:
return NULL;
}
+struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
+ const char *base, gfp_t gfp)
+{
+ struct aa_profile *profile;
+
+ mutex_lock_nested(&parent->ns->lock, parent->ns->level);
+ profile = __aa_new_learning_profile(parent, hat, base, gfp);
+ mutex_unlock(&parent->ns->lock);
+
+ return profile;
+}
+
/**
* replacement_allowed - test to see if replacement is allowed
* @profile: profile to test if it can be replaced (MAYBE NULL)
@@ -1158,6 +1171,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
* @label: label that is attempting to load/replace policy
* @mask: permission mask
* @udata: serialized data stream (NOT NULL)
+ * @compressed_profile: The userspace-provided compressed profile. May be NULL
+ * @compressed_size: If compressed_data is not NULL, the compressed data size
*
* unpack and replace a profile on the profile list and uses of that profile
* by any task creds via invalidating the old version of the profile, which
@@ -1167,7 +1182,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
* Returns: size of data consumed else error code on failure.
*/
ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
- u32 mask, struct aa_loaddata *udata)
+ u32 mask, struct aa_loaddata *udata,
+ char *compressed_profile, size_t compressed_size)
{
const char *ns_name = NULL, *info = NULL;
struct aa_ns *ns = NULL;
@@ -1180,7 +1196,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
aa_get_profile_loaddata(udata);
/* released below */
- error = aa_unpack(udata, &lh, &ns_name);
+ error = aa_unpack(udata, &lh, &ns_name, compressed_profile, compressed_size);
if (error)
goto out;
diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c
index 5fc16d56fbf4..94e4b781d33c 100644
--- a/security/apparmor/policy_compat.c
+++ b/security/apparmor/policy_compat.c
@@ -97,7 +97,7 @@ static u32 map_old_perms(u32 old)
return new;
}
-static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa,
+static void compute_fperms_allow(struct aa_perms *perms, const struct aa_dfa *dfa,
aa_state_t state)
{
perms->allow |= AA_MAY_GETATTR;
@@ -109,7 +109,7 @@ static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa,
perms->allow |= AA_MAY_ONEXEC;
}
-static struct aa_perms compute_fperms_user(struct aa_dfa *dfa,
+static struct aa_perms compute_fperms_user(const struct aa_dfa *dfa,
aa_state_t state)
{
struct aa_perms perms = { };
@@ -124,7 +124,7 @@ static struct aa_perms compute_fperms_user(struct aa_dfa *dfa,
return perms;
}
-static struct aa_perms compute_fperms_other(struct aa_dfa *dfa,
+static struct aa_perms compute_fperms_other(const struct aa_dfa *dfa,
aa_state_t state)
{
struct aa_perms perms = { };
@@ -147,7 +147,7 @@ static struct aa_perms compute_fperms_other(struct aa_dfa *dfa,
*
* Returns: remapped perm table
*/
-static struct aa_perms *compute_fperms(struct aa_dfa *dfa,
+static struct aa_perms *compute_fperms(const struct aa_dfa *dfa,
u32 *size)
{
aa_state_t state;
@@ -171,7 +171,7 @@ static struct aa_perms *compute_fperms(struct aa_dfa *dfa,
return table;
}
-static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch,
+static struct aa_perms *compute_xmatch_perms(const struct aa_dfa *xmatch,
u32 *size)
{
struct aa_perms *perms;
@@ -207,7 +207,7 @@ static u32 map_xbits(u32 x)
((x & 0x7e) << 9);
}
-static struct aa_perms compute_perms_entry(struct aa_dfa *dfa,
+static struct aa_perms compute_perms_entry(const struct aa_dfa *dfa,
aa_state_t state,
u32 version)
{
@@ -246,7 +246,7 @@ static struct aa_perms compute_perms_entry(struct aa_dfa *dfa,
return perms;
}
-static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version,
+static struct aa_perms *compute_perms(const struct aa_dfa *dfa, u32 version,
u32 *size)
{
unsigned int state;
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index d9dcff167c48..f1fc48e72d0e 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -25,6 +25,7 @@
#include "include/crypto.h"
#include "include/file.h"
#include "include/match.h"
+#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
@@ -730,7 +731,7 @@ static bool verify_tags(struct aa_tags_struct *tags, const char **info)
/* count followed by count indexes into hdrs */
u32 cnt = tags->sets.table[i];
- if (i+cnt >= tags->sets.size) {
+ if ((u64)i + cnt >= tags->sets.size) {
AA_DEBUG(DEBUG_UNPACK,
"tagset too large %d+%d > sets.table[%d]",
i, cnt, tags->sets.size);
@@ -1482,7 +1483,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
* @dfa: the dfa to check accept indexes are in range
* @table_size: the permission table size the indexes should be within
*/
-static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size)
+static bool verify_dfa_accept_index(const struct aa_dfa *dfa, int table_size)
{
int i;
for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
@@ -1492,7 +1493,7 @@ static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size)
return true;
}
-static bool verify_perm(struct aa_perms *perm)
+static bool verify_perm(const struct aa_perms *perm)
{
/* TODO: allow option to just force the perms into a valid state */
if (perm->allow & perm->deny)
@@ -1717,6 +1718,8 @@ static int compress_loaddata(struct aa_loaddata *data)
* @udata: user data copied to kmem (NOT NULL)
* @lh: list to place unpacked profiles in a aa_repl_ws
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
+ * @compressed_data: The userspace-provided compressed data. May be NULL
+ * @compressed_size: If compressed_data is not NULL, the compressed data size
*
* Unpack user data and return refcounted allocated profile(s) stored in
* @lh in order of discovery, with the list chain stored in base.list
@@ -1725,12 +1728,12 @@ static int compress_loaddata(struct aa_loaddata *data)
* Returns: profile(s) on @lh else error pointer if fails to unpack
*/
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
- const char **ns)
+ const char **ns, char *compressed_data, size_t compressed_size)
{
struct aa_load_ent *tmp, *ent;
struct aa_profile *profile = NULL;
char *ns_name = NULL;
- int error;
+ int error = 0;
struct aa_ext e = {
.start = udata->data,
.end = udata->data + udata->size,
@@ -1783,10 +1786,23 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
}
if (aa_g_export_binary) {
- error = compress_loaddata(udata);
+ /* Do we have userspace-compressed data? */
+ if (compressed_data) {
+ kvfree(udata->data);
+ udata->data = compressed_data;
+ udata->compressed_size = compressed_size;
+ compressed_data = NULL; /* consumed */
+
+ } else
+ error = compress_loaddata(udata);
+
if (error)
goto fail;
+ } else if (compressed_data) {
+ kvfree(compressed_data);
+ compressed_data = NULL;
}
+
return 0;
fail_profile:
@@ -1794,6 +1810,8 @@ fail_profile:
aa_put_profile(profile);
fail:
+ if (compressed_data)
+ kvfree(compressed_data);
list_for_each_entry_safe(ent, tmp, lh, list) {
list_del_init(&ent->list);
aa_load_ent_free(ent);
diff --git a/security/apparmor/task.c b/security/apparmor/task.c
index b9fb3738124e..e16ff4130bc2 100644
--- a/security/apparmor/task.c
+++ b/security/apparmor/task.c
@@ -14,6 +14,7 @@
#include <linux/gfp.h>
#include <linux/ptrace.h>
+#include <linux/task_work.h>
#include "include/path.h"
#include "include/audit.h"
@@ -89,6 +90,32 @@ int aa_replace_current_label(struct aa_label *label)
return 0;
}
+static void aa_replace_stale_label_tw_func(struct callback_head *tw)
+{
+ struct aa_task_ctx *ctx = task_ctx(current);
+ struct aa_label *label;
+
+ ctx->label_replacement_pending = false;
+ label = aa_current_raw_label();
+ if (!label_is_stale(label))
+ return;
+ label = aa_get_newest_label(label);
+ aa_replace_current_label(label);
+ aa_put_label(label);
+}
+
+/* replace the current task's stale label on syscall return */
+void aa_schedule_stale_label_replacement(void)
+{
+ struct aa_task_ctx *ctx = task_ctx(current);
+
+ if (ctx->label_replacement_pending)
+ return;
+ init_task_work(&ctx->label_replacement_tw, aa_replace_stale_label_tw_func);
+ if (task_work_add(current, &ctx->label_replacement_tw, TWA_RESUME) == 0)
+ ctx->label_replacement_pending = true;
+}
+
/**
* aa_set_current_onexec - set the tasks change_profile to happen onexec
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index b3a9f86809b0..72654cf797cd 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -46,12 +46,16 @@ config IMA_KEXEC
config IMA_MEASURE_PCR_IDX
int
+ prompt "PCR Index for Aggregate" if EXPERT
range 8 14
default 10
help
IMA_MEASURE_PCR_IDX determines the TPM PCR register index
that IMA uses to maintain the integrity aggregate of the
- measurement list. If unsure, use the default 10.
+ measurement list. Most attestation tooling expects PCR 10.
+
+ The default is almost always what you want. Only change this
+ if you know what you are doing.
config IMA_LSM_RULES
bool
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index ced2e131b061..b280488e15fc 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -748,6 +748,8 @@ static int validate_hash_algo(struct dentry *dentry,
return -EACCES;
path = dentry_path(dentry, pathbuf, PATH_MAX);
+ if (IS_ERR(path))
+ path = NULL;
integrity_audit_msg(AUDIT_INTEGRITY_DATA, d_inode(dentry), path,
"set_data", errmsg, -EACCES, 0);
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index f79d07bb63c6..68d9a5e6c232 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -165,7 +165,10 @@ static struct ima_rule_entry dont_measure_rules[] __ro_after_init = {
{.action = DONT_MEASURE, .fsmagic = CGROUP2_SUPER_MAGIC,
.flags = IMA_FSMAGIC},
{.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC},
- {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC}
+ {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC,
+ .flags = IMA_FSMAGIC},
+ {.action = DONT_MEASURE, .fsmagic = CONFIGFS_MAGIC,
+ .flags = IMA_FSMAGIC}
};
static struct ima_rule_entry original_measurement_rules[] __ro_after_init = {
@@ -211,6 +214,8 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = {
{.action = DONT_APPRAISE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC},
{.action = DONT_APPRAISE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC},
+ {.action = DONT_APPRAISE, .fsmagic = CONFIGFS_MAGIC,
+ .flags = IMA_FSMAGIC},
#ifdef CONFIG_IMA_WRITE_POLICY
{.action = APPRAISE, .func = POLICY_CHECK,
.flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index f89f0ca3d4ed..0f1b7e4113c4 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -75,8 +75,7 @@ static struct hlist_head *ima_alloc_replace_htable(void)
struct hlist_head *old_htable, *new_htable;
/* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */
- new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head),
- GFP_KERNEL);
+ new_htable = kzalloc_objs(struct hlist_head, IMA_MEASURE_HTABLE_SIZE);
if (!new_htable)
return ERR_PTR(-ENOMEM);
diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c
index 13513819991e..bf0bf7f36970 100644
--- a/security/keys/trusted-keys/trusted_tpm1.c
+++ b/security/keys/trusted-keys/trusted_tpm1.c
@@ -18,7 +18,6 @@
#include <keys/trusted-type.h>
#include <linux/key-type.h>
#include <linux/tpm.h>
-#include <linux/tpm_command.h>
#include <keys/trusted_tpm.h>
@@ -317,9 +316,8 @@ static int TSS_checkhmac2(unsigned char *buffer,
* For key specific tpm requests, we will generate and send our
* own TPM command packets using the drivers send function.
*/
-static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
+static int trusted_tpm_send(struct tpm_buf *buf)
{
- struct tpm_buf buf;
int rc;
if (!chip)
@@ -329,12 +327,9 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen)
if (rc)
return rc;
- buf.flags = 0;
- buf.length = buflen;
- buf.data = cmd;
- dump_tpm_buf(cmd);
- rc = tpm_transmit_cmd(chip, &buf, 4, "sending data");
- dump_tpm_buf(cmd);
+ dump_tpm_buf(buf->data);
+ rc = tpm_transmit_cmd(chip, buf, 4, "sending data");
+ dump_tpm_buf(buf->data);
if (rc > 0)
/* TPM error */
@@ -380,7 +375,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s,
tpm_buf_append_u32(tb, handle);
tpm_buf_append(tb, ononce, TPM_NONCE_SIZE);
- ret = trusted_tpm_send(tb->data, tb->length);
+ ret = trusted_tpm_send(tb);
if (ret < 0)
return ret;
@@ -404,7 +399,7 @@ static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce)
return -ENODEV;
tpm_buf_reset(tb, TPM_TAG_RQU_COMMAND, TPM_ORD_OIAP);
- ret = trusted_tpm_send(tb->data, tb->length);
+ ret = trusted_tpm_send(tb);
if (ret < 0)
return ret;
@@ -513,7 +508,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype,
tpm_buf_append_u8(tb, cont);
tpm_buf_append(tb, td->pubauth, SHA1_DIGEST_SIZE);
- ret = trusted_tpm_send(tb->data, tb->length);
+ ret = trusted_tpm_send(tb);
if (ret < 0)
goto out;
@@ -604,7 +599,7 @@ static int tpm_unseal(struct tpm_buf *tb,
tpm_buf_append_u8(tb, cont);
tpm_buf_append(tb, authdata2, SHA1_DIGEST_SIZE);
- ret = trusted_tpm_send(tb->data, tb->length);
+ ret = trusted_tpm_send(tb);
if (ret < 0) {
pr_info("authhmac failed (%d)\n", ret);
return ret;
@@ -631,23 +626,23 @@ static int tpm_unseal(struct tpm_buf *tb,
static int key_seal(struct trusted_key_payload *p,
struct trusted_key_options *o)
{
- struct tpm_buf tb;
int ret;
- ret = tpm_buf_init(&tb, 0, 0);
- if (ret)
- return ret;
+ struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+ if (!tb)
+ return -ENOMEM;
+
+ tpm_buf_init(tb, TPM_BUFSIZE);
/* include migratable flag at end of sealed key */
p->key[p->key_len] = p->migratable;
- ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth,
+ ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth,
p->key, p->key_len + 1, p->blob, &p->blob_len,
o->blobauth, o->pcrinfo, o->pcrinfo_len);
if (ret < 0)
pr_info("srkseal failed (%d)\n", ret);
- tpm_buf_destroy(&tb);
return ret;
}
@@ -657,14 +652,15 @@ static int key_seal(struct trusted_key_payload *p,
static int key_unseal(struct trusted_key_payload *p,
struct trusted_key_options *o)
{
- struct tpm_buf tb;
int ret;
- ret = tpm_buf_init(&tb, 0, 0);
- if (ret)
- return ret;
+ struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+ if (!tb)
+ return -ENOMEM;
+
+ tpm_buf_init(tb, TPM_BUFSIZE);
- ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
+ ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len,
o->blobauth, p->key, &p->key_len);
if (ret < 0)
pr_info("srkunseal failed (%d)\n", ret);
@@ -672,7 +668,6 @@ static int key_unseal(struct trusted_key_payload *p,
/* pull migratable flag out of sealed key */
p->migratable = p->key[--p->key_len];
- tpm_buf_destroy(&tb);
return ret;
}
@@ -987,9 +982,9 @@ err_put:
static void trusted_tpm_exit(void)
{
if (chip) {
+ unregister_key_type(&key_type_trusted);
put_device(&chip->dev);
kfree(digests);
- 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 6340823f8b53..67225dd562a9 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -9,7 +9,6 @@
#include <linux/string.h>
#include <linux/err.h>
#include <linux/tpm.h>
-#include <linux/tpm_command.h>
#include <keys/trusted-type.h>
#include <keys/trusted_tpm.h>
@@ -234,7 +233,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
struct trusted_key_options *options)
{
off_t offset = TPM_HEADER_SIZE;
- struct tpm_buf buf, sized;
+ struct tpm_buf *buf __free(kfree) = NULL;
+ struct tpm_buf *sized __free(kfree) = NULL;
int blob_len = 0;
int hash;
u32 flags;
@@ -255,97 +255,100 @@ int tpm2_seal_trusted(struct tpm_chip *chip,
if (rc)
goto out_put;
- rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
- if (rc) {
+ buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+ if (!buf) {
+ rc = -ENOMEM;
tpm2_end_auth_session(chip);
goto out_put;
}
- rc = tpm_buf_init_sized(&sized);
- if (rc) {
- tpm_buf_destroy(&buf);
+ tpm_buf_init(buf, TPM_BUFSIZE);
+ tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE);
+
+ sized = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+ if (!sized) {
+ rc = -ENOMEM;
tpm2_end_auth_session(chip);
goto out_put;
}
- rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+ tpm_buf_init_sized(sized, TPM_BUFSIZE);
+
+ rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
if (rc)
goto out;
- tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT,
+ tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT,
options->keyauth, TPM_DIGEST_SIZE);
/* sensitive */
- tpm_buf_append_u16(&sized, options->blobauth_len);
+ tpm_buf_append_u16(sized, options->blobauth_len);
if (options->blobauth_len)
- tpm_buf_append(&sized, options->blobauth, options->blobauth_len);
+ tpm_buf_append(sized, options->blobauth, options->blobauth_len);
- tpm_buf_append_u16(&sized, payload->key_len);
- tpm_buf_append(&sized, payload->key, payload->key_len);
- tpm_buf_append(&buf, sized.data, sized.length);
+ tpm_buf_append_u16(sized, payload->key_len);
+ tpm_buf_append(sized, payload->key, payload->key_len);
+ tpm_buf_append(buf, sized->data, sized->length);
/* public */
- tpm_buf_reset_sized(&sized);
- tpm_buf_append_u16(&sized, TPM_ALG_KEYEDHASH);
- tpm_buf_append_u16(&sized, hash);
+ tpm_buf_reset_sized(sized);
+ tpm_buf_append_u16(sized, TPM_ALG_KEYEDHASH);
+ tpm_buf_append_u16(sized, hash);
/* key properties */
flags = 0;
flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH;
flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT);
- tpm_buf_append_u32(&sized, flags);
+ tpm_buf_append_u32(sized, flags);
/* policy */
- tpm_buf_append_u16(&sized, options->policydigest_len);
+ tpm_buf_append_u16(sized, options->policydigest_len);
if (options->policydigest_len)
- tpm_buf_append(&sized, options->policydigest, options->policydigest_len);
+ tpm_buf_append(sized, options->policydigest, options->policydigest_len);
/* public parameters */
- tpm_buf_append_u16(&sized, TPM_ALG_NULL);
- tpm_buf_append_u16(&sized, 0);
+ tpm_buf_append_u16(sized, TPM_ALG_NULL);
+ tpm_buf_append_u16(sized, 0);
- tpm_buf_append(&buf, sized.data, sized.length);
+ tpm_buf_append(buf, sized->data, sized->length);
/* outside info */
- tpm_buf_append_u16(&buf, 0);
+ tpm_buf_append_u16(buf, 0);
/* creation PCR */
- tpm_buf_append_u32(&buf, 0);
+ tpm_buf_append_u32(buf, 0);
- if (buf.flags & TPM_BUF_OVERFLOW) {
+ if (buf->flags & TPM_BUF_INVALID) {
rc = -E2BIG;
tpm2_end_auth_session(chip);
goto out;
}
- rc = 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);
+ rc = tpm_transmit_cmd(chip, buf, 4, "sealing data");
+ rc = tpm_buf_check_hmac_response(chip, buf, rc);
if (rc)
goto out;
- blob_len = tpm_buf_read_u32(&buf, &offset);
- if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) {
+ blob_len = tpm_buf_read_u32(buf, &offset);
+ if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) {
rc = -E2BIG;
goto out;
}
- if (buf.length - offset < blob_len) {
+ if (buf->length - offset < blob_len) {
rc = -EFAULT;
goto out;
}
- blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len);
+ 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)
payload->blob_len = blob_len;
@@ -373,7 +376,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
u32 *blob_handle)
{
u8 *blob_ref __free(kfree) = NULL;
- struct tpm_buf buf;
+ struct tpm_buf *buf __free(kfree) = NULL;
unsigned int private_len;
unsigned int public_len;
unsigned int blob_len;
@@ -427,39 +430,38 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
if (rc)
return rc;
- rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
- if (rc) {
+ buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+ if (!buf) {
tpm2_end_auth_session(chip);
- return rc;
+ return -ENOMEM;
}
- rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL);
+ tpm_buf_init(buf, TPM_BUFSIZE);
+ tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
+
+ rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL);
if (rc)
- goto out;
+ return rc;
- tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth,
+ tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth,
TPM_DIGEST_SIZE);
- tpm_buf_append(&buf, blob, blob_len);
+ tpm_buf_append(buf, blob, blob_len);
- if (buf.flags & TPM_BUF_OVERFLOW) {
- rc = -E2BIG;
+ if (buf->flags & TPM_BUF_INVALID) {
tpm2_end_auth_session(chip);
- goto out;
+ return -E2BIG;
}
- rc = tpm_buf_fill_hmac_session(chip, &buf);
+ rc = tpm_buf_fill_hmac_session(chip, buf);
if (rc)
- goto out;
+ return rc;
- rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob");
- rc = tpm_buf_check_hmac_response(chip, &buf, rc);
+ rc = tpm_transmit_cmd(chip, buf, 4, "loading blob");
+ rc = tpm_buf_check_hmac_response(chip, buf, rc);
if (!rc)
*blob_handle = be32_to_cpup(
- (__be32 *) &buf.data[TPM_HEADER_SIZE]);
-
-out:
- tpm_buf_destroy(&buf);
+ (__be32 *)&buf->data[TPM_HEADER_SIZE]);
return tpm_ret_to_err(rc);
}
@@ -482,7 +484,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
u32 blob_handle)
{
struct tpm_header *head;
- struct tpm_buf buf;
+ struct tpm_buf *buf __free(kfree) = NULL;
u16 data_len;
int offset;
u8 *data;
@@ -492,18 +494,21 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
if (rc)
return rc;
- rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
- if (rc) {
+ buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL);
+ if (!buf) {
tpm2_end_auth_session(chip);
- return rc;
+ return -ENOMEM;
}
- rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL);
+ tpm_buf_init(buf, TPM_BUFSIZE);
+ tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL);
+
+ rc = tpm_buf_append_name(chip, buf, blob_handle, NULL);
if (rc)
- goto out;
+ return rc;
if (!options->policyhandle) {
- tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT,
+ tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT,
options->blobauth,
options->blobauth_len);
} else {
@@ -518,39 +523,36 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
* could repeat our actions with the exfiltrated
* password.
*/
- tpm2_buf_append_auth(&buf, options->policyhandle,
+ tpm2_buf_append_auth(buf, options->policyhandle,
NULL /* nonce */, 0, 0,
options->blobauth, options->blobauth_len);
if (tpm2_chip_auth(chip)) {
- tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0);
+ 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)
+ 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);
}
}
- rc = tpm_buf_fill_hmac_session(chip, &buf);
+ rc = tpm_buf_fill_hmac_session(chip, buf);
if (rc)
- goto out;
+ return rc;
- rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing");
- rc = tpm_buf_check_hmac_response(chip, &buf, rc);
+ rc = tpm_transmit_cmd(chip, buf, 6, "unsealing");
+ rc = tpm_buf_check_hmac_response(chip, buf, rc);
if (!rc) {
data_len = be16_to_cpup(
- (__be16 *) &buf.data[TPM_HEADER_SIZE + 4]);
- if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) {
- rc = -EFAULT;
- goto out;
- }
+ (__be16 *)&buf->data[TPM_HEADER_SIZE + 4]);
+ if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE)
+ return -EFAULT;
- if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) {
- rc = -EFAULT;
- goto out;
- }
- data = &buf.data[TPM_HEADER_SIZE + 6];
+ if (tpm_buf_length(buf) < TPM_HEADER_SIZE + 6 + data_len)
+ return -EFAULT;
+ data = &buf->data[TPM_HEADER_SIZE + 6];
if (payload->old_format) {
/* migratable flag is at the end of the key */
@@ -567,8 +569,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip,
}
}
-out:
- tpm_buf_destroy(&buf);
return tpm_ret_to_err(rc);
}
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
index 3f1493402052..7aeac29160e8 100644
--- a/security/landlock/Kconfig
+++ b/security/landlock/Kconfig
@@ -21,6 +21,11 @@ config SECURITY_LANDLOCK
you should also prepend "landlock," to the content of CONFIG_LSM to
enable Landlock at boot time.
+config SECURITY_LANDLOCK_LOG
+ bool
+ depends on SECURITY_LANDLOCK
+ default y if AUDIT || TRACEPOINTS
+
config SECURITY_LANDLOCK_KUNIT_TEST
bool "KUnit tests for Landlock" if !KUNIT_ALL_TESTS
depends on KUNIT=y
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index ffa7646d99f3..2711f4876939 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -8,11 +8,15 @@ landlock-y := \
cred.o \
task.o \
fs.o \
- tsync.o
+ tsync.o \
+ domain.o
landlock-$(CONFIG_INET) += net.o
-landlock-$(CONFIG_AUDIT) += \
+landlock-$(CONFIG_SECURITY_LANDLOCK_LOG) += \
id.o \
- audit.o \
- domain.o
+ log.o
+
+landlock-$(CONFIG_AUDIT) += audit.o
+
+landlock-$(CONFIG_TRACEPOINTS) += trace.o
diff --git a/security/landlock/access.h b/security/landlock/access.h
index d926078bf0a5..bbbb41f41147 100644
--- a/security/landlock/access.h
+++ b/security/landlock/access.h
@@ -19,7 +19,7 @@
/*
* All access rights that are denied by default whether they are handled or not
- * by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
+ * by a ruleset/layer. This must be ORed with all domain->handled_masks[]
* entries when we need to get the absolute handled access masks, see
* landlock_upgrade_handled_access_masks().
*/
@@ -74,13 +74,13 @@ struct layer_mask {
* @access: The unfulfilled access rights for this layer.
*/
access_mask_t access : LANDLOCK_NUM_ACCESS_MAX;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @quiet: Whether we have encountered a rule with the quiet flag for
* this layer. Used to control logging.
*/
access_mask_t quiet : 1;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
} __packed __aligned(sizeof(access_mask_t));
/*
diff --git a/security/landlock/audit.c b/security/landlock/audit.c
index 50536c568526..e02963834e48 100644
--- a/security/landlock/audit.c
+++ b/security/landlock/audit.c
@@ -5,9 +5,9 @@
* Copyright © 2023-2025 Microsoft Corporation
*/
-#include <kunit/test.h>
#include <linux/audit.h>
#include <linux/bitops.h>
+#include <linux/landlock.h>
#include <linux/lsm_audit.h>
#include <linux/pid.h>
#include <uapi/linux/landlock.h>
@@ -18,40 +18,30 @@
#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",
-};
+#include "log.h"
+
+/*
+ * Access-right and scope names are built from the lists shared with the trace
+ * events (see <linux/landlock.h>). The designated initializer places each name
+ * at its bit index, so the lookup stays O(1) and does not depend on the entry
+ * order. log_blockers() adds the "fs."/"net."/"scope." category prefix.
+ */
+#define _LANDLOCK_NAME_ENTRY(mask, name) [BIT_INDEX(mask)] = name
+
+static const char *const fs_access_strings[] = { _LANDLOCK_ACCESS_FS_NAMES };
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",
- [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp",
- [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)] =
- "net.connect_send_udp",
-};
+static const char *const net_access_strings[] = { _LANDLOCK_ACCESS_NET_NAMES };
static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
+static const char *const scope_strings[] = { _LANDLOCK_SCOPE_NAMES };
+
+static_assert(ARRAY_SIZE(scope_strings) == LANDLOCK_NUM_SCOPE);
+
+#undef _LANDLOCK_NAME_ENTRY
+
static __attribute_const__ const char *
get_blocker(const enum landlock_request_type type,
const unsigned long access_bit)
@@ -63,7 +53,7 @@ get_blocker(const enum landlock_request_type type,
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
WARN_ON_ONCE(access_bit != -1);
- return "fs.change_topology";
+ return "change_topology";
case LANDLOCK_REQUEST_FS_ACCESS:
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
@@ -77,32 +67,63 @@ get_blocker(const enum landlock_request_type type,
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
WARN_ON_ONCE(access_bit != -1);
- return "scope.abstract_unix_socket";
+ return scope_strings[BIT_INDEX(
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET)];
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
WARN_ON_ONCE(access_bit != -1);
- return "scope.signal";
+ return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)];
}
WARN_ON_ONCE(1);
return "unknown";
}
+/*
+ * Returns the audit category prefix prepended to the unprefixed blocker name
+ * returned by get_blocker() (filesystem and network access rights,
+ * change_topology, and scopes). The ptrace blocker is standalone and carries
+ * its full name in get_blocker(), so it uses no prefix.
+ */
+static __attribute_const__ const char *
+blocker_prefix(const enum landlock_request_type type)
+{
+ switch (type) {
+ case LANDLOCK_REQUEST_PTRACE:
+ return "";
+
+ case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
+ case LANDLOCK_REQUEST_FS_ACCESS:
+ return "fs.";
+
+ case LANDLOCK_REQUEST_NET_ACCESS:
+ return "net.";
+
+ case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
+ case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ return "scope.";
+ }
+
+ WARN_ON_ONCE(1);
+ return "";
+}
+
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;
+ const char *const prefix = blocker_prefix(type);
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 ? "" : ",",
+ audit_log_format(ab, "%s%s%s", is_first ? "" : ",", prefix,
get_blocker(type, access_bit));
is_first = false;
}
if (is_first)
- audit_log_format(ab, "%s", get_blocker(type, -1));
+ audit_log_format(ab, "%s%s", prefix, get_blocker(type, -1));
}
static void log_domain(struct landlock_hierarchy *const hierarchy)
@@ -137,526 +158,32 @@ static void log_domain(struct landlock_hierarchy *const hierarchy)
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_masks *masks)
-{
- for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
- if (masks->layers[i].access & *access_request) {
- *access_request &= masks->layers[i].access;
- 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_masks masks = {
- .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
- LANDLOCK_ACCESS_FS_READ_DIR,
- .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
- LANDLOCK_ACCESS_FS_READ_DIR,
- .layers[2].access = 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,
- optional_access_t quiet_optional_accesses,
- bool *quiet)
-{
- 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;
- bool should_quiet = false;
-
- /* 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 *
- HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
- (LANDLOCK_MAX_NUM_LAYERS - 1);
- const bool layer_has_quiet =
- !!(quiet_optional_accesses & BIT(access_index));
-
- if (layer > youngest_layer) {
- youngest_layer = layer;
- missing = BIT(access_bit);
- should_quiet = layer_has_quiet;
- } else if (layer == youngest_layer) {
- missing |= BIT(access_bit);
- /*
- * Whether the layer has rules with quiet flag
- * covering the file accessed does not depend on
- * the access, and so the following
- * WARN_ON_ONCE() should not fail.
- */
- WARN_ON_ONCE(should_quiet && !layer_has_quiet);
- should_quiet = layer_has_quiet;
- }
- }
- access_index++;
- }
-
- *access_request = missing;
- *quiet = should_quiet;
- 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;
- optional_access_t quiet_optional_accesses;
- bool quiet;
-
- /* truncate:0 ioctl_dev:2 */
- deny_mask = 0x20;
- quiet_optional_accesses = 0;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- 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, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* layer denying truncate: quiet, ioctl: not quiet */
- quiet_optional_accesses = 0b01;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- 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, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* Reverse order - truncate:2 ioctl_dev:0 */
- deny_mask = 0x02;
- quiet_optional_accesses = 0;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- 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, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* layer denying truncate: quiet, ioctl: not quiet */
- quiet_optional_accesses = 0b01;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- 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, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- /* layer denying truncate: not quiet, ioctl: quiet */
- quiet_optional_accesses = 0b10;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 2,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
- KUNIT_EXPECT_EQ(test, 0,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- 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, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* truncate:15 ioctl_dev:15 */
- deny_mask = 0xff;
- quiet_optional_accesses = 0;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- 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, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access,
- LANDLOCK_ACCESS_FS_TRUNCATE |
- LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, false);
-
- /* Both quiet (same layer so quietness must be the same) */
- quiet_optional_accesses = 0b11;
-
- access = LANDLOCK_ACCESS_FS_TRUNCATE;
- KUNIT_EXPECT_EQ(test, 15,
- get_layer_from_deny_masks(
- &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
- deny_mask, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
- KUNIT_EXPECT_EQ(test, quiet, true);
-
- 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, quiet_optional_accesses, &quiet));
- KUNIT_EXPECT_EQ(test, access,
- LANDLOCK_ACCESS_FS_TRUNCATE |
- LANDLOCK_ACCESS_FS_IOCTL_DEV);
- KUNIT_EXPECT_EQ(test, quiet, true);
-}
-
-#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;
- static_assert(sizeof(request->all_existing_optional_access) ==
- sizeof(u32));
- if (WARN_ON_ONCE(
- request->quiet_optional_accesses >=
- BIT(hweight32(
- request->all_existing_optional_access))))
- return false;
- }
-
- return true;
-}
-
-static access_mask_t
-pick_access_mask_for_request_type(const enum landlock_request_type type,
- const struct access_masks access_masks)
-{
- switch (type) {
- case LANDLOCK_REQUEST_FS_ACCESS:
- return access_masks.fs;
- case LANDLOCK_REQUEST_NET_ACCESS:
- return access_masks.net;
- default:
- WARN_ONCE(1, "Invalid request type %d passed to %s", type,
- __func__);
- return 0;
- }
-}
-
/**
- * landlock_log_denial - Create audit records related to a denial
+ * landlock_audit_denial - Create an audit record for a denied access request
*
- * @subject: The Landlock subject's credential denying an action.
* @request: Detail of the user space request.
+ * @youngest_denied: The youngest hierarchy node that denied the access.
+ * @missing: The set of denied access rights.
+ * @logged: Whether the denial is selected for logging, as computed by
+ * landlock_log_denial() (domain policy and quiet rules).
+ *
+ * Emits the record when audit is enabled and the denial is selected for
+ * logging.
*/
-void landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request)
+void landlock_audit_denial(const struct landlock_request *const request,
+ struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool logged)
{
struct audit_buffer *ab;
- struct landlock_hierarchy *youngest_denied;
- size_t youngest_layer;
- access_mask_t missing;
- bool object_quiet_flag = false, quiet_applicable_to_access = false;
-
- 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);
- object_quiet_flag =
- request->layer_masks->layers[youngest_layer]
- .quiet;
- } else {
- youngest_layer = get_layer_from_deny_masks(
- &missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
- request->deny_masks,
- request->quiet_optional_accesses,
- &object_quiet_flag);
- }
- 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;
- }
-
/*
- * Checks if the object is marked quiet by the layer that denied the
- * request. If it's a different layer that marked it as quiet, but that
- * layer is not the one that denied the request, we should still audit
- * log the denial.
+ * Skips denials the domain's policy or a quiet rule excludes from
+ * logging (folded into @logged by landlock_log_denial()).
*/
- if (object_quiet_flag) {
- /*
- * We now check if the denied requests are all covered by the
- * layer's quiet access bits.
- */
- const access_mask_t quiet_mask =
- pick_access_mask_for_request_type(
- request->type, youngest_denied->quiet_masks);
-
- quiet_applicable_to_access = (quiet_mask & missing) == missing;
- } else {
- /*
- * Either the object is not quiet, or this is a scope request.
- * We check request->type to distinguish between the two cases.
- */
- const access_mask_t quiet_mask =
- youngest_denied->quiet_masks.scope;
-
- switch (request->type) {
- case LANDLOCK_REQUEST_SCOPE_SIGNAL:
- quiet_applicable_to_access =
- !!(quiet_mask & LANDLOCK_SCOPE_SIGNAL);
- break;
- case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
- quiet_applicable_to_access =
- !!(quiet_mask &
- LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
- break;
- /*
- * Leave LANDLOCK_REQUEST_PTRACE and
- * LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they
- * are never quiet.
- */
- default:
- break;
- }
- }
-
- if (quiet_applicable_to_access)
+ if (!logged)
return;
/* Uses consistent allocation flags wrt common_lsm_audit(). */
@@ -675,23 +202,19 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
}
/**
- * landlock_log_drop_domain - Create an audit record on domain deallocation
+ * landlock_audit_free_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().
+ * Called from landlock_log_free_domain().
*/
-void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
+void landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy)
{
struct audit_buffer *ab;
- if (WARN_ON_ONCE(!hierarchy))
- return;
-
if (!audit_enabled)
return;
@@ -712,23 +235,3 @@ void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
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
index 620f8a24291d..14a514065e08 100644
--- a/security/landlock/audit.h
+++ b/security/landlock/audit.h
@@ -8,66 +8,33 @@
#ifndef _SECURITY_LANDLOCK_AUDIT_H
#define _SECURITY_LANDLOCK_AUDIT_H
-#include <linux/audit.h>
-#include <linux/lsm_audit.h>
+#include <linux/types.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_masks *layer_masks;
-
- /* Required fields for requests with deny masks. */
- const access_mask_t all_existing_optional_access;
- deny_masks_t deny_masks;
- optional_access_t quiet_optional_accesses;
-};
+struct landlock_hierarchy;
+struct landlock_request;
#ifdef CONFIG_AUDIT
-void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy);
+void landlock_audit_denial(const struct landlock_request *const request,
+ struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool logged);
-void landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request);
+void landlock_audit_free_domain(
+ const struct landlock_hierarchy *const hierarchy);
#else /* CONFIG_AUDIT */
static inline void
-landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
+landlock_audit_denial(const struct landlock_request *const request,
+ struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool logged)
{
}
static inline void
-landlock_log_denial(const struct landlock_cred_security *const subject,
- const struct landlock_request *const request)
+landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy)
{
}
diff --git a/security/landlock/cred.c b/security/landlock/cred.c
index cc419de75cd6..03449c26247e 100644
--- a/security/landlock/cred.c
+++ b/security/landlock/cred.c
@@ -22,7 +22,7 @@ static void hook_cred_transfer(struct cred *const new,
const struct landlock_cred_security *const old_llcred =
landlock_cred(old);
- landlock_get_ruleset(old_llcred->domain);
+ landlock_get_domain(old_llcred->domain);
*landlock_cred(new) = *old_llcred;
}
@@ -35,13 +35,13 @@ static int hook_cred_prepare(struct cred *const new,
static void hook_cred_free(struct cred *const cred)
{
- struct landlock_ruleset *const dom = landlock_cred(cred)->domain;
+ struct landlock_domain *const dom = landlock_cred(cred)->domain;
if (dom)
- landlock_put_ruleset_deferred(dom);
+ landlock_put_domain_deferred(dom);
}
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
{
@@ -50,16 +50,16 @@ static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
return 0;
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
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
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec),
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
};
__init void landlock_add_cred_hooks(void)
diff --git a/security/landlock/cred.h b/security/landlock/cred.h
index f287c56b5fd4..a5ff9957949a 100644
--- a/security/landlock/cred.h
+++ b/security/landlock/cred.h
@@ -16,6 +16,7 @@
#include <linux/rcupdate.h>
#include "access.h"
+#include "domain.h"
#include "limits.h"
#include "ruleset.h"
#include "setup.h"
@@ -31,11 +32,11 @@
*/
struct landlock_cred_security {
/**
- * @domain: Immutable ruleset enforced on a task.
+ * @domain: Immutable domain enforced on a task.
*/
- struct landlock_ruleset *domain;
+ struct landlock_domain *domain;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @domain_exec: Bitmask identifying the domain layers that were enforced by
* the current task's executed file (i.e. no new execve(2) since
@@ -49,17 +50,17 @@ struct landlock_cred_security {
* not require a current domain.
*/
u8 log_subdomains_off : 1;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
} __packed;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/* 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 */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline struct landlock_cred_security *
landlock_cred(const struct cred *cred)
@@ -70,22 +71,20 @@ landlock_cred(const struct cred *cred)
static inline void landlock_cred_copy(struct landlock_cred_security *dst,
const struct landlock_cred_security *src)
{
- landlock_put_ruleset(dst->domain);
+ landlock_put_domain(dst->domain);
*dst = *src;
- landlock_get_ruleset(src->domain);
+ landlock_get_domain(src->domain);
}
-static inline struct landlock_ruleset *landlock_get_current_domain(void)
+static inline struct landlock_domain *landlock_get_current_domain(void)
{
return landlock_cred(current_cred())->domain;
}
-/*
- * The call needs to come from an RCU read-side critical section.
- */
-static inline const struct landlock_ruleset *
+/* The call needs to come from an RCU read-side critical section. */
+static inline const struct landlock_domain *
landlock_get_task_domain(const struct task_struct *const task)
{
return landlock_cred(__task_cred(task))->domain;
@@ -126,7 +125,7 @@ landlock_get_applicable_subject(const struct cred *const cred,
const union access_masks_all masks_all = {
.masks = masks,
};
- const struct landlock_ruleset *domain;
+ const struct landlock_domain *domain;
ssize_t layer_level;
if (!cred)
@@ -139,7 +138,7 @@ landlock_get_applicable_subject(const struct cred *const cred,
for (layer_level = domain->num_layers - 1; layer_level >= 0;
layer_level--) {
union access_masks_all layer = {
- .masks = domain->access_masks[layer_level],
+ .masks = domain->handled_masks[layer_level],
};
if (layer.all & masks_all.all) {
diff --git a/security/landlock/domain.c b/security/landlock/domain.c
index 9a8355fccd26..93c7104fd6b2 100644
--- a/security/landlock/domain.c
+++ b/security/landlock/domain.c
@@ -5,26 +5,486 @@
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
* Copyright © 2024-2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
*/
#include <kunit/test.h>
#include <linux/bitops.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/cred.h>
+#include <linux/err.h>
#include <linux/file.h>
+#include <linux/lockdep.h>
#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
#include <linux/path.h>
#include <linux/pid.h>
+#include <linux/rbtree.h>
+#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/signal.h>
+#include <linux/slab.h>
#include <linux/uidgid.h>
+#include <linux/workqueue.h>
#include "access.h"
#include "common.h"
#include "domain.h"
#include "id.h"
+#include "limits.h"
+#include "ruleset.h"
-#ifdef CONFIG_AUDIT
+static void build_check_domain(void)
+{
+ const struct landlock_domain domain = {
+ .num_layers = ~0,
+ };
+
+ BUILD_BUG_ON(domain.num_layers < LANDLOCK_MAX_NUM_LAYERS);
+}
+
+static struct landlock_domain *create_domain(const u32 num_layers)
+{
+ struct landlock_domain *new_domain;
+
+ build_check_domain();
+ new_domain = kzalloc_flex(*new_domain, handled_masks, num_layers,
+ GFP_KERNEL_ACCOUNT);
+ if (!new_domain)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&new_domain->usage, 1);
+ new_domain->rules.root_inode = RB_ROOT;
+
+#if IS_ENABLED(CONFIG_INET)
+ new_domain->rules.root_net_port = RB_ROOT;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ new_domain->num_layers = num_layers;
+ return new_domain;
+}
+
+static void free_domain(struct landlock_domain *const domain)
+{
+ might_sleep();
+ landlock_free_rules(&domain->rules);
+ landlock_put_hierarchy(domain->hierarchy);
+ kfree(domain);
+}
+
+void landlock_put_domain(struct landlock_domain *const domain)
+{
+ might_sleep();
+ if (domain && refcount_dec_and_test(&domain->usage))
+ free_domain(domain);
+}
+
+static void free_domain_work(struct work_struct *const work)
+{
+ struct landlock_domain *domain;
+
+ domain = container_of(work, struct landlock_domain, work_free);
+ free_domain(domain);
+}
+
+void landlock_put_domain_deferred(struct landlock_domain *const domain)
+{
+ if (domain && refcount_dec_and_test(&domain->usage)) {
+ INIT_WORK(&domain->work_free, free_domain_work);
+ schedule_work(&domain->work_free);
+ }
+}
+
+/* The returned access has the same lifetime as the domain. */
+static const struct landlock_rule *
+find_rule(const struct landlock_domain *const domain,
+ const struct landlock_id id)
+{
+ const struct rb_root *root;
+ const struct rb_node *node;
+
+ root = landlock_get_rule_root((struct landlock_rules *)&domain->rules,
+ id.type);
+ if (IS_ERR(root))
+ return NULL;
+ node = root->rb_node;
+
+ while (node) {
+ struct landlock_rule *this =
+ rb_entry(node, struct landlock_rule, node);
+
+ if (this->key.data == id.key.data)
+ return this;
+ if (this->key.data < id.key.data)
+ node = node->rb_right;
+ else
+ node = node->rb_left;
+ }
+ return NULL;
+}
+
+/**
+ * landlock_unmask_layers - Remove the access rights in @masks which are
+ * granted by a matching rule
+ *
+ * Looks up the rule matching @id in @domain, then updates the set of
+ * (per-layer) unfulfilled access rights @masks so that all the access rights
+ * granted by that rule are removed (because they are now fulfilled).
+ *
+ * @domain: The Landlock domain to search for a matching rule.
+ * @id: Identifier for the rule target (e.g. inode, port).
+ * @masks: A matrix of unfulfilled access rights for each layer.
+ * @matched_rule: Optional output for the matched rule (for tracing); set to
+ * the matching rule when non-NULL, unchanged otherwise.
+ *
+ * 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_domain *const domain,
+ const struct landlock_id id,
+ struct layer_masks *masks,
+ const struct landlock_rule **matched_rule)
+{
+ const struct landlock_rule *rule;
+
+ if (!masks)
+ return true;
+
+ rule = find_rule(domain, id);
+ if (!rule)
+ return false;
+
+ if (matched_rule)
+ *matched_rule = rule;
+
+ /*
+ * An access is granted if, for each policy layer, at least one rule
+ * encountered on the pathwalk grants the requested access, regardless
+ * of its position in the layer stack. We must then check the remaining
+ * layers for each inode, from the first added layer to the last one.
+ * When there are multiple requested accesses, for each policy layer,
+ * the full set of requested accesses may not be granted by only one
+ * rule, but by the union (binary OR) of multiple rules. For example,
+ * /a/b <execute> + /a <read> grants /a/b <execute + read>.
+ *
+ * This function is called once per matching rule during the pathwalk,
+ * progressively clearing bits in @masks. The overall access decision
+ * is per-layer: access is granted iff masks->layers[l].access == 0 for
+ * all layers l. When two independent mechanisms can each grant access
+ * within a layer (e.g. a path rule OR a scope exception), the
+ * composition must evaluate per-layer: FOR-ALL l (A(l) OR B(l)), not
+ * (FOR-ALL l A(l)) OR (FOR-ALL l B(l)), to prevent bypass when
+ * different layers grant via different mechanisms.
+ */
+ for (size_t i = 0; i < rule->num_layers; i++) {
+ const struct landlock_layer *const layer = &rule->layers[i];
+
+ /* Clear the bits where the layer in the rule grants access. */
+ masks->layers[layer->level - 1].access &= ~layer->access;
+
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ /* Collect rule flags for each layer. */
+ if (layer->flags.quiet)
+ masks->layers[layer->level - 1].quiet = true;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
+ if (masks->layers[i].access)
+ return false;
+ }
+ return true;
+}
+
+typedef access_mask_t
+get_access_mask_t(const struct landlock_domain *const domain,
+ const u16 layer_level);
+
+/**
+ * landlock_init_layer_masks - Initialize layer masks from an 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. Rule flags
+ * are also zeroed.
+ *
+ * @domain: The domain that defines the current restrictions.
+ * @access_request: The requested access rights to check.
+ * @masks: Layer access masks to populate.
+ * @key_type: The key type to switch between access masks of different types.
+ *
+ * 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_domain *const domain,
+ const access_mask_t access_request,
+ struct layer_masks *const masks,
+ const enum landlock_key_type key_type)
+{
+ access_mask_t handled_accesses = 0;
+ get_access_mask_t *get_access_mask;
+
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ get_access_mask = landlock_get_fs_access_mask;
+ break;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ get_access_mask = landlock_get_net_access_mask;
+ break;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ /* An empty access request can happen because of O_WRONLY | O_RDWR. */
+ if (!access_request)
+ return 0;
+
+ for (size_t i = 0; i < domain->num_layers; i++) {
+ const access_mask_t handled = get_access_mask(domain, i);
+
+ masks->layers[i].access = access_request & handled;
+ handled_accesses |= masks->layers[i].access;
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ masks->layers[i].quiet = false;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+ }
+ for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
+ i++) {
+ masks->layers[i].access = 0;
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ masks->layers[i].quiet = false;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+ }
+
+ return handled_accesses;
+}
+
+static int merge_tree(struct landlock_domain *const dst,
+ struct landlock_ruleset *const src,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *src_root;
+ int err = 0;
+
+ might_sleep();
+ lockdep_assert_held(&src->lock);
+
+ src_root = landlock_get_rule_root(&src->rules, key_type);
+ if (IS_ERR(src_root))
+ return PTR_ERR(src_root);
+
+ /* Merges the @src tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
+ node) {
+ struct landlock_layer layers[] = { {
+ .level = dst->num_layers,
+ } };
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ if (WARN_ON_ONCE(walker_rule->num_layers != 1))
+ return -EINVAL;
+
+ if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
+ return -EINVAL;
+
+ layers[0].access = walker_rule->layers[0].access;
+ layers[0].flags = walker_rule->layers[0].flags;
+
+ err = landlock_store_rule(&dst->rules, id, &layers,
+ ARRAY_SIZE(layers));
+ if (err)
+ return err;
+ }
+ return err;
+}
+
+static int merge_ruleset(struct landlock_domain *const dst,
+ struct landlock_ruleset *const src)
+{
+ int err = 0;
+
+ might_sleep();
+ /* Should already be checked by landlock_merge_ruleset() */
+ if (WARN_ON_ONCE(!src))
+ return 0;
+ /* Only merge into a domain. */
+ if (WARN_ON_ONCE(!dst || !dst->hierarchy))
+ return -EINVAL;
+
+ lockdep_assert_held(&src->lock);
+
+ /* Stacks the new layer. */
+ if (WARN_ON_ONCE(dst->num_layers < 1))
+ return -EINVAL;
+
+ dst->handled_masks[dst->num_layers - 1] =
+ landlock_upgrade_handled_access_masks(src->handled_masks);
+
+ /* Merges the @src inode tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
+ if (err)
+ return err;
+
+#if IS_ENABLED(CONFIG_INET)
+ /* Merges the @src network port tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ return err;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ return 0;
+}
+
+static int inherit_tree(struct landlock_domain *const parent,
+ struct landlock_domain *const child,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *parent_root;
+ int err = 0;
+
+ might_sleep();
+
+ parent_root = landlock_get_rule_root(&parent->rules, key_type);
+ if (IS_ERR(parent_root))
+ return PTR_ERR(parent_root);
+
+ /* Copies the @parent inode or network tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
+ parent_root, node) {
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ err = landlock_store_rule(&child->rules, id,
+ &walker_rule->layers,
+ walker_rule->num_layers);
+ if (err)
+ return err;
+ }
+ return err;
+}
+
+static int inherit_ruleset(struct landlock_domain *const parent,
+ struct landlock_domain *const child)
+{
+ int err = 0;
+
+ might_sleep();
+ if (!parent)
+ return 0;
+
+ /* Copies the @parent inode tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
+ if (err)
+ return err;
+
+#if IS_ENABLED(CONFIG_INET)
+ /* Copies the @parent network port tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ return err;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ if (WARN_ON_ONCE(child->num_layers <= parent->num_layers))
+ return -EINVAL;
+
+ /*
+ * Copies the parent layer stack and leaves a space for the new layer.
+ */
+ memcpy(child->handled_masks, parent->handled_masks,
+ flex_array_size(parent, handled_masks, parent->num_layers));
+
+ if (WARN_ON_ONCE(!parent->hierarchy))
+ return -EINVAL;
+
+ landlock_get_hierarchy(parent->hierarchy);
+ child->hierarchy->parent = parent->hierarchy;
+
+ return 0;
+}
+
+/**
+ * landlock_merge_ruleset - Merge a ruleset with a domain
+ *
+ * @parent: Parent domain.
+ * @ruleset: New ruleset to be merged.
+ *
+ * The current task is requesting to be restricted. The subjective credentials
+ * must not be in an overridden state. cf. landlock_init_hierarchy_log().
+ *
+ * The caller must hold @ruleset->lock.
+ *
+ * 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_domain *
+landlock_merge_ruleset(struct landlock_domain *const parent,
+ struct landlock_ruleset *const ruleset)
+{
+ struct landlock_domain *new_dom __free(landlock_put_domain) = NULL;
+ u32 num_layers;
+ int err;
+
+ might_sleep();
+ lockdep_assert_held(&ruleset->lock);
+ if (WARN_ON_ONCE(!ruleset))
+ return ERR_PTR(-EINVAL);
+
+ if (parent) {
+ if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS)
+ return ERR_PTR(-E2BIG);
+ num_layers = parent->num_layers + 1;
+ } else {
+ num_layers = 1;
+ }
+
+ /* Creates a new domain... */
+ new_dom = create_domain(num_layers);
+ if (IS_ERR(new_dom))
+ return new_dom;
+
+ new_dom->hierarchy =
+ kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT);
+ if (!new_dom->hierarchy)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&new_dom->hierarchy->usage, 1);
+
+ /* ...as a child of @parent... */
+ err = inherit_ruleset(parent, new_dom);
+ if (err)
+ return ERR_PTR(err);
+
+ /* ...and including @ruleset. */
+ err = merge_ruleset(new_dom, ruleset);
+ if (err)
+ return ERR_PTR(err);
+
+ err = landlock_init_hierarchy_log(new_dom->hierarchy);
+ if (err)
+ return ERR_PTR(err);
+
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+
+ return no_free_ptr(new_dom);
+}
+
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* get_current_exe - Get the current's executable path, if any
@@ -128,7 +588,13 @@ int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
hierarchy->details = details;
hierarchy->id = landlock_get_id_range(1);
- hierarchy->log_status = LANDLOCK_LOG_PENDING;
+ /*
+ * The hierarchy is born unobservable: landlock_restrict_self() moves it
+ * out of LANDLOCK_LOG_UNCOMMITTED once it has emitted the creation
+ * event, so the matching free_domain event fires for it and not for a
+ * hierarchy whose creation was never observed.
+ */
+ hierarchy->log_status = LANDLOCK_LOG_UNCOMMITTED;
hierarchy->log_same_exec = true;
hierarchy->log_new_exec = false;
atomic64_set(&hierarchy->num_denials, 0);
@@ -306,4 +772,4 @@ kunit_test_suite(test_suite);
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
diff --git a/security/landlock/domain.h b/security/landlock/domain.h
index 2a1660e3dea7..5ce2f91488d5 100644
--- a/security/landlock/domain.h
+++ b/security/landlock/domain.h
@@ -5,11 +5,13 @@
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
* Copyright © 2024-2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
*/
#ifndef _SECURITY_LANDLOCK_DOMAIN_H
#define _SECURITY_LANDLOCK_DOMAIN_H
+#include <linux/cleanup.h>
#include <linux/limits.h>
#include <linux/mm.h>
#include <linux/path.h>
@@ -17,12 +19,28 @@
#include <linux/refcount.h>
#include <linux/sched.h>
#include <linux/slab.h>
+#include <linux/workqueue.h>
#include "access.h"
-#include "audit.h"
+#include "log.h"
+#include "ruleset.h"
enum landlock_log_status {
- LANDLOCK_LOG_PENDING = 0,
+ /*
+ * Hierarchy whose creation event has not been emitted, so it is not yet
+ * observable from user space. A hierarchy is born in this state (the
+ * zero value, so a partially initialized hierarchy defaults to "not
+ * observable") and leaves it when landlock_restrict_self() emits its
+ * creation event, right after the merge and before the thread-sync
+ * wait. No trace free_domain event (and no audit deallocation record)
+ * fires while a hierarchy is in this state, so a hierarchy that never
+ * became observable (e.g. its initialization failed) is freed silently.
+ * A domain aborted by a thread-sync failure already emitted its
+ * creation event, so it is no longer UNCOMMITTED and does fire
+ * free_domain.
+ */
+ LANDLOCK_LOG_UNCOMMITTED = 0,
+ LANDLOCK_LOG_PENDING,
LANDLOCK_LOG_RECORDED,
LANDLOCK_LOG_DISABLED,
};
@@ -81,7 +99,7 @@ struct landlock_hierarchy {
*/
refcount_t usage;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @log_status: Whether this domain should be logged or not. Because
* concurrent log entries may be created at the same time, it is still
@@ -116,10 +134,10 @@ struct landlock_hierarchy {
* logged) if the related object is marked as quiet.
*/
struct access_masks quiet_masks;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
};
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
deny_masks_t
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
@@ -142,7 +160,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
kfree(hierarchy->details);
}
-#else /* CONFIG_AUDIT */
+#else /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline int
landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
@@ -155,7 +173,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
{
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline void
landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -169,11 +187,140 @@ 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_log_free_domain(hierarchy);
landlock_free_hierarchy_details(hierarchy);
hierarchy = hierarchy->parent;
kfree(freeme);
}
}
+/**
+ * struct landlock_domain - Immutable Landlock domain
+ *
+ * A domain is created from a ruleset by landlock_merge_ruleset() and enforced
+ * on a task. Once created, its rules and access masks are immutable. Unlike
+ * &struct landlock_ruleset, a domain has no lock field.
+ */
+struct landlock_domain {
+ /**
+ * @rules: Red-black tree storage for rules.
+ */
+ struct landlock_rules rules;
+ /**
+ * @hierarchy: Enables hierarchy identification even when a parent
+ * domain vanishes. This is needed for the ptrace and scope
+ * restrictions.
+ */
+ struct landlock_hierarchy *hierarchy;
+ union {
+ /**
+ * @work_free: Enables to free a domain within a lockless
+ * section. This is only used by landlock_put_domain_deferred()
+ * when @usage reaches zero. The fields @usage, @num_layers and
+ * @handled_masks are then unused.
+ */
+ struct work_struct work_free;
+ struct {
+ /**
+ * @usage: Number of credentials referencing this
+ * domain.
+ */
+ refcount_t usage;
+ /**
+ * @num_layers: Number of layers that are used in this
+ * domain. This enables to check that all the layers
+ * allow an access request.
+ */
+ u32 num_layers;
+ /**
+ * @handled_masks: Contains the subset of filesystem and
+ * network actions that are restricted by a domain. A
+ * domain saves all layers of merged rulesets in a stack
+ * (FAM), starting from the first layer to the last one.
+ * These layers are used when merging rulesets, for user
+ * space backward compatibility (i.e. future-proof), and
+ * to properly handle merged rulesets without
+ * overlapping access rights. These layers are set once
+ * and never changed for the lifetime of the domain.
+ */
+ struct access_masks handled_masks[];
+ };
+ };
+};
+
+static inline access_mask_t
+landlock_get_fs_access_mask(const struct landlock_domain *const domain,
+ const u16 layer_level)
+{
+ /* Handles all initially denied by default access rights. */
+ return domain->handled_masks[layer_level].fs |
+ _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+}
+
+static inline access_mask_t
+landlock_get_net_access_mask(const struct landlock_domain *const domain,
+ const u16 layer_level)
+{
+ return domain->handled_masks[layer_level].net;
+}
+
+static inline access_mask_t
+landlock_get_scope_mask(const struct landlock_domain *const domain,
+ const u16 layer_level)
+{
+ return domain->handled_masks[layer_level].scope;
+}
+
+/**
+ * landlock_union_access_masks - Return all access rights handled in the
+ * domain
+ *
+ * @domain: Landlock domain
+ *
+ * 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_domain *const domain)
+{
+ union access_masks_all matches = {};
+ size_t layer_level;
+
+ for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+ union access_masks_all layer = {
+ .masks = domain->handled_masks[layer_level],
+ };
+
+ matches.all |= layer.all;
+ }
+
+ return matches.masks;
+}
+
+void landlock_put_domain(struct landlock_domain *const domain);
+void landlock_put_domain_deferred(struct landlock_domain *const domain);
+
+DEFINE_FREE(landlock_put_domain, struct landlock_domain *,
+ if (!IS_ERR_OR_NULL(_T)) landlock_put_domain(_T))
+
+struct landlock_domain *
+landlock_merge_ruleset(struct landlock_domain *const parent,
+ struct landlock_ruleset *const ruleset);
+
+bool landlock_unmask_layers(const struct landlock_domain *const domain,
+ const struct landlock_id id,
+ struct layer_masks *masks,
+ const struct landlock_rule **matched_rule);
+
+access_mask_t
+landlock_init_layer_masks(const struct landlock_domain *const domain,
+ const access_mask_t access_request,
+ struct layer_masks *masks,
+ const enum landlock_key_type key_type);
+
+static inline void landlock_get_domain(struct landlock_domain *const domain)
+{
+ if (domain)
+ refcount_inc(&domain->usage);
+}
+
#endif /* _SECURITY_LANDLOCK_DOMAIN_H */
diff --git a/security/landlock/errata/abi-1.h b/security/landlock/errata/abi-1.h
index 3f099555f059..315ea7e0fe50 100644
--- a/security/landlock/errata/abi-1.h
+++ b/security/landlock/errata/abi-1.h
@@ -22,3 +22,26 @@
* from their original mount points.
*/
LANDLOCK_ERRATUM(3)
+
+/**
+ * DOC: erratum_4
+ *
+ * Erratum 4: Creation of whiteout objects
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This fix changes the access rights required for the creation of whiteout
+ * objects through :manpage:`mknod(2)`, :manpage:`renameat2(2)`, or
+ * :manpage:`link(2)`. Creating whiteout objects is now guarded by
+ * ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of ``LANDLOCK_ACCESS_FS_MAKE_CHAR``.
+ *
+ * Whiteout objects are used in OverlayFS to mark the absence of a file in an
+ * upper file system. Despite being created with ``S_IFCHR``, whiteout objects
+ * do not count as character devices.
+ *
+ * Impact:
+ *
+ * Sandboxed programs that create OverlayFS whiteouts (such as fuse-overlayfs)
+ * now require ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of
+ * ``LANDLOCK_ACCESS_FS_MAKE_CHAR``.
+ */
+LANDLOCK_ERRATUM(4)
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index f7e5e4ef9eac..30aa6ce13590 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -20,6 +20,7 @@
#include <linux/falloc.h>
#include <linux/fs.h>
#include <linux/init.h>
+#include <linux/kdev_t.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/list.h>
@@ -42,16 +43,18 @@
#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 "log.h"
#include "object.h"
#include "ruleset.h"
#include "setup.h"
+#include <trace/events/landlock.h>
+
/* Underlying object management */
static void release_inode(struct landlock_object *const object)
@@ -336,18 +339,34 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
if (!d_is_dir(path->dentry) &&
!access_mask_subset(access_rights, ACCESS_FILE))
return -EINVAL;
- if (WARN_ON_ONCE(ruleset->num_layers != 1))
- return -EINVAL;
/* Transforms relative access rights to absolute ones. */
access_rights |= LANDLOCK_MASK_ACCESS_FS &
- ~landlock_get_fs_access_mask(ruleset, 0);
+ ~(ruleset->handled_masks.fs |
+ _LANDLOCK_ACCESS_FS_INITIALLY_DENIED);
id.key.object = get_inode_object(d_backing_inode(path->dentry));
if (IS_ERR(id.key.object))
return PTR_ERR(id.key.object);
mutex_lock(&ruleset->lock);
err = landlock_insert_rule(ruleset, id, access_rights, flags);
+
+ /*
+ * Emit after the rule insertion succeeds, so every event corresponds to
+ * a rule that is actually in the ruleset. The ruleset lock is still
+ * held for BTF consistency (enforced by lockdep_assert_held in
+ * TP_fast_assign).
+ */
+ if (!err && trace_landlock_add_rule_fs_enabled()) {
+ char *buffer __free(__putname) = __getname();
+ const char *pathname =
+ buffer ? resolve_path_for_trace(path, buffer) :
+ "<no_mem>";
+
+ trace_landlock_add_rule_fs(ruleset, access_rights, path,
+ pathname);
+ }
mutex_unlock(&ruleset->lock);
+
/*
* No need to check for an error because landlock_insert_rule()
* increments the refcount for the new object if needed.
@@ -358,31 +377,55 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
/* Access-control management */
-/*
- * The lifetime of the returned rule is tied to @domain.
+/**
+ * get_inode_id - Look up the Landlock object for a dentry
+ * @dentry: The dentry to look up.
+ * @id: Filled with the inode's Landlock object pointer on success.
*
- * Returns NULL if no rule is found or if @dentry is negative.
+ * Extracts the Landlock object pointer from @dentry's inode security blob and
+ * stores it in @id for use as a rule-tree lookup key.
+ *
+ * When this returns false (negative dentry or no Landlock object), no rule can
+ * match this inode, so landlock_unmask_layers() need not be called. Callers
+ * that gate landlock_unmask_layers() on this function must handle the NULL
+ * masks case independently, since the !masks-returns-true early-return in
+ * landlock_unmask_layers() will not be reached. See the allowed_parent2
+ * initialization in is_access_to_paths_allowed().
+ *
+ * Return: True if a Landlock object exists for @dentry, false otherwise.
*/
-static const struct landlock_rule *
-find_rule(const struct landlock_ruleset *const domain,
- const struct dentry *const dentry)
+static bool get_inode_id(const struct dentry *const dentry,
+ struct landlock_id *id)
{
- const struct landlock_rule *rule;
- const struct inode *inode;
- struct landlock_id id = {
- .type = LANDLOCK_KEY_INODE,
- };
-
/* Ignores nonexistent leafs. */
if (d_is_negative(dentry))
- return NULL;
+ return false;
- inode = d_backing_inode(dentry);
- rcu_read_lock();
- id.key.object = rcu_dereference(landlock_inode(inode)->object);
- rule = landlock_find_rule(domain, id);
- rcu_read_unlock();
- return rule;
+ /*
+ * rcu_access_pointer() is sufficient: the pointer is used only as a
+ * numeric comparison key for rule lookup, not dereferenced. The object
+ * cannot be freed while the domain exists because the domain's rule
+ * tree holds its own reference to it.
+ */
+ id->key.object = rcu_access_pointer(
+ landlock_inode(d_backing_inode(dentry))->object);
+ return !!id->key.object;
+}
+
+static bool unmask_layers_fs(const struct landlock_domain *const domain,
+ const struct landlock_id id,
+ const access_mask_t access_request,
+ struct layer_masks *masks,
+ const struct dentry *const dentry)
+{
+ const struct landlock_rule *rule = NULL;
+ bool ret;
+
+ ret = landlock_unmask_layers(domain, id, masks, &rule);
+ if (rule)
+ trace_landlock_check_rule_fs(domain, rule, access_request,
+ dentry);
+ return ret;
}
/*
@@ -749,7 +792,7 @@ static void test_is_eacces_with_write(struct kunit *const test)
* Return: True if the access request is granted, false otherwise.
*/
static bool
-is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
+is_access_to_paths_allowed(const struct landlock_domain *const domain,
const struct path *const path,
const access_mask_t access_request_parent1,
struct layer_masks *layer_masks_parent1,
@@ -763,6 +806,9 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
child1_is_directory = true, child2_is_directory = true;
struct path walker_path;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
access_mask_t access_masked_parent1, access_masked_parent2;
struct layer_masks _layer_masks_child1, _layer_masks_child2;
struct layer_masks *layer_masks_child1 = NULL,
@@ -802,28 +848,46 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
/* For a simple request, only check for requested accesses. */
access_masked_parent1 = access_request_parent1;
access_masked_parent2 = access_request_parent2;
+ /*
+ * Simple requests have no parent2 to check, so parent2 is
+ * trivially allowed. This must be set explicitly because the
+ * get_inode_id() gate in the pathwalk loop may prevent
+ * landlock_unmask_layers() from being called (which would
+ * otherwise return true for NULL masks as a side effect).
+ */
+ allowed_parent2 = true;
is_dom_check = false;
}
if (unlikely(dentry_child1)) {
- /*
- * Get the layer masks for the child dentries for use by domain
- * check later.
- */
- 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);
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
+ access_mask_t handled;
+
+ handled = landlock_init_layer_masks(domain,
+ LANDLOCK_MASK_ACCESS_FS,
+ &_layer_masks_child1,
+ LANDLOCK_KEY_INODE);
+ if (handled && get_inode_id(dentry_child1, &id))
+ unmask_layers_fs(domain, id, handled,
+ &_layer_masks_child1, dentry_child1);
layer_masks_child1 = &_layer_masks_child1;
child1_is_directory = d_is_dir(dentry_child1);
}
if (unlikely(dentry_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);
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
+ access_mask_t handled;
+
+ handled = landlock_init_layer_masks(domain,
+ LANDLOCK_MASK_ACCESS_FS,
+ &_layer_masks_child2,
+ LANDLOCK_KEY_INODE);
+ if (handled && get_inode_id(dentry_child2, &id))
+ unmask_layers_fs(domain, id, handled,
+ &_layer_masks_child2, dentry_child2);
layer_masks_child2 = &_layer_masks_child2;
child2_is_directory = d_is_dir(dentry_child2);
}
@@ -835,8 +899,6 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
* restriction.
*/
while (true) {
- const struct landlock_rule *rule;
-
/*
* If at least all accesses allowed on the destination are
* already allowed on the source, respectively if there is at
@@ -877,13 +939,20 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
break;
}
- rule = find_rule(domain, walker_path.dentry);
- allowed_parent1 =
- allowed_parent1 ||
- landlock_unmask_layers(rule, layer_masks_parent1);
- allowed_parent2 =
- allowed_parent2 ||
- landlock_unmask_layers(rule, layer_masks_parent2);
+ if (get_inode_id(walker_path.dentry, &id)) {
+ allowed_parent1 =
+ allowed_parent1 ||
+ unmask_layers_fs(domain, id,
+ access_masked_parent1,
+ layer_masks_parent1,
+ walker_path.dentry);
+ allowed_parent2 =
+ allowed_parent2 ||
+ unmask_layers_fs(domain, id,
+ access_masked_parent2,
+ layer_masks_parent2,
+ walker_path.dentry);
+ }
/* Stops when a rule from each layer grants access. */
if (allowed_parent1 && allowed_parent2)
@@ -933,10 +1002,11 @@ jump_up:
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.
+ * Check CONFIG_SECURITY_LANDLOCK_LOG to enable elision of
+ * log_request_parent* and associated caller's stack variables thanks to
+ * dead code elimination.
*/
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
if (!allowed_parent1 && log_request_parent1) {
log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
@@ -952,7 +1022,7 @@ jump_up:
log_request_parent2->access = access_masked_parent2;
log_request_parent2->layer_masks = layer_masks_parent2;
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
return allowed_parent1 && allowed_parent2;
}
@@ -983,7 +1053,8 @@ static int current_check_access_path(const struct path *const path,
return -EACCES;
}
-static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
+static __attribute_const__ access_mask_t get_mode_access(const umode_t mode,
+ const dev_t dev)
{
switch (mode & S_IFMT) {
case S_IFLNK:
@@ -991,6 +1062,9 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
case S_IFDIR:
return LANDLOCK_ACCESS_FS_MAKE_DIR;
case S_IFCHR:
+ /* Whiteout objects are guarded with MAKE_REG. */
+ if (dev == WHITEOUT_DEV)
+ return LANDLOCK_ACCESS_FS_MAKE_REG;
return LANDLOCK_ACCESS_FS_MAKE_CHAR;
case S_IFBLK:
return LANDLOCK_ACCESS_FS_MAKE_BLOCK;
@@ -1007,6 +1081,13 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
}
}
+static access_mask_t get_dentry_access(const struct dentry *const dentry)
+{
+ const struct inode *const inode = d_backing_inode(dentry);
+
+ return get_mode_access(inode->i_mode, inode->i_rdev);
+}
+
static access_mask_t maybe_remove(const struct dentry *const dentry)
{
if (d_is_negative(dentry))
@@ -1039,29 +1120,36 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
* 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,
+static bool collect_domain_accesses(const struct landlock_domain *const domain,
const struct dentry *const mnt_root,
struct dentry *dir,
struct layer_masks *layer_masks_dom)
{
bool ret = false;
+ access_mask_t access_masked_dom;
if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
return true;
if (is_nouser_or_private(dir))
return true;
- if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
- layer_masks_dom, LANDLOCK_KEY_INODE))
+ access_masked_dom =
+ landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
+ layer_masks_dom, LANDLOCK_KEY_INODE);
+ if (!access_masked_dom)
return true;
dget(dir);
while (true) {
struct dentry *parent_dentry;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
/* Gets all layers allowing all domain accesses. */
- if (landlock_unmask_layers(find_rule(domain, dir),
- layer_masks_dom)) {
+ if (get_inode_id(dir, &id) &&
+ unmask_layers_fs(domain, id, access_masked_dom,
+ layer_masks_dom, dir)) {
/*
* Stops when all handled accesses are allowed by at
* least one rule in each layer.
@@ -1093,6 +1181,7 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
* @new_dentry: Destination file or directory.
* @removable: Sets to true if it is a rename operation.
* @exchange: Sets to true if it is a rename operation with RENAME_EXCHANGE.
+ * @whiteout: Sets to true if it is a rename operation with RENAME_WHITEOUT.
*
* Because of its unprivileged constraints, Landlock relies on file hierarchies
* (and not only inodes) to tie access rights to files. Being able to link or
@@ -1140,7 +1229,8 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
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 bool removable, const bool exchange,
+ const bool whiteout)
{
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs, NULL);
@@ -1159,18 +1249,25 @@ static int current_check_refer_path(struct dentry *const old_dentry,
if (exchange) {
if (unlikely(d_is_negative(new_dentry)))
return -ENOENT;
- access_request_parent1 =
- get_mode_access(d_backing_inode(new_dentry)->i_mode);
+ access_request_parent1 = get_dentry_access(new_dentry);
} else {
access_request_parent1 = 0;
}
- access_request_parent2 =
- get_mode_access(d_backing_inode(old_dentry)->i_mode);
+ access_request_parent2 = get_dentry_access(old_dentry);
if (removable) {
access_request_parent1 |= maybe_remove(old_dentry);
access_request_parent2 |= maybe_remove(new_dentry);
}
+ /*
+ * In case of renameat2(2) with RENAME_WHITEOUT, a whiteout object is
+ * created in the source location, so we require an additional access
+ * right there.
+ */
+ if (whiteout)
+ access_request_parent1 |=
+ get_mode_access(S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV);
+
/* The mount points are the same for old and new paths, cf. EXDEV. */
if (old_dentry->d_parent == new_dir->dentry) {
/*
@@ -1520,7 +1617,7 @@ static int hook_path_link(struct dentry *const old_dentry,
struct dentry *const new_dentry)
{
return current_check_refer_path(old_dentry, new_dir, new_dentry, false,
- false);
+ false, false);
}
static int hook_path_rename(const struct path *const old_dir,
@@ -1531,7 +1628,8 @@ static int hook_path_rename(const struct path *const old_dir,
{
/* old_dir refers to old_dentry->d_parent and new_dir->mnt */
return current_check_refer_path(old_dentry, new_dir, new_dentry, true,
- !!(flags & RENAME_EXCHANGE));
+ !!(flags & RENAME_EXCHANGE),
+ !!(flags & RENAME_WHITEOUT));
}
static int hook_path_mkdir(const struct path *const dir,
@@ -1544,7 +1642,8 @@ static int hook_path_mknod(const struct path *const dir,
struct dentry *const dentry, const umode_t mode,
const unsigned int dev)
{
- return current_check_access_path(dir, get_mode_access(mode));
+ return current_check_access_path(
+ dir, get_mode_access(mode, new_decode_dev(dev)));
}
static int hook_path_symlink(const struct path *const dir,
@@ -1589,8 +1688,8 @@ static int hook_path_truncate(const struct path *const path)
* @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,
+static void unmask_scoped_access(const struct landlock_domain *const client,
+ const struct landlock_domain *const server,
struct layer_masks *const masks,
const access_mask_t access)
{
@@ -1644,7 +1743,7 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client,
static int hook_unix_find(const struct path *const path, struct sock *other,
int flags)
{
- const struct landlock_ruleset *dom_other;
+ const struct landlock_domain *dom_other;
const struct landlock_cred_security *subject;
struct layer_masks layer_masks;
struct landlock_request request = {};
@@ -1802,14 +1901,14 @@ 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
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
landlock_file(file)->deny_masks = landlock_get_deny_masks(
_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks);
landlock_file(file)->quiet_optional_accesses =
landlock_get_quiet_optional_accesses(
_LANDLOCK_ACCESS_FS_OPTIONAL,
landlock_file(file)->deny_masks, &layer_masks);
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
if (access_mask_subset(open_access_request, allowed_access))
return 0;
@@ -1843,10 +1942,10 @@ static int hook_file_truncate(struct file *const file)
},
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
.access = LANDLOCK_ACCESS_FS_TRUNCATE,
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.deny_masks = landlock_file(file)->deny_masks,
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
});
return -EACCES;
}
@@ -1883,10 +1982,10 @@ static int hook_file_ioctl_common(const struct file *const file,
},
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
.access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.deny_masks = landlock_file(file)->deny_masks,
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
});
return -EACCES;
}
@@ -1939,7 +2038,7 @@ static bool control_current_fowner(struct fown_struct *const fown)
static void hook_file_set_fowner(struct file *file)
{
- struct landlock_ruleset *prev_dom;
+ struct landlock_domain *prev_dom;
struct landlock_cred_security fown_subject = {};
struct pid *prev_tg, *fown_tg = NULL;
size_t fown_layer = 0;
@@ -1952,7 +2051,7 @@ static void hook_file_set_fowner(struct file *file)
landlock_get_applicable_subject(
current_cred(), signal_scope, &fown_layer);
if (new_subject) {
- landlock_get_ruleset(new_subject->domain);
+ landlock_get_domain(new_subject->domain);
fown_subject = *new_subject;
fown_tg = get_pid(task_tgid(current));
}
@@ -1962,19 +2061,19 @@ static void hook_file_set_fowner(struct file *file)
prev_tg = landlock_file(file)->fown_tg;
landlock_file(file)->fown_subject = fown_subject;
landlock_file(file)->fown_tg = fown_tg;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
landlock_file(file)->fown_layer = fown_layer;
-#endif /* CONFIG_AUDIT*/
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/* May be called in an RCU read-side critical section. */
- landlock_put_ruleset_deferred(prev_dom);
+ landlock_put_domain_deferred(prev_dom);
put_pid(prev_tg);
}
static void hook_file_free_security(struct file *file)
{
put_pid(landlock_file(file)->fown_tg);
- landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
+ landlock_put_domain_deferred(landlock_file(file)->fown_subject.domain);
}
static struct security_hook_list landlock_hooks[] __ro_after_init = {
diff --git a/security/landlock/fs.h b/security/landlock/fs.h
index b4421d9df68f..50b3ddb8ac1c 100644
--- a/security/landlock/fs.h
+++ b/security/landlock/fs.h
@@ -11,6 +11,7 @@
#define _SECURITY_LANDLOCK_FS_H
#include <linux/build_bug.h>
+#include <linux/cleanup.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/rcupdate.h>
@@ -20,6 +21,8 @@
#include "ruleset.h"
#include "setup.h"
+DEFINE_FREE(__putname, char *, if (_T) __putname(_T))
+
/**
* struct landlock_inode_security - Inode security blob
*
@@ -57,7 +60,7 @@ struct landlock_file_security {
*/
access_mask_t allowed_access;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/**
* @deny_masks: Domain layer levels that deny an optional access (see
* _LANDLOCK_ACCESS_FS_OPTIONAL).
@@ -75,7 +78,7 @@ struct landlock_file_security {
* LANDLOCK_SCOPE_SIGNAL.
*/
u8 fown_layer;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/**
* @fown_subject: Landlock credential of the task that set the PID that
@@ -97,7 +100,7 @@ struct landlock_file_security {
struct pid *fown_tg;
};
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
/* Makes sure all layers can be identified. */
/* clang-format off */
@@ -113,7 +116,7 @@ static_assert(BITS_PER_TYPE(typeof_member(struct landlock_file_security,
quiet_optional_accesses)) >=
HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/**
* struct landlock_superblock_security - Superblock security blob
@@ -153,4 +156,33 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
const struct path *const path,
access_mask_t access_hierarchy, const u32 flags);
+/**
+ * resolve_path_for_trace - Resolve a path for tracepoint display
+ *
+ * @path: The path to resolve.
+ * @buf: A buffer of at least PATH_MAX bytes for the resolved path.
+ *
+ * Uses d_absolute_path() to produce a namespace-independent absolute path,
+ * unlike d_path() which resolves relative to the process's chroot. This
+ * ensures trace output is deterministic regardless of the tracer's mount
+ * namespace.
+ *
+ * Return: A pointer into @buf with the resolved path, or an error string
+ * ("<too_long>", "<unreachable>").
+ */
+static inline const char *resolve_path_for_trace(const struct path *path,
+ char *buf)
+{
+ const char *p;
+
+ p = d_absolute_path(path, buf, PATH_MAX);
+ if (!IS_ERR_OR_NULL(p))
+ return p;
+
+ if (PTR_ERR(p) == -ENAMETOOLONG)
+ return "<too_long>";
+
+ return "<unreachable>";
+}
+
#endif /* _SECURITY_LANDLOCK_FS_H */
diff --git a/security/landlock/id.h b/security/landlock/id.h
index 45dcfb9e9a8b..2a43c2b523a8 100644
--- a/security/landlock/id.h
+++ b/security/landlock/id.h
@@ -8,18 +8,18 @@
#ifndef _SECURITY_LANDLOCK_ID_H
#define _SECURITY_LANDLOCK_ID_H
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
void __init landlock_init_id(void);
u64 landlock_get_id_range(size_t number_of_ids);
-#else /* CONFIG_AUDIT */
+#else /* CONFIG_SECURITY_LANDLOCK_LOG */
static inline void __init landlock_init_id(void)
{
}
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
#endif /* _SECURITY_LANDLOCK_ID_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 08d5f2f6d321..1a7c5fb8f6fd 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -34,7 +34,7 @@
#define LANDLOCK_NUM_ACCESS_MAX \
MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE)
-#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
+#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
/* clang-format on */
diff --git a/security/landlock/log.c b/security/landlock/log.c
new file mode 100644
index 000000000000..a8578a6f2ce9
--- /dev/null
+++ b/security/landlock/log.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Log helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#include <kunit/test.h>
+#include <linux/bitops.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 "log.h"
+#include "ruleset.h"
+#include "trace.h"
+
+static struct landlock_hierarchy *
+get_hierarchy(const struct landlock_domain *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_domain 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_domain *const domain,
+ access_mask_t *const access_request,
+ const struct layer_masks *masks)
+{
+ for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
+ if (masks->layers[i].access & *access_request) {
+ *access_request &= masks->layers[i].access;
+ 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_domain dom = {
+ .num_layers = 5,
+ };
+ const struct layer_masks masks = {
+ .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
+ LANDLOCK_ACCESS_FS_READ_DIR,
+ .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
+ LANDLOCK_ACCESS_FS_READ_DIR,
+ .layers[2].access = 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,
+ optional_access_t quiet_optional_accesses,
+ bool *quiet)
+{
+ 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;
+ bool should_quiet = false;
+
+ /* 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 *
+ HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
+ (LANDLOCK_MAX_NUM_LAYERS - 1);
+ const bool layer_has_quiet =
+ !!(quiet_optional_accesses & BIT(access_index));
+
+ if (layer > youngest_layer) {
+ youngest_layer = layer;
+ missing = BIT(access_bit);
+ should_quiet = layer_has_quiet;
+ } else if (layer == youngest_layer) {
+ missing |= BIT(access_bit);
+ /*
+ * Whether the layer has rules with quiet flag
+ * covering the file accessed does not depend on
+ * the access, and so the following
+ * WARN_ON_ONCE() should not fail.
+ */
+ WARN_ON_ONCE(should_quiet && !layer_has_quiet);
+ should_quiet = layer_has_quiet;
+ }
+ }
+ access_index++;
+ }
+
+ *access_request = missing;
+ *quiet = should_quiet;
+ 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;
+ optional_access_t quiet_optional_accesses;
+ bool quiet;
+
+ /* truncate:0 ioctl_dev:2 */
+ deny_mask = 0x20;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ 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, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* layer denying truncate: quiet, ioctl: not quiet */
+ quiet_optional_accesses = 0b01;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ 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, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* Reverse order - truncate:2 ioctl_dev:0 */
+ deny_mask = 0x02;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ 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, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* layer denying truncate: quiet, ioctl: not quiet */
+ quiet_optional_accesses = 0b01;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ 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, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ /* layer denying truncate: not quiet, ioctl: quiet */
+ quiet_optional_accesses = 0b10;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 2,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ KUNIT_EXPECT_EQ(test, 0,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ 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, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* truncate:15 ioctl_dev:15 */
+ deny_mask = 0xff;
+ quiet_optional_accesses = 0;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ 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, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, false);
+
+ /* Both quiet (same layer so quietness must be the same) */
+ quiet_optional_accesses = 0b11;
+
+ access = LANDLOCK_ACCESS_FS_TRUNCATE;
+ KUNIT_EXPECT_EQ(test, 15,
+ get_layer_from_deny_masks(
+ &access, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ deny_mask, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+
+ 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, quiet_optional_accesses, &quiet));
+ KUNIT_EXPECT_EQ(test, access,
+ LANDLOCK_ACCESS_FS_TRUNCATE |
+ LANDLOCK_ACCESS_FS_IOCTL_DEV);
+ KUNIT_EXPECT_EQ(test, quiet, true);
+}
+
+#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;
+ static_assert(sizeof(request->all_existing_optional_access) ==
+ sizeof(u32));
+ if (WARN_ON_ONCE(
+ request->quiet_optional_accesses >=
+ BIT(hweight32(
+ request->all_existing_optional_access))))
+ return false;
+ }
+
+ return true;
+}
+
+static access_mask_t
+pick_access_mask_for_request_type(const enum landlock_request_type type,
+ const struct access_masks access_masks)
+{
+ switch (type) {
+ case LANDLOCK_REQUEST_FS_ACCESS:
+ return access_masks.fs;
+ case LANDLOCK_REQUEST_NET_ACCESS:
+ return access_masks.net;
+ default:
+ WARN_ONCE(1, "Invalid request type %d passed to %s", type,
+ __func__);
+ return 0;
+ }
+}
+
+/*
+ * Whether a quiet rule silences the denial: the rule must cover the whole
+ * denied access in the layer that denied it (a quiet rule in a non-denying
+ * layer does not suppress the denial).
+ */
+static bool
+is_denial_quieted(const struct landlock_request *const request,
+ const struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool object_quiet_flag)
+{
+ if (object_quiet_flag) {
+ const access_mask_t quiet_mask =
+ pick_access_mask_for_request_type(
+ request->type, youngest_denied->quiet_masks);
+
+ return (quiet_mask & missing) == missing;
+ }
+
+ /*
+ * Either the object is not quiet, or this is a scope request. We check
+ * request->type to distinguish between the two cases.
+ */
+ switch (request->type) {
+ case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ return !!(youngest_denied->quiet_masks.scope &
+ LANDLOCK_SCOPE_SIGNAL);
+ case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
+ return !!(youngest_denied->quiet_masks.scope &
+ LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
+ /*
+ * Leave LANDLOCK_REQUEST_PTRACE and LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY
+ * unhandled for now - they are never quiet.
+ */
+ default:
+ return false;
+ }
+}
+
+/*
+ * Computes whether a denial from youngest_denied is selected for logging by the
+ * domain's policy: its logging must not be disabled (by both per-execution
+ * flags being off, or by an ancestor's
+ * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF), the per-execution flag matching
+ * same_exec must be set, and no quiet rule may cover the denied access.
+ * landlock_log_denial() computes this once and passes it to
+ * landlock_audit_denial(), which additionally requires audit_enabled.
+ */
+static bool
+is_denial_logged(const struct landlock_request *const request,
+ const struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool same_exec,
+ const bool object_quiet_flag)
+{
+ if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
+ return false;
+
+ if (!(same_exec ? youngest_denied->log_same_exec :
+ youngest_denied->log_new_exec))
+ return false;
+
+ return !is_denial_quieted(request, youngest_denied, missing,
+ object_quiet_flag);
+}
+
+/**
+ * landlock_log_denial - Log a denied access
+ *
+ * @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 landlock_hierarchy *youngest_denied;
+ size_t youngest_layer;
+ access_mask_t missing;
+ bool object_quiet_flag = false;
+
+ 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);
+ object_quiet_flag =
+ request->layer_masks->layers[youngest_layer]
+ .quiet;
+ } else {
+ youngest_layer = get_layer_from_deny_masks(
+ &missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
+ request->deny_masks,
+ request->quiet_optional_accesses,
+ &object_quiet_flag);
+ }
+ youngest_denied =
+ get_hierarchy(subject->domain, youngest_layer);
+ } else {
+ youngest_layer = request->layer_plus_one - 1;
+ youngest_denied =
+ get_hierarchy(subject->domain, youngest_layer);
+ }
+
+ const bool same_exec = !!(subject->domain_exec & BIT(youngest_layer));
+ const bool logged = is_denial_logged(request, youngest_denied, missing,
+ same_exec, object_quiet_flag);
+
+ /*
+ * 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);
+
+ landlock_trace_denial(request, youngest_denied, missing, same_exec,
+ logged);
+ landlock_audit_denial(request, youngest_denied, missing, logged);
+}
+
+/**
+ * landlock_log_free_domain - Log domain deallocation
+ *
+ * @hierarchy: The domain's hierarchy being deallocated.
+ *
+ * Called from landlock_put_domain_deferred() (via a work queue scheduled by
+ * hook_cred_free()) or directly from landlock_put_domain().
+ */
+void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy)
+{
+ if (WARN_ON_ONCE(!hierarchy))
+ return;
+
+ landlock_trace_free_domain(hierarchy);
+ landlock_audit_free_domain(hierarchy);
+}
+
+#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_log",
+ .test_cases = test_cases,
+};
+
+kunit_test_suite(test_suite);
+
+#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
diff --git a/security/landlock/log.h b/security/landlock/log.h
new file mode 100644
index 000000000000..e0a6e44f3ddd
--- /dev/null
+++ b/security/landlock/log.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Log helpers
+ *
+ * Copyright © 2023-2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#ifndef _SECURITY_LANDLOCK_LOG_H
+#define _SECURITY_LANDLOCK_LOG_H
+
+#include <linux/lsm_audit.h>
+
+#include "access.h"
+
+struct landlock_cred_security;
+struct landlock_hierarchy;
+
+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_SECURITY_LANDLOCK_LOG 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_masks *layer_masks;
+
+ /* Required fields for requests with deny masks. */
+ const access_mask_t all_existing_optional_access;
+ deny_masks_t deny_masks;
+ optional_access_t quiet_optional_accesses;
+
+ /*
+ * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if
+ * that party is unsandboxed. An ID, not a pointer: the other task can
+ * replace its credential and free the domain it referenced. Trace path
+ * only; audit ignores it.
+ */
+ u64 other_domain_id;
+};
+
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+
+void landlock_log_free_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_SECURITY_LANDLOCK_LOG */
+
+static inline void
+landlock_log_free_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_SECURITY_LANDLOCK_LOG */
+
+#endif /* _SECURITY_LANDLOCK_LOG_H */
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 46c17116fcf4..8f2aaac54b33 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -12,13 +12,16 @@
#include <linux/socket.h>
#include <net/ipv6.h>
-#include "audit.h"
#include "common.h"
#include "cred.h"
+#include "domain.h"
#include "limits.h"
+#include "log.h"
#include "net.h"
#include "ruleset.h"
+#include <trace/events/landlock.h>
+
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
const u16 port, access_mask_t access_rights,
const u32 flags)
@@ -32,16 +35,40 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
/* Transforms relative access rights to absolute ones. */
- access_rights |= LANDLOCK_MASK_ACCESS_NET &
- ~landlock_get_net_access_mask(ruleset, 0);
+ access_rights |= LANDLOCK_MASK_ACCESS_NET & ~ruleset->handled_masks.net;
mutex_lock(&ruleset->lock);
err = landlock_insert_rule(ruleset, id, access_rights, flags);
+
+ /*
+ * Emit after the rule insertion succeeds, so every event corresponds to
+ * a rule that is actually in the ruleset. The ruleset lock is still
+ * held for BTF consistency (enforced by lockdep_assert_held in
+ * TP_fast_assign).
+ */
+ if (!err)
+ trace_landlock_add_rule_net(ruleset, access_rights, port);
mutex_unlock(&ruleset->lock);
return err;
}
+static bool unmask_layers_net(const struct landlock_domain *const domain,
+ const struct landlock_id id,
+ struct layer_masks *masks,
+ access_mask_t access_request)
+{
+ const struct landlock_rule *rule = NULL;
+ bool ret;
+
+ ret = landlock_unmask_layers(domain, id, masks, &rule);
+ if (rule)
+ trace_landlock_check_rule_net(
+ domain, rule, access_request,
+ ntohs((__force __be16)id.key.data));
+ return ret;
+}
+
static int current_check_access_socket(struct socket *const sock,
struct sockaddr *const address,
const int addrlen,
@@ -51,7 +78,6 @@ static int current_check_access_socket(struct socket *const sock,
unsigned short sock_family;
__be16 port;
struct layer_masks layer_masks = {};
- const struct landlock_rule *rule;
struct landlock_id id = {
.type = LANDLOCK_KEY_NET_PORT,
};
@@ -237,14 +263,14 @@ static int current_check_access_socket(struct socket *const sock,
id.key.data = (__force uintptr_t)port;
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
- 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))
+ if (unmask_layers_net(subject->domain, id, &layer_masks,
+ access_request))
return 0;
audit_net.family = address->sa_family;
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 4dd09ea22c84..0d07707523cd 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -4,6 +4,7 @@
*
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
+ * Copyright © 2026 Cloudflare, Inc.
*/
#include <linux/bits.h>
@@ -20,59 +21,64 @@
#include <linux/refcount.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
-#include <linux/workqueue.h>
#include <uapi/linux/landlock.h>
#include "access.h"
-#include "domain.h"
+#include "id.h"
#include "limits.h"
#include "object.h"
#include "ruleset.h"
-static struct landlock_ruleset *create_ruleset(const u32 num_layers)
+#include <trace/events/landlock.h>
+
+struct landlock_ruleset *
+landlock_create_ruleset(const access_mask_t fs_access_mask,
+ const access_mask_t net_access_mask,
+ const access_mask_t scope_mask)
{
struct landlock_ruleset *new_ruleset;
- new_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers,
- GFP_KERNEL_ACCOUNT);
+ /* Informs about useless ruleset. */
+ if (!fs_access_mask && !net_access_mask && !scope_mask)
+ return ERR_PTR(-ENOMSG);
+
+ new_ruleset = kzalloc_obj(*new_ruleset, GFP_KERNEL_ACCOUNT);
if (!new_ruleset)
return ERR_PTR(-ENOMEM);
+
refcount_set(&new_ruleset->usage, 1);
mutex_init(&new_ruleset->lock);
- new_ruleset->root_inode = RB_ROOT;
+ new_ruleset->rules.root_inode = RB_ROOT;
#if IS_ENABLED(CONFIG_INET)
- new_ruleset->root_net_port = RB_ROOT;
+ new_ruleset->rules.root_net_port = RB_ROOT;
#endif /* IS_ENABLED(CONFIG_INET) */
- new_ruleset->num_layers = num_layers;
- /*
- * hierarchy = NULL
- * num_rules = 0
- * access_masks[] = 0
- */
- return new_ruleset;
-}
+#ifdef CONFIG_TRACEPOINTS
+ new_ruleset->id = landlock_get_id_range(1);
+#endif /* CONFIG_TRACEPOINTS */
-struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask,
- const access_mask_t net_access_mask,
- const access_mask_t scope_mask)
-{
- struct landlock_ruleset *new_ruleset;
+ /* Should already be checked in landlock_create_ruleset(). */
+ if (fs_access_mask) {
+ const access_mask_t mask = fs_access_mask &
+ LANDLOCK_MASK_ACCESS_FS;
- /* Informs about useless ruleset. */
- if (!fs_access_mask && !net_access_mask && !scope_mask)
- return ERR_PTR(-ENOMSG);
- new_ruleset = create_ruleset(1);
- if (IS_ERR(new_ruleset))
- return new_ruleset;
- if (fs_access_mask)
- landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
- if (net_access_mask)
- landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
- if (scope_mask)
- landlock_add_scope_mask(new_ruleset, scope_mask, 0);
+ WARN_ON_ONCE(fs_access_mask != mask);
+ new_ruleset->handled_masks.fs |= mask;
+ }
+ if (net_access_mask) {
+ const access_mask_t mask = net_access_mask &
+ LANDLOCK_MASK_ACCESS_NET;
+
+ WARN_ON_ONCE(net_access_mask != mask);
+ new_ruleset->handled_masks.net |= mask;
+ }
+ if (scope_mask) {
+ const access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
+
+ WARN_ON_ONCE(scope_mask != mask);
+ new_ruleset->handled_masks.scope |= mask;
+ }
return new_ruleset;
}
@@ -129,7 +135,7 @@ create_rule(const struct landlock_id id,
return ERR_PTR(-ENOMEM);
RB_CLEAR_NODE(&new_rule->node);
if (is_object_pointer(id.type)) {
- /* This should have been caught by insert_rule(). */
+ /* This should have been caught by landlock_store_rule(). */
WARN_ON_ONCE(!id.key.object);
landlock_get_object(id.key.object);
}
@@ -145,24 +151,6 @@ create_rule(const struct landlock_id id,
return new_rule;
}
-static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
- const enum landlock_key_type key_type)
-{
- switch (key_type) {
- case LANDLOCK_KEY_INODE:
- return &ruleset->root_inode;
-
-#if IS_ENABLED(CONFIG_INET)
- case LANDLOCK_KEY_NET_PORT:
- return &ruleset->root_net_port;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
- default:
- WARN_ON_ONCE(1);
- return ERR_PTR(-EINVAL);
- }
-}
-
static void free_rule(struct landlock_rule *const rule,
const enum landlock_key_type key_type)
{
@@ -176,19 +164,20 @@ static void free_rule(struct landlock_rule *const rule,
static void build_check_ruleset(void)
{
- const struct landlock_ruleset ruleset = {
+ const struct landlock_rules rules = {
.num_rules = ~0,
- .num_layers = ~0,
};
- BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
- BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
+ BUILD_BUG_ON(rules.num_rules < LANDLOCK_MAX_NUM_RULES);
}
/**
- * insert_rule - Create and insert a rule in a ruleset
+ * landlock_store_rule - Create and insert a rule into the rule storage
*
- * @ruleset: The ruleset to be updated.
+ * @rules: The rule storage to be updated. The caller is responsible for
+ * any required locking. For rulesets, this means holding
+ * &landlock_ruleset.lock. For domains under construction, no lock is
+ * needed because the domain is not yet visible to other tasks.
* @id: The ID to build the new rule with. The underlying kernel object, if
* any, must be held by the caller.
* @layers: One or multiple layers to be copied into the new rule.
@@ -196,19 +185,19 @@ static void build_check_ruleset(void)
*
* When user space requests to add a new rule to a ruleset, @layers only
* contains one entry and this entry is not assigned to any level. In this
- * case, the new rule will extend @ruleset, similarly to a boolean OR between
+ * case, the new rule will extend @rules, similarly to a boolean OR between
* access rights.
*
* 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.
+ * added to @rules 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 (*layers)[],
- const size_t num_layers)
+int landlock_store_rule(struct landlock_rules *const rules,
+ const struct landlock_id id,
+ const struct landlock_layer (*layers)[],
+ const size_t num_layers)
{
struct rb_node **walker_node;
struct rb_node *parent_node = NULL;
@@ -216,14 +205,13 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
struct rb_root *root;
might_sleep();
- lockdep_assert_held(&ruleset->lock);
if (WARN_ON_ONCE(!layers))
return -ENOENT;
if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object))
return -ENOENT;
- root = get_root(ruleset, id.type);
+ root = landlock_get_rule_root(rules, id.type);
if (IS_ERR(root))
return PTR_ERR(root);
@@ -249,7 +237,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
if ((*layers)[0].level == 0) {
/*
* Extends access rights when the request comes from
- * landlock_add_rule(2), i.e. @ruleset is not a domain.
+ * landlock_add_rule(2), i.e. @rules is not a domain.
*/
if (WARN_ON_ONCE(this->num_layers != 1))
return -EINVAL;
@@ -278,14 +266,14 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
/* There is no match for @id. */
build_check_ruleset();
- if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
+ if (rules->num_rules >= LANDLOCK_MAX_NUM_RULES)
return -E2BIG;
new_rule = create_rule(id, layers, num_layers, NULL);
if (IS_ERR(new_rule))
return PTR_ERR(new_rule);
rb_link_node(&new_rule->node, parent_node, walker_node);
rb_insert_color(&new_rule->node, root);
- ruleset->num_rules++;
+ rules->num_rules++;
return 0;
}
@@ -311,197 +299,50 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
{
struct landlock_layer layers[] = { {
.access = access,
- /* When @level is zero, insert_rule() extends @ruleset. */
+ /*
+ * When @level is zero, landlock_store_rule() extends @ruleset.
+ */
.level = 0,
.flags = {
.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
},
} };
+ int err;
build_check_layer();
- return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
-}
-
-static int merge_tree(struct landlock_ruleset *const dst,
- struct landlock_ruleset *const src,
- const enum landlock_key_type key_type)
-{
- struct landlock_rule *walker_rule, *next_rule;
- struct rb_root *src_root;
- int err = 0;
-
- might_sleep();
- lockdep_assert_held(&dst->lock);
- lockdep_assert_held(&src->lock);
-
- src_root = get_root(src, key_type);
- if (IS_ERR(src_root))
- return PTR_ERR(src_root);
-
- /* Merges the @src tree. */
- rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
- node) {
- struct landlock_layer layers[] = { {
- .level = dst->num_layers,
- } };
- const struct landlock_id id = {
- .key = walker_rule->key,
- .type = key_type,
- };
-
- if (WARN_ON_ONCE(walker_rule->num_layers != 1))
- return -EINVAL;
-
- if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
- return -EINVAL;
-
- layers[0].access = walker_rule->layers[0].access;
- layers[0].flags = walker_rule->layers[0].flags;
-
- err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
- if (err)
- return err;
- }
- return err;
-}
-
-static int merge_ruleset(struct landlock_ruleset *const dst,
- struct landlock_ruleset *const src)
-{
- int err = 0;
-
- might_sleep();
- /* Should already be checked by landlock_merge_ruleset() */
- if (WARN_ON_ONCE(!src))
- return 0;
- /* Only merge into a domain. */
- if (WARN_ON_ONCE(!dst || !dst->hierarchy))
- return -EINVAL;
-
- /* Locks @dst first because we are its only owner. */
- mutex_lock(&dst->lock);
- mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
-
- /* Stacks the new layer. */
- if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
- err = -EINVAL;
- goto out_unlock;
- }
- dst->access_masks[dst->num_layers - 1] =
- landlock_upgrade_handled_access_masks(src->access_masks[0]);
-
- /* Merges the @src inode tree. */
- err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
- if (err)
- goto out_unlock;
-
-#if IS_ENABLED(CONFIG_INET)
- /* Merges the @src network port tree. */
- err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
- if (err)
- goto out_unlock;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
-out_unlock:
- mutex_unlock(&src->lock);
- mutex_unlock(&dst->lock);
- return err;
-}
-
-static int inherit_tree(struct landlock_ruleset *const parent,
- struct landlock_ruleset *const child,
- const enum landlock_key_type key_type)
-{
- struct landlock_rule *walker_rule, *next_rule;
- struct rb_root *parent_root;
- int err = 0;
-
- might_sleep();
- lockdep_assert_held(&parent->lock);
- lockdep_assert_held(&child->lock);
-
- parent_root = get_root(parent, key_type);
- if (IS_ERR(parent_root))
- return PTR_ERR(parent_root);
-
- /* Copies the @parent inode or network tree. */
- rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
- parent_root, node) {
- const struct landlock_id id = {
- .key = walker_rule->key,
- .type = key_type,
- };
-
- err = insert_rule(child, id, &walker_rule->layers,
- walker_rule->num_layers);
- if (err)
- return err;
- }
- return err;
-}
-
-static int inherit_ruleset(struct landlock_ruleset *const parent,
- struct landlock_ruleset *const child)
-{
- int err = 0;
-
- might_sleep();
- if (!parent)
- return 0;
-
- /* Locks @child first because we are its only owner. */
- mutex_lock(&child->lock);
- mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
-
- /* Copies the @parent inode tree. */
- err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
- if (err)
- goto out_unlock;
-
-#if IS_ENABLED(CONFIG_INET)
- /* Copies the @parent network port tree. */
- err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
- if (err)
- goto out_unlock;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
- if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
- err = -EINVAL;
- goto out_unlock;
- }
- /* Copies the parent layer stack and leaves a space for the new layer. */
- memcpy(child->access_masks, parent->access_masks,
- flex_array_size(parent, access_masks, parent->num_layers));
+ lockdep_assert_held(&ruleset->lock);
+ err = landlock_store_rule(&ruleset->rules, id, &layers,
+ ARRAY_SIZE(layers));
- if (WARN_ON_ONCE(!parent->hierarchy)) {
- err = -EINVAL;
- goto out_unlock;
- }
- landlock_get_hierarchy(parent->hierarchy);
- child->hierarchy->parent = parent->hierarchy;
+#ifdef CONFIG_TRACEPOINTS
+ if (!err)
+ ruleset->version++;
+#endif /* CONFIG_TRACEPOINTS */
-out_unlock:
- mutex_unlock(&parent->lock);
- mutex_unlock(&child->lock);
return err;
}
-static void free_ruleset(struct landlock_ruleset *const ruleset)
+void landlock_free_rules(struct landlock_rules *const rules)
{
struct landlock_rule *freeme, *next;
might_sleep();
- rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
+ rbtree_postorder_for_each_entry_safe(freeme, next, &rules->root_inode,
node)
free_rule(freeme, LANDLOCK_KEY_INODE);
#if IS_ENABLED(CONFIG_INET)
rbtree_postorder_for_each_entry_safe(freeme, next,
- &ruleset->root_net_port, node)
+ &rules->root_net_port, node)
free_rule(freeme, LANDLOCK_KEY_NET_PORT);
#endif /* IS_ENABLED(CONFIG_INET) */
+}
- landlock_put_hierarchy(ruleset->hierarchy);
+static void free_ruleset(struct landlock_ruleset *const ruleset)
+{
+ might_sleep();
+ trace_landlock_free_ruleset(ruleset);
+ landlock_free_rules(&ruleset->rules);
kfree(ruleset);
}
@@ -511,234 +352,3 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset)
if (ruleset && refcount_dec_and_test(&ruleset->usage))
free_ruleset(ruleset);
}
-
-static void free_ruleset_work(struct work_struct *const work)
-{
- struct landlock_ruleset *ruleset;
-
- ruleset = container_of(work, struct landlock_ruleset, work_free);
- 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)) {
- INIT_WORK(&ruleset->work_free, free_ruleset_work);
- schedule_work(&ruleset->work_free);
- }
-}
-
-/**
- * landlock_merge_ruleset - Merge a ruleset with a domain
- *
- * @parent: Parent domain.
- * @ruleset: New ruleset to be merged.
- *
- * 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,
- struct landlock_ruleset *const ruleset)
-{
- struct landlock_ruleset *new_dom __free(landlock_put_ruleset) = NULL;
- u32 num_layers;
- int err;
-
- might_sleep();
- if (WARN_ON_ONCE(!ruleset || parent == ruleset))
- return ERR_PTR(-EINVAL);
-
- if (parent) {
- if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS)
- return ERR_PTR(-E2BIG);
- num_layers = parent->num_layers + 1;
- } else {
- num_layers = 1;
- }
-
- /* Creates a new domain... */
- new_dom = create_ruleset(num_layers);
- if (IS_ERR(new_dom))
- return new_dom;
-
- new_dom->hierarchy =
- kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT);
- if (!new_dom->hierarchy)
- return ERR_PTR(-ENOMEM);
-
- refcount_set(&new_dom->hierarchy->usage, 1);
-
- /* ...as a child of @parent... */
- err = inherit_ruleset(parent, new_dom);
- if (err)
- return ERR_PTR(err);
-
- /* ...and including @ruleset. */
- err = merge_ruleset(new_dom, ruleset);
- if (err)
- return ERR_PTR(err);
-
- err = landlock_init_hierarchy_log(new_dom->hierarchy);
- if (err)
- return ERR_PTR(err);
-
-#ifdef CONFIG_AUDIT
- new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
-#endif /* CONFIG_AUDIT */
-
- return no_free_ptr(new_dom);
-}
-
-/*
- * The returned access has the same lifetime as @ruleset.
- */
-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
- const struct landlock_id id)
-{
- const struct rb_root *root;
- const struct rb_node *node;
-
- root = get_root((struct landlock_ruleset *)ruleset, id.type);
- if (IS_ERR(root))
- return NULL;
- node = root->rb_node;
-
- while (node) {
- struct landlock_rule *this =
- rb_entry(node, struct landlock_rule, node);
-
- if (this->key.data == id.key.data)
- return this;
- if (this->key.data < id.key.data)
- node = node->rb_right;
- else
- node = node->rb_left;
- }
- return NULL;
-}
-
-/**
- * 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).
- *
- * @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,
- struct layer_masks *masks)
-{
- if (!masks)
- return true;
- if (!rule)
- return false;
-
- /*
- * An access is granted if, for each policy layer, at least one rule
- * encountered on the pathwalk grants the requested access,
- * regardless of its position in the layer stack. We must then check
- * the remaining layers for each inode, from the first added layer to
- * the last one. When there is multiple requested accesses, for each
- * policy layer, the full set of requested accesses may not be granted
- * 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 (size_t i = 0; i < rule->num_layers; i++) {
- const struct landlock_layer *const layer = &rule->layers[i];
-
- /* Clear the bits where the layer in the rule grants access. */
- masks->layers[layer->level - 1].access &= ~layer->access;
-
-#ifdef CONFIG_AUDIT
- /* Collect rule flags for each layer. */
- if (layer->flags.quiet)
- masks->layers[layer->level - 1].quiet = true;
-#endif /* CONFIG_AUDIT */
- }
-
- for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
- if (masks->layers[i].access)
- return false;
- }
- return true;
-}
-
-typedef access_mask_t
-get_access_mask_t(const struct landlock_ruleset *const ruleset,
- const u16 layer_level);
-
-/**
- * landlock_init_layer_masks - Initialize layer masks from an 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. Rule flags
- * are also zeroed.
- *
- * @domain: The domain that defines the current restrictions.
- * @access_request: The requested access rights to check.
- * @masks: Layer access masks to populate.
- * @key_type: The key type to switch between access masks of different types.
- *
- * 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,
- struct layer_masks *const masks,
- const enum landlock_key_type key_type)
-{
- access_mask_t handled_accesses = 0;
- get_access_mask_t *get_access_mask;
-
- switch (key_type) {
- case LANDLOCK_KEY_INODE:
- get_access_mask = landlock_get_fs_access_mask;
- break;
-
-#if IS_ENABLED(CONFIG_INET)
- case LANDLOCK_KEY_NET_PORT:
- get_access_mask = landlock_get_net_access_mask;
- break;
-#endif /* IS_ENABLED(CONFIG_INET) */
-
- default:
- WARN_ON_ONCE(1);
- return 0;
- }
-
- /* An empty access request can happen because of O_WRONLY | O_RDWR. */
- if (!access_request)
- return 0;
-
- for (size_t i = 0; i < domain->num_layers; i++) {
- const access_mask_t handled = get_access_mask(domain, i);
-
- masks->layers[i].access = access_request & handled;
- handled_accesses |= masks->layers[i].access;
-#ifdef CONFIG_AUDIT
- masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
- }
- for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
- i++) {
- masks->layers[i].access = 0;
-#ifdef CONFIG_AUDIT
- masks->layers[i].quiet = false;
-#endif /* CONFIG_AUDIT */
- }
-
- return handled_accesses;
-}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 0437adf17428..b536fa0425b7 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -4,6 +4,7 @@
*
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
* Copyright © 2018-2020 ANSSI
+ * Copyright © 2026 Cloudflare, Inc.
*/
#ifndef _SECURITY_LANDLOCK_RULESET_H
@@ -14,14 +15,11 @@
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/refcount.h>
-#include <linux/workqueue.h>
#include "access.h"
#include "limits.h"
#include "object.h"
-struct landlock_hierarchy;
-
/**
* struct landlock_layer - Access rights for a given layer
*/
@@ -68,13 +66,12 @@ union landlock_key {
*/
enum landlock_key_type {
/**
- * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
- * keys.
+ * @LANDLOCK_KEY_INODE: Type of &landlock_rules.root_inode's node keys.
*/
LANDLOCK_KEY_INODE = 1,
/**
- * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
- * node keys.
+ * @LANDLOCK_KEY_NET_PORT: Type of &landlock_rules.root_net_port's node
+ * keys.
*/
LANDLOCK_KEY_NET_PORT,
};
@@ -122,88 +119,78 @@ struct landlock_rule {
};
/**
- * struct landlock_ruleset - Landlock ruleset
+ * struct landlock_rules - Red-black tree storage for Landlock rules
*
- * This data structure must contain unique entries, be updatable, and quick to
- * match an object.
+ * This structure holds the rule trees shared by both rulesets and domains.
*/
-struct landlock_ruleset {
+struct landlock_rules {
/**
* @root_inode: Root of a red-black tree containing &struct
- * landlock_rule nodes with inode object. Once a ruleset is tied to a
- * process (i.e. as a domain), this tree is immutable until @usage
- * reaches zero.
+ * landlock_rule nodes with inode object. Immutable for domains.
*/
struct rb_root root_inode;
#if IS_ENABLED(CONFIG_INET)
/**
* @root_net_port: Root of a red-black tree containing &struct
- * landlock_rule nodes with network port. Once a ruleset is tied to a
- * process (i.e. as a domain), this tree is immutable until @usage
- * reaches zero.
+ * landlock_rule nodes with network port. Immutable for domains.
*/
struct rb_root root_net_port;
#endif /* IS_ENABLED(CONFIG_INET) */
/**
- * @hierarchy: Enables hierarchy identification even when a parent
- * domain vanishes. This is needed for the ptrace protection.
+ * @num_rules: Number of non-overlapping (i.e. not for the same object)
+ * rules in this tree storage.
*/
- struct landlock_hierarchy *hierarchy;
- union {
- /**
- * @work_free: Enables to free a ruleset within a lockless
- * section. This is only used by
- * landlock_put_ruleset_deferred() when @usage reaches zero.
- * The fields @lock, @usage, @num_rules, @num_layers,
- * @quiet_masks and @access_masks are then unused.
- */
- struct work_struct work_free;
- struct {
- /**
- * @lock: Protects against concurrent modifications of
- * @root, if @usage is greater than zero.
- */
- struct mutex lock;
- /**
- * @usage: Number of processes (i.e. domains) or file
- * descriptors referencing this ruleset.
- */
- refcount_t usage;
- /**
- * @num_rules: Number of non-overlapping (i.e. not for
- * the same object) rules in this ruleset.
- */
- u32 num_rules;
- /**
- * @num_layers: Number of layers that are used in this
- * ruleset. This enables to check that all the layers
- * allow an access request. A value of 0 identifies a
- * non-merged ruleset (i.e. not a domain).
- */
- u32 num_layers;
- /**
- * @quiet_masks: Stores the quiet flags for an unmerged
- * ruleset. For a merged domain, this is stored in each
- * layer's struct landlock_hierarchy instead.
- */
- struct access_masks quiet_masks;
- /**
- * @access_masks: Contains the subset of filesystem and
- * network actions that are restricted by a ruleset.
- * A domain saves all layers of merged rulesets in a
- * stack (FAM), starting from the first layer to the
- * last one. These layers are used when merging
- * rulesets, for user space backward compatibility
- * (i.e. future-proof), and to properly handle merged
- * rulesets without overlapping access rights. These
- * layers are set once and never changed for the
- * lifetime of the ruleset.
- */
- struct access_masks access_masks[];
- };
- };
+ u32 num_rules;
+};
+
+/**
+ * struct landlock_ruleset - Landlock ruleset
+ *
+ * This data structure must contain unique entries, be updatable, and quick to
+ * match an object.
+ */
+struct landlock_ruleset {
+ /**
+ * @rules: Red-black tree storage for rules.
+ */
+ struct landlock_rules rules;
+ /**
+ * @lock: Protects against concurrent modifications of @rules, if @usage
+ * is greater than zero.
+ */
+ struct mutex lock;
+ /**
+ * @usage: Number of file descriptors referencing this ruleset.
+ */
+ refcount_t usage;
+
+#ifdef CONFIG_TRACEPOINTS
+ /**
+ * @version: Counter incremented on each successful
+ * landlock_add_rule(2), including when it only extends an existing
+ * rule's access rights. Used by tracepoints to correlate a domain with
+ * the exact ruleset state it was created from. Protected by @lock.
+ */
+ u32 version;
+ /**
+ * @id: Unique identifier for this ruleset, used for tracing.
+ */
+ u64 id;
+#endif /* CONFIG_TRACEPOINTS */
+
+ /**
+ * @quiet_masks: Stores the quiet flags for an unmerged ruleset. For a
+ * merged domain, this is stored in each layer's struct
+ * landlock_hierarchy instead.
+ */
+ struct access_masks quiet_masks;
+ /**
+ * @handled_masks: Contains the subset of filesystem and network actions
+ * that are handled by this ruleset.
+ */
+ struct access_masks handled_masks;
};
struct landlock_ruleset *
@@ -212,7 +199,6 @@ landlock_create_ruleset(const access_mask_t access_mask_fs,
const access_mask_t scope_mask);
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
-void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *,
if (!IS_ERR_OR_NULL(_T)) landlock_put_ruleset(_T))
@@ -221,110 +207,44 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
const struct landlock_id id,
const access_mask_t access, const u32 flags);
-struct landlock_ruleset *
-landlock_merge_ruleset(struct landlock_ruleset *const parent,
- struct landlock_ruleset *const ruleset);
+int landlock_store_rule(struct landlock_rules *const rules,
+ const struct landlock_id id,
+ const struct landlock_layer (*layers)[],
+ const size_t num_layers);
-const struct landlock_rule *
-landlock_find_rule(const struct landlock_ruleset *const ruleset,
- const struct landlock_id id);
-
-static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
-{
- if (ruleset)
- refcount_inc(&ruleset->usage);
-}
+void landlock_free_rules(struct landlock_rules *const rules);
/**
- * landlock_union_access_masks - Return all access rights handled in the
- * domain
+ * landlock_get_rule_root - Get the root of a rule tree by key type
*
- * @domain: Landlock ruleset (used as a domain)
+ * @rules: The rules storage to look up.
+ * @key_type: The type of key to select the tree for.
*
- * Return: An access_masks result of the OR of all the domain's access masks.
+ * Return: A pointer to the rb_root, or ERR_PTR(-EINVAL) on unknown type.
*/
-static inline struct access_masks
-landlock_union_access_masks(const struct landlock_ruleset *const domain)
+static inline struct rb_root *
+landlock_get_rule_root(struct landlock_rules *const rules,
+ const enum landlock_key_type key_type)
{
- union access_masks_all matches = {};
- size_t layer_level;
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ return &rules->root_inode;
- for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
- union access_masks_all layer = {
- .masks = domain->access_masks[layer_level],
- };
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ return &rules->root_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
- matches.all |= layer.all;
+ default:
+ WARN_ON_ONCE(1);
+ return ERR_PTR(-EINVAL);
}
-
- return matches.masks;
-}
-
-static inline void
-landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
- const access_mask_t fs_access_mask,
- const u16 layer_level)
-{
- access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
-
- /* Should already be checked in sys_landlock_create_ruleset(). */
- WARN_ON_ONCE(fs_access_mask != fs_mask);
- ruleset->access_masks[layer_level].fs |= fs_mask;
}
-static inline void
-landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
- const access_mask_t net_access_mask,
- const u16 layer_level)
-{
- access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET;
-
- /* Should already be checked in sys_landlock_create_ruleset(). */
- WARN_ON_ONCE(net_access_mask != net_mask);
- ruleset->access_masks[layer_level].net |= net_mask;
-}
-
-static inline void
-landlock_add_scope_mask(struct landlock_ruleset *const ruleset,
- const access_mask_t scope_mask, const u16 layer_level)
-{
- access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE;
-
- /* Should already be checked in sys_landlock_create_ruleset(). */
- WARN_ON_ONCE(scope_mask != mask);
- ruleset->access_masks[layer_level].scope |= mask;
-}
-
-static inline access_mask_t
-landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
- const u16 layer_level)
-{
- /* Handles all initially denied by default access rights. */
- return ruleset->access_masks[layer_level].fs |
- _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
-}
-
-static inline access_mask_t
-landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
- const u16 layer_level)
-{
- return ruleset->access_masks[layer_level].net;
-}
-
-static inline access_mask_t
-landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
- const u16 layer_level)
+static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
{
- return ruleset->access_masks[layer_level].scope;
+ if (ruleset)
+ refcount_inc(&ruleset->usage);
}
-bool landlock_unmask_layers(const struct landlock_rule *const rule,
- struct layer_masks *masks);
-
-access_mask_t
-landlock_init_layer_masks(const struct landlock_ruleset *const domain,
- const access_mask_t access_request,
- struct layer_masks *masks,
- const enum landlock_key_type key_type);
-
#endif /* _SECURITY_LANDLOCK_RULESET_H */
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index 36b02892c62f..1d02d57f4c48 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -22,6 +22,7 @@
#include <linux/mount.h>
#include <linux/path.h>
#include <linux/sched.h>
+#include <linux/sched/signal.h>
#include <linux/security.h>
#include <linux/stddef.h>
#include <linux/syscalls.h>
@@ -38,6 +39,8 @@
#include "setup.h"
#include "tsync.h"
+#include <trace/events/landlock.h>
+
static bool is_initialized(void)
{
if (likely(landlock_initialized))
@@ -169,7 +172,7 @@ static const struct file_operations ruleset_fops = {
* 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 = 10;
+const int landlock_abi_version = 11;
/**
* sys_landlock_create_ruleset - Create a new ruleset
@@ -281,6 +284,15 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
ruleset->quiet_masks.net = ruleset_attr.quiet_access_net;
ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped;
+ /*
+ * Emits before anon_inode_getfd() installs the file descriptor, while
+ * the ruleset is still private to this thread: no lock is needed, and
+ * the event cannot race a concurrent close() freeing the ruleset under
+ * the tracepoint's BTF read. This is the last point at which the
+ * ruleset is guaranteed alive and unshared.
+ */
+ trace_landlock_create_ruleset(ruleset);
+
/* Creates anonymous FD referring to the ruleset. */
ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops,
ruleset, O_RDWR | O_CLOEXEC);
@@ -308,8 +320,6 @@ static struct landlock_ruleset *get_ruleset_from_fd(const int fd,
if (!(fd_file(ruleset_f)->f_mode & mode))
return ERR_PTR(-EPERM);
ruleset = fd_file(ruleset_f)->private_data;
- if (WARN_ON_ONCE(ruleset->num_layers != 1))
- return ERR_PTR(-EINVAL);
landlock_get_ruleset(ruleset);
return ruleset;
}
@@ -367,7 +377,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
- mask = ruleset->access_masks[0].fs;
+ mask = ruleset->handled_masks.fs;
if ((path_beneath_attr.allowed_access | mask) != mask)
return -EINVAL;
@@ -408,7 +418,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
return -ENOMSG;
/* Checks that allowed_access matches the @ruleset constraints. */
- mask = landlock_get_net_access_mask(ruleset, 0);
+ mask = ruleset->handled_masks.net;
if ((net_port_attr.allowed_access | mask) != mask)
return -EINVAL;
@@ -502,21 +512,28 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
* - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON
* - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF
* - %LANDLOCK_RESTRICT_SELF_TSYNC
+ * - %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS
*
* 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.
*
+ * With %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, the no_new_privs attribute of the
+ * calling thread is set only once the enforcement of the ruleset succeeded,
+ * which fulfills the above requirement: no_new_privs is set if and only if the
+ * call succeeds.
+ *
* 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 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
- * current thread is not running with no_new_privs, or it doesn't have
- * %CAP_SYS_ADMIN in its namespace.
+ * - %EPERM: @ruleset_fd has no read access to the underlying ruleset, or
+ * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS is not set while the current thread
+ * is not running with no_new_privs and doesn't have %CAP_SYS_ADMIN in its
+ * namespace.
* - %E2BIG: The maximum number of stacked rulesets is reached for the current
* thread.
*
@@ -527,26 +544,30 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
flags)
{
struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
+ struct landlock_domain *new_dom = NULL;
struct cred *new_cred;
struct landlock_cred_security *new_llcred;
+ bool process_wide;
bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
prev_log_subdomains;
if (!is_initialized())
return -EOPNOTSUPP;
+ if ((flags | LANDLOCK_MASK_RESTRICT_SELF) !=
+ LANDLOCK_MASK_RESTRICT_SELF)
+ return -EINVAL;
+
/*
* Similar checks as for seccomp(2), except that an -EPERM may be
- * returned.
+ * returned. LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS fulfills this
+ * requirement.
*/
- if (!task_no_new_privs(current) &&
+ if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) &&
+ !task_no_new_privs(current) &&
!ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
return -EPERM;
- if ((flags | LANDLOCK_MASK_RESTRICT_SELF) !=
- LANDLOCK_MASK_RESTRICT_SELF)
- return -EINVAL;
-
/* Translates "off" flag to boolean. */
log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
/* Translates "on" flag to boolean. */
@@ -576,11 +597,11 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
new_llcred = landlock_cred(new_cred);
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
prev_log_subdomains = !new_llcred->log_subdomains_off;
new_llcred->log_subdomains_off = !prev_log_subdomains ||
!log_subdomains;
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/*
* The only case when a ruleset may not be set is if
@@ -595,37 +616,91 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
* manipulating the current credentials because they are
* dedicated per thread.
*/
- struct landlock_ruleset *const new_dom =
- landlock_merge_ruleset(new_llcred->domain, ruleset);
+ mutex_lock(&ruleset->lock);
+ new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset);
if (IS_ERR(new_dom)) {
+ mutex_unlock(&ruleset->lock);
abort_creds(new_cred);
return PTR_ERR(new_dom);
}
+ /*
+ * Emits the domain-creation event while @ruleset->lock is still
+ * held, right after the merge, so an eBPF program attached to
+ * the tracepoint reads the exact ruleset that was merged into
+ * the domain: a consistent snapshot that a concurrent
+ * landlock_add_rule() (which holds the same lock) cannot
+ * modify.
+ *
+ * This must come before the thread-sync wait below. Holding
+ * @ruleset->lock across landlock_restrict_sibling_threads()
+ * would hang: a sibling thread blocked in landlock_add_rule()
+ * on the same @ruleset->lock cannot run the task_work that
+ * thread-sync waits for (the lock wait is uninterruptible).
+ * Emitting here keeps the lock off the thread-sync path.
+ *
+ * The trade-off is that the event fires for a domain that a
+ * later (rare) thread-sync failure aborts. That path emits the
+ * matching free_domain event so the create/free pair stays
+ * balanced (see the thread-sync error path below).
+ */
+ trace_landlock_create_domain(new_dom, ruleset);
+ mutex_unlock(&ruleset->lock);
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
new_dom->hierarchy->log_same_exec = log_same_exec;
new_dom->hierarchy->log_new_exec = log_new_exec;
+ /*
+ * The creation event fired above, so move the domain out of
+ * LANDLOCK_LOG_UNCOMMITTED: its free_domain event must fire
+ * too, even if a thread-sync failure aborts it below. Audit
+ * logging may still be disabled (DISABLED); tracing observes it
+ * anyway.
+ */
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
-#endif /* CONFIG_AUDIT */
+ else
+ new_dom->hierarchy->log_status = LANDLOCK_LOG_PENDING;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
/* Replaces the old (prepared) domain. */
- landlock_put_ruleset(new_llcred->domain);
+ landlock_put_domain(new_llcred->domain);
new_llcred->domain = new_dom;
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
const int err = landlock_restrict_sibling_threads(
- current_cred(), new_cred);
+ current_cred(), new_cred, flags);
if (err) {
+ /*
+ * Thread-sync failed (rare), so the new domain is
+ * aborted instead of committed. Its creation event
+ * already fired above, so the imminent free must emit
+ * the matching free_domain event to keep the
+ * create/free pair balanced; no special log_status is
+ * set here.
+ */
abort_creds(new_cred);
return err;
}
}
- return commit_creds(new_cred);
+ /* Sets no_new_privs past the last point of failure. */
+ if (flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)
+ task_set_no_new_privs(current);
+
+ /* Whole process: thread-sync swept siblings, or single-threaded. */
+ process_wide = (flags & LANDLOCK_RESTRICT_SELF_TSYNC) ||
+ get_nr_threads(current) == 1;
+ commit_creds(new_cred);
+
+ /* The caller commits last, so its event concludes the operation. */
+ if (ruleset)
+ trace_landlock_enforce_domain(new_dom, true, process_wide,
+ task_no_new_privs(current));
+
+ return 0;
}
diff --git a/security/landlock/task.c b/security/landlock/task.c
index 360d226d0f51..4491ce31ae04 100644
--- a/security/landlock/task.c
+++ b/security/landlock/task.c
@@ -20,11 +20,11 @@
#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 "log.h"
#include "ruleset.h"
#include "setup.h"
#include "task.h"
@@ -41,8 +41,8 @@
* 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)
+static bool domain_scope_le(const struct landlock_domain *const parent,
+ const struct landlock_domain *const child)
{
const struct landlock_hierarchy *walker;
@@ -63,8 +63,8 @@ static bool domain_scope_le(const struct landlock_ruleset *const parent,
return false;
}
-static int domain_ptrace(const struct landlock_ruleset *const parent,
- const struct landlock_ruleset *const child)
+static int domain_ptrace(const struct landlock_domain *const parent,
+ const struct landlock_domain *const child)
{
if (domain_scope_le(parent, child))
return 0;
@@ -88,6 +88,7 @@ static int hook_ptrace_access_check(struct task_struct *const child,
const unsigned int mode)
{
const struct landlock_cred_security *parent_subject;
+ u64 tracee_domain_id = 0;
int err;
/* Quick return for non-landlocked tasks. */
@@ -96,9 +97,13 @@ static int hook_ptrace_access_check(struct task_struct *const child,
return 0;
scoped_guard(rcu) {
- const struct landlock_ruleset *const child_dom =
+ const struct landlock_domain *const child_dom =
landlock_get_task_domain(child);
err = domain_ptrace(parent_subject->domain, child_dom);
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ if (child_dom)
+ tracee_domain_id = child_dom->hierarchy->id;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
if (!err)
@@ -116,6 +121,7 @@ static int hook_ptrace_access_check(struct task_struct *const child,
.u.tsk = child,
},
.layer_plus_one = parent_subject->domain->num_layers,
+ .other_domain_id = tracee_domain_id,
});
return err;
@@ -135,7 +141,8 @@ static int hook_ptrace_access_check(struct task_struct *const child,
static int hook_ptrace_traceme(struct task_struct *const parent)
{
const struct landlock_cred_security *parent_subject;
- const struct landlock_ruleset *child_dom;
+ const struct landlock_domain *child_dom;
+ u64 tracee_domain_id = 0;
int err;
child_dom = landlock_get_current_domain();
@@ -147,6 +154,12 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
if (!err)
return 0;
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ /* The tracee is the current task; its domain is stable here. */
+ if (child_dom)
+ tracee_domain_id = child_dom->hierarchy->id;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+
/*
* 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
@@ -161,6 +174,7 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
.u.tsk = current,
},
.layer_plus_one = parent_subject->domain->num_layers,
+ .other_domain_id = tracee_domain_id,
});
return err;
}
@@ -176,8 +190,8 @@ static int hook_ptrace_traceme(struct task_struct *const parent)
* 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,
+static bool domain_is_scoped(const struct landlock_domain *const client,
+ const struct landlock_domain *const server,
access_mask_t scope)
{
int client_layer, server_layer;
@@ -236,13 +250,28 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client,
}
static bool sock_is_scoped(struct sock *const other,
- const struct landlock_ruleset *const domain)
+ const struct landlock_domain *const domain,
+ u64 *const peer_domain_id)
{
- const struct landlock_ruleset *dom_other;
+ const struct landlock_domain *dom_other;
/* The credentials will not change. */
lockdep_assert_held(&unix_sk(other)->lock);
+
+ /*
+ * A live kernel socket (e.g. from sock_create_kern()) has no backing
+ * file, hence no Landlock domain, so treat it as unscoped. The
+ * sk_socket check only guards that dereference; sk_socket is NULL
+ * solely for a dead peer, which the caller already excludes under the
+ * held lock, so no separate SOCK_DEAD check is needed.
+ */
+ if (unlikely(!other->sk_socket || !other->sk_socket->file))
+ return false;
+
dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain;
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
return domain_is_scoped(domain, dom_other,
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
}
@@ -270,6 +299,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
struct sock *const newsk)
{
size_t handle_layer;
+ u64 peer_domain_id = 0;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), unix_scope,
&handle_layer);
@@ -281,7 +311,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
if (!is_abstract_socket(other))
return 0;
- if (!sock_is_scoped(other, subject->domain))
+ if (!sock_is_scoped(other, subject->domain, &peer_domain_id))
return 0;
landlock_log_denial(subject, &(struct landlock_request) {
@@ -293,6 +323,7 @@ static int hook_unix_stream_connect(struct sock *const sock,
},
},
.layer_plus_one = handle_layer + 1,
+ .other_domain_id = peer_domain_id,
});
return -EPERM;
}
@@ -301,6 +332,7 @@ static int hook_unix_may_send(struct socket *const sock,
struct socket *const other)
{
size_t handle_layer;
+ u64 peer_domain_id = 0;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), unix_scope,
&handle_layer);
@@ -318,7 +350,7 @@ static int hook_unix_may_send(struct socket *const sock,
if (!is_abstract_socket(other->sk))
return 0;
- if (!sock_is_scoped(other->sk, subject->domain))
+ if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id))
return 0;
landlock_log_denial(subject, &(struct landlock_request) {
@@ -330,6 +362,7 @@ static int hook_unix_may_send(struct socket *const sock,
},
},
.layer_plus_one = handle_layer + 1,
+ .other_domain_id = peer_domain_id,
});
return -EPERM;
}
@@ -344,6 +377,7 @@ static int hook_task_kill(struct task_struct *const p,
{
bool is_scoped;
size_t handle_layer;
+ u64 target_domain_id = 0;
const struct landlock_cred_security *subject;
if (!cred) {
@@ -370,9 +404,15 @@ static int hook_task_kill(struct task_struct *const p,
return 0;
scoped_guard(rcu) {
- is_scoped = domain_is_scoped(subject->domain,
- landlock_get_task_domain(p),
+ const struct landlock_domain *const other =
+ landlock_get_task_domain(p);
+
+ is_scoped = domain_is_scoped(subject->domain, other,
signal_scope.scope);
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ if (other)
+ target_domain_id = other->hierarchy->id;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
if (!is_scoped)
@@ -385,6 +425,7 @@ static int hook_task_kill(struct task_struct *const p,
.u.tsk = p,
},
.layer_plus_one = handle_layer + 1,
+ .other_domain_id = target_domain_id,
});
return -EPERM;
}
@@ -394,6 +435,7 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
{
const struct landlock_cred_security *subject;
bool is_scoped = false;
+ u64 target_domain_id = 0;
/* Lock already held by send_sigio() and send_sigurg(). */
lockdep_assert_held(&fown->lock);
@@ -421,9 +463,15 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
return 0;
scoped_guard(rcu) {
- is_scoped = domain_is_scoped(subject->domain,
- landlock_get_task_domain(tsk),
+ const struct landlock_domain *const other =
+ landlock_get_task_domain(tsk);
+
+ is_scoped = domain_is_scoped(subject->domain, other,
signal_scope.scope);
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
+ if (other)
+ target_domain_id = other->hierarchy->id;
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
}
if (!is_scoped)
@@ -435,9 +483,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
.type = LSM_AUDIT_DATA_TASK,
.u.tsk = tsk,
},
-#ifdef CONFIG_AUDIT
+#ifdef CONFIG_SECURITY_LANDLOCK_LOG
.layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
-#endif /* CONFIG_AUDIT */
+#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
+ .other_domain_id = target_domain_id,
});
return -EPERM;
}
diff --git a/security/landlock/trace.c b/security/landlock/trace.c
new file mode 100644
index 000000000000..2ea7aac8d75d
--- /dev/null
+++ b/security/landlock/trace.c
@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock - Tracepoint helpers
+ *
+ * Copyright © 2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/dcache.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/lsm_audit.h>
+#include <net/sock.h>
+
+#include "access.h"
+#include "domain.h"
+#include "fs.h"
+#include "log.h"
+#include "ruleset.h"
+#include "trace.h"
+
+/*
+ * Generates the tracepoint definitions in this translation unit. The trace
+ * event header dereferences the traced objects in TP_fast_assign, so the full
+ * struct definitions (e.g. ruleset.h, domain.h) must be included before it.
+ */
+#define CREATE_TRACE_POINTS
+#include <trace/events/landlock.h>
+
+/**
+ * landlock_trace_free_domain - Emit a tracepoint on domain deallocation
+ *
+ * @hierarchy: The domain's hierarchy being deallocated.
+ *
+ * Fires only for a hierarchy whose creation event was emitted, i.e. one that
+ * left LANDLOCK_LOG_UNCOMMITTED in landlock_restrict_self(). This keeps the
+ * create/free pair balanced: a hierarchy that never became observable is freed
+ * silently, while a domain that landlock_restrict_self() created and a
+ * thread-sync failure then aborted still fires free_domain, because its
+ * creation event already fired.
+ *
+ * Called from landlock_log_free_domain().
+ */
+void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy)
+{
+ /*
+ * The log_status read is a correctness guard (keep the create/free pair
+ * balanced), not a cost guard, so this cold path needs no
+ * trace_..._enabled() check: the tracepoint is a static-branch no-op
+ * when disabled. The denial path guards trace_..._enabled() instead
+ * because it does expensive __getname()/path work before emitting.
+ */
+ if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_UNCOMMITTED)
+ trace_landlock_free_domain(hierarchy);
+}
+
+/**
+ * landlock_trace_denial - Emit a tracepoint for a denied access request
+ *
+ * @request: Detail of the user space request.
+ * @youngest_denied: The youngest hierarchy node that denied the access.
+ * @missing: The set of denied access rights.
+ * @same_exec: Whether the current task is the same executable that called
+ * landlock_restrict_self() for the denying domain, as computed
+ * by landlock_log_denial().
+ * @logged: Whether the domain's policy selects this denial for logging, as
+ * computed by landlock_log_denial().
+ *
+ * Emits the tracepoint matching @request->type when its event is enabled.
+ * Unlike audit, fires regardless of @logged; the value is recorded in the event
+ * so consumers can filter on it.
+ *
+ * Called from landlock_log_denial().
+ */
+void landlock_trace_denial(
+ const struct landlock_request *const request,
+ const struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool same_exec, const bool logged)
+{
+ switch (request->type) {
+ case LANDLOCK_REQUEST_FS_ACCESS:
+ case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
+ if (trace_landlock_deny_access_fs_enabled()) {
+ char *buf __free(__putname) = __getname();
+ struct path dentry_path;
+ const char *pathname;
+ const struct path *path = NULL;
+
+ /*
+ * Selects the path from the audit data type, as
+ * dump_common_audit_data() does. A FS_ACCESS denial
+ * carries a file (hook_file_truncate) or an ioctl op
+ * (hook_file_ioctl) rather than a path;
+ * FS_CHANGE_TOPOLOGY carries a path or a bare dentry.
+ * Reading the wrong union member would dereference
+ * garbage, so every reachable type is handled here.
+ */
+ switch (request->audit.type) {
+ case LSM_AUDIT_DATA_FILE:
+ path = &request->audit.u.file->f_path;
+ break;
+ case LSM_AUDIT_DATA_IOCTL_OP:
+ path = &request->audit.u.op->path;
+ break;
+ case LSM_AUDIT_DATA_DENTRY:
+ /*
+ * Build a path on the stack with the real
+ * dentry so TP_fast_assign can extract dev and
+ * ino; the mnt field is unused there.
+ */
+ dentry_path = (struct path){
+ .dentry = request->audit.u.dentry,
+ };
+ path = &dentry_path;
+ break;
+ case LSM_AUDIT_DATA_PATH:
+ path = &request->audit.u.path;
+ break;
+ default:
+ WARN_ONCE(1,
+ "Unhandled Landlock FS audit type %d",
+ request->audit.type);
+ break;
+ }
+
+ if (!path)
+ break;
+
+ if (!buf) {
+ pathname = "<no_mem>";
+ } else if (request->audit.type ==
+ LSM_AUDIT_DATA_DENTRY) {
+ /* No vfsmount: render the dentry path alone. */
+ pathname = dentry_path_raw(
+ request->audit.u.dentry, buf, PATH_MAX);
+ if (IS_ERR(pathname))
+ pathname =
+ PTR_ERR(pathname) ==
+ -ENAMETOOLONG ?
+ "<too_long>" :
+ "<unreachable>";
+ } else {
+ pathname = resolve_path_for_trace(path, buf);
+ }
+
+ trace_landlock_deny_access_fs(youngest_denied,
+ same_exec, logged,
+ missing, path, pathname);
+ }
+ break;
+ case LANDLOCK_REQUEST_NET_ACCESS:
+ if (trace_landlock_deny_access_net_enabled())
+ trace_landlock_deny_access_net(
+ youngest_denied, same_exec, logged, missing,
+ request->audit.u.net->sk,
+ ntohs(request->audit.u.net->sport),
+ ntohs(request->audit.u.net->dport));
+ break;
+ case LANDLOCK_REQUEST_PTRACE:
+ if (trace_landlock_deny_ptrace_enabled())
+ trace_landlock_deny_ptrace(youngest_denied, same_exec,
+ logged,
+ request->other_domain_id,
+ request->audit.u.tsk);
+ break;
+ case LANDLOCK_REQUEST_SCOPE_SIGNAL:
+ if (trace_landlock_deny_scope_signal_enabled())
+ trace_landlock_deny_scope_signal(
+ youngest_denied, same_exec, logged,
+ request->other_domain_id, request->audit.u.tsk);
+ break;
+ case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
+ if (trace_landlock_deny_scope_abstract_unix_socket_enabled())
+ trace_landlock_deny_scope_abstract_unix_socket(
+ youngest_denied, same_exec, logged,
+ request->other_domain_id,
+ request->audit.u.net->sk);
+ break;
+ default:
+ WARN_ONCE(1, "Unhandled Landlock request type %d",
+ request->type);
+ break;
+ }
+}
diff --git a/security/landlock/trace.h b/security/landlock/trace.h
new file mode 100644
index 000000000000..7be98e748855
--- /dev/null
+++ b/security/landlock/trace.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock - Tracepoint helpers
+ *
+ * Copyright © 2025 Microsoft Corporation
+ * Copyright © 2026 Cloudflare, Inc.
+ */
+
+#ifndef _SECURITY_LANDLOCK_TRACE_H
+#define _SECURITY_LANDLOCK_TRACE_H
+
+#include "access.h"
+
+struct landlock_hierarchy;
+struct landlock_request;
+
+#ifdef CONFIG_TRACEPOINTS
+
+void landlock_trace_free_domain(
+ const struct landlock_hierarchy *const hierarchy);
+
+void landlock_trace_denial(
+ const struct landlock_request *const request,
+ const struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool same_exec, const bool logged);
+
+#else /* CONFIG_TRACEPOINTS */
+
+static inline void
+landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy)
+{
+}
+
+static inline void
+landlock_trace_denial(const struct landlock_request *const request,
+ const struct landlock_hierarchy *const youngest_denied,
+ const access_mask_t missing, const bool same_exec,
+ const bool logged)
+{
+}
+
+#endif /* CONFIG_TRACEPOINTS */
+
+#endif /* _SECURITY_LANDLOCK_TRACE_H */
diff --git a/security/landlock/tsync.c b/security/landlock/tsync.c
index c5730bbd9ed3..cfa73a2a67ca 100644
--- a/security/landlock/tsync.c
+++ b/security/landlock/tsync.c
@@ -17,10 +17,13 @@
#include <linux/sched/task.h>
#include <linux/slab.h>
#include <linux/task_work.h>
+#include <uapi/linux/landlock.h>
#include "cred.h"
#include "tsync.h"
+#include <trace/events/landlock.h>
+
/*
* Shared state between multiple threads which are enforcing Landlock rulesets
* in lockstep with each other.
@@ -78,6 +81,8 @@ struct tsync_work {
*/
static void restrict_one_thread(struct tsync_shared_context *ctx)
{
+ const struct landlock_domain *new_dom =
+ landlock_cred(ctx->new_cred)->domain;
int err;
struct cred *cred = NULL;
@@ -146,6 +151,18 @@ static void restrict_one_thread(struct tsync_shared_context *ctx)
commit_creds(cred);
+ /*
+ * Emitted strictly after commit_creds() and before the out: label, so
+ * it fires only for a thread now enforcing new_dom, and every
+ * non-concluding (complete == false) event happens-before the
+ * operation's single concluding one. Skipped on the flags-only path,
+ * where old_cred and new_cred carry the same domain. A sibling never
+ * concludes the operation and its enforcement is always process-wide.
+ */
+ if (new_dom != landlock_cred(ctx->old_cred)->domain)
+ trace_landlock_enforce_domain(new_dom, false, true,
+ task_no_new_privs(current));
+
out:
/* Notify the calling thread once all threads are done */
if (atomic_dec_return(&ctx->num_unfinished) == 0)
@@ -466,7 +483,8 @@ static void cancel_tsync_works(const struct tsync_works *works,
* 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)
+ const struct cred *new_cred,
+ const u32 restrict_flags)
{
int err;
struct tsync_shared_context shared_ctx;
@@ -481,7 +499,9 @@ int landlock_restrict_sibling_threads(const struct cred *old_cred,
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);
+ shared_ctx.set_no_new_privs =
+ (restrict_flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) ||
+ task_no_new_privs(current);
/*
* Serialize concurrent TSYNC operations to prevent deadlocks when
diff --git a/security/landlock/tsync.h b/security/landlock/tsync.h
index ef86bb61c2f6..2ae4f938ca00 100644
--- a/security/landlock/tsync.h
+++ b/security/landlock/tsync.h
@@ -9,8 +9,10 @@
#define _SECURITY_LANDLOCK_TSYNC_H
#include <linux/cred.h>
+#include <linux/types.h>
int landlock_restrict_sibling_threads(const struct cred *old_cred,
- const struct cred *new_cred);
+ const struct cred *new_cred,
+ u32 restrict_flags);
#endif /* _SECURITY_LANDLOCK_TSYNC_H */
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 035aaf113d1d..e5e17f100aae 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -7267,24 +7267,6 @@ static int selinux_bpf_prog(struct bpf_prog *prog)
BPF__PROG_RUN, NULL);
}
-static u32 selinux_bpffs_creator_sid(u32 fd)
-{
- struct path path;
- struct super_block *sb;
- struct superblock_security_struct *sbsec;
-
- CLASS(fd, f)(fd);
-
- if (fd_empty(f))
- return SECSID_NULL;
-
- path = fd_file(f)->f_path;
- sb = path.dentry->d_sb;
- sbsec = selinux_superblock(sb);
-
- return sbsec->creator_sid;
-}
-
static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
struct bpf_token *token, bool kernel)
{
@@ -7297,7 +7279,7 @@ static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
if (!token)
ssid = bpfsec->sid;
else
- ssid = selinux_bpffs_creator_sid(attr->map_token_fd);
+ ssid = selinux_bpf_token_security(token)->grantor_sid;
return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__MAP_CREATE,
NULL);
@@ -7315,7 +7297,7 @@ static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
if (!token)
ssid = bpfsec->sid;
else
- ssid = selinux_bpffs_creator_sid(attr->prog_token_fd);
+ ssid = selinux_bpf_token_security(token)->grantor_sid;
return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_LOAD,
NULL);
@@ -7329,12 +7311,14 @@ static int selinux_bpf_token_create(struct bpf_token *token,
const struct path *path)
{
struct bpf_security_struct *bpfsec;
- u32 sid = selinux_bpffs_creator_sid(attr->token_create.bpffs_fd);
+ struct superblock_security_struct *sbsec;
int err;
+ sbsec = selinux_superblock(path->dentry->d_sb);
+
bpfsec = selinux_bpf_token_security(token);
bpfsec->sid = current_sid();
- bpfsec->grantor_sid = sid;
+ bpfsec->grantor_sid = sbsec->creator_sid;
bpfsec->perms = 0;
/**
@@ -7343,15 +7327,15 @@ static int selinux_bpf_token_create(struct bpf_token *token,
* 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);
+ err = avc_has_perm(bpfsec->sid, bpfsec->grantor_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);
+ err = avc_has_perm(bpfsec->sid, bpfsec->grantor_sid,
+ SECCLASS_BPF, BPF__PROG_LOAD_AS, NULL);
if (err)
return err;
bpfsec->perms |= BPF__PROG_LOAD;