> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index dbe321aa8591..34500f81db6e 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -710,6 +710,9 @@ struct bpf_log_attr {
> int bpf_log_attr_init(struct bpf_log_attr *log, u64 log_buf, u32 log_size,
> u32 log_level,
> u32 offsetof_log_true_size, bpfptr_t uattr, struct
> bpf_common_attr *common,
> bpfptr_t uattr_common, u32 size_common);
> +struct bpf_verifier_log *bpf_log_attr_create_vlog(struct bpf_log_attr
> *attr_log,
> + struct bpf_common_attr
> *common, bpfptr_t uattr,
> + u32 size);
> int bpf_log_attr_finalize(struct bpf_log_attr *attr, struct bpf_verifier_log
> *log);
>
> #define BPF_MAX_SUBPROGS 256
> diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
> index dffe3a0180a7..5a977a52ef61 100644
> --- a/kernel/bpf/log.c
> +++ b/kernel/bpf/log.c
> @@ -860,6 +860,36 @@ int bpf_log_attr_init(struct bpf_log_attr *log, u64
> log_buf, u32 log_size, u32 l
> return 0;
> }
>
> +
> +struct bpf_verifier_log *bpf_log_attr_create_vlog(struct bpf_log_attr
> *attr_log,
> + struct bpf_common_attr
> *common, bpfptr_t uattr,
> + u32 size)
> +{
> + struct bpf_verifier_log *log;
> + int err;
> +
> + memset(attr_log, 0, sizeof(*attr_log));
> + attr_log->uattr = uattr;
> + if (size >= offsetofend(struct bpf_common_attr, log_true_size))
> + attr_log->offsetof_true_size = offsetof(struct bpf_common_attr,
> log_true_size);
> +
> + if (!common->log_buf)
> + return NULL;
Does this skip validation that bpf_log_attr_init() performs?
The sibling helper bpf_log_attr_init() validates struct bpf_common_attr
fields via bpf_verifier_log_attr_valid() unconditionally:
kernel/bpf/log.c:bpf_log_attr_init() {
if (!bpf_verifier_log_attr_valid(common->log_level, ubuf_common,
common->log_size) ||
!bpf_verifier_log_attr_valid(log_level, ubuf, log_size))
return -EINVAL;
}
This returns -EINVAL for combinations like (log_buf==0 && log_size!=0)
or (log_buf==0 && log_level!=0), enforced by:
kernel/bpf/log.c:bpf_verifier_log_attr_valid() {
/* ubuf and len_total should both be specified (or not) together */
if (!!log_buf != !!log_size)
return false;
if (log_buf && log_level == 0)
return false;
if (log_level & ~BPF_LOG_MASK)
return false;
if (log_size > UINT_MAX >> 2)
return false;
}
When bpf_log_attr_create_vlog() returns early because common->log_buf == 0,
these four checks are skipped entirely. When common->log_buf != 0, the
checks run later via bpf_vlog_init().
This creates an ABI validation inconsistency: BPF_PROG_LOAD and BPF_BTF_LOAD
reject (log_buf==0 && log_size!=0) with -EINVAL, but BPF_MAP_CREATE
silently accepts it. Userspace passing the same invalid attribute
combination receives different results depending on the syscall command.
Should bpf_log_attr_create_vlog() call
bpf_verifier_log_attr_valid(common->log_level,
u64_to_user_ptr(common->log_buf), common->log_size)
before the early return, and return ERR_PTR(-EINVAL) on failure?
> +
> + log = kzalloc_obj(*log, GFP_KERNEL);
> + if (!log)
> + return ERR_PTR(-ENOMEM);
> +
> + err = bpf_vlog_init(log, common->log_level,
> u64_to_user_ptr(common->log_buf),
> + common->log_size);
> + if (err) {
> + kfree(log);
> + return ERR_PTR(err);
> + }
> +
> + return log;
> +}
> +
> int bpf_log_attr_finalize(struct bpf_log_attr *attr, struct bpf_verifier_log
> *log)
> {
> u32 log_true_size;
[ ... ]
---
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/24672239158