From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Martin KaFai Lau <martin.lau@kernel.org>,
David Vernet <void@manifault.com>, Tejun Heo <tj@kernel.org>,
Raj Sahu <rjsu26@vt.edu>, Dan Williams <djwillia@vt.edu>,
Rishabh Iyer <rishabh.iyer@epfl.ch>,
Sanidhya Kashyap <sanidhya.kashyap@epfl.ch>
Subject: [RFC PATCH v1 07/14] bpf: Use hidden subprog trampoline for bpf_throw
Date: Thu, 1 Feb 2024 04:21:02 +0000 [thread overview]
Message-ID: <20240201042109.1150490-8-memxor@gmail.com> (raw)
In-Reply-To: <20240201042109.1150490-1-memxor@gmail.com>
When we perform a bpf_throw kfunc call, callee saved registers in BPF
calling convention (R6-R9) may end up getting saved and clobbered by
bpf_throw. Typically, the kernel will restore the registers before
returning back to the BPF program, but in case of bpf_throw, the
function will never return. Therefore, any acquired resources sitting in
these registers will end up getting destroyed if not saved on the
stack, without any cleanup happening for them.
Also, when in a BPF call chain, caller frames may have held acquired
resources in R6-R9 and called their subprogs, which may have spilled
these on their stack frame to reuse these registers before entering the
bpf_throw kfunc. Thus, we also need to locate and free these callee
saved registers for each frame.
It is thus necessary to save these registers somewhere before we call
into the bpf_throw kfunc. Instead of adding spills everywhere bpf_throw
is called, we can use a new hidden subprog that saves R6-R9 on the stack
and then calls into bpf_throw. This way, all of the bpf_throw call sites
can be turned into call instructions for this subprog, and the hidden
subprog in turn will save the callee-saved registers before calling into
the bpf_throw kfunc.
In this way, when unwinding the stack, we can locate the callee saved
registers on the hidden subprog stack frame and perform their cleanup.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
arch/x86/net/bpf_jit_comp.c | 24 ++++++++--------
include/linux/bpf.h | 5 ++++
include/linux/bpf_verifier.h | 3 +-
kernel/bpf/verifier.c | 55 ++++++++++++++++++++++++++++++++++--
4 files changed, 71 insertions(+), 16 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index e1390d1e331b..87692d983ffd 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -640,9 +640,10 @@ static void emit_bpf_tail_call_indirect(struct bpf_prog *bpf_prog,
offset = ctx->tail_call_indirect_label - (prog + 2 - start);
EMIT2(X86_JE, offset); /* je out */
- if (bpf_prog->aux->exception_boundary) {
+ if (bpf_prog->aux->exception_boundary || bpf_prog->aux->bpf_throw_tramp) {
pop_callee_regs(&prog, all_callee_regs_used);
- pop_r12(&prog);
+ if (bpf_prog->aux->exception_boundary)
+ pop_r12(&prog);
} else {
pop_callee_regs(&prog, callee_regs_used);
}
@@ -699,9 +700,10 @@ static void emit_bpf_tail_call_direct(struct bpf_prog *bpf_prog,
emit_jump(&prog, (u8 *)poke->tailcall_target + X86_PATCH_SIZE,
poke->tailcall_bypass);
- if (bpf_prog->aux->exception_boundary) {
+ if (bpf_prog->aux->exception_boundary || bpf_prog->aux->bpf_throw_tramp) {
pop_callee_regs(&prog, all_callee_regs_used);
- pop_r12(&prog);
+ if (bpf_prog->aux->exception_boundary)
+ pop_r12(&prog);
} else {
pop_callee_regs(&prog, callee_regs_used);
}
@@ -1164,12 +1166,9 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image, u8 *rw_image
/* Exception callback will clobber callee regs for its own use, and
* restore the original callee regs from main prog's stack frame.
*/
- if (bpf_prog->aux->exception_boundary) {
- /* We also need to save r12, which is not mapped to any BPF
- * register, as we throw after entry into the kernel, which may
- * overwrite r12.
- */
- push_r12(&prog);
+ if (bpf_prog->aux->exception_boundary || bpf_prog->aux->bpf_throw_tramp) {
+ if (bpf_prog->aux->exception_boundary)
+ push_r12(&prog);
push_callee_regs(&prog, all_callee_regs_used);
} else {
push_callee_regs(&prog, callee_regs_used);
@@ -2031,9 +2030,10 @@ st: if (is_imm8(insn->off))
seen_exit = true;
/* Update cleanup_addr */
ctx->cleanup_addr = proglen;
- if (bpf_prog->aux->exception_boundary) {
+ if (bpf_prog->aux->exception_boundary || bpf_prog->aux->bpf_throw_tramp) {
pop_callee_regs(&prog, all_callee_regs_used);
- pop_r12(&prog);
+ if (bpf_prog->aux->exception_boundary)
+ pop_r12(&prog);
} else {
pop_callee_regs(&prog, callee_regs_used);
}
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 463c8d22ad72..83cff18a1b66 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -3369,6 +3369,11 @@ static inline bool bpf_is_subprog(const struct bpf_prog *prog)
return prog->aux->func_idx != 0;
}
+static inline bool bpf_is_hidden_subprog(const struct bpf_prog *prog)
+{
+ return prog->aux->func_idx >= prog->aux->func_cnt;
+}
+
struct bpf_frame_desc_reg_entry {
u32 type;
s16 spill_type;
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 0113a3a940e2..04e27fce33d6 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -683,6 +683,7 @@ struct bpf_verifier_env {
u32 id_gen; /* used to generate unique reg IDs */
u32 hidden_subprog_cnt; /* number of hidden subprogs */
int exception_callback_subprog;
+ int bpf_throw_tramp_subprog;
bool explore_alu_limits;
bool allow_ptr_leaks;
/* Allow access to uninitialized stack memory. Writes with fixed offset are
@@ -699,7 +700,7 @@ struct bpf_verifier_env {
struct bpf_insn_aux_data *insn_aux_data; /* array of per-insn state */
const struct bpf_line_info *prev_linfo;
struct bpf_verifier_log log;
- struct bpf_subprog_info subprog_info[BPF_MAX_SUBPROGS + 2]; /* max + 2 for the fake and exception subprogs */
+ struct bpf_subprog_info subprog_info[BPF_MAX_SUBPROGS + 3]; /* max + 3 for the fake, exception, and bpf_throw_tramp subprogs */
union {
struct bpf_idmap idmap_scratch;
struct bpf_idset idset_scratch;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e5b1db1db679..942243cba9f1 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -19836,9 +19836,9 @@ static int add_hidden_subprog(struct bpf_verifier_env *env, struct bpf_insn *pat
int cnt = env->subprog_cnt;
struct bpf_prog *prog;
- /* We only reserve one slot for hidden subprogs in subprog_info. */
- if (env->hidden_subprog_cnt) {
- verbose(env, "verifier internal error: only one hidden subprog supported\n");
+ /* We only reserve two slots for hidden subprogs in subprog_info. */
+ if (env->hidden_subprog_cnt == 2) {
+ verbose(env, "verifier internal error: only two hidden subprogs supported\n");
return -EFAULT;
}
/* We're not patching any existing instruction, just appending the new
@@ -19892,6 +19892,42 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
mark_subprog_exc_cb(env, env->exception_callback_subprog);
}
+ if (env->seen_exception) {
+ struct bpf_insn patch[] = {
+ /* Use the correct insn_cnt here, as we want to append past the hidden subprog above. */
+ env->prog->insnsi[env->prog->len - 1],
+ /* Scratch R6-R9 so that the JIT spills them to the stack on entry. */
+ BPF_MOV64_IMM(BPF_REG_6, 0),
+ BPF_MOV64_IMM(BPF_REG_7, 0),
+ BPF_MOV64_IMM(BPF_REG_8, 0),
+ BPF_MOV64_IMM(BPF_REG_9, 0),
+ BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 0, special_kfunc_list[KF_bpf_throw]),
+ };
+ const bool all_callee_regs_used[4] = {true, true, true, true};
+
+ ret = add_hidden_subprog(env, patch, ARRAY_SIZE(patch));
+ if (ret < 0)
+ return ret;
+ prog = env->prog;
+ insn = prog->insnsi;
+
+ env->bpf_throw_tramp_subprog = env->subprog_cnt - 1;
+ /* Ensure to mark callee_regs_used, so that we can collect any saved_regs if necessary. */
+ memcpy(env->subprog_info[env->bpf_throw_tramp_subprog].callee_regs_used, all_callee_regs_used, sizeof(all_callee_regs_used));
+ /* Certainly, we have seen a bpf_throw call in this program, as
+ * seen_exception is true, therefore the bpf_kfunc_desc entry for it must
+ * be populated and found here. We need to do the fixup now, otherwise
+ * the loop over insn_cnt below won't see this kfunc call.
+ */
+ ret = fixup_kfunc_call(env, &prog->insnsi[prog->len - 1], insn_buf, prog->len - 1, &cnt);
+ if (ret < 0)
+ return ret;
+ if (cnt != 0) {
+ verbose(env, "verifier internal error: unhandled patching for bpf_throw fixup in bpf_throw_tramp subprog\n");
+ return -EFAULT;
+ }
+ }
+
for (i = 0; i < insn_cnt; i++, insn++) {
/* Make divide-by-zero exceptions impossible. */
if (insn->code == (BPF_ALU64 | BPF_MOD | BPF_X) ||
@@ -20012,6 +20048,19 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
if (insn->src_reg == BPF_PSEUDO_CALL)
continue;
if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) {
+ /* All bpf_throw calls in this program must be patched to call the
+ * bpf_throw_tramp subprog instead. This ensures we correctly save
+ * the R6-R9 before entry into kernel, and can clean them up if
+ * needed.
+ * Note: seen_exception must be set, otherwise no bpf_throw_tramp is
+ * generated.
+ */
+ if (env->seen_exception && is_bpf_throw_kfunc(insn)) {
+ *insn = BPF_CALL_REL(0);
+ insn->imm = (int)env->subprog_info[env->bpf_throw_tramp_subprog].start - (i + delta) - 1;
+ continue;
+ }
+
ret = fixup_kfunc_call(env, insn, insn_buf, i + delta, &cnt);
if (ret)
return ret;
--
2.40.1
next prev parent reply other threads:[~2024-02-01 4:21 UTC|newest]
Thread overview: 52+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-02-01 4:20 [RFC PATCH v1 00/14] Exceptions - Resource Cleanup Kumar Kartikeya Dwivedi
2024-02-01 4:20 ` [RFC PATCH v1 01/14] bpf: Mark subprogs as throw reachable before do_check pass Kumar Kartikeya Dwivedi
2024-02-12 19:35 ` David Vernet
2024-02-12 22:28 ` Kumar Kartikeya Dwivedi
2024-02-15 1:01 ` Eduard Zingerman
2024-02-16 21:34 ` Kumar Kartikeya Dwivedi
2024-02-01 4:20 ` [RFC PATCH v1 02/14] bpf: Process global subprog's exception propagation Kumar Kartikeya Dwivedi
2024-02-15 1:10 ` Eduard Zingerman
2024-02-16 21:50 ` Kumar Kartikeya Dwivedi
2024-02-17 14:04 ` Eduard Zingerman
2024-02-01 4:20 ` [RFC PATCH v1 03/14] selftests/bpf: Add test for throwing global subprog with acquired refs Kumar Kartikeya Dwivedi
2024-02-15 1:10 ` Eduard Zingerman
2024-02-01 4:20 ` [RFC PATCH v1 04/14] bpf: Refactor check_pseudo_btf_id's BTF reference bump Kumar Kartikeya Dwivedi
2024-02-15 1:11 ` Eduard Zingerman
2024-02-16 21:50 ` Kumar Kartikeya Dwivedi
2024-02-01 4:21 ` [RFC PATCH v1 05/14] bpf: Implement BPF exception frame descriptor generation Kumar Kartikeya Dwivedi
2024-02-15 18:24 ` Eduard Zingerman
2024-02-16 11:23 ` Eduard Zingerman
2024-02-16 22:06 ` Kumar Kartikeya Dwivedi
2024-02-17 17:14 ` Eduard Zingerman
2024-02-20 21:58 ` Kumar Kartikeya Dwivedi
2024-02-16 22:24 ` Kumar Kartikeya Dwivedi
2024-02-01 4:21 ` [RFC PATCH v1 06/14] bpf: Adjust frame descriptor pc on instruction patching Kumar Kartikeya Dwivedi
2024-02-15 16:31 ` Eduard Zingerman
2024-02-16 21:52 ` Kumar Kartikeya Dwivedi
2024-02-17 14:08 ` Eduard Zingerman
2024-02-01 4:21 ` Kumar Kartikeya Dwivedi [this message]
2024-02-15 22:11 ` [RFC PATCH v1 07/14] bpf: Use hidden subprog trampoline for bpf_throw Eduard Zingerman
2024-02-16 21:59 ` Kumar Kartikeya Dwivedi
2024-02-17 14:22 ` Eduard Zingerman
2024-02-01 4:21 ` [RFC PATCH v1 08/14] bpf: Compute used callee saved registers for subprogs Kumar Kartikeya Dwivedi
2024-02-15 22:12 ` Eduard Zingerman
2024-02-16 22:02 ` Kumar Kartikeya Dwivedi
2024-02-17 14:26 ` Eduard Zingerman
2024-02-01 4:21 ` [RFC PATCH v1 09/14] bpf, x86: Fix up pc offsets for frame descriptor entries Kumar Kartikeya Dwivedi
2024-02-15 22:12 ` Eduard Zingerman
2024-02-16 13:33 ` Eduard Zingerman
2024-02-01 4:21 ` [RFC PATCH v1 10/14] bpf, x86: Implement runtime resource cleanup for exceptions Kumar Kartikeya Dwivedi
2024-02-16 12:02 ` Eduard Zingerman
2024-02-16 22:28 ` Kumar Kartikeya Dwivedi
2024-02-19 12:01 ` Eduard Zingerman
2024-02-01 4:21 ` [RFC PATCH v1 11/14] bpf: Release references in verifier state when throwing exceptions Kumar Kartikeya Dwivedi
2024-02-16 12:21 ` Eduard Zingerman
2024-02-01 4:21 ` [RFC PATCH v1 12/14] bpf: Register cleanup dtors for runtime unwinding Kumar Kartikeya Dwivedi
2024-02-01 4:21 ` [RFC PATCH v1 13/14] bpf: Make bpf_throw available to all program types Kumar Kartikeya Dwivedi
2024-02-01 4:21 ` [RFC PATCH v1 14/14] selftests/bpf: Add tests for exceptions runtime cleanup Kumar Kartikeya Dwivedi
2024-02-12 20:53 ` David Vernet
2024-02-12 22:43 ` Kumar Kartikeya Dwivedi
2024-02-13 19:33 ` David Vernet
2024-02-13 20:51 ` Kumar Kartikeya Dwivedi
2024-03-14 11:08 ` [RFC PATCH v1 00/14] Exceptions - Resource Cleanup Eduard Zingerman
2024-03-18 5:40 ` Kumar Kartikeya Dwivedi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240201042109.1150490-8-memxor@gmail.com \
--to=memxor@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=djwillia@vt.edu \
--cc=martin.lau@kernel.org \
--cc=rishabh.iyer@epfl.ch \
--cc=rjsu26@vt.edu \
--cc=sanidhya.kashyap@epfl.ch \
--cc=tj@kernel.org \
--cc=void@manifault.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox