Merge branch 'bpf-align-syscall-writeback-behavior-with-user-declared-size'

Yuyang Huang says:

====================
bpf: Align syscall writeback behavior with user-declared size

This series fixes an out-of-bounds write vulnerability in BPF_PROG_QUERY
while maintaining backward compatibility for older userspace applications.

BPF_PROG_QUERY unconditionally writes back the 'query.revision' field
to userspace. If userspace passes a smaller 'bpf_attr' structure (e.g. 40
bytes, which was the cgroup query layout before 'query.revision' was
added), the kernel performs an out-of-bounds write.

We address this by propagating the user-provided 'uattr_size' down to
the cgroup query handlers and conditionally skipping the write-back of
'query.revision' if the buffer is too small. This allows legacy cgroup
queries to succeed safely.

tcx and netkit queries are left unchanged since they were introduced in
the same merge window as 'query.revision' and have no legacy callers.

Finally, we add a selftest to verify these boundary behaviors.

Changes since v2:
- Propagate uattr_size to __cgroup_bpf_query() and conditionally write
  revision (instead of unconditionally rejecting smaller sizes in front-gate).
- Update BPF selftests to verify that cgroup queries succeed with
  OLD_QUERY_SIZE without writing revision, and succeed with FULL_QUERY_SIZE.
- Remove early size checks in the front-gate to keep the patch minimal.

Changes since v1:
- Simplify the kernel fix to checking the size only in bpf_prog_query().
- Revert all other subsystem query plumbing changes.
- Update BPF selftest to target BPF_CGROUP_INET_INGRESS cgroup query, and
  add verification for attr size boundaries.
====================

Link: https://patch.msgid.link/20260531075600.4058207-1-yuyanghuang@google.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-05-31 09:16:55 -07:00
commit b573cf651b
4 changed files with 82 additions and 11 deletions

View File

@ -421,7 +421,7 @@ int cgroup_bpf_prog_detach(const union bpf_attr *attr,
enum bpf_prog_type ptype);
int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog);
int cgroup_bpf_prog_query(const union bpf_attr *attr,
union bpf_attr __user *uattr);
union bpf_attr __user *uattr, u32 uattr_size);
const struct bpf_func_proto *
cgroup_common_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog);
@ -452,7 +452,8 @@ static inline int cgroup_bpf_link_attach(const union bpf_attr *attr,
}
static inline int cgroup_bpf_prog_query(const union bpf_attr *attr,
union bpf_attr __user *uattr)
union bpf_attr __user *uattr,
u32 uattr_size)
{
return -EINVAL;
}

View File

