* Re: [PATCH bpf v3 2/2] bpf, libbpf: reject non-exclusive metadata maps in the signed loader
From: Alexei Starovoitov @ 2026-05-23 15:12 UTC (permalink / raw)
To: KP Singh
Cc: bpf, LSM List, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi
In-Reply-To: <20260522215337.662271-3-kpsingh@kernel.org>
On Fri, May 22, 2026 at 11:53 PM KP Singh <kpsingh@kernel.org> wrote:
>
> The loader verifies map->sha against the metadata hash in its
> instructions. map->sha is calculated when BPF_OBJ_GET_INFO_BY_FD is called
> on the frozen map.
>
> While the map is frozen, the loader must also ensure the map is
> exclusive, as, without exclusivity, another BPF program with map access
> can mutate the contents afterwards, so the check passes on stale data.
Hold on. How is this an issue? excl_prog_sha guarantees
that only loader prog can use this map.
Are you saying the same loader prog will use the same map
for the 2nd time. Ok. I still don't see a problem.
> Place excl_prog_sha right after sha[] in struct bpf_map and have
> gen_loader bail with -EINVAL when it is NULL, via BPF_PSEUDO_MAP_IDX at
> fixed offset 32. The 8-byte read of the pointer field limits this to
> 64-bit kernels; gen_loader needs target pointer size tracking to emit
> the right sized read on 32-bit (follow-up).
I don't think we can go from maybe-racy to certainly-broken-on-32-bit.
So only applied patch 1.
^ permalink raw reply
* Re: [PATCH bpf-next 00/13] Signed BPF + IPE Policies
From: Blaise Boscaccy @ 2026-05-23 15:43 UTC (permalink / raw)
To: Paul Moore
Cc: KP Singh, LSM List, bpf, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, James Bottomley
In-Reply-To: <19e54ddf1a0.2843.85c95baa4474aabc7814e68940a78392@paul-moore.com>
Paul Moore <paul@paul-moore.com> writes:
> On May 23, 2026 7:40:42 AM Paul Moore <paul@paul-moore.com> wrote:
>> On May 23, 2026 3:40:46 AM Alexei Starovoitov
>> <alexei.starovoitov@gmail.com> wrote:
>>>
>>> sashiko spotted it too.
>>> All other sashiko bugs were ignored as well.
>>
>> Link? I didn't see any feedback from sashiko feedback on list and to the
>> best of my knowledge it hasn't been enabled for LSM patches.
>
> https://sashiko.dev/#/patchset/20260507191416.2984054-1-bboscaccy%40linux.microsoft.com
>
> Blaise, I know you've got another patch coming soon - please take a look at
> the link above and see if there is anything else that needs to be addressed.
>
Yeah, it found a few things I corrected. It's hooked into the bpf list,
not the lsm list currently. With all melodrama and bravado aside, The
TOCTOU issue it found wasn't the actual attack vector and it completely
missed the real one that Eric found. It seems to be lacking the
multi-step reasoning that vuln researchers actually use.
Most of it looked like AI slop, and I'm not too keen on providing more
free training material for AI folks to run inference on, so I didn't
respond directly to the bot spam emails.
Sashiko seems to take major issue with the existing user keyring
verification too *shrug*.
I'll take a second look when I'm back home next week and see if there is
anything real leftover after this patchset.
-blaise
> --
> paul-moore.com
^ permalink raw reply
* [PATCH] crypto: pkcs7: export verify_pkcs7_message_sig() as EXPORT_SYMBOL_GPL
From: Paul Moore @ 2026-05-23 15:57 UTC (permalink / raw)
To: linux-security-module; +Cc: James Bottomley
For the sake of consistency with pther PKCS7 functions, export
verify_pkcs7_message_sig() as EXPORT_SYMBOL_GPL.
Cc: James Bottomley <James.Bottomley@HansenPartnership.com>
Fixes: 188cfb7ec81c ("crypto: pkcs7: add flag for validated trust on a signed info block")
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
certs/system_keyring.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 9bda49295bd0..e1268d0cffff 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -380,7 +380,7 @@ int verify_pkcs7_message_sig(const void *data, size_t len,
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}
-EXPORT_SYMBOL(verify_pkcs7_message_sig);
+EXPORT_SYMBOL_GPL(verify_pkcs7_message_sig);
/**
* verify_pkcs7_signature - Verify a PKCS#7-based signature on system data.
--
2.54.0
^ permalink raw reply related
* [PATCH] lsm,bpf: fix security_bpf_prog_load() error handling
From: Paul Moore @ 2026-05-23 16:00 UTC (permalink / raw)
To: bpf, linux-security-module
If security_bpf_prog_load() fails there is no need to call into
security_bpf_prog_free() as the LSM will handle the cleanup of any partial
LSM state before returning to the caller with an error. Thankfully this
isn't an issue with any of the existing code as the LSMs which currently
provide BPF hook callback implementations don't allocate any internal
state, but this is something we want to fix for potential future users.
Cc: bpf@vger.kernel.org
Cc: linux-security-module@vger.kernel.org
Signed-off-by: Paul Moore <paul@paul-moore.com>
---
kernel/bpf/syscall.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index a3c0214ca934..ac07280098e9 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3076,7 +3076,7 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
err = security_bpf_prog_load(prog, attr, token, uattr.is_kernel);
if (err)
- goto free_prog_sec;
+ goto free_prog;
/* run eBPF verifier */
err = bpf_check(&prog, attr, uattr, uattr_size);
@@ -3122,8 +3122,6 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
__bpf_prog_put_noref(prog, prog->aux->real_func_cnt);
return err;
-free_prog_sec:
- security_bpf_prog_free(prog);
free_prog:
free_uid(prog->aux->user);
if (prog->aux->attach_btf)
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] lsm,bpf: fix security_bpf_prog_load() error handling
From: Paul Moore @ 2026-05-23 16:06 UTC (permalink / raw)
To: bpf, linux-security-module
In-Reply-To: <20260523160025.16363-2-paul@paul-moore.com>
On Sat, May 23, 2026 at 12:00 PM Paul Moore <paul@paul-moore.com> wrote:
>
> If security_bpf_prog_load() fails there is no need to call into
> security_bpf_prog_free() as the LSM will handle the cleanup of any partial
> LSM state before returning to the caller with an error. Thankfully this
> isn't an issue with any of the existing code as the LSMs which currently
> provide BPF hook callback implementations don't allocate any internal
> state, but this is something we want to fix for potential future users.
>
> Cc: bpf@vger.kernel.org
> Cc: linux-security-module@vger.kernel.org
> Signed-off-by: Paul Moore <paul@paul-moore.com>
> ---
> kernel/bpf/syscall.c | 4 +---
> 1 file changed, 1 insertion(+), 3 deletions(-)
Alexei, I'm assuming you would prefer to take this via the BPF tree?
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH] lsm,bpf: fix security_bpf_prog_load() error handling
From: Alexei Starovoitov @ 2026-05-23 16:25 UTC (permalink / raw)
To: Paul Moore; +Cc: bpf, LSM List
In-Reply-To: <CAHC9VhR6RSq1TUgAyvHmaF01h87Cazy=BNJhk654BP8yfeSwng@mail.gmail.com>
On Sat, May 23, 2026 at 6:06 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On Sat, May 23, 2026 at 12:00 PM Paul Moore <paul@paul-moore.com> wrote:
> >
> > If security_bpf_prog_load() fails there is no need to call into
> > security_bpf_prog_free() as the LSM will handle the cleanup of any partial
> > LSM state before returning to the caller with an error. Thankfully this
> > isn't an issue with any of the existing code as the LSMs which currently
> > provide BPF hook callback implementations don't allocate any internal
> > state, but this is something we want to fix for potential future users.
> >
> > Cc: bpf@vger.kernel.org
> > Cc: linux-security-module@vger.kernel.org
> > Signed-off-by: Paul Moore <paul@paul-moore.com>
> > ---
> > kernel/bpf/syscall.c | 4 +---
> > 1 file changed, 1 insertion(+), 3 deletions(-)
>
> Alexei, I'm assuming you would prefer to take this via the BPF tree?
Paul, I see that you're intentionally trying to piss me off.
It's not going to work :)
^ permalink raw reply
* Re: [PATCH bpf-next 00/13] Signed BPF + IPE Policies
From: Blaise Boscaccy @ 2026-05-23 16:34 UTC (permalink / raw)
To: KP Singh, linux-security-module, bpf
Cc: ast, daniel, memxor, James.Bottomley, paul
In-Reply-To: <20260522023234.3778588-1-kpsingh@kernel.org>
KP Singh <kpsingh@kernel.org> writes:
> This series continues the "Signed BPF programs" work and adds
> the missing pieces needed for an LSM to do policy enforcement
> and addresses the concerns raised by the developers of Hornet.
>
> One signing scheme, please.
>
> BPF does not need a second signing scheme. It needs a policy
> framework that consumes the verdict the existing signing pipeline
> produces. Two parallel signing stacks is harmful UX for Cilium,
> bpftrace, systemd, distros, and everyone shipping signed lskels.
> Hornet has been NACK'd repeatedly by the BPF maintainers [1][2]
> on layering and TOCTOU grounds.
>
> What this series adds
>
> - prog->aux->sig (verdict + keyring) and prog->aux->is_kernel,
> populated by the syscall path before security_bpf_prog_load
> fires.
> - bpf_loader_verify_metadata kfunc -- the metadata check is now
> kernel C code, not BPF bytecode. The verifier injects the
> calling prog->aux as an implicit argument via KF_IMPLICIT_ARGS.
> - Loader-side prog BTF with BPF_PSEUDO_KFUNC_CALL_PROG_BTF so
> the kfunc CALL is reproducible across build hosts and resolved
> at load time.
> - security_bpf_prog_load_post_integrity LSM hook, fired by the
> kfunc on a successful metadata check.
> - IPE properties (bpf_signature, bpf_keyring, bpf_kernel) and
> two ops (BPF_PROG_LOAD, BPF_PROG_LOAD_POST_INTEGRITY).
>
> This series address concerns raised by the Hornet developers:
>
> * The metadata hash check should be in kernel C, not BPF
> bytecode -- Blaise Boscaccy [3]:
>
That's a gross misrepresentation of some of my previous statements on
the subject. We can go back and forth on this until the cows home with
increasing vitriolic rhetoric, but that's really just a waste of
everyone's time. Your "trusted loader" design flat-out doesn't work for
our security requirements, and those of others. You keep screaming that
we need to "write our own trusted loader" and that isn't really solving
anything.
You just posted a trusted loader bugfix here.
https://lore.kernel.org/linux-security-module/20260522215337.662271-1-kpsingh@kernel.org/
What's your path for that now and in the future? How are you getting
people to rebuild their out-of-tree trusted loaders if there is a bug in
them? Are you expecting sysadmins to subscribe to the bpf mailing list
and watch for patches to libbpf and then rebuild an entire corpus of
eBPF lskel programs?
What if there is a security vulnerability or a CVE in the generated code
that gets emitted, how are you handling that? We have processes in place
to handle updates, bugfixes and vulnerabilities in the kernel. None
exist for your "trusted loader" paradigm. You can publish a CVE for
libbpf, but there is no way to publish a CVE for an infinite number of
random unknown bpf program in the wild or to notify users that their
programs are effected, or for them to know which programs are actually
effected and which ones aren't.
Also as an aside, it looks like some of this patchset is copy-pasted
from https://lore.kernel.org/linux-security-module/20260507191416.2984054-11-bboscaccy@linux.microsoft.com/
Which is fine of course, since this is open source software and all, but
attribution would be appreciated if you use my code in the future :)
-blaise
> The bpf_loader_verify_metadata kfunc moves the hash check from
> inline BPF instructions into kernel C code.
>
> * LSMs cannot observe the verification result at hook time --
> Paul Moore [4]:
>
> prog->aux->sig.verdict and sig.keyring are populated before any
> LSM hook runs. Furthermore, security_bpf_prog_load_post_integrity
> hook fires after the in-kernel hash check for consumers that want
> to observe or gate the post-integrity transition.
>
>
> [1] Alexei Starovoitov, NACK on Hornet (TOCTOU + layering),
> https://lore.kernel.org/all/CAADnVQJ1CRvTXBU771KaYzrx-vRaWF+k164DcFOqOsCxmuL+ig@mail.gmail.com/
> [2] Daniel Borkmann, NACK on Hornet v3,
> https://lore.kernel.org/all/798dba24-b5a7-4584-a1f6-793883fe9b5e@iogearbox.net/
> [3] Blaise Boscaccy, Hornet v6 (C-side hash verification rationale),
> https://lore.kernel.org/all/20260429191431.2345448-1-bboscaccy@linux.microsoft.com/
> [4] Paul Moore, push for post-verifier observability,
> https://lore.kernel.org/all/CACYkzJ4+=3owK+ELD9Nw7Rrm-UajxXEw8kVtOTJJ+SNAXpsOpw@mail.gmail.com/
>
>
> KP Singh (13):
> bpf: expose signature verdict to LSMs via bpf_prog_aux
> bpf: include prog BTF in the signed loader signature scope
> bpf, libbpf: load prog BTF in the skel_internal loader
> bpf: add bpf_loader_verify_metadata kfunc
> bpf: compute prog->digest at BPF_PROG_LOAD entry
> bpf: resolve loader-style kfunc CALLs against prog BTF
> libbpf: generate prog BTF for loader programs
> bpftool gen: embed loader prog BTF in the lskel header
> lsm: add bpf_prog_load_post_integrity hook
> bpf: invoke security_bpf_prog_load_post_integrity from the metadata
> kfunc
> ipe: add BPF program signature properties
> ipe: gate post-integrity BPF program loads
> selftests/bpf: add IPE BPF policy integration tests
>
> include/linux/bpf.h | 19 +++
> include/linux/bpf_verifier.h | 6 +
> include/linux/btf.h | 1 +
> include/linux/lsm_hook_defs.h | 1 +
> include/linux/security.h | 6 +
> include/uapi/linux/bpf.h | 5 +
> kernel/bpf/btf.c | 8 +
> kernel/bpf/check_btf.c | 18 +-
> kernel/bpf/helpers.c | 65 ++++++++
> kernel/bpf/syscall.c | 76 ++++++++-
> kernel/bpf/verifier.c | 58 ++++++-
> security/ipe/Kconfig | 14 ++
> security/ipe/audit.c | 13 ++
> security/ipe/eval.c | 57 +++++++
> security/ipe/eval.h | 5 +
> security/ipe/hooks.c | 42 +++++
> security/ipe/hooks.h | 9 +
> security/ipe/ipe.c | 4 +
> security/ipe/policy.h | 11 ++
> security/ipe/policy_parser.c | 20 +++
> security/security.c | 17 ++
> tools/bpf/bpftool/gen.c | 21 +++
> tools/bpf/bpftool/sign.c | 17 +-
> tools/include/uapi/linux/bpf.h | 5 +
> tools/lib/bpf/bpf_gen_internal.h | 2 +
> tools/lib/bpf/gen_loader.c | 127 +++++++++++---
> tools/lib/bpf/libbpf.h | 4 +-
> tools/lib/bpf/skel_internal.h | 67 +++++---
> .../selftests/bpf/test_signed_bpf_ipe.sh | 156 ++++++++++++++++++
> tools/testing/selftests/bpf/vmtest.sh | 4 +-
> 30 files changed, 775 insertions(+), 83 deletions(-)
> create mode 100755 tools/testing/selftests/bpf/test_signed_bpf_ipe.sh
>
> --
> 2.53.0
^ permalink raw reply
* Re: [PATCH bpf-next 05/13] bpf: compute prog->digest at BPF_PROG_LOAD entry
From: Alexei Starovoitov @ 2026-05-23 16:49 UTC (permalink / raw)
To: KP Singh
Cc: LSM List, bpf, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, James Bottomley, Paul Moore
In-Reply-To: <20260522023234.3778588-6-kpsingh@kernel.org>
On Fri, May 22, 2026 at 4:32 AM KP Singh <kpsingh@kernel.org> wrote:
>
> add_subprog_and_kfunc relocates kfunc CALLs by patching insn->imm
> and src_reg, and bpf_prog_calc_tag has no rule to mask kfunc CALL
> fields.
add_subprog_and_kfunc() doesn't modify the insn stream afaict.
Do you mean that bpf_check_btf_info() resolves CO-RE relos?
but loader prog doesn't use CO-RE.
I don't quite see the point of the patch.
^ permalink raw reply
* Re: [PATCH] lsm,bpf: fix security_bpf_prog_load() error handling
From: Paul Moore @ 2026-05-23 16:53 UTC (permalink / raw)
To: Alexei Starovoitov; +Cc: bpf, LSM List
In-Reply-To: <CAADnVQ+WTfgO636GqgxzJ6r=jENpG704kmNAsy-X8=WyjMOV+g@mail.gmail.com>
On May 23, 2026 11:25:55 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
> On Sat, May 23, 2026 at 6:06 PM Paul Moore <paul@paul-moore.com> wrote:
>> On Sat, May 23, 2026 at 12:00 PM Paul Moore <paul@paul-moore.com> wrote:
>>>
>>> If security_bpf_prog_load() fails there is no need to call into
>>> security_bpf_prog_free() as the LSM will handle the cleanup of any partial
>>> LSM state before returning to the caller with an error. Thankfully this
>>> isn't an issue with any of the existing code as the LSMs which currently
>>> provide BPF hook callback implementations don't allocate any internal
>>> state, but this is something we want to fix for potential future users.
>>>
>>> Cc: bpf@vger.kernel.org
>>> Cc: linux-security-module@vger.kernel.org
>>> Signed-off-by: Paul Moore <paul@paul-moore.com>
>>> ---
>>> kernel/bpf/syscall.c | 4 +---
>>> 1 file changed, 1 insertion(+), 3 deletions(-)
>>
>> Alexei, I'm assuming you would prefer to take this via the BPF tree?
>
> Paul, I see that you're intentionally trying to piss me off.
> It's not going to work :)
I promise you that is not the case. I was looking at the sashiko results of
the latest Hornet patch and it identified this potential issue in the error
handling code that is an issue independent of Hornet. I posted the quick
little patch above to fix the issue, and since the diffstat is solely in
kernel/bpf/syscall.c I figured you would want to merge it via the BPF tree;
if that is not the case let me know.
--
paul-moore.com
^ permalink raw reply
* Re: [PATCH bpf-next 06/13] bpf: resolve loader-style kfunc CALLs against prog BTF
From: Alexei Starovoitov @ 2026-05-23 17:01 UTC (permalink / raw)
To: KP Singh
Cc: LSM List, bpf, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, James Bottomley, Paul Moore
In-Reply-To: <20260522023234.3778588-7-kpsingh@kernel.org>
On Fri, May 22, 2026 at 4:32 AM KP Singh <kpsingh@kernel.org> wrote:
>
> gen_loader-emitted signed loaders cannot bake vmlinux BTF ids into
> kfunc CALL imm at sign time. Add a new pseudo
> BPF_PSEUDO_KFUNC_CALL_PROG_BTF that gen_loader emits in src_reg, with
> imm holding the FUNC type id in the loader's own prog BTF.
>
> In add_subprog_and_kfunc, look the FUNC up by name in vmlinux BTF,
> patch imm with the resolved id, and rewrite src_reg back to
> BPF_PSEUDO_KFUNC_CALL so downstream passes see a normal kfunc CALL.
> Leave standard src_reg calls unchanged.
>
> Signed-off-by: KP Singh <kpsingh@kernel.org>
> ---
> include/linux/bpf_verifier.h | 6 ++++
> include/uapi/linux/bpf.h | 5 ++++
> kernel/bpf/verifier.c | 54 ++++++++++++++++++++++++++++++++--
> tools/include/uapi/linux/bpf.h | 5 ++++
> 4 files changed, 67 insertions(+), 3 deletions(-)
>
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 185b2aa43a42..396b85830996 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -959,6 +959,12 @@ static inline bool bpf_pseudo_kfunc_call(const struct bpf_insn *insn)
> insn->src_reg == BPF_PSEUDO_KFUNC_CALL;
> }
>
> +static inline bool bpf_pseudo_kfunc_call_prog_btf(const struct bpf_insn *insn)
> +{
> + return insn->code == (BPF_JMP | BPF_CALL) &&
> + insn->src_reg == BPF_PSEUDO_KFUNC_CALL_PROG_BTF;
> +}
> +
> __printf(2, 0) void bpf_verifier_vlog(struct bpf_verifier_log *log,
> const char *fmt, va_list args);
> __printf(2, 3) void bpf_verifier_log_write(struct bpf_verifier_env *env,
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index 552bc5d9afbd..06056e714e8e 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -1382,6 +1382,11 @@ enum {
> * bpf_call->imm == btf_id of a BTF_KIND_FUNC in the running kernel
> */
> #define BPF_PSEUDO_KFUNC_CALL 2
> +/* when bpf_call->src_reg == BPF_PSEUDO_KFUNC_CALL_PROG_BTF,
> + * bpf_call->imm == btf_id of a BTF_KIND_FUNC in the program's
> + * prog BTF. The verifier resolves it to a vmlinux btf_id by name.
> + */
> +#define BPF_PSEUDO_KFUNC_CALL_PROG_BTF 3
>
> enum bpf_addr_space_cast {
> BPF_ADDR_SPACE_CAST = 1,
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index f0e45cfa5b34..1b5d06b9d74a 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -3088,6 +3088,47 @@ bool bpf_prog_has_kfunc_call(const struct bpf_prog *prog)
> return !!prog->aux->kfunc_tab;
> }
>
> +/*
> + * Resolve a gen_loader-emitted kfunc CALL by FUNC name in vmlinux BTF,
> + * then rewrite src_reg back to BPF_PSEUDO_KFUNC_CALL. Caller must have
> + * already filtered for BPF_PSEUDO_KFUNC_CALL_PROG_BTF.
> + */
> +static int resolve_loader_kfunc(struct bpf_verifier_env *env,
> + struct bpf_insn *insn, int insn_idx)
> +{
> + struct btf *prog_btf = env->prog->aux->btf;
> + const struct btf_type *t;
> + const char *name;
> + s32 vmlinux_id;
> +
> + if (!prog_btf || !btf_vmlinux || insn->off) {
> + verbose(env, "kfunc call insn %d: PROG_BTF resolution requires prog BTF and insn->off == 0\n",
> + insn_idx);
> + return -EINVAL;
> + }
> + t = btf_type_by_id(prog_btf, insn->imm);
> + if (!t || !btf_type_is_func(t)) {
> + verbose(env, "kfunc call insn %d: imm %d is not a FUNC in prog BTF\n",
> + insn_idx, insn->imm);
> + return -EINVAL;
> + }
> + name = btf_name_by_offset(prog_btf, t->name_off);
> + if (!name || !name[0]) {
> + verbose(env, "kfunc call insn %d: prog-BTF FUNC has no name\n",
> + insn_idx);
> + return -EINVAL;
> + }
> + vmlinux_id = btf_find_by_name_kind(btf_vmlinux, name, BTF_KIND_FUNC);
> + if (vmlinux_id < 0) {
> + verbose(env, "kfunc call insn %d: %s not found in vmlinux BTF\n",
> + insn_idx, name);
> + return vmlinux_id;
> + }
> + insn->imm = vmlinux_id;
> + insn->src_reg = BPF_PSEUDO_KFUNC_CALL;
> + return 0;
Ohh. So this patch does it.
Probably worth squashing the patches then?
Also since the resolution into kernel kfunc is by name only,
it feels odd to create a fake prog BTF proto just for the name.
What is an alternative?
Also note that we were discussing allowing the replacement of kfuncs
by modules with the same name. This kfunc should be
immutable?
^ permalink raw reply
* Re: [PATCH] lsm,bpf: fix security_bpf_prog_load() error handling
From: Alexei Starovoitov @ 2026-05-23 17:19 UTC (permalink / raw)
To: Paul Moore; +Cc: bpf, LSM List
In-Reply-To: <19e55c1c1f0.2843.85c95baa4474aabc7814e68940a78392@paul-moore.com>
On Sat, May 23, 2026 at 6:53 PM Paul Moore <paul@paul-moore.com> wrote:
>
> On May 23, 2026 11:25:55 AM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> > On Sat, May 23, 2026 at 6:06 PM Paul Moore <paul@paul-moore.com> wrote:
> >> On Sat, May 23, 2026 at 12:00 PM Paul Moore <paul@paul-moore.com> wrote:
> >>>
> >>> If security_bpf_prog_load() fails there is no need to call into
> >>> security_bpf_prog_free() as the LSM will handle the cleanup of any partial
> >>> LSM state before returning to the caller with an error. Thankfully this
> >>> isn't an issue with any of the existing code as the LSMs which currently
> >>> provide BPF hook callback implementations don't allocate any internal
> >>> state, but this is something we want to fix for potential future users.
> >>>
> >>> Cc: bpf@vger.kernel.org
> >>> Cc: linux-security-module@vger.kernel.org
> >>> Signed-off-by: Paul Moore <paul@paul-moore.com>
> >>> ---
> >>> kernel/bpf/syscall.c | 4 +---
> >>> 1 file changed, 1 insertion(+), 3 deletions(-)
> >>
> >> Alexei, I'm assuming you would prefer to take this via the BPF tree?
> >
> > Paul, I see that you're intentionally trying to piss me off.
> > It's not going to work :)
>
> I promise you that is not the case. I was looking at the sashiko results of
> the latest Hornet patch and it identified this potential issue in the error
> handling code that is an issue independent of Hornet. I posted the quick
> little patch above to fix the issue, and since the diffstat is solely in
> kernel/bpf/syscall.c I figured you would want to merge it via the BPF tree;
> if that is not the case let me know.
It's in a queue. You can monitor it here:
https://patchwork.kernel.org/project/netdevbpf/list/?series=&submitter=&state=&q=&archive=&delegate=121173
But please be advised that when submitters ignore issues
found by bots and maintainers agree with bot findings
we mark patches as changes requested.
^ permalink raw reply
* Re: [PATCH] lsm,bpf: fix security_bpf_prog_load() error handling
From: bot+bpf-ci @ 2026-05-23 17:44 UTC (permalink / raw)
To: paul, bpf, linux-security-module
Cc: ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
In-Reply-To: <20260523160025.16363-2-paul@paul-moore.com>
[-- Attachment #1: Type: text/plain, Size: 1302 bytes --]
> commit a03af62bb4a3613aadacc8cb75f0007d761e785a
> Author: Paul Moore <paul@paul-moore.com>
> Date: Sat May 23 12:00:26 2026 -0400
>
> lsm,bpf: fix security_bpf_prog_load() error handling
>
> If security_bpf_prog_load() fails there is no need to call into
> security_bpf_prog_free() as the LSM will handle the cleanup of any partial
> LSM state before returning to the caller with an error. Thankfully this
> isn't an issue with any of the existing code as the LSMs which currently
> provide BPF hook callback implementations don't allocate any internal
> state, but this is something we want to fix for potential future users.
>
> Cc: bpf@vger.kernel.org
> Cc: linux-security-module@vger.kernel.org
> Signed-off-by: Paul Moore <paul@paul-moore.com>
This looks like a bug fix for the error handling logic introduced in
commit b3beebbe6154 which imported the kernel/bpf/syscall.c file with
incorrect error handling for security_bpf_prog_load() failure. Should
this include:
Fixes: b3beebbe6154 ("adding ci files")
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26337503379
^ permalink raw reply
* [RFC PATCH] ipe: support multiple BPF integrity verification LSMs
From: Paul Moore @ 2026-05-23 20:09 UTC (permalink / raw)
To: linux-security-module; +Cc: wufan, bboscaccy
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>
---
include/linux/security.h | 14 +++++++-------
security/ipe/eval.h | 7 ++++++-
security/ipe/hooks.c | 20 +++++++++++++++++---
3 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/include/linux/security.h b/include/linux/security.h
index 598cd2eb1dcd..6a987a0347a0 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -103,13 +103,13 @@ enum lsm_integrity_type {
enum lsm_integrity_verdict {
LSM_INT_VERDICT_NONE = 0,
- LSM_INT_VERDICT_OK,
- LSM_INT_VERDICT_UNSIGNED,
- LSM_INT_VERDICT_PARTIALSIG,
- LSM_INT_VERDICT_UNKNOWNKEY,
- LSM_INT_VERDICT_UNEXPECTED,
- LSM_INT_VERDICT_FAULT,
- LSM_INT_VERDICT_BADSIG,
+ LSM_INT_VERDICT_OK = 1,
+ LSM_INT_VERDICT_UNSIGNED = 2,
+ LSM_INT_VERDICT_PARTIALSIG = 3,
+ LSM_INT_VERDICT_UNKNOWNKEY = 4,
+ LSM_INT_VERDICT_UNEXPECTED = 5,
+ LSM_INT_VERDICT_FAULT = 6,
+ LSM_INT_VERDICT_BADSIG = 7,
};
/*
diff --git a/security/ipe/eval.h b/security/ipe/eval.h
index b061cb5ade27..90c7b66b9ca8 100644
--- a/security/ipe/eval.h
+++ b/security/ipe/eval.h
@@ -8,6 +8,7 @@
#include <linux/file.h>
#include <linux/types.h>
+#include <linux/lsm_count.h>
#include "policy.h"
#include "hooks.h"
@@ -39,7 +40,11 @@ struct ipe_inode {
#ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
struct ipe_bpf_prog {
- enum lsm_integrity_verdict verdict;
+ struct {
+ const struct lsm_id *lsmid;
+ enum lsm_integrity_verdict verdict;
+ } verdicts[MAX_LSM_COUNT];
+ unsigned int count;
};
#endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
index 9271e129a2cf..143bb2ae2b12 100644
--- a/security/ipe/hooks.c
+++ b/security/ipe/hooks.c
@@ -9,6 +9,7 @@
#include <linux/binfmts.h>
#include <linux/mman.h>
#include <linux/blk_types.h>
+#include <linux/lsm_count.h>
#include "ipe.h"
#include "hooks.h"
@@ -355,7 +356,8 @@ int ipe_inode_setintegrity(const struct inode *inode,
* so that ipe_bpf_prog_load() can later read it for policy evaluation.
*
* Return:
- * * %0 - Always succeeds (policy is evaluated in bpf_prog_load)
+ * * %0 - Recorded the verdict (policy is evaluated in bpf_prog_load)
+ * * %-ENOMEM - Exhausted room for recording verdicts
*/
int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog,
union bpf_attr *attr,
@@ -365,8 +367,14 @@ int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog,
enum lsm_integrity_verdict verdict)
{
struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
+ unsigned int count = blob->count;
- blob->verdict = verdict;
+ if (count == MAX_LSM_COUNT)
+ return -ENOMEM;
+
+ blob->verdicts[count].lsmid = lsmid;
+ blob->verdicts[count].verdict = verdict;
+ blob->count++;
return 0;
}
@@ -391,12 +399,18 @@ int ipe_bpf_prog_load(struct bpf_prog *prog,
struct bpf_token *token,
bool kernel)
{
+ unsigned int iter;
struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
ctx.op = IPE_OP_BPF_PROG_LOAD;
ctx.hook = IPE_HOOK_BPF_PROG_LOAD;
- ctx.bpf_verdict = blob->verdict;
+ ctx.bpf_verdict = LSM_INT_VERDICT_NONE;
+ for (iter = 0; iter < blob->count; iter++) {
+ /* pick the "wosrt" verdict */
+ if (blob->verdicts[iter].verdict > ctx.bpf_verdict)
+ ctx.bpf_verdict = blob->verdicts[iter].verdict;
+ }
ctx.bpf_keyring_id = attr->keyring_id;
ctx.bpf_kernel = kernel;
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v8 1/9] landlock: Add a place for flags to layer rules
From: Mickaël Salaün @ 2026-05-23 20:48 UTC (permalink / raw)
To: Tingmao Wang, Günther Noack
Cc: Justin Suess, Jan Kara, Abhinav Saxena, linux-security-module
In-Reply-To: <6f418b431ccf88636ea3a1b930d14bfdcd420233.1775490344.git.m@maowtm.org>
This patch doesn't build.
On Mon, Apr 06, 2026 at 04:52:14PM +0100, Tingmao Wang wrote:
> To avoid unnecessarily increasing the size of struct landlock_layer, we
> make the layer level a u8 and use the space to store the flags struct.
>
> Cc: Justin Suess <utilityemal77@gmail.com>
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> Co-developed-by: Justin Suess <utilityemal77@gmail.com>
> Signed-off-by: Justin Suess <utilityemal77@gmail.com>
> ---
>
> Because the no inherit patchset [2] from Justin Suess will depend on
> this rule flags mechanism, I and Justin discussed a bit whether this
> patch should in fact be a standalone thing separate from quiet flags
> (i.e. add the infrastructure for rule flags, but don't actually add any
> rule flags in this commit), so that the two series can be developed and
> merged independently. However in the end I decided to not do this and
> send this patch as-is.
>
> Changes in v8:
> - Rebase on top of mic/next
> - Add Co-developed-by: Justin Suess for handling this rebase initially
> - layer_mask_t was removed in [1] but we still need it for the
> collected_rule_flags. Rather than using raw u16, I've chosen to
> re-define it back in ruleset.h (it was in access.h).
>
> Changes in v7:
> - Take rule_flags separately from landlock_request in
> is_access_to_paths_allowed to avoid writing to the landlock_request
> variable if CONFIG_AUDIT is disabled (to enable compiler elision).
> - Due to the above change, we don't need rule_flags in landlock_request in
> this commit anymore (will be added later).
>
> Changes in v6:
> - Rebased to include the revised disconnected directory handling changes
> (without the "reverting" behaviour)
>
> Changes in v5:
> - Move rule_flags into landlock_request. This lets us get rid of the
> extra parameters to is_access_to_paths_allowed (and later on,
> landlock_log_denial), and thus less code changes.
>
> Changes in v3:
> - Comment changes, move local variables, simplify if branch
>
> Changes in v2:
> - Comment changes
> - Rebased to include disconnected directory handling changes on mic/next
> and add backing up of collected_rule_flags.
>
> [1]: https://lore.kernel.org/all/20260125195853.109967-1-gnoack3000@gmail.com/
> [2]: https://lore.kernel.org/all/20251221194301.247484-1-utilityemal77@gmail.com/
>
> security/landlock/fs.c | 96 +++++++++++++++++++++++--------------
> security/landlock/net.c | 3 +-
> security/landlock/ruleset.c | 8 +++-
> security/landlock/ruleset.h | 32 ++++++++++++-
> 4 files changed, 99 insertions(+), 40 deletions(-)
>
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index c1ecfe239032..6f63e0182ef0 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -717,6 +717,9 @@ static void test_is_eacces_with_write(struct kunit *const test)
> * those identified by @access_request_parent1). This matrix can
> * initially refer to domain layer masks and, when the accesses for the
> * destination and source are the same, to requested layer masks.
> + * @rule_flags_parent1: Pointer to a collected_rule_flags struct
> + * corresponding to the accumulated rule flags for parent1 to be read from
> + * and filled as we traverse the path.
> * @log_request_parent1: Audit request to fill if the related access is denied.
> * @dentry_child1: Dentry to the initial child of the parent1 path. This
> * pointer must be NULL for non-refer actions (i.e. not link nor rename).
> @@ -726,6 +729,7 @@ static void test_is_eacces_with_write(struct kunit *const test)
> * the source. Must be set to 0 when using a simple path request.
> * @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer
> * action. This must be NULL otherwise.
> + * @rule_flags_parent2: Similar to @rule_flags_parent1 but for parent2.
> * @log_request_parent2: Audit request to fill if the related access is denied.
> * @dentry_child2: Dentry to the initial child of the parent2 path. This
> * pointer is only set for RENAME_EXCHANGE actions and must be NULL
> @@ -739,17 +743,19 @@ static void test_is_eacces_with_write(struct kunit *const test)
> *
> * Return: True if the access request is granted, false otherwise.
> */
> -static bool
> -is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
> - const struct path *const path,
> - const access_mask_t access_request_parent1,
> - struct layer_access_masks *layer_masks_parent1,
> - struct landlock_request *const log_request_parent1,
> - struct dentry *const dentry_child1,
> - const access_mask_t access_request_parent2,
> - struct layer_access_masks *layer_masks_parent2,
> - struct landlock_request *const log_request_parent2,
> - struct dentry *const dentry_child2)
> +static bool is_access_to_paths_allowed(
> + const struct landlock_ruleset *const domain,
> + const struct path *const path,
> + const access_mask_t access_request_parent1,
> + struct layer_access_masks *layer_masks_parent1,
> + struct collected_rule_flags *const rule_flags_parent1,
> + struct landlock_request *const log_request_parent1,
> + struct dentry *const dentry_child1,
> + const access_mask_t access_request_parent2,
> + struct layer_access_masks *layer_masks_parent2,
> + struct collected_rule_flags *const rule_flags_parent2,
> + struct landlock_request *const log_request_parent2,
> + struct dentry *const dentry_child2)
> {
> bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
> child1_is_directory = true, child2_is_directory = true;
> @@ -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.
> if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
> &_layer_masks_child2,
> LANDLOCK_KEY_INODE))
> landlock_unmask_layers(find_rule(domain, dentry_child2),
> - &_layer_masks_child2);
> + &_layer_masks_child2, NULL);
> layer_masks_child2 = &_layer_masks_child2;
> child2_is_directory = d_is_dir(dentry_child2);
> }
> @@ -865,12 +879,14 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
> }
>
> rule = find_rule(domain, walker_path.dentry);
> - allowed_parent1 =
> - allowed_parent1 ||
> - landlock_unmask_layers(rule, layer_masks_parent1);
> - allowed_parent2 =
> - allowed_parent2 ||
> - landlock_unmask_layers(rule, layer_masks_parent2);
> + allowed_parent1 = allowed_parent1 ||
> + landlock_unmask_layers(rule,
> + layer_masks_parent1,
> + rule_flags_parent1);
> + allowed_parent2 = allowed_parent2 ||
> + landlock_unmask_layers(rule,
> + layer_masks_parent2,
> + rule_flags_parent2);
>
> /* Stops when a rule from each layer grants access. */
> if (allowed_parent1 && allowed_parent2)
> @@ -954,6 +970,7 @@ static int current_check_access_path(const struct path *const path,
> landlock_get_applicable_subject(current_cred(), masks, NULL);
> struct layer_access_masks layer_masks;
> struct landlock_request request = {};
> + struct collected_rule_flags rule_flags = {};
>
> if (!subject)
> return 0;
> @@ -962,8 +979,8 @@ static int current_check_access_path(const struct path *const path,
> access_request, &layer_masks,
> LANDLOCK_KEY_INODE);
> if (is_access_to_paths_allowed(subject->domain, path, access_request,
> - &layer_masks, &request, NULL, 0, NULL,
> - NULL, NULL))
> + &layer_masks, &rule_flags, &request,
> + NULL, 0, NULL, NULL, NULL, NULL))
> return 0;
>
> landlock_log_denial(subject, &request);
> @@ -1026,10 +1043,11 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
> * Return: True if all the domain access rights are allowed for @dir, false if
> * the walk reached @mnt_root.
> */
> -static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
> - const struct dentry *const mnt_root,
> - struct dentry *dir,
> - struct layer_access_masks *layer_masks_dom)
> +static bool
> +collect_domain_accesses(const struct landlock_ruleset *const domain,
> + const struct dentry *const mnt_root, struct dentry *dir,
> + struct layer_access_masks *layer_masks_dom,
> + struct collected_rule_flags *const rule_flags)
> {
> bool ret = false;
>
> @@ -1048,7 +1066,7 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
>
> /* Gets all layers allowing all domain accesses. */
> if (landlock_unmask_layers(find_rule(domain, dir),
> - layer_masks_dom)) {
> + layer_masks_dom, rule_flags)) {
> /*
> * Stops when all handled accesses are allowed by at
> * least one rule in each layer.
> @@ -1138,6 +1156,8 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> struct layer_access_masks layer_masks_parent1 = {},
> layer_masks_parent2 = {};
> struct landlock_request request1 = {}, request2 = {};
> + struct collected_rule_flags rule_flags_parent1 = {},
> + rule_flags_parent2 = {};
>
> if (!subject)
> return 0;
> @@ -1169,10 +1189,10 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> subject->domain,
> access_request_parent1 | access_request_parent2,
> &layer_masks_parent1, LANDLOCK_KEY_INODE);
> - if (is_access_to_paths_allowed(subject->domain, new_dir,
> - access_request_parent1,
> - &layer_masks_parent1, &request1,
> - NULL, 0, NULL, NULL, NULL))
> + if (is_access_to_paths_allowed(
> + subject->domain, new_dir, access_request_parent1,
> + &layer_masks_parent1, &rule_flags_parent1,
> + &request1, NULL, 0, NULL, NULL, NULL, NULL))
> return 0;
>
> landlock_log_denial(subject, &request1);
> @@ -1198,11 +1218,12 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> /* new_dir->dentry is equal to new_dentry->d_parent */
> allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
> old_parent,
> - &layer_masks_parent1);
> + &layer_masks_parent1,
> + &rule_flags_parent1);
> allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
> new_dir->dentry,
> - &layer_masks_parent2);
> -
> + &layer_masks_parent2,
> + &rule_flags_parent2);
> if (allow_parent1 && allow_parent2)
> return 0;
>
> @@ -1214,8 +1235,9 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> */
> if (is_access_to_paths_allowed(
> subject->domain, &mnt_dir, access_request_parent1,
> - &layer_masks_parent1, &request1, old_dentry,
> - access_request_parent2, &layer_masks_parent2, &request2,
> + &layer_masks_parent1, &rule_flags_parent1, &request1,
> + old_dentry, access_request_parent2, &layer_masks_parent2,
> + &rule_flags_parent2, &request2,
> exchange ? new_dentry : NULL))
> return 0;
>
> @@ -1745,6 +1767,7 @@ static int hook_file_open(struct file *const file)
> const struct landlock_cred_security *const subject =
> landlock_get_applicable_subject(file->f_cred, any_fs, NULL);
> struct landlock_request request = {};
> + struct collected_rule_flags rule_flags = {};
>
> if (!subject)
> return 0;
> @@ -1771,7 +1794,8 @@ static int hook_file_open(struct file *const file)
> landlock_init_layer_masks(subject->domain,
> full_access_request, &layer_masks,
> LANDLOCK_KEY_INODE),
> - &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) {
> + &layer_masks, &rule_flags, &request, NULL, 0, NULL, NULL,
> + NULL, NULL)) {
> allowed_access = full_access_request;
> } else {
> /*
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> index c368649985c5..dc82ce4a2bd4 100644
> --- a/security/landlock/net.c
> +++ b/security/landlock/net.c
> @@ -48,6 +48,7 @@ static int current_check_access_socket(struct socket *const sock,
> {
> __be16 port;
> struct layer_access_masks layer_masks = {};
> + struct collected_rule_flags rule_flags = {};
> const struct landlock_rule *rule;
> struct landlock_id id = {
> .type = LANDLOCK_KEY_NET_PORT,
> @@ -194,7 +195,7 @@ static int current_check_access_socket(struct socket *const sock,
> if (!access_request)
> return 0;
>
> - if (landlock_unmask_layers(rule, &layer_masks))
> + if (landlock_unmask_layers(rule, &layer_masks, &rule_flags))
> return 0;
>
> audit_net.family = address->sa_family;
> diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
> index 181df7736bb9..e4e6b730b581 100644
> --- a/security/landlock/ruleset.c
> +++ b/security/landlock/ruleset.c
> @@ -628,7 +628,8 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
> * remaining unfulfilled access rights and masks has no leftover set bits).
> */
> bool landlock_unmask_layers(const struct landlock_rule *const rule,
> - struct layer_access_masks *masks)
> + struct layer_access_masks *masks,
> + struct collected_rule_flags *const rule_flags)
> {
> if (!masks)
> return true;
> @@ -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).
> }
>
> for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
> diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
> index 889f4b30301a..3b31552f0c95 100644
> --- a/security/landlock/ruleset.h
> +++ b/security/landlock/ruleset.h
> @@ -29,7 +29,18 @@ struct landlock_layer {
> /**
> * @level: Position of this layer in the layer stack. Starts from 1.
> */
> - u16 level;
> + u8 level;
> + /**
> + * @flags: Bitfield for special flags attached to this rule.
> + */
> + struct {
> + /**
> + * @quiet: Suppresses denial audit logs for the object covered by
> + * this rule in this domain. For filesystem rules, this inherits
> + * down the file hierarchy.
> + */
> + bool quiet:1;
> + } flags;
> /**
> * @access: Bitfield of allowed actions on the kernel object. They are
> * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
> @@ -37,6 +48,22 @@ struct landlock_layer {
> access_mask_t access;
> };
>
> +typedef u16 layer_mask_t;
> +
> +/* Makes sure this is enough to include all layers. */
> +static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
> +
> +
Two new lines.
> +/**
> + * struct collected_rule_flags - Hold accumulated flags for each layer.
> + */
> +struct collected_rule_flags {
> + /**
> + * @quiet_masks: Layers for which the quiet flag is effective.
> + */
> + layer_mask_t quiet_masks;
> +};
> +
> /**
> * union landlock_key - Key of a ruleset's red-black tree
> */
> @@ -302,7 +329,8 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
> }
>
> bool landlock_unmask_layers(const struct landlock_rule *const rule,
> - struct layer_access_masks *masks);
> + struct layer_access_masks *masks,
> + struct collected_rule_flags *const rule_flags);
>
> access_mask_t
> landlock_init_layer_masks(const struct landlock_ruleset *const domain,
> --
> 2.53.0
>
^ permalink raw reply
* Re: [RFC PATCH] ipe: support multiple BPF integrity verification LSMs
From: Paul Moore @ 2026-05-23 23:05 UTC (permalink / raw)
To: linux-security-module; +Cc: wufan, bboscaccy
In-Reply-To: <20260523200859.13527-2-paul@paul-moore.com>
On May 23, 2026 3:09:05 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>
> ---
> include/linux/security.h | 14 +++++++-------
> security/ipe/eval.h | 7 ++++++-
> security/ipe/hooks.c | 20 +++++++++++++++++---
> 3 files changed, 30 insertions(+), 11 deletions(-)
>
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 598cd2eb1dcd..6a987a0347a0 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -103,13 +103,13 @@ enum lsm_integrity_type {
>
> enum lsm_integrity_verdict {
> LSM_INT_VERDICT_NONE = 0,
> - LSM_INT_VERDICT_OK,
> - LSM_INT_VERDICT_UNSIGNED,
> - LSM_INT_VERDICT_PARTIALSIG,
> - LSM_INT_VERDICT_UNKNOWNKEY,
> - LSM_INT_VERDICT_UNEXPECTED,
> - LSM_INT_VERDICT_FAULT,
> - LSM_INT_VERDICT_BADSIG,
> + LSM_INT_VERDICT_OK = 1,
> + LSM_INT_VERDICT_UNSIGNED = 2,
> + LSM_INT_VERDICT_PARTIALSIG = 3,
> + LSM_INT_VERDICT_UNKNOWNKEY = 4,
> + LSM_INT_VERDICT_UNEXPECTED = 5,
> + LSM_INT_VERDICT_FAULT = 6,
> + LSM_INT_VERDICT_BADSIG = 7,
> };
>
> /*
> diff --git a/security/ipe/eval.h b/security/ipe/eval.h
> index b061cb5ade27..90c7b66b9ca8 100644
> --- a/security/ipe/eval.h
> +++ b/security/ipe/eval.h
> @@ -8,6 +8,7 @@
>
> #include <linux/file.h>
> #include <linux/types.h>
> +#include <linux/lsm_count.h>
>
> #include "policy.h"
> #include "hooks.h"
> @@ -39,7 +40,11 @@ struct ipe_inode {
>
> #ifdef CONFIG_IPE_PROP_BPF_SIGNATURE
> struct ipe_bpf_prog {
> - enum lsm_integrity_verdict verdict;
> + struct {
> + const struct lsm_id *lsmid;
> + enum lsm_integrity_verdict verdict;
> + } verdicts[MAX_LSM_COUNT];
> + unsigned int count;
> };
> #endif /* CONFIG_IPE_PROP_BPF_SIGNATURE */
>
> diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
> index 9271e129a2cf..143bb2ae2b12 100644
> --- a/security/ipe/hooks.c
> +++ b/security/ipe/hooks.c
> @@ -9,6 +9,7 @@
> #include <linux/binfmts.h>
> #include <linux/mman.h>
> #include <linux/blk_types.h>
> +#include <linux/lsm_count.h>
>
> #include "ipe.h"
> #include "hooks.h"
> @@ -355,7 +356,8 @@ int ipe_inode_setintegrity(const struct inode *inode,
> * so that ipe_bpf_prog_load() can later read it for policy evaluation.
> *
> * Return:
> - * * %0 - Always succeeds (policy is evaluated in bpf_prog_load)
> + * * %0 - Recorded the verdict (policy is evaluated in bpf_prog_load)
> + * * %-ENOMEM - Exhausted room for recording verdicts
> */
> int ipe_bpf_prog_load_post_integrity(struct bpf_prog *prog,
> union bpf_attr *attr,
> @@ -365,8 +367,14 @@ int ipe_bpf_prog_load_post_integrity(struct bpf_prog
> *prog,
> enum lsm_integrity_verdict verdict)
> {
> struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
> + unsigned int count = blob->count;
>
> - blob->verdict = verdict;
> + if (count == MAX_LSM_COUNT)
> + return -ENOMEM;
> +
> + blob->verdicts[count].lsmid = lsmid;
> + blob->verdicts[count].verdict = verdict;
> + blob->count++;
>
> return 0;
> }
> @@ -391,12 +399,18 @@ int ipe_bpf_prog_load(struct bpf_prog *prog,
> struct bpf_token *token,
> bool kernel)
> {
> + unsigned int iter;
> struct ipe_bpf_prog *blob = ipe_bpf_prog(prog);
> struct ipe_eval_ctx ctx = IPE_EVAL_CTX_INIT;
>
> ctx.op = IPE_OP_BPF_PROG_LOAD;
> ctx.hook = IPE_HOOK_BPF_PROG_LOAD;
> - ctx.bpf_verdict = blob->verdict;
> + ctx.bpf_verdict = LSM_INT_VERDICT_NONE;
> + for (iter = 0; iter < blob->count; iter++) {
> + /* pick the "wosrt" verdict */
That should obviously be "worst" :)
> + if (blob->verdicts[iter].verdict > ctx.bpf_verdict)
> + ctx.bpf_verdict = blob->verdicts[iter].verdict;
> + }
> ctx.bpf_keyring_id = attr->keyring_id;
> ctx.bpf_kernel = kernel;
>
> --
> 2.54.0
--
paul-moore.com
^ permalink raw reply
* 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
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox