On 2026/10/17 22:48, [email protected] wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s)
> to consider:
> - [Critical] bpf: NULL deref and format string bug in bpf_xdp_link_attach()
> via bpf_log()
> - [Critical] bpf: prog refcount underflow in link_create() if
> bpf_log_attr_finalize() fails
> - [Low] bpf: link failure in bpf_xdp_link_attach() when !CONFIG_BPF_SYSCALL
> --
>
> commit fa16fd00d8499a718392032e57d440248e048afc
> Author: Leon Hwang <[email protected]>
>
> bpf: Add syscall common attrs support for xdp link_create
>
> This commit passes the verifier log to bpf_xdp_link_attach() so that XDP
> link creation can report target_ifindex and driver errors through BPF
> syscall common attributes.
>
>> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
>> index 252b479937a4d..e96d5f09486f5 100644
>> --- a/kernel/bpf/syscall.c
>> +++ b/kernel/bpf/syscall.c
>> @@ -5921,7 +5921,7 @@ static int link_create(union bpf_attr *attr, bpfptr_t
>> uattr, struct bpf_common_a
>> break;
>> #ifdef CONFIG_NET
>> case BPF_PROG_TYPE_XDP:
>> - ret = bpf_xdp_link_attach(attr, prog);
>> + ret = bpf_xdp_link_attach(attr, prog, log);
>> break;
>> case BPF_PROG_TYPE_SCHED_CLS:
>> if (attr->link_create.attach_type == BPF_TCX_INGRESS ||
>
> [Severity: Critical]
> Can the cleanup path in link_create() underflow prog's reference count if
> bpf_log_attr_finalize() fails after bpf_xdp_link_attach() succeeds?
Will fix the issue by reporting log_true_size twice. See the pseudocode
in patch 1 reply.
>
> When bpf_xdp_link_attach() succeeds, bpf_link_init() transfers ownership of
> prog's reference to link->link.prog and bpf_link_settle() installs the link
> file descriptor, returning a non-negative fd in ret.
>
> At the out label in link_create(), bpf_log_attr_finalize() writes
> log_true_size to uattr_common even when nothing was logged:
>
> kernel/bpf/log.c:bpf_log_attr_finalize() {
> ...
> if (attr->offsetof_true_size &&
> copy_to_bpfptr_offset(attr->uattr, attr->offsetof_true_size,
> &log_true_size,
> sizeof(log_true_size)))
> return -EFAULT;
> }
>
> If uattr_common points to read-only memory, copy_from_bpfptr() in
> __sys_bpf() succeeds, bpf_xdp_link_attach() attaches and settles the link,
> and then bpf_log_attr_finalize() returns -EFAULT:
>
> kernel/bpf/syscall.c:link_create() {
> out:
> /* preserve original error even if log finalization is successful */
> err = bpf_log_attr_finalize(&attr_log, log);
> if (err)
> ret = err;
>
> kfree(log);
>
> if (ret < 0 && prog)
> bpf_prog_put(prog);
> return ret;
> }
>
> Overwriting ret with err causes link_create() to call bpf_prog_put(prog) on
> a reference that is now owned by the settled bpf_link, leading to a
> use-after-free of struct bpf_prog while the link remains attached in the
> process fd table.
>
>> diff --git a/net/core/dev.c b/net/core/dev.c
>> index 18dc88990510a..fdc1de21f3589 100644
>> --- a/net/core/dev.c
>> +++ b/net/core/dev.c
> [ ... ]
>> @@ -10771,7 +10772,8 @@ static const struct bpf_link_ops bpf_xdp_link_lops =
>> {
>> .update_prog = bpf_xdp_link_update,
>> };
>>
>> -int bpf_xdp_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
>> +int bpf_xdp_link_attach(const union bpf_attr *attr, struct bpf_prog *prog,
>> + struct bpf_verifier_log *log)
>> {
>> struct net *net = current->nsproxy->net_ns;
>> struct bpf_link_primer link_primer;
>> @@ -10784,6 +10786,7 @@ int bpf_xdp_link_attach(const union bpf_attr *attr,
>> struct bpf_prog *prog)
>> dev = dev_get_by_index(net, attr->link_create.target_ifindex);
>> if (!dev) {
>> rtnl_unlock();
>> + bpf_log(log, "Invalid target_ifindex.\n");
>> return -EINVAL;
>> }
>
> [Severity: Low]
> Will calling bpf_log() in bpf_xdp_link_attach() break linking when
> CONFIG_NET=y and CONFIG_BPF_SYSCALL=n?
Correct.
Will provide a stub bpf_log() when CONFIG_BPF_SYSCALL is not set.
>
> net/core/dev.c is compiled whenever CONFIG_NET=y, and bpf_xdp_link_attach()
> is not guarded by CONFIG_BPF_SYSCALL. While helpers like bpf_link_init() and
> bpf_link_prime() have static inline stubs in include/linux/bpf.h when
> !CONFIG_BPF_SYSCALL, bpf_log() is only compiled via kernel/bpf/log.o when
> CONFIG_BPF_SYSCALL=y and has no stub in include/linux/bpf_verifier.h.
>
>>
>> @@ -10812,6 +10815,7 @@ int bpf_xdp_link_attach(const union bpf_attr *attr,
>> struct bpf_prog *prog)
>> if (err) {
>> link->dev = NULL;
>> bpf_link_cleanup(&link_primer);
>> + bpf_log(log, extack._msg);
>> trace_bpf_xdp_link_attach_failed(extack._msg);
>> goto out_put_dev;
>> }
>
> [Severity: Critical]
> What happens here when dev_xdp_attach_link() fails without setting an extack
> message, or when extack._msg contains format specifiers?>
> In bpf_xdp_link_attach(), extack is zero-initialized on the stack, so
> extack._msg starts as NULL. Many failure paths under dev_xdp_attach_link() ->
> dev_xdp_attach() -> dev_xdp_install() return an error without calling
> NL_SET_ERR_MSG(), such as netif_xdp_propagate() when a device has no ndo_bpf
> callback, or driver ndo_bpf callbacks like netkit_xdp() and veth_xdp()
> returning -EOPNOTSUPP or -EINVAL.
>
> When a user provides a log buffer via syscall common attributes and
> extack._msg is NULL, bpf_log() passes fmt = NULL into
> bpf_verifier_vlog() -> vscnprintf() -> vsnprintf(), which dereferences fmt
> unconditionally and crashes the kernel.
>
> Additionally, because bpf_log() is a printf-style function, passing
> extack._msg directly as the format string rather than using a "%s\n" format
> specifier allows any '%' characters in a formatted extack message (for
> example from NL_SET_ERR_MSG_FMT() containing an interface name) to be
> interpreted as format specifiers by vscnprintf(), and omits the trailing
> newline. Should this be guarded with if (extack._msg) and logged with
> bpf_log(log, "%s\n", extack._msg)?
Sure, will change to
if (extack._msg)
bpf_log(log, "%s\n", extack._msg);
Thanks,
Leon