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

Fix this by propagating the user-provided attribute size 'uattr_size'
down to the cgroup query handlers, and conditionally skipping writing
the revision field to userspace when the provided buffer size is
insufficient.

query.revision in bpf_mprog_query is structurally identical to the
cgroup case: a late tail field, written unconditionally.

But the backward-compat hazard is not the same.

The min-historical-size test is per command, and bpf_mprog_query only
serves attach types that were born with revision in the struct:

- tcx_prog_query -> BPF_TCX_INGRESS/EGRESS
- netkit_prog_query -> BPF_NETKIT_PRIMARY/PEER

tcx, netkit, the revision field, and bpf_mprog_query itself all landed in
the same v6.6 merge window (053c8e1f235d added the mprog query API +
revision; tcx in e420bed02507, netkit in 35dfaad7188c). There has never
been a tcx/netkit BPF_PROG_QUERY userspace that doesn't know about
revision. So for these commands the minimum legitimate struct already
covers offset 56-64 — no old binary can be broken here.

Contrast with cgroup: BPF_PROG_QUERY on cgroup attach types shipped in
2017; revision write-back was bolted on years later (120933984460). That
path has a real population of pre-revision callers.

Fixes: 120933984460 ("bpf: Implement mprog API on top of existing cgroup progs")
Cc: Maciej Żenczykowski <[email protected]>
Cc: Lorenzo Colitti <[email protected]>
Signed-off-by: Yuyang Huang <[email protected]>
---
 include/linux/bpf-cgroup.h |  5 +++--
 kernel/bpf/cgroup.c        | 13 +++++++------
 kernel/bpf/syscall.c       |  6 +++---
 3 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/include/linux/bpf-cgroup.h b/include/linux/bpf-cgroup.h
index b2e79c2b41d5..4d0cc65976a1 100644
--- a/include/linux/bpf-cgroup.h
+++ b/include/linux/bpf-cgroup.h
@@ -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;
 }
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
index 876f6a81a9b6..2c2bdaa86aa7 100644
--- a/kernel/bpf/cgroup.c
+++ b/kernel/bpf/cgroup.c
@@ -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;
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index a3c0214ca934..edd6b0dad0d3 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -4654,7 +4654,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;
@@ -4693,7 +4693,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:
@@ -6260,7 +6260,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);
-- 
2.54.0.823.g6e5bcc1fc9-goog


Reply via email to