css_rstat_updated() is exposed as a BPF kfunc and accepts a caller-provided cpu argument. The function uses cpu for per-cpu rstat lookups without checking whether it refers to a valid possible CPU. A BPF iter/cgroup program with CAP_BPF and CAP_PERFMON can pass an invalid cpu value. On an unfixed UBSCAN_BOUNDS test kernel, cpu == 0x7fffffff triggers: UBSAN: array-index-out-of-bounds in kernel/cgroup/rstat.c:31:9 index 2147483647 is out of range for type 'long unsigned int [64]' Call Trace: css_rstat_updated bpf_iter_run_prog cgroup_iter_seq_show bpf_seq_read Add cpu validation to the BPF-facing css_rstat_updated() kfunc and move the common implementation to __css_rstat_updated() for in-kernel callers.
Showing the changes to kernel (1 of 4 files). Show all
Unified · Split
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | 2 | #include "cgroup-internal.h" |
| 3 | 3 | |
| 4 | +#include <linux/cpumask.h> | |
| 4 | 5 | #include <linux/sched/cputime.h> |
| 5 | 6 | |
| 6 | 7 | #include <linux/bpf.h> |
| @@ -53,7 +54,7 @@ static inline struct llist_head *ss_lhead_cpu(struct cgroup_subsys *ss, int cpu) · 46 unmodified lines | ||
| 53 | 54 | } |
| 54 | 55 | |
| 55 | 56 | /** |
| 56 | − * css_rstat_updated - keep track of updated rstat_cpu | |
| 57 | + * __css_rstat_updated - keep track of updated rstat_cpu | |
| 57 | 58 | * @css: target cgroup subsystem state |
| 58 | 59 | * @cpu: cpu on which rstat_cpu was updated |
| 59 | 60 | * |
| @@ -63,20 +64,17 @@ static inline struct llist_head *ss_lhead_cpu(struct cgroup_subsys *ss, int cpu) · 3 unmodified lines | ||
| 63 | 64 | * |
| 64 | 65 | * NOTE: if the user needs the guarantee that the updater either add itself in |
| 65 | 66 | * the lockless list or the concurrent flusher flushes its updated stats, a |
| 66 | − * memory barrier is needed before the call to css_rstat_updated() i.e. a | |
| 67 | + * memory barrier is needed before the call to __css_rstat_updated() i.e. a | |
| 67 | 68 | * barrier after updating the per-cpu stats and before calling |
| 68 | − * css_rstat_updated(). | |
| 69 | + * __css_rstat_updated(). | |
| 69 | 70 | */ |
| 70 | −__bpf_kfunc void css_rstat_updated(struct cgroup_subsys_state *css, int cpu) | |
| 71 | +void __css_rstat_updated(struct cgroup_subsys_state *css, int cpu) | |
| 71 | 72 | { |
| 72 | 73 | struct llist_head *lhead; |
| 73 | 74 | struct css_rstat_cpu *rstatc; |
| 74 | 75 | struct llist_node *self; |
| 75 | 76 | |
| 76 | − /* | |
| 77 | − * Since bpf programs can call this function, prevent access to | |
| 78 | − * uninitialized rstat pointers. | |
| 79 | − */ | |
| 77 | + /* Prevent access to uninitialized rstat pointers. */ | |
| 80 | 78 | if (!css_uses_rstat(css)) |
| 81 | 79 | return; |
| 82 | 80 | |
| @@ -125,6 +123,18 @@ __bpf_kfunc void css_rstat_updated(struct cgroup_subsys_state *css, int cpu) · 42 unmodified lines | ||
| 125 | 123 | llist_add(&rstatc->lnode, lhead); |
| 126 | 124 | } |
| 127 | 125 | |
| 126 | +/* | |
| 127 | + * BPF-facing wrapper for __css_rstat_updated(). Validate the caller-provided | |
| 128 | + * CPU before passing it to the internal rstat updater. | |
| 129 | + */ | |
| 130 | +__bpf_kfunc void css_rstat_updated(struct cgroup_subsys_state *css, int cpu) | |
| 131 | +{ | |
| 132 | + if (unlikely(cpu < 0 || cpu >= nr_cpu_ids || !cpu_possible(cpu))) | |
| 133 | + return; | |
| 134 | + | |
| 135 | + __css_rstat_updated(css, cpu); | |
| 136 | +} | |
| 137 | + | |
| 128 | 138 | static void __css_process_update_tree(struct cgroup_subsys_state *css, int cpu) |
| 129 | 139 | { |
| 130 | 140 | /* put @css and all ancestors on the corresponding updated lists */ |
| @@ -170,7 +180,7 @@ static void css_process_update_tree(struct cgroup_subsys *ss, int cpu) · 39 unmodified lines | ||
| 170 | 180 | * flusher flush the stats updated by the updater who have |
| 171 | 181 | * observed that they are already on the list. The |
| 172 | 182 | * corresponding barrier pair for this one should be before |
| 173 | − * css_rstat_updated() by the user. | |
| 183 | + * __css_rstat_updated() by the user. | |
| 174 | 184 | * |
| 175 | 185 | * For now, there aren't any such user, so not adding the |
| 176 | 186 | * barrier here but if such a use-case arise, please add |
| @@ -614,7 +624,7 @@ static void cgroup_base_stat_cputime_account_end(struct cgroup *cgrp, · 437 unmodified lines | ||
| 614 | 624 | unsigned long flags) |
| 615 | 625 | { |
| 616 | 626 | u64_stats_update_end_irqrestore(&rstatbc->bsync, flags); |
| 617 | − css_rstat_updated(&cgrp->self, smp_processor_id()); | |
| 627 | + __css_rstat_updated(&cgrp->self, smp_processor_id()); | |
| 618 | 628 | put_cpu_ptr(rstatbc); |
| 619 | 629 | } |
| 620 | 630 | |