@ -1208,7 +1208,7 @@ static int cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
/* Must be called with cgroup_mutex held to avoid races. */
static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
union bpf_attr __user *uattr)
union bpf_attr __user *uattr, u32 uattr_size)
{
__u32 __user *prog_attach_flags = u64_to_user_ptr(attr->query.prog_attach_flags);
bool effective_query = attr->query.query_flags & BPF_F_QUERY_EFFECTIVE;
@ -1259,7 +1259,8 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
return -EFAULT;
if (!effective_query && from_atype == to_atype)
revision = cgrp->bpf.revisions[from_atype];
if (copy_to_user(&uattr->query.revision, &revision, sizeof(revision)))
if (uattr_size >= offsetofend(union bpf_attr, query.revision) &&
copy_to_user(&uattr->query.revision, &revision, sizeof(revision)))
return -EFAULT;
if (attr->query.prog_cnt == 0 || !prog_ids || !total_cnt)
/* return early if user requested only program count + flags */
@ -1312,12 +1313,12 @@ static int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
}
static int cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
union bpf_attr __user *uattr)
union bpf_attr __user *uattr, u32 uattr_size)
{
int ret;
cgroup_lock();
ret = __cgroup_bpf_query(cgrp, attr, uattr);
ret = __cgroup_bpf_query(cgrp, attr, uattr, uattr_size);
cgroup_unlock();
return ret;
}
@ -1520,7 +1521,7 @@ int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
}
int cgroup_bpf_prog_query(const union bpf_attr *attr,
union bpf_attr __user *uattr)
union bpf_attr __user *uattr, u32 uattr_size)
{
struct cgroup *cgrp;
int ret;
@ -1529,7 +1530,7 @@ int cgroup_bpf_prog_query(const union bpf_attr *attr,
if (IS_ERR(cgrp))
return PTR_ERR(cgrp);
ret = cgroup_bpf_query(cgrp, attr, uattr);
ret = cgroup_bpf_query(cgrp, attr, uattr, uattr_size);
cgroup_put(cgrp);
return ret;

View File

@ -4719,7 +4719,7 @@ static int bpf_prog_detach(const union bpf_attr *attr)
#define BPF_PROG_QUERY_LAST_FIELD query.revision
static int bpf_prog_query(const union bpf_attr *attr,
union bpf_attr __user *uattr)
union bpf_attr __user *uattr, u32 uattr_size)
{
if (!bpf_net_capable())
return -EPERM;
@ -4758,7 +4758,7 @@ static int bpf_prog_query(const union bpf_attr *attr,
case BPF_CGROUP_GETSOCKOPT:
case BPF_CGROUP_SETSOCKOPT:
case BPF_LSM_CGROUP:
return cgroup_bpf_prog_query(attr, uattr);
return cgroup_bpf_prog_query(attr, uattr, uattr_size);
case BPF_LIRC_MODE2:
return lirc_prog_query(attr, uattr);
case BPF_FLOW_DISSECTOR:
@ -6376,7 +6376,7 @@ static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size,
err = bpf_prog_detach(&attr);
break;
case BPF_PROG_QUERY:
err = bpf_prog_query(&attr, uattr.user);
err = bpf_prog_query(&attr, uattr.user, size);
break;
case BPF_PROG_TEST_RUN:
err = bpf_prog_test_run(&attr, uattr.user);

View File

@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Google LLC */
#include <linux/bpf.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <test_progs.h>
#include <cgroup_helpers.h>
#include "cgroup_skb_direct_packet_access.skel.h"
#define OLD_QUERY_SIZE offsetofend(union bpf_attr, query.prog_cnt)
#define FULL_QUERY_SIZE offsetofend(union bpf_attr, query.revision)
static void test_query_size_boundaries(void)
{
struct cgroup_skb_direct_packet_access *skel;
struct bpf_link *link = NULL;
union bpf_attr attr;
int cg_fd = -1;
int err;
skel = cgroup_skb_direct_packet_access__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_load"))
return;
cg_fd = test__join_cgroup("/attr_size_cg");
if (!ASSERT_GE(cg_fd, 0, "join_cgroup"))
goto cleanup;
link = bpf_program__attach_cgroup(skel->progs.direct_packet_access,
cg_fd);
if (!ASSERT_OK_PTR(link, "cg_attach"))
goto cleanup;
memset(&attr, 0, sizeof(attr));
attr.query.target_fd = cg_fd;
attr.query.attach_type = BPF_CGROUP_INET_INGRESS;
attr.query.revision = 0xdeadbeefdeadbeefULL;
err = syscall(__NR_bpf, BPF_PROG_QUERY, &attr, OLD_QUERY_SIZE);
if (ASSERT_OK(err, "query_old_size")) {
ASSERT_EQ(attr.query.prog_cnt, 1, "prog_cnt_written_old");
ASSERT_EQ(attr.query.revision, 0xdeadbeefdeadbeefULL,
"revision_not_written_old");
}
memset(&attr, 0, sizeof(attr));
attr.query.target_fd = cg_fd;
attr.query.attach_type = BPF_CGROUP_INET_INGRESS;
err = syscall(__NR_bpf, BPF_PROG_QUERY, &attr, FULL_QUERY_SIZE);
if (!ASSERT_OK(err, "query_full_size"))
goto cleanup;
ASSERT_EQ(attr.query.prog_cnt, 1, "prog_cnt_written");
ASSERT_GT(attr.query.revision, 0, "revision_written");
cleanup:
if (link)
bpf_link__destroy(link);
if (cg_fd >= 0)
close(cg_fd);
cgroup_skb_direct_packet_access__destroy(skel);
}
void test_bpf_attr_size(void)
{
if (test__start_subtest("query_size_boundaries"))
test_query_size_boundaries();
}