From: sashiko-bot@kernel.org
To: "Thiébaud Weksteen" <tweek@google.com>
Cc: selinux@vger.kernel.org, bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next 2/5] bpf: Introduce BPF_LOADER_LOAD_FD command
Date: Thu, 13 Aug 2026 00:42:21 +0000 [thread overview]
Message-ID: <20260813004221.D6D0B1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260813002618.3755631-3-tweek@google.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] Information Leak / Out-of-Bounds Read in BPF verifier via loader_load_fd
- [High] Out-of-bounds Read / Denial of Service in ELF header validation
- [Medium] File Descriptor Hijacking Race Condition (TOCTOU)
- [Medium] API Misuse / Broken functionality on non-JIT configurations
- [Low] Direct cast of `__aligned_u64` to `void *` for `copy_to_user`
--
--- Patch [2]: [PATCH bpf-next 2/5] bpf: Introduce BPF_LOADER_LOAD_FD command ---
commit f9a928bc7f89cffc836753c26236211ed8039c51
Author: Thiébaud Weksteen <tweek@google.com>
bpf: Introduce BPF_LOADER_LOAD_FD command
This commit introduces the BPF_LOADER_LOAD_FD command to allow loading
and executing loader BPF programs directly from an ELF file.
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 8d111da886553..d79cd63f9f7cd 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
[ ... ]
> @@ -6291,6 +6292,336 @@ static int prog_assoc_struct_ops(union bpf_attr *attr)
[ ... ]
> +static int bpf_elf_validity_cache_sechdrs(struct elf_info *info)
> +{
> + Elf64_Shdr *sechdrs;
> + Elf64_Shdr *shdr;
> + int i;
> + int err;
> +
> + err = bpf_elf_validity_ehdr(info);
> + if (err < 0)
> + return err;
> +
> + if (info->hdr->e_shentsize != sizeof(Elf64_Shdr)) {
> + pr_err("Invalid ELF section header size\n");
> + return -ENOEXEC;
> + }
> +
> + /*
> + * e_shnum is 16 bits, and sizeof(Elf64_Shdr) is
> + * known and small. So e_shnum * sizeof(Elf64_Shdr)
> + * will not overflow unsigned long on any platform.
> + */
> + if (info->hdr->e_shoff >= info->len
> + || (info->hdr->e_shnum * sizeof(Elf64_Shdr) >
> + info->len - info->hdr->e_shoff)) {
> + pr_err("Invalid ELF section header overflow\n");
> + return -ENOEXEC;
> + }
> +
> + sechdrs = (void *)info->hdr + info->hdr->e_shoff;
> +
> + /*
> + * The code assumes that section 0 has a length of zero and
> + * an addr of zero, so check for it.
> + */
> + if (sechdrs[0].sh_type != SHT_NULL
[Severity: High]
Does this bounds check properly handle the case where info->hdr->e_shnum
is 0?
If an attacker provides an ELF file with e_shnum set to 0 and e_shoff set
near the end of the file (e.g., info->len - 1), the size check evaluates
0 > 1, which is false. The code will then proceed to access sechdrs[0]
unconditionally.
Since info->hdr is backed by a vmalloc buffer, could this read past the
buffer boundary into the unmapped guard page and cause a panic?
[ ... ]
> +static int loader_load_fd(union bpf_attr *attr)
> +{
[ ... ]
> + memset(&sattr, 0, sizeof(sattr));
> + sattr.prog_type = BPF_PROG_TYPE_SYSCALL;
> + sattr.license = (unsigned long) license;
> + sattr.insns = (unsigned long) insns;
> + sattr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
> + sattr.fd_array = (unsigned long) &map_fd;
[Severity: Critical]
Is it safe to pass a kernel stack pointer (&map_fd) via sattr.fd_array
while leaving fd_array_cnt implicitly as 0?
When fd_array_cnt is 0, the BPF verifier falls back to the legacy sparse
array resolution path in fd_array_get_map_idx_sparse(). In this path, the
verifier extracts an immediate value from a BPF_PSEUDO_MAP_FD instruction
and uses it as an index.
Since fd_array is treated as a kernel pointer, copy_from_bpfptr_offset()
expands directly to a memory copy:
memcpy(&fd, &map_fd + idx, sizeof(int));
Because the index is attacker-controlled and lacks bounds checking, could
this allow an unprivileged user to reliably read arbitrary memory from the
kernel stack?
> + sattr.prog_flags = BPF_F_SLEEPABLE;
> + strscpy(sattr.prog_name, BPF_LOADER_PROG_SEC, sizeof(BPF_LOADER_PROG_SEC));
> + prog_fd = kern_sys_bpf(BPF_PROG_LOAD, &sattr, sizeof(sattr));
> + if (prog_fd < 0) {
> + err = prog_fd;
> + goto close_map_err;
> + }
> +
> + memset(&sattr, 0, sizeof(sattr));
> + ctx_sz = attr->load_fd.ctx_size;
> + kctx = kzalloc(ctx_sz, GFP_KERNEL);
> + if (kctx == NULL) {
> + err = -ENOMEM;
> + goto close_prog_err;
> + }
> + sattr.test.prog_fd = prog_fd;
> + sattr.test.ctx_in = (unsigned long) kctx;
> + sattr.test.ctx_size_in = ctx_sz;
> + err = kern_sys_bpf(BPF_PROG_TEST_RUN, &sattr, sizeof(sattr));
[Severity: Medium]
Does kern_sys_bpf() support BPF_PROG_TEST_RUN when CONFIG_BPF_JIT is
disabled?
Looking at kern_sys_bpf(), it only intercepts BPF_PROG_TEST_RUN when
CONFIG_BPF_JIT is enabled. Without JIT, it falls through to ____bpf_sys_bpf()
which explicitly rejects BPF_PROG_TEST_RUN and returns -EINVAL.
Could this cause the BPF_LOADER_LOAD_FD command to be completely broken on
non-JIT kernel configurations?
> + if (err < 0)
> + goto free_ctx;
> + err = sattr.test.retval;
> + if (err < 0)
> + goto free_ctx;
> +
> + if (copy_to_user((void *) attr->load_fd.ctx, kctx, ctx_sz) != 0)
[Severity: Low]
Is it correct to directly cast __aligned_u64 to a void pointer here?
Directly casting attr->load_fd.ctx to (void *) bypasses Sparse address space
annotations for __user memory. On 32-bit architectures, this can also cause
pointer truncation warnings.
Should this use the u64_to_user_ptr() macro instead?
> + err = -EFAULT;
> +
> +free_ctx:
> + kfree(kctx);
> +close_prog_err:
> + close_fd(prog_fd);
> +close_map_err:
> + close_fd(map_fd);
[Severity: Medium]
Could unconditionally closing these file descriptors introduce a race
condition?
The kern_sys_bpf() function allocates file descriptors and installs them,
making them temporarily visible in the calling process's file descriptor
table.
If a concurrent thread blocks copy_to_user() (for instance, by passing a
userfaultfd-backed memory region for attr->load_fd.ctx), it could close
these exposed file descriptors and open new ones.
When the page fault resolves, would loader_load_fd() resume and inadvertently
close the newly opened files that now occupy those descriptor slots?
> +out_free_buf:
> + vfree(buf);
> +out:
> + return err;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260813002618.3755631-1-tweek@google.com?part=2
next prev parent reply other threads:[~2026-08-13 0:42 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-13 0:26 [PATCH bpf-next 0/5] bpf: Introduce LOADER_LOAD_FD Thiébaud Weksteen
2026-08-13 0:26 ` [PATCH bpf-next 1/5] fs/kernel_read_file,selinux: Add BPF_LOADER constant Thiébaud Weksteen
2026-08-13 0:36 ` sashiko-bot
2026-08-13 1:25 ` bot+bpf-ci
2026-08-13 0:26 ` [PATCH bpf-next 2/5] bpf: Introduce BPF_LOADER_LOAD_FD command Thiébaud Weksteen
2026-08-13 0:42 ` sashiko-bot [this message]
2026-08-13 1:40 ` bot+bpf-ci
2026-08-13 0:26 ` [PATCH bpf-next 3/5] selinux: use kernel sid in security_bpf_* Thiébaud Weksteen
2026-08-13 0:40 ` sashiko-bot
2026-08-13 1:25 ` bot+bpf-ci
2026-08-13 0:26 ` [PATCH bpf-next 4/5] selinux: Add BPF_LOADER_LOAD_FD syscall permission Thiébaud Weksteen
2026-08-13 0:41 ` sashiko-bot
2026-08-13 0:26 ` [PATCH bpf-next 5/5] selftests/bpf: add loader_load_fd tests Thiébaud Weksteen
2026-08-13 0:36 ` sashiko-bot
2026-08-13 1:25 ` bot+bpf-ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260813004221.D6D0B1F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=selinux@vger.kernel.org \
--cc=tweek@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox