Linux Security Modules development
 help / color / mirror / Atom feed
* Re: [RFC PATCH] ipe: support multiple BPF integrity verification LSMs
From: Fan Wu @ 2026-05-24  0:39 UTC (permalink / raw)
  To: Paul Moore; +Cc: linux-security-module, wufan, bboscaccy
In-Reply-To: <20260523200859.13527-2-paul@paul-moore.com>

On Sat, May 23, 2026 at 1:09 PM Paul Moore <paul@paul-moore.com> wrote:
>
> Currently IPE always records the last BPF integrity verification verdict,
> which is reasonable with only a single BPF verification LSM, but it
> becomes problematic when multiple mechanisms end up submitting BPF
> program integrity verdicts.
>
> This patch updates IPE to record all of the received BPF program
> integrity verdicts, along with their associated LSM IDs, ultimately using
> the "worst" verdict in the policy enforcement engine.  Policy support for
> selecting individual integrity verdicts was intentionally omitted from
> this patch to keep things simple both from a code and policy developer
> perspective, however future work to add selector support should be
> trivial.
>
> Signed-off-by: Paul Moore <paul@paul-moore.com>
> ---

I would say the current code is fine because there is only one provider.

The verdicts for different LSMs may have different semantics,
therefore I would suggest the second verdict provider should extend
the policy to provide a rule property like "LSM=hornet" to
differentiate the integrity provider. However this will need a major
parser and policy validation refactoring with a proper documentation
of the policy semantics and use case examples.

-Fan

^ permalink raw reply

* Re: [PATCH bpf-next 09/13] lsm: add bpf_prog_load_post_integrity hook
From: Paul Moore @ 2026-05-24  0:55 UTC (permalink / raw)
  To: KP Singh
  Cc: linux-security-module, bpf, ast, daniel, memxor, James.Bottomley,
	bboscaccy, Fan Wu
In-Reply-To: <20260522023234.3778588-10-kpsingh@kernel.org>

On Thu, May 21, 2026 at 10:32 PM KP Singh <kpsingh@kernel.org> wrote:
>
> Add a companion to security_bpf_prog_load. The existing hook fires
> at PROG_LOAD entry where the verdict is at most BPF_SIG_OK; the new
> hook fires from bpf_loader_verify_metadata after the in-kernel
> metadata check, just before sig.verdict is promoted to
> BPF_SIG_METADATA_VERIFIED. Policy LSMs that want to gate on
> metadata verification (not just signature presence) register here.
>
> Signed-off-by: KP Singh <kpsingh@kernel.org>
> ---
>  include/linux/lsm_hook_defs.h |  1 +
>  include/linux/security.h      |  6 ++++++
>  security/security.c           | 17 +++++++++++++++++
>  3 files changed, 24 insertions(+)

...

> +/**
> + * security_bpf_prog_load_post_integrity() - Notify LSMs that a signed loader
> + * has just verified its metadata map.
> + * @prog: the loader BPF program whose metadata check passed.
> + *
> + * Invoked by bpf_loader_verify_metadata() after the kernel-side hash check
> + * succeeds, before prog->aux->sig_verdict is promoted to
> + * BPF_SIG_METADATA_VERIFIED. A non-zero return aborts the kfunc and leaves
> + * the verdict at BPF_SIG_OK.
> + *
> + * Return: 0 on success, negative errno to deny.
> + */
> +int security_bpf_prog_load_post_integrity(struct bpf_prog *prog)
> +{
> +       return call_int_hook(bpf_prog_load_post_integrity, prog);
> +}

Since you're using essentially the same LSM infrastructure and IPE
work that Blaise, Fan, and I developed for policy-based enforcement of
BPF signature verification, perhaps this is where we can find some
common ground to start working together once again.

I would be happy to support and maintain a
security_bpf_prog_load_post_integrity() kfunc wrapper as part of the
LSM framework, similar to what the VFS folks do with
fs/bpf_fs_kfuncs.c, so that either a lskel loader, or a BPF LSM
program if you like, could register a BPF integrity verification
verdict with the LSM.  This would provide a single unified approach
for LSMs, including BPF LSMs, to build their BPF program integrity
controls regardless of what the system builder, or admin, chooses for
a BPF signature verification scheme: the loader based scheme you
developed, or Hornet.

There is no technical reason we can't support these things, e.g.
multiple coexisting verification schemes supported by a single LSM
enforcement interface, we just need to be willing to accept that we
have different needs and show a willingness to accept different
solutions as a result.

-- 
paul-moore.com

^ permalink raw reply

* Re: [PATCH v8 1/9] landlock: Add a place for flags to layer rules
From: Tingmao Wang @ 2026-05-24  1:29 UTC (permalink / raw)
  To: Mickaël Salaün, Günther Noack
  Cc: Justin Suess, Jan Kara, Abhinav Saxena, linux-security-module
In-Reply-To: <20260523.Uephughee8as@digikod.net>

On 5/23/26 21:48, Mickaël Salaün wrote:
> This patch doesn't build.

Missed a hunk in this patch (ended up in the next one), will add.

>> @@ -797,20 +803,28 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
>>  	}
>>  
>>  	if (unlikely(dentry_child1)) {
>> +		/*
>> +		 * Get the layer masks for the child dentries for use by domain
>> +		 * check later.  The rule_flags for child1 should have been
>> +		 * included in rule_flags_parent1 already (cf.
>> +		 * collect_domain_accesses), and is not relevant for domain check,
>> +		 * so we don't have to pass it to landlock_unmask_layers.
>> +		 */
>>  		if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
>>  					      &_layer_masks_child1,
>>  					      LANDLOCK_KEY_INODE))
>>  			landlock_unmask_layers(find_rule(domain, dentry_child1),
>> -					       &_layer_masks_child1);
>> +					       &_layer_masks_child1, NULL);
>>  		layer_masks_child1 = &_layer_masks_child1;
>>  		child1_is_directory = d_is_dir(dentry_child1);
>>  	}
>>  	if (unlikely(dentry_child2)) {
>> +		/* See above comment for why NULL is passed as rule_flags_masks. */
> 
> rule_flags_masks doesn't exist.

I guess I was probably referring to the rule_flags argument - will fix.

>> [...]
>> @@ -647,9 +648,14 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
>>  	 */
>>  	for (size_t i = 0; i < rule->num_layers; i++) {
>>  		const struct landlock_layer *const layer = &rule->layers[i];
>> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
>>  
> 
>>  		/* Clear the bits where the layer in the rule grants access. */
>>  		masks->access[layer->level - 1] &= ~layer->access;
>> +
>> +		/* Collect rule flags for each layer. */
>> +		if (rule_flags && layer->flags.quiet)
>> +			rule_flags->quiet_masks |= layer_bit;
> 
> Why not store the quiet bit in masks?  That would not only be "access"
> bits anymore but it makes sense to store all this bits it the same
> place.
> 
> We should then probably rename struct layer_access_masks to just struct
> layer_masks.
> 
> We need to be careful to not increase too much the size of this struct
> though while keeping the [LANDLOCK_MAX_NUM_LAYERS] approach if possible
> (see Günther's commit that added it).

Most uses of struct layer_access_masks do not actually care about the rule
flags tho, e.g. in unmask_scoped_access, scope_to_request, or may_refer.
Such a rename would touch 31 places (and only a few of them would actually
touch the quiet flag).

If we want to refactor to make this be in the layer_access_masks (then
rename it), I guess there are 3 options, which do you prefer?

1. Add a u16 bitfield for which layers are quieted.  Future rule flags
   will be additional bitfields.  struct layer_masks becomes 68 bytes (+4).

struct layer_masks {
	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
	layer_mask_t quiet_layers;
};

2. Make the [LANDLOCK_MAX_NUM_LAYERS] array store both the access mask and
   the quiet bit (or more bits for future rule flags).  Size of struct stays
   the same.

static_assert(LANDLOCK_NUM_ACCESS_NET <= LANDLOCK_NUM_ACCESS_FS);
static_assert(LANDLOCK_NUM_SCOPE <= LANDLOCK_NUM_ACCESS_FS);
struct layer_mask {
	access_mask_t access:LANDLOCK_NUM_ACCESS_FS;
	bool quiet:1;
};
struct layer_masks {
	struct layer_mask layer[LANDLOCK_MAX_NUM_LAYERS];
};

   (Maybe we can just make struct layer_masks a typedef to
   layer_mask[LANDLOCK_MAX_NUM_LAYERS] instead?  But currently not sure if
   there are any gotchas with a typedef like that)

3. Mirror layer_access_masks::access[] - add a
   rule_flags[LANDLOCK_MAX_NUM_LAYERS] too.  struct layer_masks becomes 80
   bytes (+16).

struct rule_flags {
	bool quiet:1;
};
struct layer_masks {
	/**
	 * @access: The unfulfilled access rights for each layer.
	 */
	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
	struct rule_flags rule_flags[LANDLOCK_MAX_NUM_LAYERS];
};

(3 seems very wasteful to me)

^ permalink raw reply

* [PATCH net v2 0/4] net: trust-after-modification fixes for IPv4 options + netlabel
From: Qi Tang @ 2026-05-24  4:14 UTC (permalink / raw)
  To: davem, kuba, pabeni, edumazet
  Cc: netdev, fw, lyutoon, stable, Qi Tang, David Ahern, Ido Schimmel,
	Simon Horman, Paul Moore, Casey Schaufler, Huw Davies,
	linux-security-module

Four small bounds-check fixes for a recurring pattern in IPv4 options
and CIPSO/CALIPSO consumers.  The parse-time validator stores only
the option offset into IPCB / skb metadata.  Later consumers (cmsg
echo, mrouted report, netlabel getattr) re-read the length /
pointer / cat_len bytes from the skb body and use them for indexed
memcpy or bitmap walk.  An nftables payload mutation reachable from
an unprivileged user namespace (CAP_NET_ADMIN inside the namespace)
rewrites those bytes between parse and consume.

  1/4 __ip_options_echo()                40-byte stack OOB write
                                         (KASAN: stack-out-of-bounds,
                                         Write of size 255).
  2/4 ipmr_cache_report()                Up to 40-byte OOB read of
                                         skb head leaked into the
                                         IGMPMSG cmsg delivered to
                                         mrouted.
  3/4 netlbl_skbuff_getattr() / CALIPSO  ~232-byte slab OOB read
                                         driving SELinux MLS
                                         category bitmap.
  4/4 netlbl_skbuff_getattr() / CIPSO    Sibling of 3/4 on the
                                         AF_INET (CIPSO IPv4) path.

Florian Westphal's [PATCH net 05/10] netfilter: disable payload
mangling in userns blocks the unprivileged-userns side of nft
payload-set at the source:
  https://lore.kernel.org/netdev/20260522104257.2008-6-fw@strlen.de/
These four consumer-side bounds checks land in the same direction
as defense in depth, also covering root / CAP_NET_ADMIN nft
FORWARD payload mangling in the init userns and any non-nft
mutation path.

Changes v1 -> v2:
  - 3/4 + 4/4 return -EINVAL on bounds-check failure instead of
    falling through to netlbl_unlabel_getattr() (Paul Moore).
  - 3/4 commit message drops the "Smack" mention from the CALIPSO
    consume path; Smack does not currently consume CALIPSO (Casey
    Schaufler).
  - 4/4 inline comment explains the literal 8: CIPSO option header
    (type+len+DOI = 6) plus first tag header (type+len = 2) (Paul
    Moore).
  - All four pick up Cc: stable@vger.kernel.org.

v1: https://lore.kernel.org/netdev/20260514165139.436961-1-tpluszz77@gmail.com/

Qi Tang (4):
  ipv4: validate ip_options length in __ip_options_echo() against skb
    tail
  ipv4: ipmr: clamp ip_hdrlen against skb_headlen in ipmr_cache_report
  netlabel: validate CALIPSO option against skb tail in
    netlbl_skbuff_getattr
  netlabel: validate CIPSO option against skb tail in
    netlbl_skbuff_getattr

 net/ipv4/ip_options.c        |  8 ++++++++
 net/ipv4/ipmr.c              |  2 +-
 net/netlabel/netlabel_kapi.c | 32 ++++++++++++++++++++++++++++----
 3 files changed, 37 insertions(+), 5 deletions(-)

--
2.47.3

^ permalink raw reply

* [PATCH net v2 3/4] netlabel: validate CALIPSO option against skb tail in netlbl_skbuff_getattr
From: Qi Tang @ 2026-05-24  4:14 UTC (permalink / raw)
  To: davem, kuba, pabeni, edumazet
  Cc: netdev, fw, lyutoon, stable, Qi Tang, Paul Moore, Simon Horman,
	Huw Davies, linux-security-module
In-Reply-To: <20260524041442.2432071-1-tpluszz77@gmail.com>

netlbl_skbuff_getattr() locates the CALIPSO option in the IPv6 HBH
header via calipso_optptr() and hands the bare pointer to
calipso_getattr() -> calipso_opt_getattr().  The consumer re-reads
calipso[1] (option data length) and calipso[6] (cat_len/4) and walks
calipso + 10 for cat_len bytes via netlbl_bitmap_walk().

ipv6_hop_calipso() validates these bytes only at parse time inside
ipv6_parse_hopopts().  An nftables PRE_ROUTING payload write reachable
from an unprivileged user namespace can rewrite both bytes between
parse and the SELinux peer-label consume path
(selinux_sock_rcv_skb_compat -> selinux_netlbl_sock_rcv_skb ->
netlbl_skbuff_getattr).  The self-consistency check
(cat_len + 8 > len) inside calipso_opt_getattr() is defeated by
mutating both bytes consistently, allowing a ~232-byte
slab-out-of-bounds read from calipso + 10 whose set bits become MLS
categories driving the access decision.

netlbl_skbuff_getattr() has the skb; gate the consume on the option
fitting within skb_tail_pointer().  The IPv6 option layout is
type(1) + length(1) + length bytes of data, so requiring
ptr + 2 + ptr[1] <= skb_tail covers the option and its embedded
bitmap.  When the bounds check fails the packet has been mutated
after parse, so return -EINVAL rather than fall through to the
unlabeled path.

Runtime confirmation (SELinux compat path with selinux=1 enforcing=0
and a CALIPSO DOI added via netlabelctl): Udp6InDatagrams increments
to 1 with the mutated cat_len, showing
selinux_socket_sock_rcv_skb -> netlbl_skbuff_getattr ->
calipso_opt_getattr -> netlbl_bitmap_walk runs end-to-end past the
option's true bound; with this patch the consume path returns
-EINVAL at the bounds check and the counter stays 0.

Cc: stable@vger.kernel.org
Reported-by: Qi Tang <tpluszz77@gmail.com>
Reported-by: Tong Liu <lyutoon@gmail.com>
Fixes: 2917f57b6bc1 ("calipso: Allow the lsm to label the skbuff directly.")
Signed-off-by: Qi Tang <tpluszz77@gmail.com>
---
 net/netlabel/netlabel_kapi.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/net/netlabel/netlabel_kapi.c b/net/netlabel/netlabel_kapi.c
index 3583fa63dd01f..d0d6220b8d59d 100644
--- a/net/netlabel/netlabel_kapi.c
+++ b/net/netlabel/netlabel_kapi.c
@@ -1399,11 +1399,22 @@ int netlbl_skbuff_getattr(const struct sk_buff *skb,
 			return 0;
 		break;
 #if IS_ENABLED(CONFIG_IPV6)
-	case AF_INET6:
+	case AF_INET6: {
+		const unsigned char *tail = skb_tail_pointer(skb);
+		u8 opt_data_len;
+
 		ptr = calipso_optptr(skb);
-		if (ptr && calipso_getattr(ptr, secattr) == 0)
+		if (!ptr)
+			break;
+		if (ptr + 2 > tail)
+			return -EINVAL;
+		opt_data_len = ptr[1];	/* IPv6 option data length */
+		if (ptr + 2 + opt_data_len > tail)
+			return -EINVAL;
+		if (calipso_getattr(ptr, secattr) == 0)
 			return 0;
 		break;
+	}
 #endif /* IPv6 */
 	}
 
-- 
2.47.3


^ permalink raw reply related

* [PATCH net v2 4/4] netlabel: validate CIPSO option against skb tail in netlbl_skbuff_getattr
From: Qi Tang @ 2026-05-24  4:14 UTC (permalink / raw)
  To: davem, kuba, pabeni, edumazet
  Cc: netdev, fw, lyutoon, stable, Qi Tang, Paul Moore, Simon Horman,
	linux-security-module
In-Reply-To: <20260524041442.2432071-1-tpluszz77@gmail.com>

netlbl_skbuff_getattr() locates the CIPSO option in the IPv4 IP header
via cipso_v4_optptr() and hands the bare pointer to cipso_v4_getattr().
The consumer re-reads cipso[1] (option length), cipso[6] (tag type),
and then cipso_v4_parsetag_*() re-reads further bytes from the skb.

__ip_options_compile() validates these bytes only at parse time.  An
nftables LOCAL_IN payload write reachable from an unprivileged user
namespace can rewrite them after parse and before the SELinux/Smack
peer-label consume path (selinux_sock_rcv_skb_compat ->
selinux_netlbl_sock_rcv_skb -> netlbl_skbuff_getattr).  This is the
IPv4 analogue of the CALIPSO IPv6 trust-after-modification fixed in
the previous patch: the tag parsers walk the option using attacker-
controlled length bytes, producing slab-out-of-bounds reads whose
contents feed into the MLS access decision.

Validate the option fits within skb_tail_pointer(skb) before invoking
cipso_v4_getattr().  The pre-tag-walk guard "ptr + 8 > tail" covers
the CIPSO option header (type + length + DOI = 6 bytes) plus the
first tag header (type + length = 2 bytes), which are the bytes
cipso_v4_getattr() reads to dispatch on the tag.  When the bounds
check fails the packet has been mutated after parse, so return
-EINVAL rather than fall through to the unlabeled path.

Runtime confirmation (Smack peer-label policy + nft LOCAL_IN
mutation of tag_len): UdpInDatagrams increments to 1 and recvfrom
returns the payload, showing netlbl_skbuff_getattr ->
cipso_v4_getattr -> cipso_v4_parsetag_rbm -> netlbl_bitmap_walk runs
end-to-end past the option's true bound; with this patch the
consume path returns -EINVAL at the bounds check and the counter
stays 0.

Cc: stable@vger.kernel.org
Reported-by: Qi Tang <tpluszz77@gmail.com>
Reported-by: Tong Liu <lyutoon@gmail.com>
Fixes: 04f81f0154e4 ("cipso: don't use IPCB() to locate the CIPSO IP option")
Signed-off-by: Qi Tang <tpluszz77@gmail.com>
---
 net/netlabel/netlabel_kapi.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/net/netlabel/netlabel_kapi.c b/net/netlabel/netlabel_kapi.c
index d0d6220b8d59d..c2d3ea751f4e1 100644
--- a/net/netlabel/netlabel_kapi.c
+++ b/net/netlabel/netlabel_kapi.c
@@ -1393,11 +1393,24 @@ int netlbl_skbuff_getattr(const struct sk_buff *skb,
 	unsigned char *ptr;
 
 	switch (family) {
-	case AF_INET:
+	case AF_INET: {
+		const unsigned char *tail = skb_tail_pointer(skb);
+		u8 opt_len, tag_len;
+
 		ptr = cipso_v4_optptr(skb);
-		if (ptr && cipso_v4_getattr(ptr, secattr) == 0)
+		if (!ptr)
+			break;
+		/* CIPSO header (type+len+DOI = 6) + first tag header (type+len = 2) */
+		if (ptr + 8 > tail)
+			return -EINVAL;
+		opt_len = ptr[1];	/* total CIPSO option length */
+		tag_len = ptr[7];	/* first tag length */
+		if (ptr + opt_len > tail || ptr + 6 + tag_len > tail)
+			return -EINVAL;
+		if (cipso_v4_getattr(ptr, secattr) == 0)
 			return 0;
 		break;
+	}
 #if IS_ENABLED(CONFIG_IPV6)
 	case AF_INET6: {
 		const unsigned char *tail = skb_tail_pointer(skb);
-- 
2.47.3


^ permalink raw reply related

* [PATCH v8 0/3]
From: Jarkko Sakkinen @ 2026-05-24  5:15 UTC (permalink / raw)
  To: keyrings
  Cc: David Howells, linux-crypto, linux-integrity, David Woodhouse,
	James Bottomley, Stefan Berger, Herbert Xu, Jarkko Sakkinen,
	James Bottomley, Mimi Zohar, Paul Moore, James Morris,
	Serge E. Hallyn, open list:SECURITY SUBSYSTEM, open list

This series introduces key type for operating with asymmetric keys using
a TPM2 chip.

Change Log
==========

v8:
- Reset patch change logs given the overhaul of the code and patches.
- Have only single new subkey type.
- Make key type only use TPM operations.
- Use TPM2_Sign for both ECC and RSA keys.
- Align key descriptions with other key types.

Previous versions
=================

* v7: https://lore.kernel.org/linux-integrity/20240528210823.28798-1-jarkko@kernel.org/
* v6: https://lore.kernel.org/linux-integrity/20240528035136.11464-1-jarkko@kernel.org/
* v5: https://lore.kernel.org/linux-integrity/20240523212515.4875-1-jarkko@kernel.org/
* v4: https://lore.kernel.org/linux-integrity/20240522005252.17841-1-jarkko@kernel.org/
* v3: https://lore.kernel.org/linux-integrity/20240521152659.26438-1-jarkko@kernel.org/
* v2: https://lore.kernel.org/linux-integrity/336755.1716327854@warthog.procyon.org.uk/
* v1: https://lore.kernel.org/linux-integrity/20240520184727.22038-1-jarkko@kernel.org/
* Derived from https://lore.kernel.org/all/20200518172704.29608-1-prestwoj@gmail.com/


Jarkko Sakkinen (3):
  lib/asn1_encoder: Add asn1_encode_integer_bytes()
  crypto: Migrate TPMKey ASN.1 objects from trusted-keys
  keys: asymmetric: tpm2_asymmetric

 crypto/Kconfig                            |    7 +
 crypto/Makefile                           |    6 +
 crypto/asymmetric_keys/Kconfig            |   17 +
 crypto/asymmetric_keys/Makefile           |    1 +
 crypto/asymmetric_keys/tpm2_asymmetric.c  | 1096 +++++++++++++++++++++
 crypto/tpm2_key.asn1                      |   11 +
 crypto/tpm2_key.c                         |  150 +++
 include/crypto/tpm2_key.h                 |   46 +
 include/linux/asn1_encoder.h              |    3 +
 include/linux/tpm.h                       |   10 +
 lib/asn1_encoder.c                        |   62 ++
 security/keys/trusted-keys/Kconfig        |    2 +-
 security/keys/trusted-keys/Makefile       |    2 -
 security/keys/trusted-keys/tpm2key.asn1   |   11 -
 security/keys/trusted-keys/trusted_tpm2.c |  119 +--
 15 files changed, 1421 insertions(+), 122 deletions(-)
 create mode 100644 crypto/asymmetric_keys/tpm2_asymmetric.c
 create mode 100644 crypto/tpm2_key.asn1
 create mode 100644 crypto/tpm2_key.c
 create mode 100644 include/crypto/tpm2_key.h
 delete mode 100644 security/keys/trusted-keys/tpm2key.asn1

-- 
2.47.3


^ permalink raw reply

* [PATCH v8 1/3] lib/asn1_encoder: Add asn1_encode_integer_bytes()
From: Jarkko Sakkinen @ 2026-05-24  5:15 UTC (permalink / raw)
  To: keyrings
  Cc: David Howells, linux-crypto, linux-integrity, David Woodhouse,
	James Bottomley, Stefan Berger, Herbert Xu, Jarkko Sakkinen,
	Andrew Morton, James Bottomley, Mimi Zohar, Paul Moore,
	James Morris, Serge E. Hallyn, open list,
	open list:SECURITY SUBSYSTEM
In-Reply-To: <20260524051519.3708075-1-jarkko@kernel.org>

Add a helper encoding a positive integer from a byte array in big-endian
format.

Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
 include/linux/asn1_encoder.h |  3 ++
 lib/asn1_encoder.c           | 62 ++++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)

diff --git a/include/linux/asn1_encoder.h b/include/linux/asn1_encoder.h
index d17484dffb74..e206bd425854 100644
--- a/include/linux/asn1_encoder.h
+++ b/include/linux/asn1_encoder.h
@@ -12,6 +12,9 @@ unsigned char *
 asn1_encode_integer(unsigned char *data, const unsigned char *end_data,
 		    s64 integer);
 unsigned char *
+asn1_encode_integer_bytes(unsigned char *data, const unsigned char *end_data,
+			  const unsigned char *integer, u32 integer_len);
+unsigned char *
 asn1_encode_oid(unsigned char *data, const unsigned char *end_data,
 		u32 oid[], int oid_len);
 unsigned char *
diff --git a/lib/asn1_encoder.c b/lib/asn1_encoder.c
index 92f35aae13b1..22e0acd6fe08 100644
--- a/lib/asn1_encoder.c
+++ b/lib/asn1_encoder.c
@@ -10,6 +10,8 @@
 #include <linux/string.h>
 #include <linux/module.h>
 
+static int asn1_encode_length(unsigned char **data, int *data_len, int len);
+
 /**
  * asn1_encode_integer() - encode positive integer to ASN.1
  * @data:	pointer to the pointer to the data
@@ -85,6 +87,66 @@ asn1_encode_integer(unsigned char *data, const unsigned char *end_data,
 }
 EXPORT_SYMBOL_GPL(asn1_encode_integer);
 
+/**
+ * asn1_encode_integer_bytes() - encode positive integer bytes to ASN.1
+ * @data:		pointer to the pointer to the data
+ * @end_data:		end of data pointer, points one beyond last usable byte in @data
+ * @bytes:		integer bytes
+ * @bytes_len:		amount of bytes
+ *
+ * Encode a positive integer from a byte array in big-endian format. Strip
+ * leading zeros.
+ */
+unsigned char *
+asn1_encode_integer_bytes(unsigned char *data, const unsigned char *end_data,
+			  const unsigned char *bytes, u32 bytes_len)
+{
+	static const unsigned char zero;
+	int data_len = end_data - data;
+	bool add_pad = false;
+	int ret;
+
+	if (IS_ERR(data))
+		return data;
+
+	if (!bytes || !bytes_len)
+		return ERR_PTR(-EINVAL);
+
+	/* Strip leading zeros: */
+	while (bytes_len > 1 && bytes[0] == 0) {
+		bytes++;
+		bytes_len--;
+	}
+
+	if (!bytes_len) {
+		bytes = &zero;
+		bytes_len = 1;
+	} else {
+		add_pad = bytes[0] & 0x80;
+	}
+
+	if (data_len < 2)
+		return ERR_PTR(-EINVAL);
+
+	*(data++) = _tag(UNIV, PRIM, INT);
+	data_len--;
+
+	ret = asn1_encode_length(&data, &data_len, bytes_len + add_pad);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (data_len < bytes_len + add_pad)
+		return ERR_PTR(-EINVAL);
+
+	if (add_pad)
+		*(data++) = 0;
+
+	memcpy(data, bytes, bytes_len);
+	data += bytes_len;
+	return data;
+}
+EXPORT_SYMBOL_GPL(asn1_encode_integer_bytes);
+
 /* calculate the base 128 digit values setting the top bit of the first octet */
 static int asn1_encode_oid_digit(unsigned char **_data, int *data_len, u32 oid)
 {
-- 
2.47.3


^ permalink raw reply related

* [PATCH v8 2/3] crypto: Migrate TPMKey ASN.1 objects from trusted-keys
From: Jarkko Sakkinen @ 2026-05-24  5:15 UTC (permalink / raw)
  To: keyrings
  Cc: David Howells, linux-crypto, linux-integrity, David Woodhouse,
	James Bottomley, Stefan Berger, Herbert Xu, Jarkko Sakkinen,
	David S. Miller, James Bottomley, Mimi Zohar, Paul Moore,
	James Morris, Serge E. Hallyn, open list,
	open list:SECURITY SUBSYSTEM
In-Reply-To: <20260524051519.3708075-1-jarkko@kernel.org>

Migrate the TPMKey ASN.1 code from trusted-keys to the crypto subsystem,
and put the code behind CRYPTO_TPM2_KEY Kconfig flag.

Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
 crypto/Kconfig                            |   7 +
 crypto/Makefile                           |   6 +
 crypto/tpm2_key.asn1                      |  11 ++
 crypto/tpm2_key.c                         | 150 ++++++++++++++++++++++
 include/crypto/tpm2_key.h                 |  46 +++++++
 security/keys/trusted-keys/Kconfig        |   2 +-
 security/keys/trusted-keys/Makefile       |   2 -
 security/keys/trusted-keys/tpm2key.asn1   |  11 --
 security/keys/trusted-keys/trusted_tpm2.c | 119 ++---------------
 9 files changed, 232 insertions(+), 122 deletions(-)
 create mode 100644 crypto/tpm2_key.asn1
 create mode 100644 crypto/tpm2_key.c
 create mode 100644 include/crypto/tpm2_key.h
 delete mode 100644 security/keys/trusted-keys/tpm2key.asn1

diff --git a/crypto/Kconfig b/crypto/Kconfig
index 103d1f58cb7c..5476d80372a1 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -3,6 +3,13 @@
 # Generic algorithms support
 #
 
+config CRYPTO_TPM2_KEY
+	bool
+	depends on CRYPTO
+	select ASN1
+	select OID_REGISTRY
+	default n
+
 #
 # async_tx api: hardware offloaded memory transfer/transform support
 #
diff --git a/crypto/Makefile b/crypto/Makefile
index 162242593c7c..e232f9b9bee6 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -206,3 +206,9 @@ obj-$(CONFIG_CRYPTO_KDF800108_CTR) += kdf_sp800108.o
 obj-$(CONFIG_CRYPTO_DF80090A) += df_sp80090a.o
 
 obj-$(CONFIG_CRYPTO_KRB5) += krb5/
+
+ifdef CONFIG_CRYPTO_TPM2_KEY
+$(obj)/tpm2_key.asn1.o: $(obj)/tpm2_key.asn1.h $(obj)/tpm2_key.asn1.c
+$(obj)/tpm2_key.o: $(obj)/tpm2_key.asn1.h
+obj-y += tpm2_key.o tpm2_key.asn1.o
+endif
diff --git a/crypto/tpm2_key.asn1 b/crypto/tpm2_key.asn1
new file mode 100644
index 000000000000..553bf996af59
--- /dev/null
+++ b/crypto/tpm2_key.asn1
@@ -0,0 +1,11 @@
+---
+--- ASN.1 for TPM 2.0 keys
+---
+
+TPMKey ::= SEQUENCE {
+	type		OBJECT IDENTIFIER ({tpm2_key_get_type}),
+	emptyAuth	[0] EXPLICIT BOOLEAN OPTIONAL ({tpm2_key_get_empty_auth}),
+	parent		INTEGER ({tpm2_key_get_parent}),
+	pubkey		OCTET STRING ({tpm2_get_public}),
+	privkey		OCTET STRING ({tpm2_get_private})
+	}
diff --git a/crypto/tpm2_key.c b/crypto/tpm2_key.c
new file mode 100644
index 000000000000..5704ccdb7c0d
--- /dev/null
+++ b/crypto/tpm2_key.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <crypto/tpm2_key.h>
+#include <linux/oid_registry.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include "tpm2_key.asn1.h"
+
+#undef pr_fmt
+#define pr_fmt(fmt) "tpm2_key: "fmt
+
+struct tpm2_key_decoder_context {
+	u32 parent;
+	const u8 *pub;
+	u32 pub_len;
+	const u8 *priv;
+	u32 priv_len;
+	enum OID oid;
+	bool empty_auth;
+};
+
+int tpm2_key_get_parent(void *context, size_t hdrlen,
+			unsigned char tag,
+			const void *value, size_t vlen)
+{
+	struct tpm2_key_decoder_context *decoder = context;
+	const u8 *v = value;
+	int i;
+
+	decoder->parent = 0;
+	for (i = 0; i < vlen; i++) {
+		decoder->parent <<= 8;
+		decoder->parent |= v[i];
+	}
+
+	return 0;
+}
+
+int tpm2_key_get_type(void *context, size_t hdrlen,
+		      unsigned char tag,
+		      const void *value, size_t vlen)
+{
+	struct tpm2_key_decoder_context *decoder = context;
+
+	decoder->oid = look_up_OID(value, vlen);
+	return 0;
+}
+
+int tpm2_key_get_empty_auth(void *context, size_t hdrlen,
+			    unsigned char tag,
+			    const void *value, size_t vlen)
+{
+	struct tpm2_key_decoder_context *decoder = context;
+	const u8 *bool_value = value;
+
+	if (!value || vlen != 1)
+		return -EBADMSG;
+
+	decoder->empty_auth = bool_value[0] != 0;
+	return 0;
+}
+
+static inline bool tpm2_key_is_valid(const void *value, size_t vlen)
+{
+	if (vlen < 2 || vlen > TPM2_KEY_BYTES_MAX)
+		return false;
+
+	if (get_unaligned_be16(value) != vlen - 2)
+		return false;
+
+	return true;
+}
+
+int tpm2_get_public(void *context, size_t hdrlen, unsigned char tag,
+		    const void *value, size_t vlen)
+{
+	struct tpm2_key_decoder_context *decoder = context;
+
+	if (!tpm2_key_is_valid(value, vlen))
+		return -EBADMSG;
+
+	if (sizeof(struct tpm2_key_desc) > vlen - 2)
+		return -EBADMSG;
+
+	decoder->pub = value;
+	decoder->pub_len = vlen;
+	return 0;
+}
+
+int tpm2_get_private(void *context, size_t hdrlen, unsigned char tag,
+		     const void *value, size_t vlen)
+{
+	struct tpm2_key_decoder_context *decoder = context;
+
+	if (!tpm2_key_is_valid(value, vlen))
+		return -EBADMSG;
+
+	decoder->priv = value;
+	decoder->priv_len = vlen;
+	return 0;
+}
+
+/**
+ * tpm2_key_decode() - Decode TPM2 ASN.1 key
+ * @src:	ASN.1 source.
+ * @src_len:	ASN.1 source length.
+ *
+ * Decodes the TPM2 ASN.1 key and validates that the public key data has all
+ * the shared fields of TPMT_PUBLIC. This is full coverage of the memory that
+ * can be validated before doing any key type specific validation.
+ *
+ * Return:
+ * - TPM2 ASN.1 key on success.
+ * - -EBADMSG when decoding fails.
+ * - -ENOMEM when OOM while allocating struct tpm2_key.
+ */
+struct tpm2_key *tpm2_key_decode(const u8 *src, u32 src_len)
+{
+	struct tpm2_key_decoder_context decoder;
+	struct tpm2_key *key;
+	u8 *data;
+	int ret;
+
+	memset(&decoder, 0, sizeof(decoder));
+	ret = asn1_ber_decoder(&tpm2_key_decoder, &decoder, src, src_len);
+	if (ret < 0) {
+		if (ret != -EBADMSG)
+			pr_info("Decoder error %d\n", ret);
+
+		return ERR_PTR(-EBADMSG);
+	}
+
+	key = kzalloc(sizeof(*key), GFP_KERNEL);
+	if (!key)
+		return ERR_PTR(-ENOMEM);
+
+	data = &key->data[0];
+	memcpy(&data[0], decoder.priv, decoder.priv_len);
+	memcpy(&data[decoder.priv_len], decoder.pub, decoder.pub_len);
+
+	key->oid = decoder.oid;
+	key->priv_len = decoder.priv_len;
+	key->pub_len = decoder.pub_len;
+	key->parent = decoder.parent;
+	key->desc = (struct tpm2_key_desc *)&data[decoder.priv_len + 2];
+	key->empty_auth = decoder.empty_auth;
+	return key;
+}
+EXPORT_SYMBOL_GPL(tpm2_key_decode);
diff --git a/include/crypto/tpm2_key.h b/include/crypto/tpm2_key.h
new file mode 100644
index 000000000000..883afaa596e5
--- /dev/null
+++ b/include/crypto/tpm2_key.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __LINUX_TPM2_KEY_H__
+#define __LINUX_TPM2_KEY_H__
+
+#include <linux/oid_registry.h>
+#include <linux/slab.h>
+
+#define TPM2_KEY_BYTES_MAX 1024
+
+/*  TPM2 Structures 12.2.4: TPMT_PUBLIC */
+struct tpm2_key_desc {
+	__be16 type;
+	__be16 name_alg;
+	__be32 object_attributes;
+	__be16 policy_size;
+} __packed;
+
+/* Decoded TPM2 ASN.1 key. */
+struct tpm2_key {
+	u8 data[2 * TPM2_KEY_BYTES_MAX];
+	struct tpm2_key_desc *desc;
+	u16 priv_len;
+	u16 pub_len;
+	u32 parent;
+	enum OID oid;
+	bool empty_auth;
+};
+
+struct tpm2_key *tpm2_key_decode(const u8 *src, u32 src_len);
+
+static inline const void *tpm2_key_data(const struct tpm2_key *key)
+{
+	return &key->data[0];
+}
+
+static inline u16 tpm2_key_type(const struct tpm2_key *key)
+{
+	return be16_to_cpu(key->desc->type);
+}
+
+static inline int tpm2_key_policy_size(const struct tpm2_key *key)
+{
+	return be16_to_cpu(key->desc->policy_size);
+}
+
+#endif /* __LINUX_TPM2_KEY_H__ */
diff --git a/security/keys/trusted-keys/Kconfig b/security/keys/trusted-keys/Kconfig
index e5a4a53aeab2..09b1ec1d5bc2 100644
--- a/security/keys/trusted-keys/Kconfig
+++ b/security/keys/trusted-keys/Kconfig
@@ -27,9 +27,9 @@ config TRUSTED_KEYS_TPM
 	select CRYPTO_HASH_INFO
 	select CRYPTO_LIB_SHA1
 	select CRYPTO_LIB_UTILS
+	select CRYPTO_TPM2_KEY
 	select ASN1_ENCODER
 	select OID_REGISTRY
-	select ASN1
 	select HAVE_TRUSTED_KEYS
 	help
 	  Enable use of the Trusted Platform Module (TPM) as trusted key
diff --git a/security/keys/trusted-keys/Makefile b/security/keys/trusted-keys/Makefile
index 5fc053a21dad..ac09d2d90051 100644
--- a/security/keys/trusted-keys/Makefile
+++ b/security/keys/trusted-keys/Makefile
@@ -7,9 +7,7 @@ obj-$(CONFIG_TRUSTED_KEYS) += trusted.o
 trusted-y += trusted_core.o
 trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm1.o
 
-$(obj)/trusted_tpm2.o: $(obj)/tpm2key.asn1.h
 trusted-$(CONFIG_TRUSTED_KEYS_TPM) += trusted_tpm2.o
-trusted-$(CONFIG_TRUSTED_KEYS_TPM) += tpm2key.asn1.o
 
 trusted-$(CONFIG_TRUSTED_KEYS_TEE) += trusted_tee.o
 
diff --git a/security/keys/trusted-keys/tpm2key.asn1 b/security/keys/trusted-keys/tpm2key.asn1
deleted file mode 100644
index f57f869ad600..000000000000
--- a/security/keys/trusted-keys/tpm2key.asn1
+++ /dev/null
@@ -1,11 +0,0 @@
----
---- ASN.1 for TPM 2.0 keys
----
-
-TPMKey ::= SEQUENCE {
-	type		OBJECT IDENTIFIER ({tpm2_key_type}),
-	emptyAuth	[0] EXPLICIT BOOLEAN OPTIONAL,
-	parent		INTEGER ({tpm2_key_parent}),
-	pubkey		OCTET STRING ({tpm2_key_pub}),
-	privkey		OCTET STRING ({tpm2_key_priv})
-	}
diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c
index 6340823f8b53..5b079fe476d1 100644
--- a/security/keys/trusted-keys/trusted_tpm2.c
+++ b/security/keys/trusted-keys/trusted_tpm2.c
@@ -13,11 +13,10 @@
 
 #include <keys/trusted-type.h>
 #include <keys/trusted_tpm.h>
+#include <crypto/tpm2_key.h>
 
 #include <linux/unaligned.h>
 
-#include "tpm2key.asn1.h"
-
 static u32 tpm2key_oid[] = { 2, 23, 133, 10, 1, 5 };
 
 static int tpm2_key_encode(struct trusted_key_payload *payload,
@@ -90,105 +89,6 @@ static int tpm2_key_encode(struct trusted_key_payload *payload,
 	return ret;
 }
 
-struct tpm2_key_context {
-	u32 parent;
-	const u8 *pub;
-	u32 pub_len;
-	const u8 *priv;
-	u32 priv_len;
-};
-
-static int tpm2_key_decode(struct trusted_key_payload *payload,
-			   struct trusted_key_options *options,
-			   u8 **buf)
-{
-	int ret;
-	struct tpm2_key_context ctx;
-	u8 *blob;
-
-	memset(&ctx, 0, sizeof(ctx));
-
-	ret = asn1_ber_decoder(&tpm2key_decoder, &ctx, payload->blob,
-			       payload->blob_len);
-	if (ret < 0)
-		return ret;
-
-	if (ctx.priv_len + ctx.pub_len > MAX_BLOB_SIZE)
-		return -EINVAL;
-
-	blob = kmalloc(ctx.priv_len + ctx.pub_len + 4, GFP_KERNEL);
-	if (!blob)
-		return -ENOMEM;
-
-	*buf = blob;
-	options->keyhandle = ctx.parent;
-
-	memcpy(blob, ctx.priv, ctx.priv_len);
-	blob += ctx.priv_len;
-
-	memcpy(blob, ctx.pub, ctx.pub_len);
-
-	return 0;
-}
-
-int tpm2_key_parent(void *context, size_t hdrlen,
-		  unsigned char tag,
-		  const void *value, size_t vlen)
-{
-	struct tpm2_key_context *ctx = context;
-	const u8 *v = value;
-	int i;
-
-	ctx->parent = 0;
-	for (i = 0; i < vlen; i++) {
-		ctx->parent <<= 8;
-		ctx->parent |= v[i];
-	}
-
-	return 0;
-}
-
-int tpm2_key_type(void *context, size_t hdrlen,
-		unsigned char tag,
-		const void *value, size_t vlen)
-{
-	enum OID oid = look_up_OID(value, vlen);
-
-	if (oid != OID_TPMSealedData) {
-		char buffer[50];
-
-		sprint_oid(value, vlen, buffer, sizeof(buffer));
-		pr_debug("OID is \"%s\" which is not TPMSealedData\n",
-			 buffer);
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-int tpm2_key_pub(void *context, size_t hdrlen,
-	       unsigned char tag,
-	       const void *value, size_t vlen)
-{
-	struct tpm2_key_context *ctx = context;
-
-	ctx->pub = value;
-	ctx->pub_len = vlen;
-
-	return 0;
-}
-
-int tpm2_key_priv(void *context, size_t hdrlen,
-		unsigned char tag,
-		const void *value, size_t vlen)
-{
-	struct tpm2_key_context *ctx = context;
-
-	ctx->priv = value;
-	ctx->priv_len = vlen;
-
-	return 0;
-}
 
 /**
  * tpm2_buf_append_auth() - append TPMS_AUTH_COMMAND to the buffer.
@@ -372,23 +272,26 @@ static int tpm2_load_cmd(struct tpm_chip *chip,
 			 struct trusted_key_options *options,
 			 u32 *blob_handle)
 {
-	u8 *blob_ref __free(kfree) = NULL;
+	struct tpm2_key *key __free(kfree) = NULL;
 	struct tpm_buf buf;
 	unsigned int private_len;
 	unsigned int public_len;
 	unsigned int blob_len;
-	u8 *blob, *pub;
+	const u8 *blob, *pub;
 	int rc;
 	u32 attrs;
 
-	rc = tpm2_key_decode(payload, options, &blob);
-	if (rc) {
+	key = tpm2_key_decode(payload->blob, payload->blob_len);
+	if (IS_ERR(key))
+		key = NULL;
+
+	if (key && key->oid == OID_TPMSealedData) {
+		options->keyhandle = key->parent;
+		blob = tpm2_key_data(key);
+	} else {
 		/* old form */
 		blob = payload->blob;
 		payload->old_format = 1;
-	} else {
-		/* Bind for cleanup: */
-		blob_ref = blob;
 	}
 
 	/* new format carries keyhandle but old format doesn't */
-- 
2.47.3


^ permalink raw reply related

* [PATCH v8 3/3] keys: asymmetric: tpm2_asymmetric
From: Jarkko Sakkinen @ 2026-05-24  5:15 UTC (permalink / raw)
  To: keyrings
  Cc: David Howells, linux-crypto, linux-integrity, David Woodhouse,
	James Bottomley, Stefan Berger, Herbert Xu, Jarkko Sakkinen,
	James Prestwood, Lukas Wunner, Ignat Korchagin, David S. Miller,
	Peter Huewe, Jason Gunthorpe, James Bottomley, Mimi Zohar,
	Paul Moore, James Morris, Serge E. Hallyn, open list,
	open list:SECURITY SUBSYSTEM
In-Reply-To: <20260524051519.3708075-1-jarkko@kernel.org>

tpm2_asymmetric is a key type for external keys generated outside the TPM
chip but later imported to the chip's key hierarchy as leaf keys.

The key type supports ECC-NIST-P256/384/521 and RSA keys and provides
signing and verification operations for each. In addition, for RSA
encryption and decryption operations are supported.

Co-developed-by: James Prestwood <prestwoj@gmail.com>
Signed-off-by: James Prestwood <prestwoj@gmail.com>
Signed-off-by: Jarkko Sakkinen <jarkko@kernel.org>
---
 crypto/asymmetric_keys/Kconfig           |   17 +
 crypto/asymmetric_keys/Makefile          |    1 +
 crypto/asymmetric_keys/tpm2_asymmetric.c | 1096 ++++++++++++++++++++++
 include/linux/tpm.h                      |   10 +
 4 files changed, 1124 insertions(+)
 create mode 100644 crypto/asymmetric_keys/tpm2_asymmetric.c

diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig
index e50bd9b3e27b..a93e13d5768f 100644
--- a/crypto/asymmetric_keys/Kconfig
+++ b/crypto/asymmetric_keys/Kconfig
@@ -15,6 +15,7 @@ config ASYMMETRIC_PUBLIC_KEY_SUBTYPE
 	select MPILIB
 	select CRYPTO_HASH_INFO
 	select CRYPTO_AKCIPHER
+	select CRYPTO_RSA
 	select CRYPTO_SIG
 	select CRYPTO_HASH
 	help
@@ -23,6 +24,22 @@ config ASYMMETRIC_PUBLIC_KEY_SUBTYPE
 	  appropriate hash algorithms (such as SHA-1) must be available.
 	  ENOPKG will be reported if the requisite algorithm is unavailable.
 
+config ASYMMETRIC_TPM2_KEY_SUBTYPE
+	tristate "Asymmetric TPM2 crypto algorithm subtype"
+	depends on TCG_TPM
+	select CRYPTO_SHA256
+	select CRYPTO_HASH_INFO
+	select CRYPTO_TPM2_KEY
+	select ASN1
+	select ASN1_ENCODER
+	help
+	  This option provides support for asymmetric TPM2 key type handling.
+	  Asymmetric operations such as sign and verify are delegated to the
+	  TPM, and bound to the kernel crypto subsystem. Both RSA and ECDSA
+	  keys are supported.
+
+	  ENOPKG will be reported if the requisite algorithm is unavailable.
+
 config X509_CERTIFICATE_PARSER
 	tristate "X.509 certificate parser"
 	depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index bc65d3b98dcb..c83b40d021ac 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -11,6 +11,7 @@ asymmetric_keys-y := \
 	signature.o
 
 obj-$(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o
+obj-$(CONFIG_ASYMMETRIC_TPM2_KEY_SUBTYPE) += tpm2_asymmetric.o
 
 #
 # X.509 Certificate handling
diff --git a/crypto/asymmetric_keys/tpm2_asymmetric.c b/crypto/asymmetric_keys/tpm2_asymmetric.c
new file mode 100644
index 000000000000..f6598e6fd283
--- /dev/null
+++ b/crypto/asymmetric_keys/tpm2_asymmetric.c
@@ -0,0 +1,1096 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * An asymmetric TPM2 key subtype.
+ */
+
+#include <crypto/hash_info.h>
+#include <crypto/internal/ecc.h>
+#include <crypto/public_key.h>
+#include <crypto/tpm2_key.h>
+#include <keys/asymmetric-parser.h>
+#include <keys/asymmetric-subtype.h>
+#include <linux/asn1_encoder.h>
+#include <linux/keyctl.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tpm.h>
+#include <linux/unaligned.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "tpm2_asymmetric: "fmt
+
+/* TPM2 Structures 12.2.3.5: TPMS_RSA_PARMS */
+struct tpm2_asymmetric_rsa_parms {
+	__be16 symmetric;
+	__be16 scheme;
+	__be16 key_bits;
+	__be32 exponent;
+	__be16 modulus_size;
+} __packed;
+
+/* TPM2 Structures 12.2.3.6: TPMS_ECC_PARMS */
+struct tpm2_asymmetric_ecc_parms {
+	__be16 symmetric;
+	__be16 scheme;
+	__be16 ecc;
+	__be16 kdf;
+};
+
+static const void *tpm2_asymmetric_parms(const struct tpm2_key *key)
+{
+	return &key->data[key->priv_len + 2 + sizeof(*key->desc)];
+}
+
+static u16 tpm2_asymmetric_rsa_mod_size(const struct tpm2_key *key)
+{
+	const struct tpm2_asymmetric_rsa_parms *p = tpm2_asymmetric_parms(key);
+
+	return be16_to_cpu(p->modulus_size);
+}
+
+static const u8 *tpm2_asymmetric_ecc_x(const struct tpm2_key *key)
+{
+	return tpm2_asymmetric_parms(key) + sizeof(struct tpm2_asymmetric_ecc_parms);
+}
+
+static const u8 *tpm2_asymmetric_ecc_y(const struct tpm2_key *key)
+{
+	const u8 *x = tpm2_asymmetric_ecc_x(key);
+	u16 x_size = get_unaligned_be16(&x[0]);
+
+	return &x[2 + x_size];
+}
+
+static unsigned int tpm2_asymmetric_ecc_key_bits(u16 ecc)
+{
+	switch (ecc) {
+	case TPM2_ECC_NIST_P256:
+		return 256;
+	case TPM2_ECC_NIST_P384:
+		return 384;
+	case TPM2_ECC_NIST_P521:
+		return 521;
+	default:
+		return 0;
+	}
+}
+
+static int tpm2_asymmetric_hash_lookup(const char *hash_algo,
+				       int *hash_id, int *tpm_hash)
+{
+	int id, alg;
+
+	if (!hash_algo)
+		return -EINVAL;
+
+	id = match_string(hash_algo_name, HASH_ALGO__LAST, hash_algo);
+	if (id < 0)
+		return -ENOPKG;
+
+	alg = tpm2_find_hash_alg(id);
+	if (alg < 0)
+		return -ENOPKG;
+
+	if (hash_id)
+		*hash_id = id;
+
+	if (tpm_hash)
+		*tpm_hash = alg;
+
+	return 0;
+}
+
+static int tpm2_asymmetric_signature_scheme(const struct tpm2_key *key,
+					    const char *encoding,
+					    const char *hash_algo,
+					    u16 *scheme,
+					    int *tpm_hash)
+{
+	if (!encoding)
+		return -ENOPKG;
+
+	switch (tpm2_key_type(key)) {
+	case TPM_ALG_RSA:
+		if (strcmp(encoding, "pkcs1") != 0)
+			return -ENOPKG;
+		*scheme = TPM_ALG_RSASSA;
+		break;
+	case TPM_ALG_ECC:
+		if (strcmp(encoding, "x962") != 0)
+			return -ENOPKG;
+		*scheme = TPM_ALG_ECDSA;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return tpm2_asymmetric_hash_lookup(hash_algo, NULL, tpm_hash);
+}
+
+/*
+ * Load a TPM2 key blob into the TPM.
+ *
+ * On success, @buf is initialized and the authorization session is kept open.
+ * On failure, @buf is destroyed and the authorization session is closed.
+ */
+static int tpm2_asymmetric_load(struct tpm_chip *chip, struct tpm2_key *key,
+				struct tpm_buf *buf, u32 *handle_out)
+{
+	int ret;
+
+	ret = tpm2_start_auth_session(chip);
+	if (ret)
+		return ret;
+
+	ret = tpm_buf_init(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD);
+	if (ret < 0)
+		goto err_auth;
+
+	ret = tpm_buf_append_name(chip, buf, key->parent, NULL);
+	if (ret)
+		goto err_buf;
+	tpm_buf_append_hmac_session(chip, buf, TPM2_SA_CONTINUE_SESSION |
+				    TPM2_SA_ENCRYPT, NULL, 0);
+	tpm_buf_append(buf, &key->data[0], key->priv_len + key->pub_len);
+	if (buf->flags & TPM_BUF_OVERFLOW) {
+		ret = -E2BIG;
+		goto err_buf;
+	}
+	ret = tpm_buf_fill_hmac_session(chip, buf);
+	if (ret)
+		goto err_buf;
+	ret = tpm_transmit_cmd(chip, buf, 4, "TPM2_CC_LOAD");
+	ret = tpm_buf_check_hmac_response(chip, buf, ret);
+	if (ret) {
+		ret = -EIO;
+		goto err_buf;
+	}
+
+	*handle_out = be32_to_cpup((__be32 *)&buf->data[TPM_HEADER_SIZE]);
+	return 0;
+
+err_buf:
+	tpm_buf_destroy(buf);
+
+err_auth:
+	tpm2_end_auth_session(chip);
+	return ret;
+}
+
+static void tpm2_asymmetric_key_destroy(void *payload0, void *payload3)
+{
+	kfree(payload0);
+}
+
+/*
+ * Encrypt using TPM2_RSA_Encrypt with RSAES (PKCS#1 v1.5) scheme.
+ */
+static int tpm2_asymmetric_rsa_encrypt(struct tpm_chip *chip,
+				       struct tpm2_key *key,
+				       struct kernel_pkey_params *params,
+				       const void *in, void *out)
+{
+	u32 key_handle = 0;
+	struct tpm_buf buf;
+	u16 ciphertext_len;
+	u16 scheme;
+	u8 *pos;
+	int ret;
+
+	if (!params->encoding)
+		return -EINVAL;
+
+	if (strcmp(params->encoding, "pkcs1") == 0)
+		scheme = TPM_ALG_RSAES;
+	else if (strcmp(params->encoding, "raw") == 0)
+		scheme = TPM_ALG_NULL;
+	else
+		return -ENOPKG;
+
+	ret = tpm_try_get_ops(chip);
+	if (ret)
+		return ret;
+
+	ret = tpm2_asymmetric_load(chip, key, &buf, &key_handle);
+	if (ret)
+		goto err_ops;
+
+	tpm2_end_auth_session(chip);
+	tpm_buf_destroy(&buf);
+
+	ret = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS, TPM2_CC_RSA_ENCRYPT);
+	if (ret)
+		goto err_key;
+
+	tpm_buf_append_u32(&buf, key_handle);
+
+	tpm_buf_append_u16(&buf, params->in_len);
+	tpm_buf_append(&buf, in, params->in_len);
+
+	tpm_buf_append_u16(&buf, scheme);
+
+	tpm_buf_append_u16(&buf, 0);
+
+	ret = tpm_transmit_cmd(chip, &buf, 4, "TPM2_RSA_Encrypt");
+	if (ret) {
+		ret = -EIO;
+		goto err_buf;
+	}
+
+	pos = buf.data + TPM_HEADER_SIZE;
+	ciphertext_len = be16_to_cpup((__be16 *)pos);
+	pos += 2;
+	if (pos + ciphertext_len > buf.data + buf.length) {
+		ret = -EIO;
+		goto err_buf;
+	}
+
+	if (params->out_len < ciphertext_len) {
+		ret = -EMSGSIZE;
+		goto err_buf;
+	}
+
+	memcpy(out, pos, ciphertext_len);
+	ret = ciphertext_len;
+
+err_buf:
+	tpm_buf_destroy(&buf);
+
+err_key:
+	tpm2_flush_context(chip, key_handle);
+
+err_ops:
+	tpm_put_ops(chip);
+	return ret;
+}
+
+/*
+ * Convert a TPM2B_PUBLIC_KEY_RSA response into a raw RSA signature.
+ */
+static int tpm2_asymmetric_rsa_parse_signature(struct tpm_buf *buf,
+					       off_t *offset,
+					       struct kernel_pkey_params *params,
+					       void *out)
+{
+	u16 sig_len;
+
+	sig_len = tpm_buf_read_u16(buf, offset);
+	if (buf->flags & TPM_BUF_BOUNDARY_ERROR)
+		return -EIO;
+	if (*offset + sig_len > buf->length)
+		return -EIO;
+	if (sig_len > params->out_len)
+		return -EMSGSIZE;
+
+	memcpy(out, &buf->data[*offset], sig_len);
+	return sig_len;
+}
+
+/*
+ * Convert a TPMT_SIGNATURE ECDSA R/S response into DER SEQUENCE form.
+ */
+static int tpm2_asymmetric_ecc_parse_signature(struct tpm_buf *buf, off_t *offset,
+					       struct kernel_pkey_params *params,
+					       void *out)
+{
+	u8 der[2 * (2 + ECC_MAX_BYTES + 1)];
+	u8 *encoded, *ptr;
+	const u8 *s;
+	u16 r_size;
+	u16 s_size;
+
+	r_size = tpm_buf_read_u16(buf, offset);
+	if (buf->flags & TPM_BUF_BOUNDARY_ERROR)
+		return -EIO;
+	if (r_size == 0 || r_size > ECC_MAX_BYTES ||
+	    *offset + r_size + 2 > buf->length)
+		return -EIO;
+
+	s_size = get_unaligned_be16(&buf->data[*offset + r_size]);
+	s = &buf->data[*offset + r_size + 2];
+	if (s_size == 0 || s_size > ECC_MAX_BYTES ||
+	    *offset + r_size + 2 + s_size > buf->length)
+		return -EIO;
+
+	ptr = der;
+	ptr = asn1_encode_integer_bytes(ptr, der + sizeof(der),
+					&buf->data[*offset], r_size);
+	ptr = asn1_encode_integer_bytes(ptr, der + sizeof(der), s, s_size);
+	if (IS_ERR(ptr))
+		return PTR_ERR(ptr);
+
+	encoded = asn1_encode_sequence(out, (u8 *)out + params->out_len,
+				       der, ptr - der);
+	if (IS_ERR(encoded))
+		return PTR_ERR(encoded) == -EINVAL ? -EMSGSIZE : PTR_ERR(encoded);
+
+	return encoded - (u8 *)out;
+}
+
+static int tpm2_asymmetric_parse_signature(struct tpm_buf *buf,
+					   u16 scheme, int tpm_hash,
+					   struct kernel_pkey_params *params,
+					   void *out)
+{
+	off_t offset = TPM_HEADER_SIZE + 4;
+	u16 hash_alg;
+	u16 sig_alg;
+
+	sig_alg = tpm_buf_read_u16(buf, &offset);
+	hash_alg = tpm_buf_read_u16(buf, &offset);
+	if (buf->flags & TPM_BUF_BOUNDARY_ERROR)
+		return -EIO;
+	if (sig_alg != scheme || hash_alg != tpm_hash)
+		return -EIO;
+
+	switch (scheme) {
+	case TPM_ALG_RSASSA:
+		return tpm2_asymmetric_rsa_parse_signature(buf, &offset, params, out);
+	case TPM_ALG_ECDSA:
+		return tpm2_asymmetric_ecc_parse_signature(buf, &offset, params, out);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+/*
+ * Sign a digest using TPM2_Sign.
+ */
+static int tpm2_asymmetric_sign(struct tpm_chip *chip, struct tpm2_key *key,
+				struct kernel_pkey_params *params,
+				const void *in, void *out)
+{
+	struct tpm_buf buf;
+	u32 key_handle = 0;
+	int tpm_hash;
+	u16 scheme;
+	int ret;
+
+	ret = tpm2_asymmetric_signature_scheme(key, params->encoding,
+					       params->hash_algo, &scheme,
+					       &tpm_hash);
+	if (ret)
+		return ret;
+
+	ret = tpm_try_get_ops(chip);
+	if (ret)
+		return ret;
+
+	ret = tpm2_asymmetric_load(chip, key, &buf, &key_handle);
+	if (ret)
+		goto err_ops;
+
+	tpm_buf_reset(&buf, TPM2_ST_SESSIONS, TPM2_CC_SIGN);
+	ret = tpm_buf_append_name(chip, &buf, key_handle, NULL);
+	if (ret)
+		goto err_key;
+	tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, NULL, 0);
+
+	/* digest (TPM2B_DIGEST) */
+	tpm_buf_append_u16(&buf, params->in_len);
+	tpm_buf_append(&buf, in, params->in_len);
+
+	/* inScheme (TPMT_SIG_SCHEME) */
+	tpm_buf_append_u16(&buf, scheme);
+	tpm_buf_append_u16(&buf, tpm_hash);
+
+	/* validation (TPMT_TK_HASHCHECK): NULL ticket */
+	tpm_buf_append_u16(&buf, TPM2_ST_HASHCHECK);
+	tpm_buf_append_u32(&buf, TPM2_RH_NULL);
+	tpm_buf_append_u16(&buf, 0);
+
+	if (buf.flags & TPM_BUF_OVERFLOW) {
+		tpm2_end_auth_session(chip);
+		ret = -E2BIG;
+		goto err_key;
+	}
+	ret = tpm_buf_fill_hmac_session(chip, &buf);
+	if (ret)
+		goto err_key;
+	ret = tpm_transmit_cmd(chip, &buf, 4, "TPM2_Sign");
+	ret = tpm_buf_check_hmac_response(chip, &buf, ret);
+	if (ret) {
+		ret = -EIO;
+		goto err_key;
+	}
+
+	ret = tpm2_asymmetric_parse_signature(&buf, scheme, tpm_hash, params, out);
+
+err_key:
+	tpm2_flush_context(chip, key_handle);
+	tpm_buf_destroy(&buf);
+
+err_ops:
+	tpm_put_ops(chip);
+	return ret;
+}
+
+/*
+ * Decrypt using TPM2_RSA_Decrypt with RSAES-PKCS1-v1_5 scheme.
+ */
+static int tpm2_asymmetric_rsa_decrypt(struct tpm_chip *chip,
+				       struct tpm2_key *key,
+				       struct kernel_pkey_params *params,
+				       const void *in, void *out)
+{
+	u32 key_handle = 0;
+	struct tpm_buf buf;
+	u16 decrypted_len;
+	off_t offset;
+	int ret;
+
+	if (!params->encoding || strcmp(params->encoding, "pkcs1") != 0)
+		return -ENOPKG;
+
+	ret = tpm_try_get_ops(chip);
+	if (ret)
+		return ret;
+
+	ret = tpm2_asymmetric_load(chip, key, &buf, &key_handle);
+	if (ret)
+		goto err_ops;
+
+	tpm_buf_reset(&buf, TPM2_ST_SESSIONS, TPM2_CC_RSA_DECRYPT);
+	ret = tpm_buf_append_name(chip, &buf, key_handle, NULL);
+	if (ret)
+		goto err_key;
+	tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, NULL, 0);
+	tpm_buf_append_u16(&buf, params->in_len);
+	tpm_buf_append(&buf, in, params->in_len);
+	tpm_buf_append_u16(&buf, TPM_ALG_RSAES);
+	tpm_buf_append_u16(&buf, 0);
+	if (buf.flags & TPM_BUF_OVERFLOW) {
+		tpm2_end_auth_session(chip);
+		ret = -E2BIG;
+		goto err_key;
+	}
+	ret = tpm_buf_fill_hmac_session(chip, &buf);
+	if (ret)
+		goto err_key;
+	ret = tpm_transmit_cmd(chip, &buf, 4, "TPM2_RSA_DECRYPT");
+	ret = tpm_buf_check_hmac_response(chip, &buf, ret);
+	if (ret) {
+		ret = -EIO;
+		goto err_key;
+	}
+
+	offset = TPM_HEADER_SIZE + 4;
+	decrypted_len = tpm_buf_read_u16(&buf, &offset);
+	if (buf.flags & TPM_BUF_BOUNDARY_ERROR) {
+		ret = -EIO;
+		goto err_key;
+	}
+	if (offset + decrypted_len > buf.length) {
+		ret = -EIO;
+		goto err_key;
+	}
+
+	if (params->out_len < decrypted_len) {
+		ret = -EMSGSIZE;
+		goto err_key;
+	}
+
+	memcpy(out, &buf.data[offset], decrypted_len);
+	ret = decrypted_len;
+
+err_key:
+	tpm2_flush_context(chip, key_handle);
+	tpm_buf_destroy(&buf);
+
+err_ops:
+	tpm_put_ops(chip);
+	return ret;
+}
+
+/*
+ * Verify an RSA signature using TPM2_VerifySignature with RSASSA scheme.
+ */
+static int tpm2_asymmetric_rsa_verify(const struct key *key,
+				      const struct public_key_signature *sig)
+{
+	struct tpm2_key *tpm2_key = key->payload.data[asym_crypto];
+	struct tpm_chip *chip;
+	struct tpm_buf buf;
+	u32 key_handle = 0;
+	int tpm_hash;
+	int ret;
+
+	if (!sig->m)
+		return -ENOPKG;
+
+	if (!sig->encoding || strcmp(sig->encoding, "pkcs1") != 0)
+		return -ENOPKG;
+
+	if (!sig->hash_algo)
+		return -EINVAL;
+
+	chip = tpm_default_chip();
+
+	if (!chip)
+		return -ENODEV;
+
+	ret = tpm2_asymmetric_hash_lookup(sig->hash_algo, NULL, &tpm_hash);
+	if (ret)
+		goto err_chip;
+
+	ret = tpm_try_get_ops(chip);
+	if (ret)
+		goto err_chip;
+
+	ret = tpm2_asymmetric_load(chip, tpm2_key, &buf, &key_handle);
+	if (ret)
+		goto err_ops;
+
+	tpm2_end_auth_session(chip);
+	tpm_buf_destroy(&buf);
+
+	ret = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS,
+			   TPM2_CC_VERIFY_SIGNATURE);
+	if (ret)
+		goto err_key;
+
+	tpm_buf_append_u32(&buf, key_handle);
+
+	tpm_buf_append_u16(&buf, sig->m_size);
+	tpm_buf_append(&buf, sig->m, sig->m_size);
+
+	tpm_buf_append_u16(&buf, TPM_ALG_RSASSA);
+	tpm_buf_append_u16(&buf, tpm_hash);
+	tpm_buf_append_u16(&buf, sig->s_size);
+	tpm_buf_append(&buf, sig->s, sig->s_size);
+
+	ret = tpm_transmit_cmd(chip, &buf, 0, "TPM2_VerifySignature");
+	if (ret)
+		ret = -EKEYREJECTED;
+
+	tpm_buf_destroy(&buf);
+
+err_key:
+	tpm2_flush_context(chip, key_handle);
+
+err_ops:
+	tpm_put_ops(chip);
+
+err_chip:
+	put_device(&chip->dev);
+	return ret;
+}
+
+static int tpm2_asymmetric_rsa_query(const struct kernel_pkey_params *params,
+				     struct kernel_pkey_query *info)
+{
+	const struct tpm2_key *key = params->key->payload.data[asym_crypto];
+	u16 max_data_size = TPM2_MAX_DIGEST_SIZE;
+	const u16 mod_size = tpm2_asymmetric_rsa_mod_size(key);
+	int hash_id, ret;
+
+	if (!params->encoding)
+		return -EINVAL;
+
+	memset(info, 0, sizeof(*info));
+	info->key_size = mod_size * 8;
+
+	if (strcmp(params->encoding, "pkcs1") == 0) {
+		if (params->hash_algo) {
+			ret = tpm2_asymmetric_hash_lookup(params->hash_algo, &hash_id, NULL);
+			if (ret)
+				return ret;
+			max_data_size = hash_digest_size[hash_id];
+		}
+
+		info->max_data_size = max_data_size;
+		info->max_sig_size = mod_size;
+		info->max_enc_size = mod_size;
+		info->max_dec_size = mod_size;
+		info->supported_ops = KEYCTL_SUPPORTS_SIGN |
+				      KEYCTL_SUPPORTS_VERIFY |
+				      KEYCTL_SUPPORTS_ENCRYPT |
+				      KEYCTL_SUPPORTS_DECRYPT;
+		return 0;
+	}
+
+	if (strcmp(params->encoding, "raw") == 0) {
+		info->max_data_size = mod_size;
+		info->max_enc_size = mod_size;
+		info->max_dec_size = mod_size;
+		info->supported_ops = KEYCTL_SUPPORTS_ENCRYPT;
+		return 0;
+	}
+
+	return -ENOPKG;
+}
+
+static int tpm2_asymmetric_rsa_validate(const struct tpm2_key *key)
+{
+	const struct tpm2_asymmetric_rsa_parms *p = tpm2_asymmetric_parms(key);
+	u16 key_bits;
+	u16 mod_size;
+
+	if (tpm2_key_policy_size(key) != 0)
+		return -EBADMSG;
+
+	if (key->pub_len < 2 + sizeof(*key->desc) + sizeof(*p))
+		return -EBADMSG;
+
+	if (be16_to_cpu(p->symmetric) != TPM_ALG_NULL)
+		return -EBADMSG;
+
+	if (be16_to_cpu(p->scheme) != TPM_ALG_NULL)
+		return -EBADMSG;
+
+	key_bits = be16_to_cpu(p->key_bits);
+	if (key_bits != 2048 && key_bits != 3072 && key_bits != 4096)
+		return -EBADMSG;
+
+	if (be32_to_cpu(p->exponent) != 0x00000000 &&
+	    be32_to_cpu(p->exponent) != 0x00010001)
+		return -EBADMSG;
+
+	mod_size = tpm2_asymmetric_rsa_mod_size(key);
+	if (mod_size != key_bits / 8)
+		return -EBADMSG;
+
+	if (key->pub_len < 2 + sizeof(*key->desc) + sizeof(*p) + mod_size)
+		return -EBADMSG;
+
+	return 0;
+}
+
+static unsigned int tpm2_asymmetric_der_len_size(unsigned int len)
+{
+	if (len < 128)
+		return 1;
+	if (len <= 255)
+		return 2;
+	return 3;
+}
+
+/*
+ * Parse a DER-encoded ECDSA signature: SEQUENCE { INTEGER r, INTEGER s }.
+ *
+ * On success, @r/@r_len and @s/@s_len point into @der with leading zero
+ * pads stripped.
+ */
+static int tpm2_asymmetric_ecc_parse_der_signature(const u8 *der, u32 der_len,
+						   const u8 **r, u16 *r_len,
+						   const u8 **s, u16 *s_len)
+{
+	const u8 *end = der + der_len;
+	u32 seq_len, int_len;
+	const u8 *p = der;
+
+	if (p >= end || *p++ != 0x30)
+		return -EBADMSG;
+
+	if (p >= end)
+		return -EBADMSG;
+	if (*p < 0x80) {
+		seq_len = *p++;
+	} else if (*p == 0x81) {
+		if (++p >= end)
+			return -EBADMSG;
+		seq_len = *p++;
+	} else {
+		return -EBADMSG;
+	}
+
+	if (p + seq_len > end)
+		return -EBADMSG;
+	end = p + seq_len;
+
+	/* INTEGER r */
+	if (p >= end || *p++ != 0x02)
+		return -EBADMSG;
+	if (p >= end)
+		return -EBADMSG;
+	int_len = *p++;
+	if (int_len == 0 || int_len >= 0x80 || p + int_len > end)
+		return -EBADMSG;
+	while (int_len > 1 && *p == 0x00) {
+		p++;
+		int_len--;
+	}
+	*r = p;
+	*r_len = int_len;
+	p += int_len;
+
+	/* INTEGER s */
+	if (p >= end || *p++ != 0x02)
+		return -EBADMSG;
+	if (p >= end)
+		return -EBADMSG;
+	int_len = *p++;
+	if (int_len == 0 || int_len >= 0x80 || p + int_len > end)
+		return -EBADMSG;
+	while (int_len > 1 && *p == 0x00) {
+		p++;
+		int_len--;
+	}
+	*s = p;
+	*s_len = int_len;
+	p += int_len;
+
+	if (p != end)
+		return -EBADMSG;
+
+	return 0;
+}
+
+/*
+ * Verify an ECDSA signature using TPM2_VerifySignature.
+ *
+ * A DER-encoded signature is parsed into (r, s) components for the TPM command.
+ */
+static int tpm2_asymmetric_ecc_verify(const struct key *key,
+				      const struct public_key_signature *sig)
+{
+	struct tpm2_key *tpm2_key = key->payload.data[asym_crypto];
+	struct tpm_chip *chip;
+	const u8 *r, *s_data;
+	struct tpm_buf buf;
+	u32 key_handle = 0;
+	u16 r_len, s_len;
+	int tpm_hash;
+	int ret;
+
+	if (!sig->m)
+		return -ENOPKG;
+
+	if (!sig->encoding || strcmp(sig->encoding, "x962") != 0)
+		return -ENOPKG;
+
+	if (!sig->hash_algo)
+		return -EINVAL;
+
+	chip = tpm_default_chip();
+
+	if (!chip)
+		return -ENODEV;
+
+	ret = tpm2_asymmetric_hash_lookup(sig->hash_algo, NULL, &tpm_hash);
+	if (ret)
+		goto err_chip;
+
+	ret = tpm2_asymmetric_ecc_parse_der_signature(sig->s, sig->s_size,
+						      &r, &r_len, &s_data,
+						      &s_len);
+	if (ret)
+		goto err_chip;
+
+	ret = tpm_try_get_ops(chip);
+	if (ret)
+		goto err_chip;
+
+	ret = tpm2_asymmetric_load(chip, tpm2_key, &buf, &key_handle);
+	if (ret)
+		goto err_ops;
+
+	tpm2_end_auth_session(chip);
+	tpm_buf_destroy(&buf);
+
+	ret = tpm_buf_init(&buf, TPM2_ST_NO_SESSIONS,
+			   TPM2_CC_VERIFY_SIGNATURE);
+	if (ret)
+		goto err_key;
+
+	tpm_buf_append_u32(&buf, key_handle);
+
+	/* digest (TPM2B_DIGEST) */
+	tpm_buf_append_u16(&buf, sig->m_size);
+	tpm_buf_append(&buf, sig->m, sig->m_size);
+
+	/* signature (TPMT_SIGNATURE): ECDSA with the given hash */
+	tpm_buf_append_u16(&buf, TPM_ALG_ECDSA);
+	tpm_buf_append_u16(&buf, tpm_hash);
+
+	/* signatureR (TPM2B_ECC_PARAMETER) */
+	tpm_buf_append_u16(&buf, r_len);
+	tpm_buf_append(&buf, r, r_len);
+
+	/* signatureS (TPM2B_ECC_PARAMETER) */
+	tpm_buf_append_u16(&buf, s_len);
+	tpm_buf_append(&buf, s_data, s_len);
+
+	ret = tpm_transmit_cmd(chip, &buf, 0, "TPM2_VerifySignature");
+	if (ret)
+		ret = -EKEYREJECTED;
+
+	tpm_buf_destroy(&buf);
+
+err_key:
+	tpm2_flush_context(chip, key_handle);
+
+err_ops:
+	tpm_put_ops(chip);
+
+err_chip:
+	put_device(&chip->dev);
+	return ret;
+}
+
+static int tpm2_asymmetric_ecc_query(const struct kernel_pkey_params *params,
+				     struct kernel_pkey_query *info)
+{
+	const struct tpm2_key *key = params->key->payload.data[asym_crypto];
+	const struct tpm2_asymmetric_ecc_parms *p = tpm2_asymmetric_parms(key);
+	unsigned int int_len, seq_payload;
+	const u8 *x;
+	u16 ecc, n;
+	int ret;
+
+	ecc = be16_to_cpu(p->ecc);
+	x = tpm2_asymmetric_ecc_x(key);
+	n = get_unaligned_be16(&x[0]);
+	int_len = n + 1;
+
+	if (!params->encoding || strcmp(params->encoding, "x962") != 0)
+		return -ENOPKG;
+
+	ret = tpm2_asymmetric_hash_lookup(params->hash_algo, NULL, NULL);
+	if (ret)
+		return ret;
+
+	/*
+	 * SEQUENCE { INTEGER (<=n+1 bytes), INTEGER (<=n+1 bytes) }
+	 */
+	seq_payload = 2 * (1 + tpm2_asymmetric_der_len_size(int_len) + int_len);
+
+	memset(info, 0, sizeof(*info));
+	info->key_size = tpm2_asymmetric_ecc_key_bits(ecc);
+	info->max_sig_size = 1 + tpm2_asymmetric_der_len_size(seq_payload) + seq_payload;
+	info->max_data_size = TPM2_MAX_DIGEST_SIZE;
+	info->supported_ops = KEYCTL_SUPPORTS_SIGN | KEYCTL_SUPPORTS_VERIFY;
+
+	return 0;
+}
+
+static int tpm2_asymmetric_ecc_validate(const struct tpm2_key *key)
+{
+	const struct tpm2_asymmetric_ecc_parms *p = tpm2_asymmetric_parms(key);
+	size_t min_len = 2 + sizeof(*key->desc) + sizeof(*p);
+	u16 x_size, y_size;
+	const u8 *x, *y;
+
+	if (tpm2_key_policy_size(key) != 0)
+		return -EBADMSG;
+
+	if (key->pub_len < min_len + 2)
+		return -EBADMSG;
+
+	if (be16_to_cpu(p->symmetric) != TPM_ALG_NULL)
+		return -EBADMSG;
+
+	if (be16_to_cpu(p->scheme) != TPM_ALG_NULL)
+		return -EBADMSG;
+
+	if (be16_to_cpu(p->ecc) != TPM2_ECC_NIST_P256 &&
+	    be16_to_cpu(p->ecc) != TPM2_ECC_NIST_P384 &&
+	    be16_to_cpu(p->ecc) != TPM2_ECC_NIST_P521)
+		return -EBADMSG;
+
+	if (be16_to_cpu(p->kdf) != TPM_ALG_NULL)
+		return -EBADMSG;
+
+	x = tpm2_asymmetric_ecc_x(key);
+	x_size = get_unaligned_be16(&x[0]);
+	if (x_size > ECC_MAX_BYTES)
+		return -EBADMSG;
+
+	if (key->pub_len < min_len + 2 + x_size + 2)
+		return -EBADMSG;
+
+	y = tpm2_asymmetric_ecc_y(key);
+	y_size = get_unaligned_be16(&y[0]);
+	if (y_size > ECC_MAX_BYTES)
+		return -EBADMSG;
+
+	if (key->pub_len < min_len + 2 + x_size + 2 + y_size)
+		return -EBADMSG;
+
+	if (x_size != y_size)
+		return -EBADMSG;
+
+	return 0;
+}
+
+static const char *tpm2_asymmetric_ecc_name(const struct tpm2_key *key)
+{
+	const struct tpm2_asymmetric_ecc_parms *p;
+
+	p = tpm2_asymmetric_parms(key);
+
+	switch (be16_to_cpu(p->ecc)) {
+	case TPM2_ECC_NIST_P256:
+		return "ecdsa-nist-p256";
+	case TPM2_ECC_NIST_P384:
+		return "ecdsa-nist-p384";
+	case TPM2_ECC_NIST_P521:
+		return "ecdsa-nist-p521";
+	default:
+		return "ecdsa";
+	}
+}
+
+static void tpm2_asymmetric_describe(const struct key *asymmetric_key,
+				     struct seq_file *m)
+{
+	const struct tpm2_key *key;
+
+	key = asymmetric_key->payload.data[asym_crypto];
+	if (!key)
+		return;
+
+	switch (tpm2_key_type(key)) {
+	case TPM_ALG_RSA:
+		seq_puts(m, "tpm2.rsa");
+		break;
+	case TPM_ALG_ECC:
+		seq_printf(m, "tpm2.%s", tpm2_asymmetric_ecc_name(key));
+		break;
+	default:
+		seq_puts(m, "tpm2.unknown");
+		break;
+	}
+}
+
+static int tpm2_asymmetric_query(const struct kernel_pkey_params *params,
+				 struct kernel_pkey_query *info)
+{
+	struct tpm2_key *key = params->key->payload.data[asym_crypto];
+
+	switch (tpm2_key_type(key)) {
+	case TPM_ALG_RSA:
+		return tpm2_asymmetric_rsa_query(params, info);
+	case TPM_ALG_ECC:
+		return tpm2_asymmetric_ecc_query(params, info);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int tpm2_asymmetric_eds_op(struct kernel_pkey_params *params,
+				  const void *in, void *out)
+{
+	struct tpm2_key *key = params->key->payload.data[asym_crypto];
+	struct tpm_chip *chip;
+	int ret;
+
+	chip = tpm_default_chip();
+	if (!chip)
+		return -ENODEV;
+
+	switch (params->op) {
+	case kernel_pkey_encrypt:
+		if (tpm2_key_type(key) != TPM_ALG_RSA) {
+			ret = -EOPNOTSUPP;
+			break;
+		}
+		ret = tpm2_asymmetric_rsa_encrypt(chip, key, params, in, out);
+		break;
+	case kernel_pkey_decrypt:
+		if (tpm2_key_type(key) != TPM_ALG_RSA) {
+			ret = -EOPNOTSUPP;
+			break;
+		}
+		ret = tpm2_asymmetric_rsa_decrypt(chip, key, params, in, out);
+		break;
+	case kernel_pkey_sign:
+		ret = tpm2_asymmetric_sign(chip, key, params, in, out);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	put_device(&chip->dev);
+	return ret;
+}
+
+static int tpm2_asymmetric_verify_signature(const struct key *asymmetric_key,
+					    const struct public_key_signature *sig)
+{
+	struct tpm2_key *key = asymmetric_key->payload.data[asym_crypto];
+
+	switch (tpm2_key_type(key)) {
+	case TPM_ALG_RSA:
+		return tpm2_asymmetric_rsa_verify(asymmetric_key, sig);
+	case TPM_ALG_ECC:
+		return tpm2_asymmetric_ecc_verify(asymmetric_key, sig);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static struct asymmetric_key_subtype tpm2_asymmetric_subtype = {
+	.owner			= THIS_MODULE,
+	.name			= "tpm2_asymmetric_key",
+	.name_len		= sizeof("tpm2_asymmetric_key") - 1,
+	.describe		= tpm2_asymmetric_describe,
+	.destroy		= tpm2_asymmetric_key_destroy,
+	.query			= tpm2_asymmetric_query,
+	.eds_op			= tpm2_asymmetric_eds_op,
+	.verify_signature	= tpm2_asymmetric_verify_signature,
+};
+
+static int tpm2_asymmetric_preparse(struct key_preparsed_payload *prep)
+{
+	struct tpm2_key *key __free(kfree) = NULL;
+	int ret;
+
+	key = tpm2_key_decode(prep->data, prep->datalen);
+	if (IS_ERR(key)) {
+		ret = PTR_ERR(key);
+		key = NULL;
+		return ret;
+	}
+
+	if (key->oid != OID_TPMLoadableKey)
+		return -EBADMSG;
+
+	switch (tpm2_key_type(key)) {
+	case TPM_ALG_RSA:
+		ret = tpm2_asymmetric_rsa_validate(key);
+		break;
+	case TPM_ALG_ECC:
+		ret = tpm2_asymmetric_ecc_validate(key);
+		break;
+	default:
+		ret = -EBADMSG;
+		break;
+	}
+
+	if (ret < 0)
+		return ret;
+
+	__module_get(tpm2_asymmetric_subtype.owner);
+
+	prep->payload.data[asym_subtype] = &tpm2_asymmetric_subtype;
+	prep->payload.data[asym_key_ids] = NULL;
+	prep->payload.data[asym_crypto] = no_free_ptr(key);
+	prep->payload.data[asym_auth] = NULL;
+	prep->quotalen = 100;
+
+	return 0;
+}
+
+static struct asymmetric_key_parser tpm2_asymmetric_parser = {
+	.owner	= THIS_MODULE,
+	.name	= "tpm2_asymmetric_parser",
+	.parse	= tpm2_asymmetric_preparse,
+};
+
+static int __init tpm2_asymmetric_init(void)
+{
+	return register_asymmetric_key_parser(&tpm2_asymmetric_parser);
+}
+
+static void __exit tpm2_asymmetric_exit(void)
+{
+	unregister_asymmetric_key_parser(&tpm2_asymmetric_parser);
+}
+
+module_init(tpm2_asymmetric_init);
+module_exit(tpm2_asymmetric_exit);
+
+MODULE_DESCRIPTION("Asymmetric TPM2 key");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/tpm.h b/include/linux/tpm.h
index 202da079d500..d4d5ddc0173a 100644
--- a/include/linux/tpm.h
+++ b/include/linux/tpm.h
@@ -45,6 +45,7 @@ enum tpm2_session_types {
 /* if you add a new hash to this, increment TPM_MAX_HASHES below */
 enum tpm_algorithms {
 	TPM_ALG_ERROR		= 0x0000,
+	TPM_ALG_RSA		= 0x0001,
 	TPM_ALG_SHA1		= 0x0004,
 	TPM_ALG_AES		= 0x0006,
 	TPM_ALG_KEYEDHASH	= 0x0008,
@@ -53,6 +54,9 @@ enum tpm_algorithms {
 	TPM_ALG_SHA512		= 0x000D,
 	TPM_ALG_NULL		= 0x0010,
 	TPM_ALG_SM3_256		= 0x0012,
+	TPM_ALG_RSASSA		= 0x0014,
+	TPM_ALG_RSAES		= 0x0015,
+	TPM_ALG_ECDSA		= 0x0018,
 	TPM_ALG_ECC		= 0x0023,
 	TPM_ALG_CFB		= 0x0043,
 };
@@ -66,6 +70,8 @@ enum tpm_algorithms {
 enum tpm2_curves {
 	TPM2_ECC_NONE		= 0x0000,
 	TPM2_ECC_NIST_P256	= 0x0003,
+	TPM2_ECC_NIST_P384	= 0x0004,
+	TPM2_ECC_NIST_P521	= 0x0005,
 };
 
 struct tpm_digest {
@@ -242,6 +248,7 @@ enum tpm2_structures {
 	TPM2_ST_NO_SESSIONS	= 0x8001,
 	TPM2_ST_SESSIONS	= 0x8002,
 	TPM2_ST_CREATION	= 0x8021,
+	TPM2_ST_HASHCHECK	= 0x8024,
 };
 
 /* Indicates from what layer of the software stack the error comes from */
@@ -276,12 +283,15 @@ enum tpm2_command_codes {
 	TPM2_CC_NV_READ                 = 0x014E,
 	TPM2_CC_CREATE		        = 0x0153,
 	TPM2_CC_LOAD		        = 0x0157,
+	TPM2_CC_RSA_DECRYPT	        = 0x0159,
 	TPM2_CC_SEQUENCE_UPDATE         = 0x015C,
+	TPM2_CC_SIGN		        = 0x015D,
 	TPM2_CC_UNSEAL		        = 0x015E,
 	TPM2_CC_CONTEXT_LOAD	        = 0x0161,
 	TPM2_CC_CONTEXT_SAVE	        = 0x0162,
 	TPM2_CC_FLUSH_CONTEXT	        = 0x0165,
 	TPM2_CC_READ_PUBLIC		= 0x0173,
+	TPM2_CC_RSA_ENCRYPT	        = 0x0174,
 	TPM2_CC_START_AUTH_SESS		= 0x0176,
 	TPM2_CC_VERIFY_SIGNATURE        = 0x0177,
 	TPM2_CC_GET_CAPABILITY	        = 0x017A,
-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH v8 0/3]
From: Jarkko Sakkinen @ 2026-05-24  5:20 UTC (permalink / raw)
  To: keyrings
  Cc: David Howells, linux-crypto, linux-integrity, David Woodhouse,
	James Bottomley, Stefan Berger, Herbert Xu, Mimi Zohar,
	Paul Moore, James Morris, Serge E. Hallyn,
	open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <20260524051519.3708075-1-jarkko@kernel.org>

On Sun, May 24, 2026 at 08:15:11AM +0300, Jarkko Sakkinen wrote:
> This series introduces key type for operating with asymmetric keys using
> a TPM2 chip.
> 
> Change Log
> ==========
> 
> v8:
> - Reset patch change logs given the overhaul of the code and patches.
> - Have only single new subkey type.
> - Make key type only use TPM operations.
> - Use TPM2_Sign for both ECC and RSA keys.
> - Align key descriptions with other key types.
> 
> Previous versions
> =================
> 
> * v7: https://lore.kernel.org/linux-integrity/20240528210823.28798-1-jarkko@kernel.org/
> * v6: https://lore.kernel.org/linux-integrity/20240528035136.11464-1-jarkko@kernel.org/
> * v5: https://lore.kernel.org/linux-integrity/20240523212515.4875-1-jarkko@kernel.org/
> * v4: https://lore.kernel.org/linux-integrity/20240522005252.17841-1-jarkko@kernel.org/
> * v3: https://lore.kernel.org/linux-integrity/20240521152659.26438-1-jarkko@kernel.org/
> * v2: https://lore.kernel.org/linux-integrity/336755.1716327854@warthog.procyon.org.uk/
> * v1: https://lore.kernel.org/linux-integrity/20240520184727.22038-1-jarkko@kernel.org/
> * Derived from https://lore.kernel.org/all/20200518172704.29608-1-prestwoj@gmail.com/
> 
> 
> Jarkko Sakkinen (3):
>   lib/asn1_encoder: Add asn1_encode_integer_bytes()
>   crypto: Migrate TPMKey ASN.1 objects from trusted-keys
>   keys: asymmetric: tpm2_asymmetric
> 
>  crypto/Kconfig                            |    7 +
>  crypto/Makefile                           |    6 +
>  crypto/asymmetric_keys/Kconfig            |   17 +
>  crypto/asymmetric_keys/Makefile           |    1 +
>  crypto/asymmetric_keys/tpm2_asymmetric.c  | 1096 +++++++++++++++++++++
>  crypto/tpm2_key.asn1                      |   11 +
>  crypto/tpm2_key.c                         |  150 +++
>  include/crypto/tpm2_key.h                 |   46 +
>  include/linux/asn1_encoder.h              |    3 +
>  include/linux/tpm.h                       |   10 +
>  lib/asn1_encoder.c                        |   62 ++
>  security/keys/trusted-keys/Kconfig        |    2 +-
>  security/keys/trusted-keys/Makefile       |    2 -
>  security/keys/trusted-keys/tpm2key.asn1   |   11 -
>  security/keys/trusted-keys/trusted_tpm2.c |  119 +--
>  15 files changed, 1421 insertions(+), 122 deletions(-)
>  create mode 100644 crypto/asymmetric_keys/tpm2_asymmetric.c
>  create mode 100644 crypto/tpm2_key.asn1
>  create mode 100644 crypto/tpm2_key.c
>  create mode 100644 include/crypto/tpm2_key.h
>  delete mode 100644 security/keys/trusted-keys/tpm2key.asn1
> 
> -- 
> 2.47.3
> 

There's some initial test code for this too:

https://git.kernel.org/pub/scm/linux/kernel/git/jarkko/linux-tpmdd-test.git/tree/overlay/usr/local/bin/tpmdd_tpm2_asymmetric.sh?h=main

Ugh, that's one hell of an url...

BR, Jarkko

^ permalink raw reply

* [PATCH] apparmor: Constify 'nulldfa_src' and 'stacksplitdfa_src' arrays
From: Len Bao @ 2026-05-24 11:34 UTC (permalink / raw)
  To: John Johansen, Paul Moore, James Morris, Serge E. Hallyn
  Cc: Len Bao, apparmor, linux-security-module, linux-kernel

The 'nulldfa_src' and 'stacksplitdfa_src' arrays are initialized in
their declarations and never changed. So, constify them to reduce the
attack surface.

To make this possible, it is also necessary to change the 'unpack_table'
and 'aa_dfa_unpack' function prototypes to pass, as a first argument, a
pointer to a 'const' blob. At the same type, define the blob exact
pointer type (pointer to const char) since all the calls to the
mentioned functions use this same type.

Before the patch (size lsm.o):

  text	   data	    bss	    dec	    hex
128768	  28028	    704	 157500	  2673c

After the patch (size lsm.o):

  text	   data	    bss	    dec	    hex
131264	  25532	    704	 157500	  2673c

Signed-off-by: Len Bao <len.bao@gmx.us>
---
 security/apparmor/include/match.h | 2 +-
 security/apparmor/lsm.c           | 4 ++--
 security/apparmor/match.c         | 6 +++---
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h
index 7accb1c39..4a92cd044 100644
--- a/security/apparmor/include/match.h
+++ b/security/apparmor/include/match.h
@@ -125,7 +125,7 @@ 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);
+struct aa_dfa *aa_dfa_unpack(const char *blob, size_t size, int flags);
 aa_state_t aa_dfa_match_len(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,
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3491e9f60..3f995b6a7 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -2432,12 +2432,12 @@ 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 char stacksplitdfa_src[] __aligned(8) = {
+static const char stacksplitdfa_src[] __aligned(8) = {
 	#include "stacksplitdfa.in"
 };
 struct aa_dfa *stacksplitdfa;
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
index 3a2c6cf02..c6f7bea1e 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 = NULL;
 	struct table_header th;
@@ -311,11 +311,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 char *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)
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v8 1/9] landlock: Add a place for flags to layer rules
From: Justin Suess @ 2026-05-24 14:46 UTC (permalink / raw)
  To: Tingmao Wang
  Cc: Mickaël Salaün, Günther Noack, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <617fdd53-6612-4f6f-b0e0-16d85985487b@maowtm.org>

On Sun, May 24, 2026 at 02:29:40AM +0100, Tingmao Wang wrote:
> On 5/23/26 21:48, Mickaël Salaün wrote:
> > This patch doesn't build.
> 
> Missed a hunk in this patch (ended up in the next one), will add.
> 
> >> @@ -797,20 +803,28 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
> >>  	}
> >>  
> >>  	if (unlikely(dentry_child1)) {
> >> +		/*
> >> +		 * Get the layer masks for the child dentries for use by domain
> >> +		 * check later.  The rule_flags for child1 should have been
> >> +		 * included in rule_flags_parent1 already (cf.
> >> +		 * collect_domain_accesses), and is not relevant for domain check,
> >> +		 * so we don't have to pass it to landlock_unmask_layers.
> >> +		 */
> >>  		if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> >>  					      &_layer_masks_child1,
> >>  					      LANDLOCK_KEY_INODE))
> >>  			landlock_unmask_layers(find_rule(domain, dentry_child1),
> >> -					       &_layer_masks_child1);
> >> +					       &_layer_masks_child1, NULL);
> >>  		layer_masks_child1 = &_layer_masks_child1;
> >>  		child1_is_directory = d_is_dir(dentry_child1);
> >>  	}
> >>  	if (unlikely(dentry_child2)) {
> >> +		/* See above comment for why NULL is passed as rule_flags_masks. */
> > 
> > rule_flags_masks doesn't exist.
> 
> I guess I was probably referring to the rule_flags argument - will fix.
> 
> >> [...]
> >> @@ -647,9 +648,14 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
> >>  	 */
> >>  	for (size_t i = 0; i < rule->num_layers; i++) {
> >>  		const struct landlock_layer *const layer = &rule->layers[i];
> >> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
> >>  
> > 
> >>  		/* Clear the bits where the layer in the rule grants access. */
> >>  		masks->access[layer->level - 1] &= ~layer->access;
> >> +
> >> +		/* Collect rule flags for each layer. */
> >> +		if (rule_flags && layer->flags.quiet)
> >> +			rule_flags->quiet_masks |= layer_bit;
> > 
> > Why not store the quiet bit in masks?  That would not only be "access"
> > bits anymore but it makes sense to store all this bits it the same
> > place.
> > 
> > We should then probably rename struct layer_access_masks to just struct
> > layer_masks.
> > 
> > We need to be careful to not increase too much the size of this struct
> > though while keeping the [LANDLOCK_MAX_NUM_LAYERS] approach if possible
> > (see Günther's commit that added it).
> 
> Most uses of struct layer_access_masks do not actually care about the rule
> flags tho, e.g. in unmask_scoped_access, scope_to_request, or may_refer.
> Such a rename would touch 31 places (and only a few of them would actually
> touch the quiet flag).
> 
> If we want to refactor to make this be in the layer_access_masks (then
> rename it), I guess there are 3 options, which do you prefer?
> 
> 1. Add a u16 bitfield for which layers are quieted.  Future rule flags
>    will be additional bitfields.  struct layer_masks becomes 68 bytes (+4).
> 
> struct layer_masks {
> 	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
> 	layer_mask_t quiet_layers;
> };
> 
> 2. Make the [LANDLOCK_MAX_NUM_LAYERS] array store both the access mask and
>    the quiet bit (or more bits for future rule flags).  Size of struct stays
>    the same.
> 
This approach seems best.
> static_assert(LANDLOCK_NUM_ACCESS_NET <= LANDLOCK_NUM_ACCESS_FS);
> static_assert(LANDLOCK_NUM_SCOPE <= LANDLOCK_NUM_ACCESS_FS);
> struct layer_mask {
> 	access_mask_t access:LANDLOCK_NUM_ACCESS_FS;
> 	bool quiet:1;
> };

Other way to do it could be an (anonymous?) union.

union {
  access_mask_t fs_access:LANDLOCK_NUM_ACCESS_FS;
  access_mask_t net_access:LANDLOCK_NUM_ACCESS_NET;
  access_mask_t scope_access:LANDLOCK_NUM_SCOPE;
}

The union should be sized to fit the largest field automatically.

That way you don't have to change this when adding new access rights
and avoid the brittle static_asserts.

Not sure about the alignment implications here though.
> struct layer_masks {
> 	struct layer_mask layer[LANDLOCK_MAX_NUM_LAYERS];
> };
> 
>    (Maybe we can just make struct layer_masks a typedef to
>    layer_mask[LANDLOCK_MAX_NUM_LAYERS] instead?  But currently not sure if
>    there are any gotchas with a typedef like that)
> 
Typedefs are generally discouraged for structs and pointers in the
kernel coding style.

https://docs.kernel.org/process/coding-style.html
> 3. Mirror layer_access_masks::access[] - add a
>    rule_flags[LANDLOCK_MAX_NUM_LAYERS] too.  struct layer_masks becomes 80
>    bytes (+16).
> 
> struct rule_flags {
> 	bool quiet:1;
> };
> struct layer_masks {
> 	/**
> 	 * @access: The unfulfilled access rights for each layer.
> 	 */
> 	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
> 	struct rule_flags rule_flags[LANDLOCK_MAX_NUM_LAYERS];
> };
> 
> (3 seems very wasteful to me)

^ permalink raw reply

* Re: [PATCH v8 1/9] landlock: Add a place for flags to layer rules
From: Tingmao Wang @ 2026-05-24 18:20 UTC (permalink / raw)
  To: Justin Suess, Mickaël Salaün
  Cc: Günther Noack, Jan Kara, Abhinav Saxena,
	linux-security-module
In-Reply-To: <ahMLLwnDO7g64h63@zenbox>

On 5/24/26 15:46, Justin Suess wrote:
> On Sun, May 24, 2026 at 02:29:40AM +0100, Tingmao Wang wrote:
>> On 5/23/26 21:48, Mickaël Salaün wrote:
>>> [...]
>>>> @@ -647,9 +648,14 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
>>>>  	 */
>>>>  	for (size_t i = 0; i < rule->num_layers; i++) {
>>>>  		const struct landlock_layer *const layer = &rule->layers[i];
>>>> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
>>>>  
>>>
>>>>  		/* Clear the bits where the layer in the rule grants access. */
>>>>  		masks->access[layer->level - 1] &= ~layer->access;
>>>> +
>>>> +		/* Collect rule flags for each layer. */
>>>> +		if (rule_flags && layer->flags.quiet)
>>>> +			rule_flags->quiet_masks |= layer_bit;
>>>
>>> Why not store the quiet bit in masks?  That would not only be "access"
>>> bits anymore but it makes sense to store all this bits it the same
>>> place.
>>>
>>> We should then probably rename struct layer_access_masks to just struct
>>> layer_masks.
>>>
>>> We need to be careful to not increase too much the size of this struct
>>> though while keeping the [LANDLOCK_MAX_NUM_LAYERS] approach if possible
>>> (see Günther's commit that added it).
>>
>> Most uses of struct layer_access_masks do not actually care about the rule
>> flags tho, e.g. in unmask_scoped_access, scope_to_request, or may_refer.
>> Such a rename would touch 31 places (and only a few of them would actually
>> touch the quiet flag).
>>
>> If we want to refactor to make this be in the layer_access_masks (then
>> rename it), I guess there are 3 options, which do you prefer?
>>
>> 1. Add a u16 bitfield for which layers are quieted.  Future rule flags
>>    will be additional bitfields.  struct layer_masks becomes 68 bytes (+4).
>>
>> struct layer_masks {
>> 	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
>> 	layer_mask_t quiet_layers;
>> };
>>
>> 2. Make the [LANDLOCK_MAX_NUM_LAYERS] array store both the access mask and
>>    the quiet bit (or more bits for future rule flags).  Size of struct stays
>>    the same.
>>
> This approach seems best.
>> static_assert(LANDLOCK_NUM_ACCESS_NET <= LANDLOCK_NUM_ACCESS_FS);
>> static_assert(LANDLOCK_NUM_SCOPE <= LANDLOCK_NUM_ACCESS_FS);
>> struct layer_mask {
>> 	access_mask_t access:LANDLOCK_NUM_ACCESS_FS;
>> 	bool quiet:1;
>> };
> 
> Other way to do it could be an (anonymous?) union.
> 
> union {
>   access_mask_t fs_access:LANDLOCK_NUM_ACCESS_FS;
>   access_mask_t net_access:LANDLOCK_NUM_ACCESS_NET;
>   access_mask_t scope_access:LANDLOCK_NUM_SCOPE;
> }
> 
> The union should be sized to fit the largest field automatically.
> 
> That way you don't have to change this when adding new access rights
> and avoid the brittle static_asserts.
> 
> Not sure about the alignment implications here though.

Unfortunately this forces struct layer_mask to be 2x as large:
https://godbolt.org/z/5P9b4rrMW

But it turns out I could have just used MAX, seems to compile for me:

struct layer_mask {
	access_mask_t access
		: MAX(LANDLOCK_NUM_ACCESS_FS,
		      MAX(LANDLOCK_NUM_ACCESS_NET, LANDLOCK_NUM_SCOPE));
	bool quiet : 1;
};
struct layer_masks {
	struct layer_mask layer[LANDLOCK_MAX_NUM_LAYERS];
};

Maybe we could #define LANDLOCK_NUM_ACCESS_MAX to be MAX(...) then use it
here.

I'm still not sure if putting the collected rule flags in struct
layer_(access_)masks is a good idea tho.  Passing a separate struct
collected_rule_flags to the functions that needs to deal with rule flags
(quiet, and later, no inherit / has no inherit descendant) seems quite
practical to me.

^ permalink raw reply

* Re: [PATCH v8 1/9] landlock: Add a place for flags to layer rules
From: Mickaël Salaün @ 2026-05-24 20:35 UTC (permalink / raw)
  To: Tingmao Wang
  Cc: Günther Noack, Justin Suess, Jan Kara, Abhinav Saxena,
	linux-security-module
In-Reply-To: <617fdd53-6612-4f6f-b0e0-16d85985487b@maowtm.org>

On Sun, May 24, 2026 at 02:29:40AM +0100, Tingmao Wang wrote:
> On 5/23/26 21:48, Mickaël Salaün wrote:
> > This patch doesn't build.
> 
> Missed a hunk in this patch (ended up in the next one), will add.
> 
> >> @@ -797,20 +803,28 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
> >>  	}
> >>  
> >>  	if (unlikely(dentry_child1)) {
> >> +		/*
> >> +		 * Get the layer masks for the child dentries for use by domain
> >> +		 * check later.  The rule_flags for child1 should have been
> >> +		 * included in rule_flags_parent1 already (cf.
> >> +		 * collect_domain_accesses), and is not relevant for domain check,
> >> +		 * so we don't have to pass it to landlock_unmask_layers.
> >> +		 */
> >>  		if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> >>  					      &_layer_masks_child1,
> >>  					      LANDLOCK_KEY_INODE))
> >>  			landlock_unmask_layers(find_rule(domain, dentry_child1),
> >> -					       &_layer_masks_child1);
> >> +					       &_layer_masks_child1, NULL);
> >>  		layer_masks_child1 = &_layer_masks_child1;
> >>  		child1_is_directory = d_is_dir(dentry_child1);
> >>  	}
> >>  	if (unlikely(dentry_child2)) {
> >> +		/* See above comment for why NULL is passed as rule_flags_masks. */
> > 
> > rule_flags_masks doesn't exist.
> 
> I guess I was probably referring to the rule_flags argument - will fix.
> 
> >> [...]
> >> @@ -647,9 +648,14 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
> >>  	 */
> >>  	for (size_t i = 0; i < rule->num_layers; i++) {
> >>  		const struct landlock_layer *const layer = &rule->layers[i];
> >> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
> >>  
> > 
> >>  		/* Clear the bits where the layer in the rule grants access. */
> >>  		masks->access[layer->level - 1] &= ~layer->access;
> >> +
> >> +		/* Collect rule flags for each layer. */
> >> +		if (rule_flags && layer->flags.quiet)
> >> +			rule_flags->quiet_masks |= layer_bit;
> > 
> > Why not store the quiet bit in masks?  That would not only be "access"
> > bits anymore but it makes sense to store all this bits it the same
> > place.
> > 
> > We should then probably rename struct layer_access_masks to just struct
> > layer_masks.
> > 
> > We need to be careful to not increase too much the size of this struct
> > though while keeping the [LANDLOCK_MAX_NUM_LAYERS] approach if possible
> > (see Günther's commit that added it).
> 
> Most uses of struct layer_access_masks do not actually care about the rule
> flags tho, e.g. in unmask_scoped_access, scope_to_request, or may_refer.
> Such a rename would touch 31 places (and only a few of them would actually
> touch the quiet flag).

Most of these places are tests.

> 
> If we want to refactor to make this be in the layer_access_masks (then
> rename it), I guess there are 3 options, which do you prefer?
> 
> 1. Add a u16 bitfield for which layers are quieted.  Future rule flags
>    will be additional bitfields.  struct layer_masks becomes 68 bytes (+4).
> 
> struct layer_masks {
> 	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
> 	layer_mask_t quiet_layers;
> };
> 
> 2. Make the [LANDLOCK_MAX_NUM_LAYERS] array store both the access mask and
>    the quiet bit (or more bits for future rule flags).  Size of struct stays
>    the same.
> 
> static_assert(LANDLOCK_NUM_ACCESS_NET <= LANDLOCK_NUM_ACCESS_FS);
> static_assert(LANDLOCK_NUM_SCOPE <= LANDLOCK_NUM_ACCESS_FS);
> struct layer_mask {
> 	access_mask_t access:LANDLOCK_NUM_ACCESS_FS;
> 	bool quiet:1;
> };

Yes, like Justin, I prefer this approach too.  Some improvements:

// In limits.h:
#define LANDLOCK_MAX_NUM_ACCESSES \
	MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET)

// In access.h:
struct layer_mask {
	access_mask_t access:LANDLOCK_MAX_NUM_ACCESSES;
#ifdef CONFIG_AUDIT
	bool quiet:1; // I'm not sure if using bool would work for all
	// architectures though, but we can make sure with the following
	// assert.
#endif /* CONFIG_AUDIT */
};
static_assert(sizeof(struct layer_mask), sizeof(access_mask_t));

> struct layer_masks {
> 	struct layer_mask layer[LANDLOCK_MAX_NUM_LAYERS];
> };
> 
>    (Maybe we can just make struct layer_masks a typedef to
>    layer_mask[LANDLOCK_MAX_NUM_LAYERS] instead?  But currently not sure if
>    there are any gotchas with a typedef like that)

The only typedefs used in Landlock are for potentially growing types. So
no need for typedef here.

> 
> 3. Mirror layer_access_masks::access[] - add a
>    rule_flags[LANDLOCK_MAX_NUM_LAYERS] too.  struct layer_masks becomes 80
>    bytes (+16).
> 
> struct rule_flags {
> 	bool quiet:1;
> };
> struct layer_masks {
> 	/**
> 	 * @access: The unfulfilled access rights for each layer.
> 	 */
> 	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
> 	struct rule_flags rule_flags[LANDLOCK_MAX_NUM_LAYERS];
> };
> 
> (3 seems very wasteful to me)
> 

^ permalink raw reply

* Re: [PATCH v8 2/9] landlock: Add API support and docs for the quiet flags
From: Mickaël Salaün @ 2026-05-24 20:35 UTC (permalink / raw)
  To: Tingmao Wang
  Cc: Günther Noack, Justin Suess, Jan Kara, Abhinav Saxena,
	linux-security-module
In-Reply-To: <b4d25793959493f0a8ef66f03feedac1a15e7595.1775490344.git.m@maowtm.org>

On Mon, Apr 06, 2026 at 04:52:15PM +0100, Tingmao Wang wrote:
> Adds the UAPI for the quiet flags feature (but not the implementation
> yet).
> 
> According to pahole, even after adding the struct access_masks quiet_masks
> in struct landlock_hierarchy, the u32 log_* bitfield still only has a size
> of 2 bytes, so there's minimal wasted space.
> 
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> ---
> 
> Changes in v8:
> - The new Landlock ABI version is now v10 as a result of rebase.
> - Allocate a rule_flags in hook_unix_find() and pass to
>   is_access_to_paths_allowed().
> 
> Changes in v6:
> - Fix typo in doc
> 
> Changes in v5:
> - Doc fixes.
> - Fix build failure without CONFIG_AUDIT / CONFIG_INET (reported by Justin
>   Suess)
> 
> Changes in v4:
> - Minor update to this commit message.
> - Fix minor formatting
> 
> Changes in v3:
> - Updated docs from Mickaël's suggestions.
> 
> Changes in v2:
> - Per suggestion, added support for quieting only certain access bits,
>   controlled by extra quiet_access_* fields in the ruleset_attr.
> - Added docs for the extra fields and made updates to doc changes in v1.
>   In particular, call out that the effect of LANDLOCK_ADD_RULE_QUIET is
>   independent from the access bits passed in rule_attr
> - landlock_add_rule will return -EINVAL when LANDLOCK_ADD_RULE_QUIET is
>   used but the ruleset does not have any quiet access bits set for the
>   given rule type.
> - ABI version bump to v8
> - Syntactic and comment changes per suggestion.
> 
>  include/uapi/linux/landlock.h                | 64 +++++++++++++++++
>  security/landlock/domain.h                   |  5 ++
>  security/landlock/fs.c                       | 11 +--
>  security/landlock/fs.h                       |  2 +-
>  security/landlock/net.c                      |  5 +-
>  security/landlock/net.h                      |  5 +-
>  security/landlock/ruleset.c                  | 12 +++-
>  security/landlock/ruleset.h                  | 12 +++-
>  security/landlock/syscalls.c                 | 72 +++++++++++++++-----
>  tools/testing/selftests/landlock/base_test.c |  6 +-
>  10 files changed, 160 insertions(+), 34 deletions(-)
> 
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index 10a346e55e95..9a41c65623a1 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -32,6 +32,19 @@
>   * *handle* a wide range or all access rights that they know about at build time
>   * (and that they have tested with a kernel that supported them all).
>   *
> + * @quiet_access_fs and @quiet_access_net are bitmasks of actions for
> + * which a denial by this layer will not trigger an audit log if the
> + * corresponding object (or its children, for filesystem rules) is marked
> + * with the "quiet" bit via %LANDLOCK_ADD_RULE_QUIET, even if logging
> + * would normally take place per landlock_restrict_self() flags.
> + * quiet_scoped is similar, except that it does not require marking any

@quiet_scoped

> + * objects as quiet - if the ruleset is created with any bits set in
> + * quiet_scoped, then denial of such scoped resources will not trigger any

@quiet_scoped

> + * log.  These 3 fields are available since Landlock ABI version 10.
> + *
> + * @quiet_access_fs, @quiet_access_net and @quiet_scoped must be a subset
> + * of @handled_access_fs, @handled_access_net and @scoped respectively.
> + *
>   * This structure can grow in future Landlock versions.
>   */
>  struct landlock_ruleset_attr {
> @@ -51,6 +64,24 @@ struct landlock_ruleset_attr {
>  	 * resources (e.g. IPCs).
>  	 */
>  	__u64 scoped;

> +
> +	/* Since ABI 10: */

No need for this comment and new lines.

> +
> +	/**
> +	 * @quiet_access_fs: Bitmask of filesystem actions which should not be
> +	 * audit logged if per-object quiet flag is set.

Just "logged".

> +	 */
> +	__u64 quiet_access_fs;
> +	/**
> +	 * @quiet_access_net: Bitmask of network actions which should not be
> +	 * audit logged if per-object quiet flag is set.

ditto

> +	 */
> +	__u64 quiet_access_net;
> +	/**
> +	 * @quiet_scoped: Bitmask of scoped actions which should not be audit
> +	 * logged.
> +	 */
> +	__u64 quiet_scoped;
>  };
>  
>  /**
> @@ -69,6 +100,39 @@ struct landlock_ruleset_attr {
>  #define LANDLOCK_CREATE_RULESET_ERRATA			(1U << 1)
>  /* clang-format on */
>  
> +/**
> + * DOC: landlock_add_rule_flags
> + *
> + * **Flags**
> + *
> + * %LANDLOCK_ADD_RULE_QUIET
> + *     Together with the quiet_* fields in struct landlock_ruleset_attr,
> + *     this flag controls whether Landlock will log audit messages when
> + *     access to the objects covered by this rule is denied by this layer.
> + *
> + *     If audit logging is enabled, when Landlock denies an access, it will
> + *     suppress the audit log if all of the following are true:
> + *
> + *     - this layer is the innermost layer that denied the access;
> + *     - all accesses denied by this layer are part of the quiet_* fields
> + *       in the related struct landlock_ruleset_attr;
> + *     - the object (or one of its parents, for filesystem rules) is
> + *       marked as "quiet" via %LANDLOCK_ADD_RULE_QUIET.
> + *
> + *     Because logging is only suppressed by a layer if the layer denies
> + *     access, a sandboxed program cannot use this flag to "hide" access
> + *     denials, without denying itself the access in the first place.

This is not 100% correct: if a domain only handles/denies/quiet read, and a
parent domain denies write, open(, O_RDwR) would not generate a log,
which is OK.

> + *
> + *     The effect of this flag does not depend on the value of
> + *     allowed_access in the passed in rule_attr.  When this flag is
> + *     present, the caller is also allowed to pass in an empty
> + *     allowed_access.

The audit/log part in Documentation/userspace-api/landlock.rst and
Documentation/security/landlock.rst should be updated to take this quiet
flags into account.

> + */
> +
> +/* clang-format off */
> +#define LANDLOCK_ADD_RULE_QUIET			(1U << 0)

I think this name is correct because this flag will be used by the
supervisor feature, but otherwise it should be named something like
LANDLOCK_ADD_RULE_LOG_QUIET.  Tingmao, do you think that makes sense?
If yes, it should be explained in the commit message that this quiet
flag may be used for some kind of notification...

> +/* clang-format on */
> +
>  /**
>   * DOC: landlock_restrict_self_flags
>   *
> diff --git a/security/landlock/domain.h b/security/landlock/domain.h
> index a9d57db0120d..9b8aeac8ebd2 100644
> --- a/security/landlock/domain.h
> +++ b/security/landlock/domain.h
> @@ -114,6 +114,11 @@ struct landlock_hierarchy {
>  		 * %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON.  Set to false by default.
>  		 */
>  		log_new_exec : 1;
> +	/**
> +	 * @quiet_masks: Bitmasks of access that should be quieted (i.e. not
> +	 * logged) if the related object is marked as quiet.
> +	 */
> +	struct access_masks quiet_masks;
>  #endif /* CONFIG_AUDIT */
>  };
>  
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 6f63e0182ef0..06a8d2258558 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -325,7 +325,7 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
>   */
>  int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>  			    const struct path *const path,
> -			    access_mask_t access_rights)
> +			    access_mask_t access_rights, const int flags)
>  {
>  	int err;
>  	struct landlock_id id = {
> @@ -346,7 +346,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>  	if (IS_ERR(id.key.object))
>  		return PTR_ERR(id.key.object);
>  	mutex_lock(&ruleset->lock);
> -	err = landlock_insert_rule(ruleset, id, access_rights);
> +	err = landlock_insert_rule(ruleset, id, access_rights, flags);
>  	mutex_unlock(&ruleset->lock);
>  	/*
>  	 * No need to check for an error because landlock_insert_rule()
> @@ -1662,6 +1662,7 @@ static int hook_unix_find(const struct path *const path, struct sock *other,
>  	static const struct access_masks fs_resolve_unix = {
>  		.fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
>  	};
> +	struct collected_rule_flags rule_flags = {};
>  
>  	/* Lookup for the purpose of saving coredumps is OK. */
>  	if (unlikely(flags & SOCK_COREDUMP))
> @@ -1700,9 +1701,9 @@ static int hook_unix_find(const struct path *const path, struct sock *other,
>  	unix_state_unlock(other);
>  
>  	/* Checks the connections to allow-listed paths. */
> -	if (is_access_to_paths_allowed(subject->domain, path,
> -				       fs_resolve_unix.fs, &layer_masks,
> -				       &request, NULL, 0, NULL, NULL, NULL))
> +	if (is_access_to_paths_allowed(
> +		    subject->domain, path, fs_resolve_unix.fs, &layer_masks,
> +		    &rule_flags, &request, NULL, 0, NULL, NULL, NULL, NULL))
>  		return 0;
>  
>  	landlock_log_denial(subject, &request);
> diff --git a/security/landlock/fs.h b/security/landlock/fs.h
> index bf9948941f2f..cb7e654933ac 100644
> --- a/security/landlock/fs.h
> +++ b/security/landlock/fs.h
> @@ -126,6 +126,6 @@ __init void landlock_add_fs_hooks(void);
>  
>  int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
>  			    const struct path *const path,
> -			    access_mask_t access_hierarchy);
> +			    access_mask_t access_hierarchy, const int flags);
>  
>  #endif /* _SECURITY_LANDLOCK_FS_H */
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> index dc82ce4a2bd4..ade2b1750042 100644
> --- a/security/landlock/net.c
> +++ b/security/landlock/net.c
> @@ -20,7 +20,8 @@
>  #include "ruleset.h"
>  
>  int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
> -			     const u16 port, access_mask_t access_rights)
> +			     const u16 port, access_mask_t access_rights,
> +			     const int flags)
>  {
>  	int err;
>  	const struct landlock_id id = {
> @@ -35,7 +36,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
>  			 ~landlock_get_net_access_mask(ruleset, 0);
>  
>  	mutex_lock(&ruleset->lock);
> -	err = landlock_insert_rule(ruleset, id, access_rights);
> +	err = landlock_insert_rule(ruleset, id, access_rights, flags);
>  	mutex_unlock(&ruleset->lock);
>  
>  	return err;
> diff --git a/security/landlock/net.h b/security/landlock/net.h
> index 09960c237a13..72c47f4d6803 100644
> --- a/security/landlock/net.h
> +++ b/security/landlock/net.h
> @@ -16,7 +16,8 @@
>  __init void landlock_add_net_hooks(void);
>  
>  int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
> -			     const u16 port, access_mask_t access_rights);
> +			     const u16 port, access_mask_t access_rights,
> +			     const int flags);
>  #else /* IS_ENABLED(CONFIG_INET) */
>  static inline void landlock_add_net_hooks(void)
>  {
> @@ -24,7 +25,7 @@ static inline void landlock_add_net_hooks(void)
>  
>  static inline int
>  landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port,
> -			 access_mask_t access_rights)
> +			 access_mask_t access_rights, const int flags)
>  {
>  	return -EAFNOSUPPORT;
>  }
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index e4e6b730b581..d2d1e3fb6cf2 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -21,6 +21,7 @@
>  #include <linux/slab.h>
>  #include <linux/spinlock.h>
>  #include <linux/workqueue.h>
> +#include <uapi/linux/landlock.h>
>  
>  #include "access.h"
>  #include "domain.h"
> @@ -255,6 +256,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
>  			if (WARN_ON_ONCE(this->layers[0].level != 0))
>  				return -EINVAL;
>  			this->layers[0].access |= (*layers)[0].access;
> +			this->layers[0].flags.quiet |= (*layers)[0].flags.quiet;
>  			return 0;
>  		}
>  
> @@ -305,12 +307,15 @@ static void build_check_layer(void)
>  /* @ruleset must be locked by the caller. */
>  int landlock_insert_rule(struct landlock_ruleset *const ruleset,
>  			 const struct landlock_id id,
> -			 const access_mask_t access)
> +			 const access_mask_t access, const int flags)
>  {
>  	struct landlock_layer layers[] = { {
>  		.access = access,
>  		/* When @level is zero, insert_rule() extends @ruleset. */
>  		.level = 0,
> +		.flags = {
> +			.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
> +		},
>  	} };
>  
>  	build_check_layer();
> @@ -351,6 +356,7 @@ static int merge_tree(struct landlock_ruleset *const dst,
>  			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)
> @@ -581,6 +587,10 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
>  	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);
>  }
>  
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 3b31552f0c95..e369f15ae885 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -172,8 +172,8 @@ struct landlock_ruleset {
>  		 * @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 and
> -		 * @access_masks are then unused.
> +		 * The fields @lock, @usage, @num_rules, @num_layers, @quiet_masks
> +		 * and @access_masks are then unused.
>  		 */
>  		struct work_struct work_free;
>  		struct {
> @@ -199,6 +199,12 @@ struct landlock_ruleset {
>  			 * 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.
> @@ -229,7 +235,7 @@ DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *,
>  
>  int landlock_insert_rule(struct landlock_ruleset *const ruleset,
>  			 const struct landlock_id id,
> -			 const access_mask_t access);
> +			 const access_mask_t access, const int flags);
>  
>  struct landlock_ruleset *
>  landlock_merge_ruleset(struct landlock_ruleset *const parent,
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index accfd2e5a0cd..a71068c41f76 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -105,8 +105,11 @@ static void build_check_abi(void)
>  	ruleset_size = sizeof(ruleset_attr.handled_access_fs);
>  	ruleset_size += sizeof(ruleset_attr.handled_access_net);
>  	ruleset_size += sizeof(ruleset_attr.scoped);
> +	ruleset_size += sizeof(ruleset_attr.quiet_access_fs);
> +	ruleset_size += sizeof(ruleset_attr.quiet_access_net);
> +	ruleset_size += sizeof(ruleset_attr.quiet_scoped);
>  	BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
> -	BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
> +	BUILD_BUG_ON(sizeof(ruleset_attr) != 48);
>  
>  	path_beneath_size = sizeof(path_beneath_attr.allowed_access);
>  	path_beneath_size += sizeof(path_beneath_attr.parent_fd);
> @@ -166,7 +169,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 = 9;
> +const int landlock_abi_version = 10;
>  
>  /**
>   * sys_landlock_create_ruleset - Create a new ruleset
> @@ -193,6 +196,8 @@ const int landlock_abi_version = 9;
>   * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
>   * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small
>   *   @size;
> + * - %EINVAL: quiet_access_fs or quiet_access_net is not a subset of the
> + *   corresponding handled_access_fs or handled_access_net;
>   * - %E2BIG: @attr or @size inconsistencies;
>   * - %EFAULT: @attr or @size inconsistencies;
>   * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
> @@ -249,6 +254,21 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
>  	if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
>  		return -EINVAL;
>  
> +	/*
> +	 * Check that quiet masks are subsets of the respective handled masks.
> +	 * Because of the checks above this is sufficient to also ensure that
> +	 * the quiet masks are valid access masks.
> +	 */
> +	if ((ruleset_attr.quiet_access_fs | ruleset_attr.handled_access_fs) !=
> +	    ruleset_attr.handled_access_fs)
> +		return -EINVAL;
> +	if ((ruleset_attr.quiet_access_net | ruleset_attr.handled_access_net) !=
> +	    ruleset_attr.handled_access_net)
> +		return -EINVAL;
> +	if ((ruleset_attr.quiet_scoped | ruleset_attr.scoped) !=
> +	    ruleset_attr.scoped)
> +		return -EINVAL;
> +
>  	/* Checks arguments and transforms to kernel struct. */
>  	ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
>  					  ruleset_attr.handled_access_net,
> @@ -256,6 +276,10 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
>  	if (IS_ERR(ruleset))
>  		return PTR_ERR(ruleset);
>  
> +	ruleset->quiet_masks.fs = ruleset_attr.quiet_access_fs;
> +	ruleset->quiet_masks.net = ruleset_attr.quiet_access_net;
> +	ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped;
> +
>  	/* Creates anonymous FD referring to the ruleset. */
>  	ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops,
>  				      ruleset, O_RDWR | O_CLOEXEC);
> @@ -320,7 +344,7 @@ static int get_path_from_fd(const s32 fd, struct path *const path)
>  }
>  
>  static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
> -				 const void __user *const rule_attr)
> +				 const void __user *const rule_attr, int flags)
>  {
>  	struct landlock_path_beneath_attr path_beneath_attr;
>  	struct path path;
> @@ -335,9 +359,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>  
>  	/*
>  	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
> -	 * are ignored in path walks.
> +	 * are ignored in path walks.  However, the rule is not useless if it
> +	 * is there to hold a quiet flag

Missing trailing period.

>  	 */
> -	if (!path_beneath_attr.allowed_access)
> +	if (!flags && !path_beneath_attr.allowed_access)
>  		return -ENOMSG;
>  
>  	/* Checks that allowed_access matches the @ruleset constraints. */
> @@ -345,6 +370,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>  	if ((path_beneath_attr.allowed_access | mask) != mask)
>  		return -EINVAL;
>  
> +	/* Check for useless quiet flag. */

Checks... (for consistency with comments above)

> +	if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.fs)
> +		return -EINVAL;
> +
>  	/* Gets and checks the new rule. */
>  	err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
>  	if (err)
> @@ -352,13 +381,13 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
>  
>  	/* Imports the new rule. */
>  	err = landlock_append_fs_rule(ruleset, &path,
> -				      path_beneath_attr.allowed_access);
> +				      path_beneath_attr.allowed_access, flags);
>  	path_put(&path);
>  	return err;
>  }
>  
>  static int add_rule_net_port(struct landlock_ruleset *ruleset,
> -			     const void __user *const rule_attr)
> +			     const void __user *const rule_attr, int flags)
>  {
>  	struct landlock_net_port_attr net_port_attr;
>  	int res;
> @@ -371,9 +400,10 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
>  
>  	/*
>  	 * Informs about useless rule: empty allowed_access (i.e. deny rules)
> -	 * are ignored by network actions.
> +	 * are ignored by network actions.  However, the rule is not useless
> +	 * if it is there to hold a quiet flag
>  	 */
> -	if (!net_port_attr.allowed_access)
> +	if (!flags && !net_port_attr.allowed_access)
>  		return -ENOMSG;
>  
>  	/* Checks that allowed_access matches the @ruleset constraints. */
> @@ -381,13 +411,17 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
>  	if ((net_port_attr.allowed_access | mask) != mask)
>  		return -EINVAL;
>  
> +	/* Check for useless quiet flag. */
> +	if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.net)
> +		return -EINVAL;
> +
>  	/* Denies inserting a rule with port greater than 65535. */
>  	if (net_port_attr.port > U16_MAX)
>  		return -EINVAL;
>  
>  	/* Imports the new rule. */
>  	return landlock_append_net_rule(ruleset, net_port_attr.port,
> -					net_port_attr.allowed_access);
> +					net_port_attr.allowed_access, flags);
>  }
>  
>  /**
> @@ -398,7 +432,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
>   * @rule_type: Identify the structure type pointed to by @rule_attr:
>   *             %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
>   * @rule_attr: Pointer to a rule (matching the @rule_type).
> - * @flags: Must be 0.
> + * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET.
>   *
>   * This system call enables to define a new rule and add it to an existing
>   * ruleset.
> @@ -408,20 +442,25 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset,
>   * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
>   * - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
>   *   supported by the running kernel;
> - * - %EINVAL: @flags is not 0;
> + * - %EINVAL: @flags is not valid;
>   * - %EINVAL: The rule accesses are inconsistent (i.e.
>   *   &landlock_path_beneath_attr.allowed_access or
>   *   &landlock_net_port_attr.allowed_access is not a subset of the ruleset
>   *   handled accesses)
>   * - %EINVAL: &landlock_net_port_attr.port is greater than 65535;
> + * - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no
> + *   quiet access bits set for the corresponding rule type.
>   * - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is
> - *   0);
> + *   0) and no flags;
>   * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
>   *   member of @rule_attr is not a file descriptor as expected;
>   * - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
>   *   @rule_attr is not the expected file descriptor type;
>   * - %EPERM: @ruleset_fd has no write access to the underlying ruleset;
>   * - %EFAULT: @rule_attr was not a valid address.
> + *
> + * .. kernel-doc:: include/uapi/linux/landlock.h
> + *     :identifiers: landlock_add_rule_flags
>   */
>  SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>  		const enum landlock_rule_type, rule_type,
> @@ -432,8 +471,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>  	if (!is_initialized())
>  		return -EOPNOTSUPP;
>  
> -	/* No flag for now. */
> -	if (flags)
> +	if (flags && flags != LANDLOCK_ADD_RULE_QUIET)
>  		return -EINVAL;
>  
>  	/* Gets and checks the ruleset. */
> @@ -443,9 +481,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
>  
>  	switch (rule_type) {
>  	case LANDLOCK_RULE_PATH_BENEATH:
> -		return add_rule_path_beneath(ruleset, rule_attr);
> +		return add_rule_path_beneath(ruleset, rule_attr, flags);
>  	case LANDLOCK_RULE_NET_PORT:
> -		return add_rule_net_port(ruleset, rule_attr);
> +		return add_rule_net_port(ruleset, rule_attr, flags);
>  	default:
>  		return -EINVAL;
>  	}
> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> index 30d37234086c..84e91fcaa1b2 100644
> --- a/tools/testing/selftests/landlock/base_test.c
> +++ b/tools/testing/selftests/landlock/base_test.c
> @@ -76,8 +76,8 @@ TEST(abi_version)
>  	const struct landlock_ruleset_attr ruleset_attr = {
>  		.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE,
>  	};
> -	ASSERT_EQ(9, landlock_create_ruleset(NULL, 0,
> -					     LANDLOCK_CREATE_RULESET_VERSION));
> +	ASSERT_EQ(10, landlock_create_ruleset(NULL, 0,
> +					      LANDLOCK_CREATE_RULESET_VERSION));
>  
>  	ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
>  					      LANDLOCK_CREATE_RULESET_VERSION));
> @@ -201,7 +201,7 @@ TEST(add_rule_checks_ordering)
>  	ASSERT_LE(0, ruleset_fd);
>  
>  	/* Checks invalid flags. */
> -	ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1));
> +	ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 100));
>  	ASSERT_EQ(EINVAL, errno);
>  
>  	/* Checks invalid ruleset FD. */
> -- 
> 2.53.0
> 

