From: Greg Kroah-Hartman Date: Sat, 13 Aug 2022 13:15:49 +0000 (+0200) Subject: 4.9-stable patches X-Git-Tag: v5.15.61~178 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4683094affeddde8acada5c63baa34a0360e172f;p=thirdparty%2Fkernel%2Fstable-queue.git 4.9-stable patches added patches: bpf-fix-overflow-in-prog-accounting.patch --- diff --git a/queue-4.9/bpf-fix-overflow-in-prog-accounting.patch b/queue-4.9/bpf-fix-overflow-in-prog-accounting.patch new file mode 100644 index 00000000000..b844abc77b9 --- /dev/null +++ b/queue-4.9/bpf-fix-overflow-in-prog-accounting.patch @@ -0,0 +1,171 @@ +From 5ccb071e97fbd9ffe623a0d3977cc6d013bee93c Mon Sep 17 00:00:00 2001 +From: Daniel Borkmann +Date: Sun, 18 Dec 2016 01:52:58 +0100 +Subject: bpf: fix overflow in prog accounting + +From: Daniel Borkmann + +commit 5ccb071e97fbd9ffe623a0d3977cc6d013bee93c upstream. + +Commit aaac3ba95e4c ("bpf: charge user for creation of BPF maps and +programs") made a wrong assumption of charging against prog->pages. +Unlike map->pages, prog->pages are still subject to change when we +need to expand the program through bpf_prog_realloc(). + +This can for example happen during verification stage when we need to +expand and rewrite parts of the program. Should the required space +cross a page boundary, then prog->pages is not the same anymore as +its original value that we used to bpf_prog_charge_memlock() on. Thus, +we'll hit a wrap-around during bpf_prog_uncharge_memlock() when prog +is freed eventually. I noticed this that despite having unlimited +memlock, programs suddenly refused to load with EPERM error due to +insufficient memlock. + +There are two ways to fix this issue. One would be to add a cached +variable to struct bpf_prog that takes a snapshot of prog->pages at the +time of charging. The other approach is to also account for resizes. I +chose to go with the latter for a couple of reasons: i) We want accounting +rather to be more accurate instead of further fooling limits, ii) adding +yet another page counter on struct bpf_prog would also be a waste just +for this purpose. We also do want to charge as early as possible to +avoid going into the verifier just to find out later on that we crossed +limits. The only place that needs to be fixed is bpf_prog_realloc(), +since only here we expand the program, so we try to account for the +needed delta and should we fail, call-sites check for outcome anyway. +On cBPF to eBPF migrations, we don't grab a reference to the user as +they are charged differently. With that in place, my test case worked +fine. + +Fixes: aaac3ba95e4c ("bpf: charge user for creation of BPF maps and programs") +Signed-off-by: Daniel Borkmann +Acked-by: Alexei Starovoitov +Signed-off-by: David S. Miller +[Quentin: backport to 4.9: Adjust context in bpf.h ] +Signed-off-by: Quentin Monnet +Signed-off-by: Greg Kroah-Hartman +--- + include/linux/bpf.h | 11 +++++++++++ + kernel/bpf/core.c | 16 +++++++++++++--- + kernel/bpf/syscall.c | 36 ++++++++++++++++++++++++++++-------- + 3 files changed, 52 insertions(+), 11 deletions(-) + +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -246,6 +246,8 @@ struct bpf_prog *bpf_prog_get_type(u32 u + struct bpf_prog *bpf_prog_add(struct bpf_prog *prog, int i); + struct bpf_prog *bpf_prog_inc(struct bpf_prog *prog); + void bpf_prog_put(struct bpf_prog *prog); ++int __bpf_prog_charge(struct user_struct *user, u32 pages); ++void __bpf_prog_uncharge(struct user_struct *user, u32 pages); + + struct bpf_map *bpf_map_get_with_uref(u32 ufd); + struct bpf_map *__bpf_map_get(struct fd f); +@@ -328,6 +330,15 @@ static inline struct bpf_prog *bpf_prog_ + return ERR_PTR(-EOPNOTSUPP); + } + ++static inline int __bpf_prog_charge(struct user_struct *user, u32 pages) ++{ ++ return 0; ++} ++ ++static inline void __bpf_prog_uncharge(struct user_struct *user, u32 pages) ++{ ++} ++ + static inline bool unprivileged_ebpf_enabled(void) + { + return false; +--- a/kernel/bpf/core.c ++++ b/kernel/bpf/core.c +@@ -107,19 +107,29 @@ struct bpf_prog *bpf_prog_realloc(struct + gfp_t gfp_flags = GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO | + gfp_extra_flags; + struct bpf_prog *fp; ++ u32 pages, delta; ++ int ret; + + BUG_ON(fp_old == NULL); + + size = round_up(size, PAGE_SIZE); +- if (size <= fp_old->pages * PAGE_SIZE) ++ pages = size / PAGE_SIZE; ++ if (pages <= fp_old->pages) + return fp_old; + ++ delta = pages - fp_old->pages; ++ ret = __bpf_prog_charge(fp_old->aux->user, delta); ++ if (ret) ++ return NULL; ++ + fp = __vmalloc(size, gfp_flags, PAGE_KERNEL); +- if (fp != NULL) { ++ if (fp == NULL) { ++ __bpf_prog_uncharge(fp_old->aux->user, delta); ++ } else { + kmemcheck_annotate_bitfield(fp, meta); + + memcpy(fp, fp_old, fp_old->pages * PAGE_SIZE); +- fp->pages = size / PAGE_SIZE; ++ fp->pages = pages; + fp->aux->prog = fp; + + /* We keep fp->aux from fp_old around in the new +--- a/kernel/bpf/syscall.c ++++ b/kernel/bpf/syscall.c +@@ -581,19 +581,39 @@ static void free_used_maps(struct bpf_pr + kfree(aux->used_maps); + } + ++int __bpf_prog_charge(struct user_struct *user, u32 pages) ++{ ++ unsigned long memlock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; ++ unsigned long user_bufs; ++ ++ if (user) { ++ user_bufs = atomic_long_add_return(pages, &user->locked_vm); ++ if (user_bufs > memlock_limit) { ++ atomic_long_sub(pages, &user->locked_vm); ++ return -EPERM; ++ } ++ } ++ ++ return 0; ++} ++ ++void __bpf_prog_uncharge(struct user_struct *user, u32 pages) ++{ ++ if (user) ++ atomic_long_sub(pages, &user->locked_vm); ++} ++ + static int bpf_prog_charge_memlock(struct bpf_prog *prog) + { + struct user_struct *user = get_current_user(); +- unsigned long memlock_limit; +- +- memlock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; ++ int ret; + +- atomic_long_add(prog->pages, &user->locked_vm); +- if (atomic_long_read(&user->locked_vm) > memlock_limit) { +- atomic_long_sub(prog->pages, &user->locked_vm); ++ ret = __bpf_prog_charge(user, prog->pages); ++ if (ret) { + free_uid(user); +- return -EPERM; ++ return ret; + } ++ + prog->aux->user = user; + return 0; + } +@@ -602,7 +622,7 @@ static void bpf_prog_uncharge_memlock(st + { + struct user_struct *user = prog->aux->user; + +- atomic_long_sub(prog->pages, &user->locked_vm); ++ __bpf_prog_uncharge(user, prog->pages); + free_uid(user); + } + diff --git a/queue-4.9/series b/queue-4.9/series index ecd6897341d..7e80e9702fe 100644 --- a/queue-4.9/series +++ b/queue-4.9/series @@ -34,3 +34,4 @@ vfs-check-the-truncate-maximum-size-in-inode_newsize_ok.patch usbnet-fix-linkwatch-use-after-free-on-disconnect.patch parisc-fix-device-names-in-proc-iomem.patch drm-nouveau-fix-another-off-by-one-in-nvbios_addr.patch +bpf-fix-overflow-in-prog-accounting.patch