[PATCH] bpf: allow BPF_LOG_KERNEL from kernel space

From: Siddharth Nayyar

Date: Tue Jun 23 2026 - 11:39:54 EST


When loading a BPF program or BTF from kernel space (e.g. using a
light skeleton generated loader module), the kernel module may want
to output verifier logs to dmesg by setting attr->log_level =
BPF_LOG_KERNEL (16).

Currently, any attempt to do this through the standard syscall path
(kern_sys_bpf -> bpf_vlog_init) fails with -EINVAL because
bpf_verifier_log_attr_valid() rejects any log_level bit outside
BPF_LOG_MASK. This prevents in-kernel loaders from successfully
using BPF_LOG_KERNEL, causing silent failures if the BPF object
is rejected by the verifier.

This patch modifies bpf_vlog_init() to accept a new 'is_kernel'
boolean (propagated from uattr.is_kernel) and allows BPF_LOG_KERNEL
to pass the validation check strictly when invoked from kernel space.

Signed-off-by: Siddharth Nayyar <sidnayyar@xxxxxxxxxx>
---
include/linux/bpf_verifier.h | 2 +-
kernel/bpf/btf.c | 3 ++-
kernel/bpf/log.c | 17 +++++++++++------
kernel/bpf/verifier.c | 3 ++-
4 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 39a851e690ec..eb9d2a556c0e 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1059,7 +1059,7 @@ __printf(2, 3) void bpf_verifier_log_write(struct bpf_verifier_env *env,
__printf(2, 3) void bpf_log(struct bpf_verifier_log *log,
const char *fmt, ...);
int bpf_vlog_init(struct bpf_verifier_log *log, u32 log_level,
- char __user *log_buf, u32 log_size);
+ char __user *log_buf, u32 log_size, bool is_kernel);
void bpf_vlog_reset(struct bpf_verifier_log *log, u64 new_pos);
int bpf_vlog_finalize(struct bpf_verifier_log *log, u32 *log_size_actual);

diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 15ae7c43f594..69ec32f99457 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -5926,7 +5926,8 @@ static struct btf *btf_parse(const union bpf_attr *attr, bpfptr_t uattr,
/* user could have requested verbose verifier output
* and supplied buffer to store the verification trace
*/
- err = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size);
+ err = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size,
+ uattr.is_kernel);
if (err)
goto errout_free;

diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c
index b740fa73ee26..92584a314129 100644
--- a/kernel/bpf/log.c
+++ b/kernel/bpf/log.c
@@ -13,8 +13,12 @@

#define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)

-static bool bpf_verifier_log_attr_valid(u32 log_level, char __user *log_buf, u32 log_size)
+static bool bpf_verifier_log_attr_valid(u32 log_level, char __user *log_buf, u32 log_size,
+ bool is_kernel)
{
+ if (log_level == BPF_LOG_KERNEL && is_kernel)
+ return true;
+
/* ubuf and len_total should both be specified (or not) together */
if (!!log_buf != !!log_size)
return false;
@@ -29,14 +33,14 @@ static bool bpf_verifier_log_attr_valid(u32 log_level, char __user *log_buf, u32
}

int bpf_vlog_init(struct bpf_verifier_log *log, u32 log_level,
- char __user *log_buf, u32 log_size)
+ char __user *log_buf, u32 log_size, bool is_kernel)
{
log->level = log_level;
log->ubuf = log_buf;
log->len_total = log_size;

/* log attributes have to be sane */
- if (!bpf_verifier_log_attr_valid(log_level, log_buf, log_size))
+ if (!bpf_verifier_log_attr_valid(log_level, log_buf, log_size, is_kernel))
return -EINVAL;

return 0;
@@ -831,8 +835,9 @@ int bpf_log_attr_init(struct bpf_log_attr *log, u64 log_buf, u32 log_size, u32 l
char __user *ubuf_common = u64_to_user_ptr(common->log_buf);
char __user *ubuf = u64_to_user_ptr(log_buf);

- if (!bpf_verifier_log_attr_valid(common->log_level, ubuf_common, common->log_size) ||
- !bpf_verifier_log_attr_valid(log_level, ubuf, log_size))
+ if (!bpf_verifier_log_attr_valid(common->log_level, ubuf_common,
+ common->log_size, uattr_common.is_kernel) ||
+ !bpf_verifier_log_attr_valid(log_level, ubuf, log_size, uattr.is_kernel))
return -EINVAL;

if (ubuf && ubuf_common && (ubuf != ubuf_common || log_size != common->log_size ||
@@ -878,7 +883,7 @@ struct bpf_verifier_log *bpf_log_attr_create_vlog(struct bpf_log_attr *attr_log,
return ERR_PTR(-ENOMEM);

err = bpf_vlog_init(log, common->log_level, u64_to_user_ptr(common->log_buf),
- common->log_size);
+ common->log_size, uattr.is_kernel);
if (err) {
kfree(log);
return ERR_PTR(err);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 2abc79dbf281..9e76a02323e8 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -19755,7 +19755,8 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
/* user could have requested verbose verifier output
* and supplied buffer to store the verification trace
*/
- ret = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size);
+ ret = bpf_vlog_init(&env->log, attr_log->level, attr_log->ubuf, attr_log->size,
+ uattr.is_kernel);
if (ret)
goto err_unlock;


---
base-commit: 502d801f0ab03e4f32f9a33d203154ce84887921
change-id: 20260622-bpf-lskel-fixes-cafd1a83dec2

Best regards,
--
Siddharth Nayyar <sidnayyar@xxxxxxxxxx>