From: Hui Zhu <[email protected]>

If bpf_trampoline_update() fails in __bpf_trampoline_unlink_prog(),
cur_image is unchanged and still has node->link->prog's call baked
into it. All callers only WARN_ON_ONCE() the failure and then
unconditionally free the prog -- a UAF.

Fix: on failure, pin node->link->prog onto tr->cur_image via the
pinned_prog mechanism, so it outlives the link. This covers both the
multi (bpf_trampoline_multi_detach) and non-multi
(bpf_tracing_link_release, bpf_shim_tramp_link_release) paths.

Fixes: aef4dfa790b2 ("bpf: Add bpf_trampoline_multi_attach/detach functions")
Signed-off-by: Hui Zhu <[email protected]>
---
 kernel/bpf/trampoline.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index 7fac27374ece..6071261fd66b 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -991,7 +991,25 @@ static int __bpf_trampoline_unlink_prog(struct 
bpf_tramp_node *node,
                return err;
        }
        bpf_trampoline_remove_prog(tr, node);
-       return bpf_trampoline_update(tr, true /* lock_direct_mutex */, ops, 
data);
+       err = bpf_trampoline_update(tr, true /* lock_direct_mutex */, ops, 
data);
+       /*
+        * If the update above failed, tr->cur_image is unchanged, i.e. ftrace
+        * (or the direct jump, for the standard ops) is still actually
+        * calling into an image with node->link->prog's call baked into its
+        * machine code, even though this unlink is being reported as failed.
+        * Every caller of this function only WARN_ON_ONCE()'s a failure here
+        * and then unconditionally frees the underlying bpf_prog, which
+        * would be a use-after-free the next time that image is executed.
+        * Pin an extra reference on the prog onto tr->cur_image so it
+        * outlives this link, mirroring the fix applied to
+        * bpf_trampoline_multi_attach()/bpf_trampoline_multi_detach().
+        */
+       if (err && tr->cur_image) {
+               WARN_ON_ONCE(tr->cur_image->pinned_prog);
+               bpf_prog_inc(node->link->prog);
+               tr->cur_image->pinned_prog = node->link->prog;
+       }
+       return err;
 }
 
 /* bpf_trampoline_unlink_prog() should never fail. */
-- 
2.53.0


Reply via email to