From: Anton Protopopov <a.s.protopopov@gmail.com>
To: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Alan Maguire <almagui@kernel.org>,
John Fastabend <john.fastabend@gmail.com>,
Quentin Monnet <qmo@kernel.org>,
Christian Brauner <brauner@kernel.org>,
KP Singh <kpsingh@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Jiri Olsa <jolsa@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
Ihor Solodrai <ihor.solodrai@linux.dev>,
Sasha Levin <sashal@kernel.org>,
bpf@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Anton Protopopov <a.s.protopopov@gmail.com>
Subject: [PATCH v1 bpf-next 13/18] bpf: Check the return values of fmod_ret programs on found targets
Date: Fri, 6 Nov 2026 21:03:02 +0000 [thread overview]
Message-ID: <20261106210314.1767267-13-a.s.protopopov@gmail.com> (raw)
In-Reply-To: 20261106205523.1765622-1-a.s.protopopov@gmail.com
A later patch lets resolve_btfids make the functions found by bpf_scan_ir
fmod_ret targets. Each of them gets a "bpf_fmodret_retval:" decl tag with
the values it returns on paths without side effects, for example
"bpf_fmodret_retval:-22,-12". Returning such a value instead of calling
the function looks to the callers like a real failure.
Read the tag when an fmod_ret program is loaded and check at every exit
that the program returns 0 or one of these values.
A function which the static rules allow keeps today's behaviour. So do
the functions declared in BTF_FMODRET sets, which have no such tag.
Only a kernel function has such a tag, so do not look for it when the
target is a BPF program. Remember the values in the program, and do not
let an extension replace an fmod_ret program with restricted return
values: it could return any value. Check this at load and when an
extension is attached to another program later. Do not allow tail calls
in such a program either: the program it jumps to returns for it, and a
program array does not check the values it may return.
Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
---
include/linux/bpf.h | 6 ++
kernel/bpf/syscall.c | 1 +
kernel/bpf/verifier.c | 146 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 153 insertions(+)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index b011acc22c50..69387de119a6 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1967,6 +1967,12 @@ struct bpf_prog_aux {
const struct btf_type *attach_func_proto;
/* function name for valid attach_btf_id */
const char *attach_func_name;
+ /*
+ * The values which an fmod_ret program may return besides 0, from the
+ * "bpf_fmodret_retval:" tag of its target, or NULL
+ */
+ s64 *fmodret_retvals;
+ u32 fmodret_retval_cnt;
struct bpf_prog **func;
struct bpf_prog_aux *main_prog_aux;
/* main prog: there when the prog has timed may_goto */
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 9004f2673ccf..0a78f66a1622 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -2490,6 +2490,7 @@ static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred)
module_put(prog->aux->mod);
kfree(prog->aux->kfunc_tab);
kfree(prog->aux->ctx_arg_info);
+ kfree(prog->aux->fmodret_retvals);
if (prog->aux->attach_btf)
btf_put(prog->aux->attach_btf);
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index b7329d164771..a9da29a11963 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -13329,6 +13329,14 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
}
if (func_id == BPF_FUNC_tail_call) {
+ /*
+ * The program which a tail call jumps to returns for this one,
+ * and a program array does not check the values it may return.
+ */
+ if (env->prog->aux->fmodret_retvals) {
+ verbose(env, "tail calls are not allowed in fmod_ret programs with restricted return values\n");
+ return -EINVAL;
+ }
if (env->cur_state->curframe) {
struct bpf_verifier_state *branch;
@@ -19887,6 +19895,73 @@ static bool program_returns_void(struct bpf_verifier_env *env)
return false;
}
+/*
+ * A function which the build found by analysing its code has a
+ * "bpf_fmodret_retval:" decl tag with the values it returns on paths without
+ * side effects, for example "bpf_fmodret_retval:-22,-12". See the comment at
+ * the top of tools/bpf/resolve_btfids/main.c. An fmod_ret program attached to
+ * it may return only these values, or 0 to call the function.
+ */
+#define BTF_FMODRET_RETVAL_TAG "bpf_fmodret_retval:"
+
+/* Parse the next value of the list at *LIST and move *LIST past it */
+static int fmodret_retval_next(const char **list, s64 *value)
+{
+ const char *end = strchrnul(*list, ',');
+ size_t len = end - *list;
+ char buf[24];
+
+ if (!len || len >= sizeof(buf) || (*end && !end[1]))
+ return -EINVAL;
+ memcpy(buf, *list, len);
+ buf[len] = 0;
+ *list = *end ? end + 1 : end;
+ return kstrtos64(buf, 10, value);
+}
+
+static bool fmodret_retval_allowed(const struct bpf_prog_aux *aux, u64 value)
+{
+ const struct btf_type *t;
+ u32 i;
+
+ /* "w0 = -22" zero-extends, the caller of an int function sees -22 */
+ t = btf_type_skip_modifiers(aux->attach_btf, aux->attach_func_proto->type, NULL);
+ if (t->size == 4 && !upper_32_bits(value))
+ value = (s32)value;
+ if (!value)
+ return true;
+ for (i = 0; i < aux->fmodret_retval_cnt; i++) {
+ if (aux->fmodret_retvals[i] == (s64)value)
+ return true;
+ }
+ return false;
+}
+
+/* The program must return a constant: 0 or one of the values of the tag */
+static int check_fmodret_retval(struct bpf_verifier_env *env, int regno,
+ const char *exit_ctx, const char *reg_name)
+{
+ const struct bpf_prog_aux *aux = env->prog->aux;
+ struct bpf_reg_state *reg = reg_state(env, regno);
+ int err;
+ u32 i;
+
+ if (reg->type == SCALAR_VALUE) {
+ err = mark_chain_precision(env, regno);
+ if (err)
+ return err;
+ if (tnum_is_const(reg->var_off) &&
+ fmodret_retval_allowed(aux, reg->var_off.value))
+ return 0;
+ }
+
+ verbose(env, "%s the register %s must be a constant, it should be 0 or one of", exit_ctx, reg_name);
+ for (i = 0; i < aux->fmodret_retval_cnt; i++)
+ verbose(env, "%s%lld", i ? "," : " ", aux->fmodret_retvals[i]);
+ verbose(env, "\n");
+ return -EINVAL;
+}
+
static int check_return_code(struct bpf_verifier_env *env, int regno, const char *reg_name)
{
const char *exit_ctx = "At program exit";
@@ -19943,6 +20018,9 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char
if (prog_type == BPF_PROG_TYPE_CGROUP_SKB && (env->prog->expected_attach_type == BPF_CGROUP_INET_EGRESS))
enforce_attach_type_range = tnum_range(2, 3);
+ if (env->prog->aux->fmodret_retvals)
+ return check_fmodret_retval(env, regno, exit_ctx, reg_name);
+
if (!return_retval_range(env, &range))
return 0;
@@ -22874,6 +22952,58 @@ static int check_attach_modify_return(unsigned long addr, const char *func_name)
#endif /* CONFIG_FUNCTION_ERROR_INJECTION */
+/*
+ * Programs attached to a function found by the analysis may return only the
+ * values of its "bpf_fmodret_retval:" tag. A function which the static rules
+ * allow stays unrestricted.
+ */
+static int fmodret_retval_init(struct bpf_verifier_env *env, u32 btf_id,
+ unsigned long addr, const char *tname)
+{
+ struct bpf_prog_aux *aux = env->prog->aux;
+ const struct btf *btf = aux->attach_btf;
+ const struct btf_type *t = btf_type_by_id(btf, btf_id);
+ s64 *retvals = NULL;
+ const char *values, *list;
+ u32 cnt, size = 0;
+ s64 v;
+
+ if (!check_attach_modify_return(addr, tname))
+ return 0;
+ values = btf_find_decl_tag_value(btf, t, -1, BTF_FMODRET_RETVAL_TAG);
+ if (IS_ERR(values))
+ return PTR_ERR(values) == -ENOENT ? 0 : PTR_ERR(values);
+
+ t = btf_type_by_id(btf, t->type);
+ t = t ? btf_type_skip_modifiers(btf, t->type, NULL) : NULL;
+ if (t && (btf_type_is_int(t) || btf_is_any_enum(t)))
+ size = t->size;
+ if (size != 4 && size != 8)
+ goto invalid;
+
+ for (list = values, cnt = 1; *list; list++)
+ cnt += *list == ',';
+ retvals = kmalloc_array(cnt, sizeof(*retvals), GFP_KERNEL_ACCOUNT);
+ if (!retvals)
+ return -ENOMEM;
+ for (list = values, cnt = 0; *list;) {
+ if (fmodret_retval_next(&list, &v) || v >= 0 || (size == 4 && v < S32_MIN))
+ goto invalid;
+ retvals[cnt++] = v;
+ }
+ if (!cnt)
+ goto invalid;
+
+ aux->fmodret_retvals = retvals;
+ aux->fmodret_retval_cnt = cnt;
+ return 0;
+
+invalid:
+ kfree(retvals);
+ verbose(env, "invalid %s%s tag of %s()\n", BTF_FMODRET_RETVAL_TAG, values, tname);
+ return -EINVAL;
+}
+
static bool is_tracing_multi_id(const struct bpf_prog *prog, u32 btf_id)
{
return is_tracing_multi(prog->expected_attach_type) && bpf_multi_func_btf_id[0] == btf_id;
@@ -23283,6 +23413,15 @@ int bpf_check_attach_target(struct bpf_verifier_log *log,
bpf_log(log, "Cannot extend fentry/fexit/fsession\n");
return -EINVAL;
}
+ /*
+ * An extension could return any value for an fmod_ret program
+ * whose return values are restricted. This is also checked when
+ * an extension is attached to another program than at load.
+ */
+ if (prog_extension && tgt_prog->aux->fmodret_retvals) {
+ bpf_log(log, "Cannot extend an fmod_ret program with restricted return values\n");
+ return -EINVAL;
+ }
} else {
if (prog_extension) {
bpf_log(log, "Cannot replace kernel functions\n");
@@ -23662,6 +23801,13 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
return -EINVAL;
}
+ /* Only a kernel function has a "bpf_fmodret_retval:" tag */
+ if (prog->expected_attach_type == BPF_MODIFY_RETURN && !tgt_prog) {
+ ret = fmodret_retval_init(env, btf_id, tgt_info.tgt_addr, tgt_info.tgt_name);
+ if (ret)
+ return ret;
+ }
+
/*
* We don't get trampoline for tracing_multi programs at this point,
* it's done when tracing_multi link is created.
--
2.43.0
next prev parent reply other threads:[~2026-11-06 20:52 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 21:02 [PATCH v1 bpf-next 01/18] bpf: Add BTF ID sets for fmod_ret targets Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 02/18] bpf: Add a btf_id_set8_entry helper Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 03/18] bpf: Build fmod_ret target sets from BTF decl tags Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 04/18] selftests/bpf: Declare bpf_fentry_test1 in tracing tests Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 05/18] bpf: Declare fmod_ret targets with BTF_FMODRET sets Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 06/18] selftests/bpf: Test fmod_ret BTF decl tags and attach permission Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 07/18] scripts: Add LLVM plugin infrastructure Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 08/18] bpf: Add the bpf_scan_ir LLVM plugin Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 09/18] bpf: Find fmod_ret candidates in bpf_scan_ir Anton Protopopov
2026-11-06 21:02 ` [PATCH v1 bpf-next 10/18] bpf: Allow writes to private stack objects " Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 11/18] bpf: Follow PHIs " Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 12/18] bpf: Follow integer casts " Anton Protopopov
2026-11-06 21:03 ` Anton Protopopov [this message]
2026-11-06 21:03 ` [PATCH v1 bpf-next 14/18] bpf: Require the JIT for fmod_ret programs on found 64-bit targets Anton Protopopov
2026-11-06 21:03 ` [PATCH v1 bpf-next 15/18] resolve_btfids: Tag fmod_ret candidates as targets Anton Protopopov
2026-11-06 21:50 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 16/18] selftests/bpf: Test fmod_ret candidates found by bpf_scan_ir Anton Protopopov
2026-11-06 21:50 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 17/18] selftests/bpf: Enable bpf_scan_ir in the config Anton Protopopov
2026-11-06 21:35 ` bot+bpf-ci
2026-11-06 21:03 ` [PATCH v1 bpf-next 18/18] bpftool: List and show fmod_ret targets Anton Protopopov
2026-11-06 21:35 ` bot+bpf-ci
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=20261106210314.1767267-13-a.s.protopopov@gmail.com \
--to=a.s.protopopov@gmail.com \
--cc=almagui@kernel.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=kpsingh@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=memxor@gmail.com \
--cc=qmo@kernel.org \
--cc=sashal@kernel.org \
--cc=yonghong.song@linux.dev \
/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
all inboxes | Powered by JetHome®