BPF List
 help / color / mirror / Atom feed
From: Yonghong Song <yonghong.song@linux.dev>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Eduard Zingerman <eddyz87@gmail.com>,
	kernel-team@fb.com
Subject: [PATCH bpf-next 07/20] bpf: Refuse exception cleanup shapes bpf_throw() cannot dispatch
Date: Wed, 16 Sep 2026 22:57:21 -0700	[thread overview]
Message-ID: <20260917055721.3930090-1-yonghong.song@linux.dev> (raw)
In-Reply-To: <20260917055645.3926444-1-yonghong.song@linux.dev>

The table is on the instructions and the landing pads are in the control
flow graph. What is left before bpf_throw() can be taught to dispatch them
is to work out what that walk will need, and refuse the shapes it could not
handle.

bpf_check_cleanup_exceptions() runs after bpf_check_cfg(). Everything it
needs is control flow, so it reads what that walk has already worked out
rather than working it out again:

  subprog_info.might_throw   which subprograms an exception may leave,
                             closed over the call graph by
                             merge_callee_effects() as the walk pops each
                             callee
  cleanup_reachability()     what each instruction can reach -- a resume, a
                             plain exit, a throw, an indirect jump
  cleanup_mark_pad_bodies()  which instructions only ever run with an
                             exception already in flight

What it refuses:

  - a pad that reaches both a resume and a plain exit, or neither: nothing
    says whether it is a cleanup pad or a catch pad
  - a catch pad, which ends in a plain exit: the walker calls a pad as a
    subroutine and cannot hand a frame back its own execution
  - a throw in a pad, or a call from a pad to a subprogram that can throw:
    a second unwind over frames the first is still discarding
  - a bpf_unwind_resume() outside a pad body
  - a subprogram that may unwind used as a helper callback: the helper's
    own kernel frame would end the walk before it found a boundary
  - a tail call, an indirect jump, or an outgoing on-stack call argument in
    a pad body, all of which touch a stack the pad does not own

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 include/linux/bpf_verifier.h |   1 +
 kernel/bpf/exception.c       | 397 +++++++++++++++++++++++++++++++++++
 kernel/bpf/exception.h       |   2 +
 kernel/bpf/fixups.c          |   1 +
 kernel/bpf/verifier.c        |   8 +
 5 files changed, 409 insertions(+)

diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index 7463e86d15ee..fe8b26351a15 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -682,6 +682,7 @@ struct bpf_insn_aux_data {
 	bool non_sleepable; /* helper/kfunc may be called from non-sleepable context */
 	bool is_iter_next; /* bpf_iter_<type>_next() kfunc call */
 	bool cleanup_throw_site; /* call to bpf_throw() */
+	bool in_cleanup_pad; /* only runs with an exception in flight */
 	/*
 	 * 1 + the instruction index of the exception cleanup landing pad this
 	 * call site unwinds to, or 0 for none.
diff --git a/kernel/bpf/exception.c b/kernel/bpf/exception.c
index dfe9c2a9ce7d..b2bf831242b8 100644
--- a/kernel/bpf/exception.c
+++ b/kernel/bpf/exception.c
@@ -6,6 +6,7 @@
 #include <linux/btf_ids.h>
 #include <linux/filter.h>
 #include <linux/slab.h>
+#include <linux/sort.h>
 #include "exception.h"
 
 #define verbose(env, fmt, args...) bpf_verifier_log_write(env, fmt, ##args)
@@ -23,6 +24,130 @@ static bool insn_is_exc_kfunc(const struct bpf_insn *insn, int kf)
 	       insn->imm == exc_kfunc_list[kf];
 }
 
+/* What an instruction does to intra-subprog control flow. */
+enum cleanup_insn_kind {
+	CLEANUP_INSN_PLAIN,	/* the next insn runs */
+	CLEANUP_INSN_JUMP,	/* unconditional jump */
+	CLEANUP_INSN_COND,	/* the next insn runs, or the branch target */
+	CLEANUP_INSN_EXIT,
+	CLEANUP_INSN_THROW,	/* call bpf_throw: nothing after it runs */
+	CLEANUP_INSN_RESUME,	/* call bpf_unwind_resume: likewise */
+	CLEANUP_INSN_CALL,	/* call to another subprog */
+	CLEANUP_INSN_GOTOX,	/* indirect jump: successors not known here */
+};
+
+/* What each instruction can reach, computed once by cleanup_reachability(). */
+#define CLEANUP_REACH_RESUME	BIT(0)	/* a bpf_unwind_resume() call */
+#define CLEANUP_REACH_EXIT	BIT(1)	/* a plain BPF_EXIT */
+#define CLEANUP_REACH_UNKNOWN	BIT(2)	/* an indirect jump */
+#define CLEANUP_REACH_THROW	BIT(3)	/* a bpf_throw() call */
+
+/* Scratch shared by the analyses, sized once so no walker has to allocate. */
+struct cleanup_ctx {
+	struct bpf_verifier_env *env;
+	u8 *reach;		/* per insn: CLEANUP_REACH_* mask */
+	u32 *stack;		/* per insn: DFS stack */
+	void *scratch;		/* the one allocation all of the above live in */
+};
+
+static bool in_pad(struct bpf_verifier_env *env, u32 i)
+{
+	return env->insn_aux_data[i].in_cleanup_pad;
+}
+
+/* One scratch array for cleanup_alloc() to hand out. */
+struct cleanup_alloc_req {
+	void **dst;
+	size_t n, sz;
+};
+
+static void *cleanup_alloc(const struct cleanup_alloc_req *tab, u32 cnt)
+{
+	size_t total = 0;
+	char *block, *p;
+	u32 i;
+
+	for (i = 0; i < cnt; i++)
+		total += round_up(tab[i].n * tab[i].sz, 8);
+
+	block = kvzalloc(total, GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
+	if (!block)
+		return NULL;
+
+	for (i = 0, p = block; i < cnt; i++) {
+		*tab[i].dst = p;
+		p += round_up(tab[i].n * tab[i].sz, 8);
+	}
+	return block;
+}
+
+static int cleanup_subprog_of(struct bpf_verifier_env *env, u32 off)
+{
+	struct bpf_subprog_info *info = bpf_find_containing_subprog(env, off);
+
+	return info ? info - env->subprog_info : -1;
+}
+
+/* The subprogram a linear pass is currently in. */
+struct cleanup_cursor {
+	u32 start, end;		/* [start, end) of the current subprogram */
+	int sub;		/* its index */
+};
+
+#define CLEANUP_CURSOR_INIT { .sub = -1 }
+
+static void cleanup_cursor_to(struct bpf_verifier_env *env, struct cleanup_cursor *c, u32 i)
+{
+	while (i >= c->end) {
+		c->sub++;
+		c->start = env->subprog_info[c->sub].start;
+		c->end = env->subprog_info[c->sub + 1].start;
+	}
+}
+
+static enum cleanup_insn_kind cleanup_classify(struct bpf_verifier_env *env, u32 i,
+					       int *next, int *target)
+{
+	struct bpf_insn *insn = &env->prog->insnsi[i];
+	u8 class = BPF_CLASS(insn->code);
+
+	*next = i + 1;
+	*target = -1;
+
+	if (insn->code == (BPF_LD | BPF_IMM | BPF_DW)) {
+		*next = i + 2;
+		return CLEANUP_INSN_PLAIN;
+	}
+	if (class != BPF_JMP && class != BPF_JMP32)
+		return CLEANUP_INSN_PLAIN;
+
+	switch (BPF_OP(insn->code)) {
+	case BPF_EXIT:
+		*next = -1;
+		return CLEANUP_INSN_EXIT;
+	case BPF_JA:
+		*next = -1;
+		if (BPF_SRC(insn->code) == BPF_X)
+			return CLEANUP_INSN_GOTOX;
+		*target = class == BPF_JMP32 ? i + insn->imm + 1 : i + insn->off + 1;
+		return CLEANUP_INSN_JUMP;
+	case BPF_CALL:
+		if (bpf_is_throw_kfunc(insn)) {
+			*next = -1;
+			return CLEANUP_INSN_THROW;
+		}
+		if (insn_is_exc_kfunc(insn, EXC_KF_bpf_unwind_resume)) {
+			*next = -1;
+			return CLEANUP_INSN_RESUME;
+		}
+		return bpf_pseudo_call(insn) ? CLEANUP_INSN_CALL : CLEANUP_INSN_PLAIN;
+	default:
+		/* Conditional jump, including BPF_JCOND. */
+		*target = i + insn->off + 1;
+		return CLEANUP_INSN_COND;
+	}
+}
+
 static void cleanup_mark_throw_sites(struct bpf_verifier_env *env)
 {
 	u32 i;
@@ -32,6 +157,247 @@ static void cleanup_mark_throw_sites(struct bpf_verifier_env *env)
 			env->insn_aux_data[i].cleanup_throw_site = true;
 }
 
+int bpf_cleanup_check_callback(struct bpf_verifier_env *env, int subprog)
+{
+	if (!env->cleanup_info_cnt || !env->subprog_info[subprog].might_throw)
+		return 0;
+
+	verbose(env, "subprog %d may unwind and is used as a callback\n", subprog);
+	return -EINVAL;
+}
+
+/* Intra-subprog successors of @i, or -1 each when absent. */
+static enum cleanup_insn_kind cleanup_succ(struct bpf_verifier_env *env, u32 i,
+					   u32 start, u32 end, int *next, int *target)
+{
+	enum cleanup_insn_kind kind = cleanup_classify(env, i, next, target);
+
+	if (*next < (int)start || *next >= (int)end)
+		*next = -1;
+	if (*target < (int)start || *target >= (int)end)
+		*target = -1;
+	return kind;
+}
+
+static void cleanup_add_pred(u32 *head, u32 *link, u32 to, u32 e)
+{
+	link[e] = head[to];
+	head[to] = e + 1;
+}
+
+/* What every instruction can reach along intra-subprog edges, for
+ * cleanup_pad_is_catch(). One backward walk over a predecessor index, rather
+ * than a forward walk from each landing pad, which would be quadratic.
+ */
+static int cleanup_reachability(struct cleanup_ctx *ctx)
+{
+	struct bpf_verifier_env *env = ctx->env;
+	u32 len = env->prog->len;
+	struct cleanup_cursor c = CLEANUP_CURSOR_INIT;
+	u32 *head = NULL, *link = NULL;
+	bool *queued = NULL;
+	u32 i, sp = 0;
+	void *scratch;
+	const struct cleanup_alloc_req tab[] = {
+		{ (void **)&head,   len,             sizeof(*head) },
+		{ (void **)&link,   2 * (size_t)len, sizeof(*link) },
+		{ (void **)&queued, len,             sizeof(*queued) },
+	};
+
+	scratch = cleanup_alloc(tab, ARRAY_SIZE(tab));
+	if (!scratch)
+		return -ENOMEM;
+
+	/* Index the predecessors, and seed the walk at the terminators. */
+	for (i = 0; i < len; i++) {
+		enum cleanup_insn_kind kind;
+		int next, target;
+
+		cleanup_cursor_to(env, &c, i);
+		kind = cleanup_succ(env, i, c.start, c.end, &next, &target);
+
+		if (kind == CLEANUP_INSN_RESUME)
+			ctx->reach[i] |= CLEANUP_REACH_RESUME;
+		else if (kind == CLEANUP_INSN_EXIT)
+			ctx->reach[i] |= CLEANUP_REACH_EXIT;
+		else if (kind == CLEANUP_INSN_GOTOX)
+			ctx->reach[i] |= CLEANUP_REACH_UNKNOWN;
+		else if (kind == CLEANUP_INSN_THROW)
+			ctx->reach[i] |= CLEANUP_REACH_THROW;
+
+		if (next >= 0)
+			cleanup_add_pred(head, link, next, 2 * i);
+		if (target >= 0)
+			cleanup_add_pred(head, link, target, 2 * i + 1);
+
+		if (ctx->reach[i]) {
+			queued[i] = true;
+			ctx->stack[sp++] = i;
+		}
+	}
+
+	/* Each instruction re-enters the worklist at most once per bit it
+	 * gains, so this is linear in the number of edges.
+	 */
+	while (sp) {
+		u32 j = ctx->stack[--sp];
+		u8 flags = ctx->reach[j];
+		u32 e;
+
+		queued[j] = false;
+		for (e = head[j]; e; e = link[e - 1]) {
+			u32 p = (e - 1) / 2;
+
+			if ((ctx->reach[p] | flags) == ctx->reach[p])
+				continue;
+			ctx->reach[p] |= flags;
+			if (!queued[p]) {
+				queued[p] = true;
+				ctx->stack[sp++] = p;
+			}
+		}
+	}
+	kvfree(scratch);
+	return 0;
+}
+
+static int cleanup_pad_is_catch(struct cleanup_ctx *ctx, u32 pad)
+{
+	u8 reach = ctx->reach[pad];
+
+	if (reach & CLEANUP_REACH_UNKNOWN) {
+		verbose(ctx->env, "cleanup landing pad %u reaches an indirect jump\n", pad);
+		return -EINVAL;
+	}
+	if (reach & CLEANUP_REACH_THROW) {
+		verbose(ctx->env,
+			"cleanup landing pad %u can throw while an exception is in flight\n",
+			pad);
+		return -EINVAL;
+	}
+	if (!(reach & CLEANUP_REACH_RESUME) == !(reach & CLEANUP_REACH_EXIT)) {
+		verbose(ctx->env, "cleanup landing pad %u %s\n", pad,
+			(reach & CLEANUP_REACH_RESUME) ?
+			"reaches both bpf_unwind_resume() and a plain exit" :
+			"reaches neither bpf_unwind_resume() nor an exit");
+		return -EINVAL;
+	}
+	return !!(reach & CLEANUP_REACH_EXIT);
+}
+
+static int cleanup_check_pad_insn(struct bpf_verifier_env *env, u32 i)
+{
+	struct bpf_insn *insn = &env->prog->insnsi[i];
+
+	if (bpf_helper_call(insn) && insn->imm == BPF_FUNC_tail_call) {
+		verbose(env,
+			"bpf_tail_call() at insn %u is in an exception cleanup landing pad\n",
+			i);
+		return -EINVAL;
+	}
+	/* Stack arguments are not supported. */
+	if (is_stack_arg_st(insn) || is_stack_arg_stx(insn)) {
+		verbose(env,
+			"insn %u passes an on-stack call argument in an exception cleanup landing pad\n",
+			i);
+		return -EINVAL;
+	}
+	/* Likewise, stack arguments are not supported. */
+	if (bpf_pseudo_kfunc_call(insn)) {
+		struct bpf_call_summary cs;
+
+		if (bpf_get_call_summary(env, insn, &cs) &&
+		    cs.arg_slot_cnt > MAX_BPF_FUNC_REG_ARGS) {
+			verbose(env,
+				"insn %u passes an on-stack call argument in an exception cleanup landing pad\n",
+				i);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int cleanup_mark_pad_bodies(struct cleanup_ctx *ctx)
+{
+	struct bpf_verifier_env *env = ctx->env;
+	u32 i, sp = 0;
+	int ret;
+
+	for (i = 0; i < env->cleanup_info_cnt; i++) {
+		u32 pad = env->cleanup_info[i].landing_pad_off;
+
+		if (in_pad(env, pad))
+			continue;
+
+		ret = cleanup_pad_is_catch(ctx, pad);
+		if (ret < 0)
+			return ret;
+		if (ret) {
+			verbose(env,
+				"catch landing pad %u is not supported yet, only cleanup pads that resume\n",
+				pad);
+			return -EOPNOTSUPP;
+		}
+		env->insn_aux_data[pad].in_cleanup_pad = true;
+		ctx->stack[sp++] = pad;
+	}
+
+	while (sp) {
+		u32 j = ctx->stack[--sp];
+		enum cleanup_insn_kind kind;
+		int next, target, sub;
+		u32 start, end;
+
+		ret = cleanup_check_pad_insn(env, j);
+		if (ret)
+			return ret;
+
+		sub = cleanup_subprog_of(env, j);
+		start = env->subprog_info[sub].start;
+		end = env->subprog_info[sub + 1].start;
+		kind = cleanup_succ(env, j, start, end, &next, &target);
+
+		if (kind == CLEANUP_INSN_CALL) {
+			int callee = cleanup_subprog_of(env, j + env->prog->insnsi[j].imm + 1);
+
+			if (env->subprog_info[callee].might_throw) {
+				verbose(env,
+					"cleanup landing pad calls subprog %d at insn %u, which can throw while an exception is in flight\n",
+					callee, j);
+				return -EINVAL;
+			}
+		}
+
+		if (next >= 0 && !in_pad(env, next)) {
+			env->insn_aux_data[next].in_cleanup_pad = true;
+			ctx->stack[sp++] = next;
+		}
+		if (target >= 0 && !in_pad(env, target)) {
+			env->insn_aux_data[target].in_cleanup_pad = true;
+			ctx->stack[sp++] = target;
+		}
+	}
+	return 0;
+}
+
+static int cleanup_check_resumes(struct cleanup_ctx *ctx)
+{
+	struct bpf_verifier_env *env = ctx->env;
+	u32 i;
+
+	for (i = 0; i < env->prog->len; i++) {
+		if (!insn_is_exc_kfunc(&env->prog->insnsi[i], EXC_KF_bpf_unwind_resume))
+			continue;
+		if (in_pad(env, i))
+			continue;
+		verbose(env,
+			"bpf_unwind_resume() at insn %u is not in an exception cleanup landing pad\n",
+			i);
+		return -EINVAL;
+	}
+	return 0;
+}
+
 static void cleanup_mark_call_sites(struct bpf_verifier_env *env)
 {
 	u32 i, j;
@@ -78,6 +444,37 @@ int bpf_prepare_cleanup_exceptions(struct bpf_verifier_env *env)
 	return 0;
 }
 
+int bpf_check_cleanup_exceptions(struct bpf_verifier_env *env)
+{
+	u32 len = env->prog->len;
+	struct cleanup_ctx ctx = { .env = env };
+	const struct cleanup_alloc_req tab[] = {
+		{ (void **)&ctx.reach, len, sizeof(*ctx.reach) },
+		{ (void **)&ctx.stack, len, sizeof(*ctx.stack) },
+	};
+	int ret;
+
+	if (!env->cleanup_info_cnt)
+		return 0;
+
+	ctx.scratch = cleanup_alloc(tab, ARRAY_SIZE(tab));
+	if (!ctx.scratch)
+		return -ENOMEM;
+
+	ret = cleanup_reachability(&ctx);
+	if (ret)
+		goto out;
+
+	ret = cleanup_mark_pad_bodies(&ctx);
+	if (ret)
+		goto out;
+
+	ret = cleanup_check_resumes(&ctx);
+out:
+	kvfree(ctx.scratch);
+	return ret;
+}
+
 bool bpf_is_unwind_resume_kfunc(const struct bpf_insn *insn)
 {
 	return insn_is_exc_kfunc(insn, EXC_KF_bpf_unwind_resume);
diff --git a/kernel/bpf/exception.h b/kernel/bpf/exception.h
index f51383fd775c..7313dd2b65a1 100644
--- a/kernel/bpf/exception.h
+++ b/kernel/bpf/exception.h
@@ -8,6 +8,8 @@
 struct bpf_verifier_env;
 
 int bpf_prepare_cleanup_exceptions(struct bpf_verifier_env *env);
+int bpf_check_cleanup_exceptions(struct bpf_verifier_env *env);
+int bpf_cleanup_check_callback(struct bpf_verifier_env *env, int subprog);
 int bpf_cleanup_pad_of_call(struct bpf_verifier_env *env, u32 idx);
 
 #endif /* _LINUX_BPF_EXCEPTION_H */
diff --git a/kernel/bpf/fixups.c b/kernel/bpf/fixups.c
index 82b00fac6bd6..f120ae66dbbc 100644
--- a/kernel/bpf/fixups.c
+++ b/kernel/bpf/fixups.c
@@ -252,6 +252,7 @@ static void adjust_insn_aux_data(struct bpf_verifier_env *env,
 		/* Expand insni[off]'s seen count to the patched range. */
 		data[i].seen = old_seen;
 		data[i].zext_dst = bpf_insn_def32(new_prog, insn + i) >= 0;
+		data[i].in_cleanup_pad = data[off + cnt - 1].in_cleanup_pad;
 		if (!memcmp(insn + i, original_insn, sizeof(struct bpf_insn))) {
 			data[i].non_stack_access =
 				data[off + cnt - 1].non_stack_access;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6496c30a10f7..9cbdb8339701 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10485,6 +10485,10 @@ static int push_callback_call(struct bpf_verifier_env *env, struct bpf_insn *ins
 	 * callbacks
 	 */
 	env->subprog_info[subprog].is_cb = true;
+	err = bpf_cleanup_check_callback(env, subprog);
+	if (err)
+		return err;
+
 	if (bpf_pseudo_kfunc_call(insn) &&
 	    !is_callback_calling_kfunc(insn->imm)) {
 		verifier_bug(env, "kfunc %s#%d not marked as callback-calling",
@@ -21664,6 +21668,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
 	if (ret < 0)
 		goto skip_full_check;
 
+	ret = bpf_check_cleanup_exceptions(env);
+	if (ret < 0)
+		goto skip_full_check;
+
 	ret = bpf_compute_postorder(env);
 	if (ret < 0)
 		goto skip_full_check;
-- 
2.53.0-Meta


  parent reply	other threads:[~2026-09-17  5:57 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-17  5:56 [PATCH bpf-next 00/20] bpf: Run exception cleanup landing pads when bpf_throw() unwinds Yonghong Song
2026-09-17  5:56 ` [PATCH bpf-next 01/20] bpf: Accept the compiler's exception cleanup table at program load Yonghong Song
2026-09-17  5:56 ` [PATCH bpf-next 02/20] bpf: Add the bpf_unwind_resume() kfunc Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 03/20] bpf: Add lookups for exception cleanup resumes and landing pads Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 04/20] bpf: Mark the call sites an exception cleanup table covers Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 05/20] bpf: Make exception landing pads reachable in the CFG Yonghong Song
2026-09-17  6:22   ` sashiko-bot
2026-09-18  3:36     ` Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 06/20] bpf: Explore the landing pads no call site reaches Yonghong Song
2026-09-17  5:57 ` Yonghong Song [this message]
2026-09-19  4:57   ` [PATCH bpf-next 07/20] bpf: Refuse exception cleanup shapes bpf_throw() cannot dispatch Alexei Starovoitov
2026-09-19 17:32     ` Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 08/20] bpf: Walk the exception unwind in the verifier Yonghong Song
2026-09-19  4:57   ` Alexei Starovoitov
2026-09-19 17:36     ` Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 09/20] bpf: Refuse a private stack for a program with an exception cleanup table Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 10/20] bpf: Dispatch exception cleanup pads from bpf_throw() Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 11/20] bpf, x86: Dispatch exception cleanup pads at run time Yonghong Song
2026-09-19  5:02   ` Alexei Starovoitov
2026-09-19 19:13     ` Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 12/20] bpf, arm64: " Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 13/20] libbpf: Resolve the compiler's _Unwind_Resume to the kernel's kfunc Yonghong Song
2026-09-17  6:08   ` sashiko-bot
2026-09-17  7:00   ` bot+bpf-ci
2026-09-18  3:40     ` Yonghong Song
2026-09-17  5:57 ` [PATCH bpf-next 14/20] libbpf: Add cleanup_info to bpf_prog_load_opts Yonghong Song
2026-09-17  5:58 ` [PATCH bpf-next 15/20] libbpf: Collect .bpf_cleanup records and pass them to the kernel Yonghong Song
2026-09-17  6:12   ` sashiko-bot
2026-09-18  3:44     ` Yonghong Song
2026-09-17  5:58 ` [PATCH bpf-next 16/20] libbpf: Carry the exception cleanup table through the light skeleton Yonghong Song
2026-09-17  6:18   ` sashiko-bot
2026-09-18  3:52     ` Yonghong Song
2026-09-17  5:58 ` [PATCH bpf-next 17/20] libbpf: Let the static linker carry .bpf_cleanup relocations Yonghong Song
2026-09-17  6:16   ` sashiko-bot
2026-09-18  3:58     ` Yonghong Song
2026-09-17  5:58 ` [PATCH bpf-next 18/20] selftests/bpf: Add an end-to-end .bpf_cleanup exception test Yonghong Song
2026-09-17  6:12   ` sashiko-bot
2026-09-17  5:58 ` [PATCH bpf-next 19/20] selftests/bpf: Cover the exception cleanup shapes the chain does not reach Yonghong Song
2026-09-17  6:16   ` sashiko-bot
2026-09-17  5:58 ` [PATCH bpf-next 20/20] selftests/bpf: Load an exception cleanup program from a light skeleton Yonghong Song

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=20260917055721.3930090-1-yonghong.song@linux.dev \
    --to=yonghong.song@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=kernel-team@fb.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