^ permalink raw reply

* Re: [PATCH v8 1/9] landlock: Add a place for flags to layer rules
From: Justin Suess @ 2026-05-24 22:08 UTC (permalink / raw)
  To: Tingmao Wang
  Cc: Mickaël Salaün, Günther Noack, Jan Kara,
	Abhinav Saxena, linux-security-module
In-Reply-To: <42a06f7c-abb5-4f2d-8428-8d122047e8d4@maowtm.org>

On Sun, May 24, 2026 at 07:20:19PM +0100, Tingmao Wang wrote:
> On 5/24/26 15:46, Justin Suess wrote:
> > On Sun, May 24, 2026 at 02:29:40AM +0100, Tingmao Wang wrote:
> >> On 5/23/26 21:48, Mickaël Salaün wrote:
> >>> [...]
> >>>> @@ -647,9 +648,14 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
> >>>>  	 */
> >>>>  	for (size_t i = 0; i < rule->num_layers; i++) {
> >>>>  		const struct landlock_layer *const layer = &rule->layers[i];
> >>>> +		const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
> >>>>  
> >>>
> >>>>  		/* Clear the bits where the layer in the rule grants access. */
> >>>>  		masks->access[layer->level - 1] &= ~layer->access;
> >>>> +
> >>>> +		/* Collect rule flags for each layer. */
> >>>> +		if (rule_flags && layer->flags.quiet)
> >>>> +			rule_flags->quiet_masks |= layer_bit;
> >>>
> >>> Why not store the quiet bit in masks?  That would not only be "access"
> >>> bits anymore but it makes sense to store all this bits it the same
> >>> place.
> >>>
> >>> We should then probably rename struct layer_access_masks to just struct
> >>> layer_masks.
> >>>
> >>> We need to be careful to not increase too much the size of this struct
> >>> though while keeping the [LANDLOCK_MAX_NUM_LAYERS] approach if possible
> >>> (see Günther's commit that added it).
> >>
> >> Most uses of struct layer_access_masks do not actually care about the rule
> >> flags tho, e.g. in unmask_scoped_access, scope_to_request, or may_refer.
> >> Such a rename would touch 31 places (and only a few of them would actually
> >> touch the quiet flag).
> >>
> >> If we want to refactor to make this be in the layer_access_masks (then
> >> rename it), I guess there are 3 options, which do you prefer?
> >>
> >> 1. Add a u16 bitfield for which layers are quieted.  Future rule flags
> >>    will be additional bitfields.  struct layer_masks becomes 68 bytes (+4).
> >>
> >> struct layer_masks {
> >> 	access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
> >> 	layer_mask_t quiet_layers;
> >> };
> >>
> >> 2. Make the [LANDLOCK_MAX_NUM_LAYERS] array store both the access mask and
> >>    the quiet bit (or more bits for future rule flags).  Size of struct stays
> >>    the same.
> >>
> > This approach seems best.
> >> static_assert(LANDLOCK_NUM_ACCESS_NET <= LANDLOCK_NUM_ACCESS_FS);
> >> static_assert(LANDLOCK_NUM_SCOPE <= LANDLOCK_NUM_ACCESS_FS);
> >> struct layer_mask {
> >> 	access_mask_t access:LANDLOCK_NUM_ACCESS_FS;
> >> 	bool quiet:1;
> >> };
> > 
> > Other way to do it could be an (anonymous?) union.
> > 
> > union {
> >   access_mask_t fs_access:LANDLOCK_NUM_ACCESS_FS;
> >   access_mask_t net_access:LANDLOCK_NUM_ACCESS_NET;
> >   access_mask_t scope_access:LANDLOCK_NUM_SCOPE;
> > }
> > 
> > The union should be sized to fit the largest field automatically.
> > 
> > That way you don't have to change this when adding new access rights
> > and avoid the brittle static_asserts.
> > 
> > Not sure about the alignment implications here though.
> 
> Unfortunately this forces struct layer_mask to be 2x as large:
> https://godbolt.org/z/5P9b4rrMW
> 
Yeah I guess the compiler can't pack the fields with differing types.

*In theory* you could make everything a _BitInt or something but it
seems better to do what you had below.
> But it turns out I could have just used MAX, seems to compile for me:
> 
> struct layer_mask {
> 	access_mask_t access
> 		: MAX(LANDLOCK_NUM_ACCESS_FS,
> 		      MAX(LANDLOCK_NUM_ACCESS_NET, LANDLOCK_NUM_SCOPE));
> 	bool quiet : 1;
> };
This works perfectly.

Mickaël's suggestion (except w/ all three access right classes like
you have here, think he missed LANDLOCK_NUM_SCOPE) is very close
to this.
> struct layer_masks {
> 	struct layer_mask layer[LANDLOCK_MAX_NUM_LAYERS];
> };
> 
> Maybe we could #define LANDLOCK_NUM_ACCESS_MAX to be MAX(...) then use it
> here.
> 
> I'm still not sure if putting the collected rule flags in struct
> layer_(access_)masks is a good idea tho.  Passing a separate struct
> collected_rule_flags to the functions that needs to deal with rule flags
> (quiet, and later, no inherit / has no inherit descendant) seems quite
> practical to me.

(Not sure how stingy we gotta be with stack space)

There's a *slight* stack space advantage to keeping them together.

If you pass by value, (separate layer_access_masks, collected_rule_flags),
those structs must be individually padded and aligned. Which may or may not
make a difference, it's dependent on alignment and architecture.

Whereas if we keep them all together, we only pad once.

If you pass by pointer, you have to allocate stack space for each
pointer, so passing it all at once saves sizeof(collected_rule_flags*)
bytes in the pass by pointer case.

Either way it's probably a couple bytes at worst, so probably nothing to
worry about.

The more compelling argument is that we don't know how future paths
will use rule flags, so keeping it all together reduces churn later
if a function ends up needing to access flags. Moreover, it makes those
messy function signatures in fs.h/c a little less hairy, and easy to
refactor later.


^ permalink raw reply

* [PATCH 0/2] smack: restrict smackfs/{direct,mapped} values to 0-255
From: Konstantin Andreev @ 2026-05-24 22:37 UTC (permalink / raw)
  To: casey; +Cc: linux-security-module

Both smackfs/direct and smackfs/mapped incorrectly accept
the full range of integer values.

2nd patch restricts range.
1st patch deduplicates file_operations code
to apply the fix in a single place

The patch set applies on top of:
https://github.com/cschaufler/smack-next/commits/next
commit b78fede1c69a

Konstantin Andreev (2):
  smack: deduplicate smackfs/{direct,mapped} file_operations
  smack: restrict smackfs/{direct,mapped} values to 0-255

 security/smack/smack.h   |   5 +-
 security/smack/smackfs.c | 153 +++++++++++++--------------------------
 2 files changed, 53 insertions(+), 105 deletions(-)

-- 
2.47.3


^ permalink raw reply

* [PATCH 1/2] smack: deduplicate smackfs/{direct,mapped} file_operations
From: Konstantin Andreev @ 2026-05-24 22:37 UTC (permalink / raw)
  To: casey; +Cc: linux-security-module
In-Reply-To: <20260524223749.50874-1-andreev@swemel.ru>

The file_operations for smackfs/direct and smackfs/mapped are
identical up to a textual replacement of "direct" with "mapped"

This patch combines two instances of file_operations into one,
handling both files.

Fixes: f7112e6c9abf ("Smack: allow for significantly longer Smack labels v4")
Signed-off-by: Konstantin Andreev <andreev@swemel.ru>
---
 security/smack/smack.h   |   5 +-
 security/smack/smackfs.c | 135 ++++++++++++---------------------------
 2 files changed, 43 insertions(+), 97 deletions(-)

diff --git a/security/smack/smack.h b/security/smack/smack.h
index 9b9eb262fe33..6febc2ecdfe8 100644
--- a/security/smack/smack.h
+++ b/security/smack/smack.h
@@ -317,8 +317,9 @@ int smack_populate_secattr(struct smack_known *skp);
  * Shared data.
  */
 extern int smack_enabled __initdata;
-extern int smack_cipso_direct;
-extern int smack_cipso_mapped;
+extern int smack_cipso_auto_level[2];
+#define smack_cipso_direct (+smack_cipso_auto_level[0])
+#define smack_cipso_mapped (+smack_cipso_auto_level[1])
 extern struct smack_known *smack_net_ambient;
 extern struct smack_known *smack_syslog_label;
 #ifdef CONFIG_SECURITY_SMACK_BRINGUP
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index f60d5469043e..946405645d5a 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -83,18 +83,27 @@ static DEFINE_MUTEX(smk_net6addr_lock);
 struct smack_known *smack_net_ambient;
 
 /*
- * This is the level in a CIPSO header that indicates a
+ * Sensitivity levels for automatically created CIPSO labels.
+ * See smack_access.c`smack_populate_secattr()
+ *
+ * [0] "direct" labeling, label length < SMK_CIPSOLEN(24):
  * smack label is contained directly in the category set.
  * It can be reset via smackfs/direct
- */
-int smack_cipso_direct = SMACK_CIPSO_DIRECT_DEFAULT;
-
-/*
- * This is the level in a CIPSO header that indicates a
+ *
+ * [1] "mapped" labeling, label length >= SMK_CIPSOLEN(24):
  * secid is contained directly in the category set.
  * It can be reset via smackfs/mapped
  */
-int smack_cipso_mapped = SMACK_CIPSO_MAPPED_DEFAULT;
+int smack_cipso_auto_level[2] = {
+	SMACK_CIPSO_DIRECT_DEFAULT,
+	SMACK_CIPSO_MAPPED_DEFAULT,
+};
+
+static int
+smk_cipso_auto_level_idx(const struct file *file)
+{
+	return (file_inode(file)->i_ino != SMK_DIRECT);
+}
 
 #ifdef CONFIG_SECURITY_SMACK_BRINGUP
 /*
@@ -1621,15 +1630,15 @@ static const struct file_operations smk_doi_ops = {
 };
 
 /**
- * smk_read_direct - read() for /smack/direct
- * @filp: file pointer, not actually used
+ * smk_read_cipso_auto_level - read() for smackfs/direct and smackfs/mapped
+ * @filp: file pointer
  * @buf: where to put the result
  * @count: maximum to send along
  * @ppos: where to start
  *
  * Returns number of bytes read or error code, as appropriate
  */
-static ssize_t smk_read_direct(struct file *filp, char __user *buf,
+static ssize_t smk_read_cipso_auto_level(struct file *filp, char __user *buf,
 			       size_t count, loff_t *ppos)
 {
 	char temp[80];
@@ -1638,26 +1647,28 @@ static ssize_t smk_read_direct(struct file *filp, char __user *buf,
 	if (*ppos != 0)
 		return 0;
 
-	sprintf(temp, "%d", smack_cipso_direct);
+	sprintf(temp, "%d", smack_cipso_auto_level[
+			      smk_cipso_auto_level_idx(filp)]);
 	rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
 
 	return rc;
 }
 
 /**
- * smk_write_direct - write() for /smack/direct
- * @file: file pointer, not actually used
+ * smk_write_cipso_auto_level - write() for smackfs/direct and smackfs/mapped
+ * @filp: file pointer
  * @buf: where to get the data from
  * @count: bytes sent
  * @ppos: where to start
  *
  * Returns number of bytes written or error code, as appropriate
  */
-static ssize_t smk_write_direct(struct file *file, const char __user *buf,
-				size_t count, loff_t *ppos)
+static ssize_t
+smk_write_cipso_auto_level(struct file *filp, const char __user *buf,
+			   size_t count, loff_t *ppos)
 {
 	struct smack_known *skp;
-	int i, ret;
+	int i, ret, idx, old_lvl;
 
 	if (!smack_privileged(CAP_MAC_ADMIN))
 		return -EPERM;
@@ -1669,94 +1680,28 @@ static ssize_t smk_write_direct(struct file *file, const char __user *buf,
 	/*
 	 * Don't do anything if the value hasn't actually changed.
 	 * If it is changing reset the level on entries that were
-	 * set up to be direct when they were created.
+	 * set up to be "auto" level when they were created.
 	 */
-	if (smack_cipso_direct != i) {
+	idx = smk_cipso_auto_level_idx(filp);
+	old_lvl = smack_cipso_auto_level[idx];
+
+	if (old_lvl != i) {
 		mutex_lock(&smack_known_lock);
 		list_for_each_entry_rcu(skp, &smack_known_list, list)
 			if (skp->smk_netlabel.attr.mls.lvl ==
-			    smack_cipso_direct)
+			    old_lvl)
 				skp->smk_netlabel.attr.mls.lvl = i;
-		smack_cipso_direct = i;
+		smack_cipso_auto_level[idx] = i;
 		mutex_unlock(&smack_known_lock);
 	}
 
 	return count;
 }
 
-static const struct file_operations smk_direct_ops = {
-	.read		= smk_read_direct,
-	.write		= smk_write_direct,
-	.llseek		= default_llseek,
-};
-
-/**
- * smk_read_mapped - read() for /smack/mapped
- * @filp: file pointer, not actually used
- * @buf: where to put the result
- * @count: maximum to send along
- * @ppos: where to start
- *
- * Returns number of bytes read or error code, as appropriate
- */
-static ssize_t smk_read_mapped(struct file *filp, char __user *buf,
-			       size_t count, loff_t *ppos)
-{
-	char temp[80];
-	ssize_t rc;
-
-	if (*ppos != 0)
-		return 0;
-
-	sprintf(temp, "%d", smack_cipso_mapped);
-	rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
-
-	return rc;
-}
-
-/**
- * smk_write_mapped - write() for /smack/mapped
- * @file: file pointer, not actually used
- * @buf: where to get the data from
- * @count: bytes sent
- * @ppos: where to start
- *
- * Returns number of bytes written or error code, as appropriate
- */
-static ssize_t smk_write_mapped(struct file *file, const char __user *buf,
-				size_t count, loff_t *ppos)
-{
-	struct smack_known *skp;
-	int i, ret;
-
-	if (!smack_privileged(CAP_MAC_ADMIN))
-		return -EPERM;
-
-	ret = kstrtos32_from_user(buf, count, 10, &i);
-	if (unlikely(ret))
-		return ret;
-
-	/*
-	 * Don't do anything if the value hasn't actually changed.
-	 * If it is changing reset the level on entries that were
-	 * set up to be mapped when they were created.
-	 */
-	if (smack_cipso_mapped != i) {
-		mutex_lock(&smack_known_lock);
-		list_for_each_entry_rcu(skp, &smack_known_list, list)
-			if (skp->smk_netlabel.attr.mls.lvl ==
-			    smack_cipso_mapped)
-				skp->smk_netlabel.attr.mls.lvl = i;
-		smack_cipso_mapped = i;
-		mutex_unlock(&smack_known_lock);
-	}
-
-	return count;
-}
-
-static const struct file_operations smk_mapped_ops = {
-	.read		= smk_read_mapped,
-	.write		= smk_write_mapped,
+static const struct file_operations
+smk_cipso_auto_level_ops = {
+	.read		= smk_read_cipso_auto_level,
+	.write		= smk_write_cipso_auto_level,
 	.llseek		= default_llseek,
 };
 
@@ -2851,7 +2796,7 @@ static int smk_fill_super(struct super_block *sb, struct fs_context *fc)
 		[SMK_DOI] = {
 			"doi", &smk_doi_ops, S_IRUGO|S_IWUSR},
 		[SMK_DIRECT] = {
-			"direct", &smk_direct_ops, S_IRUGO|S_IWUSR},
+			"direct", &smk_cipso_auto_level_ops, 0644},
 		[SMK_AMBIENT] = {
 			"ambient", &smk_ambient_ops, S_IRUGO|S_IWUSR},
 		[SMK_NET4ADDR] = {
@@ -2867,7 +2812,7 @@ static int smk_fill_super(struct super_block *sb, struct fs_context *fc)
 		[SMK_ACCESSES] = {
 			"access", &smk_access_ops, S_IRUGO|S_IWUGO},
 		[SMK_MAPPED] = {
-			"mapped", &smk_mapped_ops, S_IRUGO|S_IWUSR},
+			"mapped", &smk_cipso_auto_level_ops, 0644},
 		[SMK_LOAD2] = {
 			"load2", &smk_load2_ops, S_IRUGO|S_IWUSR},
 		[SMK_LOAD_SELF2] = {
-- 
2.47.3


^ permalink raw reply related

* [PATCH 2/2] smack: restrict smackfs/{direct,mapped} values to 0-255
From: Konstantin Andreev @ 2026-05-24 22:37 UTC (permalink / raw)
  To: casey; +Cc: linux-security-module
In-Reply-To: <20260524223749.50874-1-andreev@swemel.ru>

Both smackfs/direct and smackfs/mapped incorrectly accept
the full range of integer values. For example:

    # cd /sys/fs/smackfs/
    # cat direct ; echo
    250

    # cat cipso2
    @ 250/2
    _ 250/2,4,5,6,7,8
    * 250/3,5,7
    ^ 250/2,4,5,6,7
    ? 250/3,4,5,6,7,8

    # echo -1234 >direct ; cat direct ; echo
    -1234
    # cat cipso2
    @ -1234/2
    _ -1234/2,4,5,6,7,8
    * -1234/3,5,7
    ^ -1234/2,4,5,6,7
    ? -1234/3,4,5,6,7,8
    #

I noticed two things regarding this:

1) sensitivity levels are truncated to 8 bits when labeling
   outgoing packets (0x2e = 46 for the -1234 example above)

2) the reverse process fails: incoming packets with sensitivity
   level 46 do not match these smackfs/cipso2 entries.

Even observation (1) on its own warrants a fix.

This patch restricts smackfs/direct and smackfs/mapped
accepted values to the 0-255 range.

Fixes: e114e473771c ("Smack: Simplified Mandatory Access Control Kernel")
Signed-off-by: Konstantin Andreev <andreev@swemel.ru>
---
 security/smack/smack.h   |  2 +-
 security/smack/smackfs.c | 26 ++++++++++++++------------
 2 files changed, 15 insertions(+), 13 deletions(-)

diff --git a/security/smack/smack.h b/security/smack/smack.h
index 6febc2ecdfe8..fe6a49820014 100644
--- a/security/smack/smack.h
+++ b/security/smack/smack.h
@@ -317,7 +317,7 @@ int smack_populate_secattr(struct smack_known *skp);
  * Shared data.
  */
 extern int smack_enabled __initdata;
-extern int smack_cipso_auto_level[2];
+extern u8  smack_cipso_auto_level[2];
 #define smack_cipso_direct (+smack_cipso_auto_level[0])
 #define smack_cipso_mapped (+smack_cipso_auto_level[1])
 extern struct smack_known *smack_net_ambient;
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index 946405645d5a..c7eae7c6427f 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -94,7 +94,7 @@ struct smack_known *smack_net_ambient;
  * secid is contained directly in the category set.
  * It can be reset via smackfs/mapped
  */
-int smack_cipso_auto_level[2] = {
+u8 smack_cipso_auto_level[2] = {
 	SMACK_CIPSO_DIRECT_DEFAULT,
 	SMACK_CIPSO_MAPPED_DEFAULT,
 };
@@ -1641,17 +1641,15 @@ static const struct file_operations smk_doi_ops = {
 static ssize_t smk_read_cipso_auto_level(struct file *filp, char __user *buf,
 			       size_t count, loff_t *ppos)
 {
-	char temp[80];
-	ssize_t rc;
+	char temp[sizeof "255"];
+	int n;
 
 	if (*ppos != 0)
 		return 0;
 
-	sprintf(temp, "%d", smack_cipso_auto_level[
-			      smk_cipso_auto_level_idx(filp)]);
-	rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp));
-
-	return rc;
+	n = sprintf(temp, "%u", (unsigned int)smack_cipso_auto_level[
+		smk_cipso_auto_level_idx(filp)]);
+	return simple_read_from_buffer(buf, count, ppos, temp, n);
 }
 
 /**
@@ -1667,13 +1665,16 @@ static ssize_t
 smk_write_cipso_auto_level(struct file *filp, const char __user *buf,
 			   size_t count, loff_t *ppos)
 {
-	struct smack_known *skp;
-	int i, ret, idx, old_lvl;
+	int ret, idx;
+	u8  i, old_lvl;
 
 	if (!smack_privileged(CAP_MAC_ADMIN))
 		return -EPERM;
-
-	ret = kstrtos32_from_user(buf, count, 10, &i);
+	/*
+	 * draft-ietf-cipso-ipsecurity-01 (CIPSO 2.2), 3.4.2.4:
+	 * "Sensitivity Level is 1 octet in length. Its value is from 0 to 255"
+	 */
+	ret = kstrtou8_from_user(buf, count, 10, &i);
 	if (unlikely(ret))
 		return ret;
 
@@ -1686,6 +1687,7 @@ smk_write_cipso_auto_level(struct file *filp, const char __user *buf,
 	old_lvl = smack_cipso_auto_level[idx];
 
 	if (old_lvl != i) {
+		struct smack_known *skp;
 		mutex_lock(&smack_known_lock);
 		list_for_each_entry_rcu(skp, &smack_known_list, list)
 			if (skp->smk_netlabel.attr.mls.lvl ==
-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH v8 0/3]
From: Jarkko Sakkinen @ 2026-05-24 23:18 UTC (permalink / raw)
  To: keyrings
  Cc: David Howells, linux-crypto, linux-integrity, David Woodhouse,
	James Bottomley, Stefan Berger, Herbert Xu, Mimi Zohar,
	Paul Moore, James Morris, Serge E. Hallyn,
	open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <20260524051519.3708075-1-jarkko@kernel.org>

On Sun, May 24, 2026 at 08:15:11AM +0300, Jarkko Sakkinen wrote:
> This series introduces key type for operating with asymmetric keys using
> a TPM2 chip.
> 
> Change Log
> ==========
> 
> v8:
> - Reset patch change logs given the overhaul of the code and patches.
> - Have only single new subkey type.
> - Make key type only use TPM operations.
> - Use TPM2_Sign for both ECC and RSA keys.
> - Align key descriptions with other key types.
> 
> Previous versions
> =================
> 
> * v7: https://lore.kernel.org/linux-integrity/20240528210823.28798-1-jarkko@kernel.org/
> * v6: https://lore.kernel.org/linux-integrity/20240528035136.11464-1-jarkko@kernel.org/
> * v5: https://lore.kernel.org/linux-integrity/20240523212515.4875-1-jarkko@kernel.org/
> * v4: https://lore.kernel.org/linux-integrity/20240522005252.17841-1-jarkko@kernel.org/
> * v3: https://lore.kernel.org/linux-integrity/20240521152659.26438-1-jarkko@kernel.org/
> * v2: https://lore.kernel.org/linux-integrity/336755.1716327854@warthog.procyon.org.uk/
> * v1: https://lore.kernel.org/linux-integrity/20240520184727.22038-1-jarkko@kernel.org/
> * Derived from https://lore.kernel.org/all/20200518172704.29608-1-prestwoj@gmail.com/
> 
> 
> Jarkko Sakkinen (3):
>   lib/asn1_encoder: Add asn1_encode_integer_bytes()
>   crypto: Migrate TPMKey ASN.1 objects from trusted-keys
>   keys: asymmetric: tpm2_asymmetric
> 
>  crypto/Kconfig                            |    7 +
>  crypto/Makefile                           |    6 +
>  crypto/asymmetric_keys/Kconfig            |   17 +
>  crypto/asymmetric_keys/Makefile           |    1 +
>  crypto/asymmetric_keys/tpm2_asymmetric.c  | 1096 +++++++++++++++++++++
>  crypto/tpm2_key.asn1                      |   11 +
>  crypto/tpm2_key.c                         |  150 +++
>  include/crypto/tpm2_key.h                 |   46 +
>  include/linux/asn1_encoder.h              |    3 +
>  include/linux/tpm.h                       |   10 +
>  lib/asn1_encoder.c                        |   62 ++
>  security/keys/trusted-keys/Kconfig        |    2 +-
>  security/keys/trusted-keys/Makefile       |    2 -
>  security/keys/trusted-keys/tpm2key.asn1   |   11 -
>  security/keys/trusted-keys/trusted_tpm2.c |  119 +--
>  15 files changed, 1421 insertions(+), 122 deletions(-)
>  create mode 100644 crypto/asymmetric_keys/tpm2_asymmetric.c
>  create mode 100644 crypto/tpm2_key.asn1
>  create mode 100644 crypto/tpm2_key.c
>  create mode 100644 include/crypto/tpm2_key.h
>  delete mode 100644 security/keys/trusted-keys/tpm2key.asn1
> 
> -- 
> 2.47.3
> 

Oops, I deleted the subject line, it was unintentional :-)

BR, Jarkko

^ permalink raw reply

* Re: [PATCH v8 0/3]
From: Jarkko Sakkinen @ 2026-05-24 23:43 UTC (permalink / raw)
  To: keyrings
  Cc: David Howells, linux-crypto, linux-integrity, David Woodhouse,
	James Bottomley, Stefan Berger, Herbert Xu, Mimi Zohar,
	Paul Moore, James Morris, Serge E. Hallyn,
	open list:SECURITY SUBSYSTEM, open list
In-Reply-To: <20260524051519.3708075-1-jarkko@kernel.org>

On Sun, May 24, 2026 at 08:15:11AM +0300, Jarkko Sakkinen wrote:
> This series introduces key type for operating with asymmetric keys using
> a TPM2 chip.

This would deserve more explanation but the original trait was to
implement TPM2 parts of:

https://datatracker.ietf.org/doc/draft-woodhouse-cert-best-practice/00/

What motivated me to reiterate are actually these coding agents and how
all secrets are sprayed across the home directory. So, besides iwd one
could  use this feature to provide per-session cryptography for coding
agents.

There's a lot to do with security and coding agents as we have literally
moved to an era where we host indeterministically rogues software in our
development workstations.

There's other questions too that we need to eventually answer like for
instace, how to deal with persistent agent memory stored at the
computer's hard drive?

The irony here is that LLM is really neither rogue nor a lier. It is
just a text predictor optimizing for maximum reward and those
descriptions are just human interpretations of the output text. It
understand neither evil, lying nor quality for that matter ;-)

BR, Jarkko
 

^ permalink raw reply

* [PATCH] Fix various spelling mistakes
From: fffsqian @ 2026-05-25  2:15 UTC (permalink / raw)
  To: john.johansen, casey
  Cc: paul, jmorris, serge, linux-security-module, linux-kernel,
	Qingshuang Fu

From: Qingshuang Fu <fuqingshuang@kylinos.cn>

Fix three spelling errors found in code comments:

- overriden  →  overridden
- interated  →  interacted
- dont      →  don't

Signed-off-by: Qingshuang Fu <fuqingshuang@kylinos.cn>
---
 security/apparmor/domain.c | 2 +-
 security/apparmor/lsm.c    | 2 +-
 security/smack/smackfs.c   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index f02bf770f638..7e097c40720a 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -135,7 +135,7 @@ static int label_compound_match(struct aa_profile *profile,
 	struct label_it i;
 	struct path_cond cond = { };
 
-	/* find first subcomponent that is in view and going to be interated with */
+	/* find first subcomponent that is in view and going to be interacted with */
 	label_for_each(i, label, tp) {
 		if (!aa_ns_visible(profile->ns, tp->ns, inview))
 			continue;
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 3491e9f60194..51a388cfea11 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -1493,7 +1493,7 @@ static int apparmor_socket_shutdown(struct socket *sock, int how)
  *
  * Note: can not sleep may be called with locks held
  *
- * dont want protocol specific in __skb_recv_datagram()
+ * 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)
diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c
index 6e62dcb36f74..2820bd3ee72e 100644
--- a/security/smack/smackfs.c
+++ b/security/smack/smackfs.c
@@ -115,7 +115,7 @@ struct smack_known *smack_syslog_label;
 /*
  * Ptrace current rule
  * SMACK_PTRACE_DEFAULT    regular smack ptrace rules (/proc based)
- * SMACK_PTRACE_EXACT      labels must match, but can be overriden with
+ * SMACK_PTRACE_EXACT      labels must match, but can be overridden with
  *			   CAP_SYS_PTRACE
  * SMACK_PTRACE_DRACONIAN  labels must match, CAP_SYS_PTRACE has no effect
  */

base-commit: e7ae89a0c97ce2b68b0983cd01eda67cf373517d
-- 
2.25.1


^ permalink raw reply related

* [PATCH v4 0/3] introduce IMA_INIT_LATE_SYNC option
From: Yeoreum Yun @ 2026-05-25  7:54 UTC (permalink / raw)
  To: linux-security-module, linux-kernel, linux-integrity
  Cc: paul, zohar, roberto.sassu, noodles, jarkko, sudeep.holla,
	jmorris, serge, dmitry.kasatkin, eric.snowberg, jgg, Yeoreum Yun

To generate the boot_aggregate log in the IMA subsystem with TPM PCR values,
the TPM driver must be built as built-in and
must be probed before the IMA subsystem is initialized.

However, when the TPM device operates over the FF-A protocol using
the CRB interface, probing fails and returns -EPROBE_DEFER if
the tpm_crb_ffa device — an FF-A device that provides the communication
interface to the tpm_crb driver — has not yet been probed.

To ensure the TPM device operating over the FF-A protocol with
the CRB interface is probed before IMA initialization,
the following conditions must be met:

1. The corresponding ffa_device must be registered,
   which is done via ffa_init().

2. The tpm_crb_driver must successfully probe this device via
   tpm_crb_ffa_init().

3. The tpm_crb driver using CRB over FF-A can then
   be probed successfully. (See crb_acpi_add() and
   tpm_crb_ffa_init() for reference.)

Unfortunately, ffa_init(), tpm_crb_ffa_init(), and crb_acpi_driver_init() are
all registered with device_initcall, which means crb_acpi_driver_init() may
be invoked before ffa_init() and tpm_crb_ffa_init() are completed.

When this occurs, probing the TPM device is deferred.
However, the deferred probe can happen after the IMA subsystem
has already been initialized, since IMA initialization is performed
during late_initcall, and deferred_probe_initcall() is performed
at the same level.

And the similar situation is reported on TPM devices attached on SPI
bus[0].

To resolve this, introduce IMA_INIT_LATE_SYNC option to initialise
IMA at late_inicall_sync so that IMA is initialized with the TPM
device probed defered.

When this option is enabled, modules that access files in the
initramfs through usermode helper calls such as request_module()
during initcall must not be built-in. Otherwise, IMA may miss
measuring those files since they're the file accesses before the
initialisation of IMA [1].

Link: https://lore.kernel.org/all/aYXEepLhUouN5f99@earth.li/ [0]
Link: https://lore.kernel.org/all/2b3782398cc17ce9d355490a0c42ebce9120a9ae.camel@linux.ibm.com/ [1]

Patch history
=============
from v3 to v4:
  - rebase on v7.1-rc5
  - introduce IMA_INIT_LATE_SYNC option to control IMA initailisation.
  - https://lore.kernel.org/all/cover.1777036497.git.noodles@meta.com/

from v2 to v3:
  - Drop ff-a/pKVM diff (this seems to have a separate set of
    discussion)
  - Rework IMA delayed initialisation to avoid delaying when unnecessary
  - Ensure IMA log clearly indicates when we've initialised late
  - https://lore.kernel.org/all/20260422162449.1814615-1-yeoreum.yun@arm.com/

from v1 to v2:
  - add notifier to make ffa-driver pkvm initialised.
  - modify to try initailisation again when IMA coudln't find proper TPM device.
  - https://lore.kernel.org/all/20260417175759.3191279-1-yeoreum.yun@arm.com/#t


Yeoreum Yun (3):
  security: lsm: Allow LSMs to register for late_initcall_sync init
  security: ima: introduce IMA_INIT_LATE_SYNC option
  tpm: tpm_crb_ffa: revert defered_probed when tpm_crb_ffa is built-in

 drivers/char/tpm/tpm_crb_ffa.c    | 18 +++---------------
 include/linux/lsm_hooks.h         |  2 ++
 security/integrity/ima/Kconfig    | 10 ++++++++++
 security/integrity/ima/ima_main.c |  4 ++++
 security/lsm_init.c               | 13 +++++++++++--
 5 files changed, 30 insertions(+), 17 deletions(-)


base-commit: e7ae89a0c97ce2b68b0983cd01eda67cf373517d
-- 
LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}


^ permalink raw reply

* [PATCH v4 1/3] security: lsm: Allow LSMs to register for late_initcall_sync init
From: Yeoreum Yun @ 2026-05-25  7:54 UTC (permalink / raw)
  To: linux-security-module, linux-kernel, linux-integrity
  Cc: paul, zohar, roberto.sassu, noodles, jarkko, sudeep.holla,
	jmorris, serge, dmitry.kasatkin, eric.snowberg, jgg, Yeoreum Yun
In-Reply-To: <20260525075404.3480282-1-yeoreum.yun@arm.com>

There are situations where LSMs have dependencies that might mean they
want to be initialised later in the boot process, to ensure those
dependencies are available. In particular there are some TPM setups (Arm
FF-A devices, SPI attached TPMs) required by IMA which are not
guaranteed to be initialised for regular initcall_late.

Add an initcall_late_sync option that can be used in these situations.

Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
---
 include/linux/lsm_hooks.h |  2 ++
 security/lsm_init.c       | 13 +++++++++++--
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index b4f8cad53ddb..c4488c4a6d8a 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -167,6 +167,7 @@ enum lsm_order {
  * @initcall_fs: LSM callback for fs_initcall setup, optional
  * @initcall_device: LSM callback for device_initcall() setup, optional
  * @initcall_late: LSM callback for late_initcall() setup, optional
+ * @initcall_late_sync: LSM callback for late_initcall_sync() setup, optional
  */
 struct lsm_info {
 	const struct lsm_id *id;
@@ -182,6 +183,7 @@ struct lsm_info {
 	int (*initcall_fs)(void);
 	int (*initcall_device)(void);
 	int (*initcall_late)(void);
+	int (*initcall_late_sync)(void);
 };
 
 #define DEFINE_LSM(lsm)							\
diff --git a/security/lsm_init.c b/security/lsm_init.c
index 7c0fd17f1601..a1ad641811de 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -556,13 +556,22 @@ device_initcall(security_initcall_device);
  * security_initcall_late - Run the LSM late initcalls
  */
 static int __init security_initcall_late(void)
+{
+	return lsm_initcall(late);
+}
+late_initcall(security_initcall_late);
+
+/**
+ * security_initcall_late_sync - Run the LSM late initcalls sync
+ */
+static int __init security_initcall_late_sync(void)
 {
 	int rc;
 
-	rc = lsm_initcall(late);
+	rc = lsm_initcall(late_sync);
 	lsm_pr_dbg("all enabled LSMs fully activated\n");
 	call_blocking_lsm_notifier(LSM_STARTED_ALL, NULL);
 
 	return rc;
 }
-late_initcall(security_initcall_late);
+late_initcall_sync(security_initcall_late_sync);
-- 
LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox