[Devel] [PATCH VZ10 1/3] bpf: report original insns of cgroup device programs

Pavel Tikhomirov ptikhomirov at virtuozzo.com
Tue Jul 28 14:08:09 MSK 2026


Since device cgroup BPF programs were recently allowed in VE, CRIU needs
an interface to checkpoint and restore those.

The verified/JITed instructions are not portable: the verifier rewrites
context accesses and helper calls into offsets and addresses specific to
the running kernel, so if we dump prog->insnsi and try to load it again,
it will break for at least two reasons: a) verifier will refuse the
translated version, b) on another kernel the translated addresses will
be completely irrelevant.

Instead keep a copy of the original, pre-verification instructions the
user loaded. Their encoding (struct bpf_insn) and the CGROUP_DEVICE
context (struct bpf_cgroup_dev_ctx) are stable UAPI, so on restore CRIU
can reload the program and let the destination kernel re-verify and
re-JIT it.

Expose the copy through BPF_OBJ_GET_INFO_BY_FD in the new
bpf_prog_info::orig_prog_insns field.

Implementation notes:

Save orig_insns for CGROUP_DEVICE programs only, and only up to
BPF_MAXINSNS - this covers every program loadable inside a VE (which is
all CRIU needs) and skips large host programs.

Unlike the xlated/jited dumps it needs no bpf_dump_raw_ok() check:
orig_insns is the original, pre-verification program and holds no kernel
address or kallsyms related information.

As this is only useful for checkpoint/restore, gate it on
CONFIG_CHECKPOINT_RESTORE.

Use kvmemdup so the copy does not require a high-order contiguous
allocation (maximum size is 32KB), with __GFP_NOWARN since the size is
user-controlled.

Feature: BPF checkpoint/restore
https://virtuozzo.atlassian.net/browse/VSTOR-121776
Signed-off-by: Pavel Tikhomirov <ptikhomirov at virtuozzo.com>
---
 include/linux/bpf.h            |  2 ++
 include/uapi/linux/bpf.h       |  2 ++
 kernel/bpf/core.c              |  1 +
 kernel/bpf/syscall.c           | 30 ++++++++++++++++++++++++++++++
 tools/include/uapi/linux/bpf.h |  2 ++
 5 files changed, 37 insertions(+)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 1e30bb0867834..87a1a48ca5141 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1654,6 +1654,8 @@ struct bpf_prog_aux {
 #ifdef CONFIG_SECURITY
 	void *security;
 #endif
+	struct bpf_insn *orig_insns;
+	u32 orig_insn_cnt;
 	struct bpf_token *token;
 #ifdef CONFIG_VE
 	/* VE that loaded the program via VE_FEATURE_BPF path and against whose
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index a31fba921e869..378db5313733e 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -6638,6 +6638,8 @@ struct bpf_prog_info {
 	__u32 verified_insns;
 	__u32 attach_btf_obj_id;
 	__u32 attach_btf_id;
+	__u32 orig_prog_len;
+	__aligned_u64 orig_prog_insns;
 } __attribute__((aligned(8)));
 
 struct bpf_map_info {
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 5503ec305a846..c9fb3739efeaf 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -285,6 +285,7 @@ void __bpf_prog_free(struct bpf_prog *fp)
 		mutex_destroy(&fp->aux->used_maps_mutex);
 		mutex_destroy(&fp->aux->dst_mutex);
 		kfree(fp->aux->poke_tab);
+		kvfree(fp->aux->orig_insns);
 		kfree(fp->aux);
 	}
 	free_percpu(fp->stats);
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index ff2a51c59f047..661efe01ca4a0 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2985,6 +2985,26 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size)
 			     make_bpfptr(attr->insns, uattr.is_kernel),
 			     bpf_prog_insn_size(prog)) != 0)
 		goto free_prog;
+
+	/*
+	 * Save the original insns so CRIU can dump the program and reload it
+	 * after migration, hence only needed with CONFIG_CHECKPOINT_RESTORE.
+	 * Restrict it to small programs (up to BPF_MAXINSNS), which covers all
+	 * programs loaded inside a VE, avoids the extra allocation for large
+	 * host progs, and keeps the copy at most BPF_MAXINSNS *
+	 * sizeof(struct bpf_insn) in size. The size is user-controlled, so use
+	 * __GFP_NOWARN to avoid log spam on failed allocations.
+	 */
+	if (IS_ENABLED(CONFIG_CHECKPOINT_RESTORE) &&
+	    type == BPF_PROG_TYPE_CGROUP_DEVICE && prog->len <= BPF_MAXINSNS) {
+		prog->aux->orig_insns = kvmemdup(prog->insns,
+						 bpf_prog_insn_size(prog),
+						 GFP_KERNEL | __GFP_NOWARN);
+		if (!prog->aux->orig_insns)
+			goto free_prog;
+		prog->aux->orig_insn_cnt = prog->len;
+	}
+
 	/* copy eBPF program license from user space */
 	if (strncpy_from_bpfptr(license,
 				make_bpfptr(attr->license, uattr.is_kernel),
@@ -5056,6 +5076,7 @@ static int bpf_prog_get_info_by_fd(struct file *file,
 		info.nr_func_info = 0;
 		info.nr_line_info = 0;
 		info.nr_jited_line_info = 0;
+		info.orig_prog_len = 0;
 		goto done;
 	}
 
@@ -5266,6 +5287,15 @@ static int bpf_prog_get_info_by_fd(struct file *file,
 		}
 	}
 
+	ulen = info.orig_prog_len;
+	info.orig_prog_len = prog->aux->orig_insn_cnt * sizeof(struct bpf_insn);
+	if (info.orig_prog_len && ulen) {
+		uinsns = u64_to_user_ptr(info.orig_prog_insns);
+		ulen = min_t(u32, info.orig_prog_len, ulen);
+		if (copy_to_user(uinsns, prog->aux->orig_insns, ulen))
+			return -EFAULT;
+	}
+
 done:
 	if (copy_to_user(uinfo, &info, info_len) ||
 	    put_user(info_len, &uattr->info.info_len))
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index 611ba71044cef..ef05a749b8ebb 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -6638,6 +6638,8 @@ struct bpf_prog_info {
 	__u32 verified_insns;
 	__u32 attach_btf_obj_id;
 	__u32 attach_btf_id;
+	__u32 orig_prog_len;
+	__aligned_u64 orig_prog_insns;
 } __attribute__((aligned(8)));
 
 struct bpf_map_info {
-- 
2.54.0



More information about the Devel mailing list