selftests/bpf: Add verifier tests for the __ctx_out plumbing

Add BPF selftests to cover what a BPF program may and may not hand
to bpf_inode_init_xattr() as the inode_init_security hook's args.

  # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_lsm_init_xattr
  [...]
  #649/1   verifier_lsm_init_xattr/reject_count_write:OK
  #649/2   verifier_lsm_init_xattr/allow_count_read:OK
  #649/3   verifier_lsm_init_xattr/reject_unchecked_xattrs:OK
  #649/4   verifier_lsm_init_xattr/reject_unchecked_qstr:OK
  #649/5   verifier_lsm_init_xattr/allow_spilled_count:OK
  #649/6   verifier_lsm_init_xattr/reject_forged_count:OK
  #649/7   verifier_lsm_init_xattr/reject_stack_count:OK
  #649/8   verifier_lsm_init_xattr/reject_other_ctx_arg:OK
  #649/9   verifier_lsm_init_xattr/reject_null_count:OK
  #649/10  verifier_lsm_init_xattr/reject_shifted_count:OK
  #649/11  verifier_lsm_init_xattr/reject_var_shifted_count:OK
  #649/12  verifier_lsm_init_xattr/reject_ctx_forged_count:OK
  #649/13  verifier_lsm_init_xattr/allow_count_via_subprog:OK
  #649/14  verifier_lsm_init_xattr/reject_shifted_xattrs:OK
  #649/15  verifier_lsm_init_xattr/reject_sleepable:OK
  #649/16  verifier_lsm_init_xattr/reject_lsm_cgroup:OK
  #649/17  verifier_lsm_init_xattr/reject_wrong_hook:OK
  #649     verifier_lsm_init_xattr:OK
  Summary: 1/17 PASSED, 0 SKIPPED, 0/0 FAILED

Co-developed-by: David Windsor <dwindsor@gmail.com>
Signed-off-by: David Windsor <dwindsor@gmail.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20260915150739.284189-8-daniel@iogearbox.net
This commit is contained in:
Daniel Borkmann 2026-09-15 17:07:38 +02:00 committed by Alexei Starovoitov
parent e5d960cfe1
commit 2757e9e539
No known key found for this signature in database
3 changed files with 262 additions and 4 deletions

View File

@ -78,18 +78,29 @@ extern void bpf_key_put(struct bpf_key *key) __ksym;
extern int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_ptr,
const struct bpf_dynptr *sig_ptr,
struct bpf_key *trusted_keyring) __ksym;
struct dentry;
/* Description
/*
* Description
* Returns xattr of a dentry
* Returns
* Error code
*/
struct dentry;
extern int bpf_get_dentry_xattr(struct dentry *dentry, const char *name,
struct bpf_dynptr *value_ptr) __ksym __weak;
extern int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,
const struct bpf_dynptr *value_p, int flags) __ksym __weak;
extern int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str) __ksym __weak;
/*
* Description
* Attach a xattr to an inode that is being created, from a program on the
* inode_init_security LSM hook. *xattrs* and *xattr_count* must be the
* hook's own arguments, passed through unmodified.
* Returns
* 0 on success, a negative value on error
*/
struct xattr;
extern int bpf_inode_init_xattr(struct xattr *xattrs, int *xattr_count,
const char *name__str,
const struct bpf_dynptr *value_p) __ksym __weak;
#endif

View File

@ -130,6 +130,7 @@
#include "verifier_bits_iter.skel.h"
#include "verifier_set_retval.skel.h"
#include "verifier_lsm.skel.h"
#include "verifier_lsm_init_xattr.skel.h"
#include "verifier_jit_inline.skel.h"
#include "irq.skel.h"
#include "verifier_ctx_ptr_param.skel.h"
@ -294,6 +295,7 @@ void test_verifier_xdp_direct_packet_access(void) { RUN(verifier_xdp_direct_pack
void test_verifier_bits_iter(void) { RUN(verifier_bits_iter); }
void test_verifier_set_retval(void) { RUN(verifier_set_retval); }
void test_verifier_lsm(void) { RUN(verifier_lsm); }
void test_verifier_lsm_init_xattr(void) { RUN(verifier_lsm_init_xattr); }
void test_irq(void) { RUN(irq); }
void test_verifier_mtu(void) { RUN(verifier_mtu); }
void test_verifier_jit_inline(void) { RUN(verifier_jit_inline); }

View File

@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h"
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include "bpf_kfuncs.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
const char xattr_zone[] = "security.bpf.zone";
char value_buf[8] = "z";
int scratch_count;
SEC("lsm/inode_init_security")
__failure __msg("cannot write into rdonly_trusted_mem")
int BPF_PROG(reject_count_write, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
*xattr_count = 0;
return 0;
}
SEC("lsm/inode_init_security")
__success
int BPF_PROG(allow_count_read, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
scratch_count = *xattr_count;
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("Possibly NULL pointer passed to trusted")
int BPF_PROG(reject_unchecked_xattrs, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, xattr_count, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("invalid mem access 'trusted_ptr_or_null_'")
int BPF_PROG(reject_unchecked_qstr, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
scratch_count = qstr->len;
return 0;
}
SEC("lsm/inode_init_security")
__success
int BPF_PROG(allow_spilled_count, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
int *saved = xattr_count;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, saved, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("expected=rdonly_trusted_mem")
int BPF_PROG(reject_forged_count, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, &scratch_count, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("expected=rdonly_trusted_mem")
int BPF_PROG(reject_stack_count, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
int local = 0;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, &local, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("expected=rdonly_trusted_mem")
int BPF_PROG(reject_other_ctx_arg, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, (int *)xattrs, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("Possibly NULL pointer passed to trusted")
int BPF_PROG(reject_null_count, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, NULL, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("dereference of modified rdonly_trusted_mem ptr")
int BPF_PROG(reject_shifted_count, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, xattr_count + 1, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("variable rdonly_trusted_mem access var_off=")
int BPF_PROG(reject_var_shifted_count, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, xattr_count + (scratch_count & 1),
xattr_zone, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("invalid bpf_context access")
int reject_ctx_forged_count(unsigned long long *ctx)
{
volatile unsigned long long *slot = ctx;
struct bpf_dynptr value;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
slot[4] = 0;
bpf_inode_init_xattr((struct xattr *)(long)slot[3],
(int *)(long)slot[4], xattr_zone, &value);
return 0;
}
static __noinline int claim_via_subprog(struct xattr *xattrs, int *xattr_count,
struct bpf_dynptr *value)
{
return bpf_inode_init_xattr(xattrs, xattr_count, xattr_zone, value);
}
SEC("lsm/inode_init_security")
__success
int BPF_PROG(allow_count_via_subprog, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
claim_via_subprog(xattrs, xattr_count, &value);
return 0;
}
SEC("lsm/inode_init_security")
__failure __msg("access beyond struct xattr at off 24")
int BPF_PROG(reject_shifted_xattrs, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs + 1, xattr_count, xattr_zone, &value);
return 0;
}
SEC("lsm.s/inode_init_security")
__failure __msg("bpf_lsm_inode_init_security is not sleepable")
int BPF_PROG(reject_sleepable, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
return 0;
}
SEC("lsm_cgroup/inode_init_security")
__failure __msg("calling kernel function bpf_inode_init_xattr is not allowed")
int BPF_PROG(reject_lsm_cgroup, struct inode *inode, struct inode *dir,
const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
{
struct bpf_dynptr value;
if (!xattrs)
return 0;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
bpf_inode_init_xattr(xattrs, xattr_count, xattr_zone, &value);
return 0;
}
SEC("lsm/inode_setxattr")
__failure __msg("calling kernel function bpf_inode_init_xattr is not allowed")
int BPF_PROG(reject_wrong_hook, struct mnt_idmap *idmap, struct dentry *dentry,
const char *name, const void *value, size_t size, int flags)
{
struct bpf_dynptr val;
bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &val);
bpf_inode_init_xattr(NULL, &scratch_count, xattr_zone, &val);
return 0;
}