summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorEduard Zingerman <eddyz87@gmail.com>2026-10-09 03:47:19 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-10-09 20:29:12 +0000
commit5b6beb95f7b4bde8333c588543a2400e52805cba (patch)
treea9b043bbcf35c57d3f51b423ee39a6173a5f73e5 /kernel
parente1d84a37cba984388988d2f1ddc84561413f0db2 (diff)
bpf: Represent stack access effects with arg_access_info
The following patches in the series add support for may_write property inference. To properly track this property there is a need to distinguish three ways the helpers/kfuncs affect stack: - read; - write and destroy all previous stack tracking information for affected slots; - both read and write the stack slots, potentially preserving some of the tracking information. The later property can't be encoded by signed access_byte convention. Hence, introduce arg_access_info with a size and separate may_read, may_write and must_write bits. Convert the liveness access recording paths to use it. Proper may_write calculation and support will be added in the following patches. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20261009-scev-minimal-rebase-v3-1-bcba9bc53ac9@gmail.com
Diffstat (limited to 'kernel')
-rw-r--r--kernel/bpf/liveness.c96
1 files changed, 61 insertions, 35 deletions
diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
index cd9523f69298..7b317fb5ca82 100644
--- a/kernel/bpf/liveness.c
+++ b/kernel/bpf/liveness.c
@@ -1437,17 +1437,14 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
/*
- * Record access_bytes from helper/kfunc or load/store insn.
- * access_bytes > 0: stack read
- * access_bytes < 0: stack write
- * access_bytes == S64_MIN: unknown — conservative, mark [0..slot] as read
- * access_bytes == 0: no access
- *
+ * Record reads for every touched half-slot. A definite
+ * write requires full coverage, a known size, and a single possible offset.
*/
static int record_stack_access_off(struct func_instance *instance, s64 fp_off,
- s64 access_bytes, u32 frame, u32 insn_idx)
+ struct arg_access_info info, u32 frame, u32 insn_idx)
{
s32 slot_hi, slot_lo;
+ int err;
if (fp_off >= 0)
/*
@@ -1456,49 +1453,53 @@ static int record_stack_access_off(struct func_instance *instance, s64 fp_off,
* by the main verifier pass later.
*/
return 0;
- if (access_bytes == S64_MIN) {
+
+ if (info.may_read && info.size == U32_MAX) {
/* helper/kfunc read unknown amount of bytes from fp_off until fp+0 */
slot_hi = (-fp_off - 1) / STACK_SLOT_SZ;
return mark_stack_read(instance, frame, insn_idx, 0, slot_hi);
}
- if (access_bytes > 0) {
+ if (info.may_read) {
/* Mark any touched slot as use */
slot_hi = (-fp_off - 1) / STACK_SLOT_SZ;
- slot_lo = max_t(s32, (-fp_off - access_bytes) / STACK_SLOT_SZ, 0);
- return mark_stack_read(instance, frame, insn_idx, slot_lo, slot_hi);
- } else if (access_bytes < 0) {
+ slot_lo = max_t(s32, (-fp_off - info.size) / STACK_SLOT_SZ, 0);
+ err = mark_stack_read(instance, frame, insn_idx, slot_lo, slot_hi);
+ if (err)
+ return err;
+ }
+ if (info.must_write && info.size != U32_MAX) {
/* Mark only fully covered slots as def */
- access_bytes = -access_bytes;
slot_hi = (-fp_off) / STACK_SLOT_SZ - 1;
- slot_lo = max_t(s32, (-fp_off - access_bytes + STACK_SLOT_SZ - 1) / STACK_SLOT_SZ, 0);
+ slot_lo = max_t(s32, (-fp_off - info.size + STACK_SLOT_SZ - 1) / STACK_SLOT_SZ, 0);
return mark_stack_write(instance, frame, insn_idx, slot_lo, slot_hi);
}
return 0;
}
-/*
- * 'arg' is FP-derived argument to helper/kfunc or load/store that
- * reads (positive) or writes (negative) 'access_bytes' into 'use' or 'def'.
- */
-static int record_stack_access(struct bpf_verifier_env *env, struct func_instance *instance,
+/* Record access through a pointer with a known frame and possibly known offsets. */
+static int record_stack_access(struct bpf_verifier_env *env,
+ struct func_instance *instance,
const struct arg_track *arg,
- s64 access_bytes, u32 frame, u32 insn_idx)
+ struct arg_access_info info, u32 frame, u32 insn_idx)
{
int i, err;
- if (access_bytes == 0)
+ if (!info.size)
return 0;
if (arg->off_cnt == 0) {
- if (access_bytes > 0 || access_bytes == S64_MIN)
- return mark_stack_read_all(env, instance, frame, insn_idx);
+ if (info.may_read) {
+ err = mark_stack_read_all(env, instance, frame, insn_idx);
+ if (err)
+ return err;
+ }
return 0;
}
- if (access_bytes != S64_MIN && access_bytes < 0 && arg->off_cnt != 1)
+ if (info.size != U32_MAX && info.must_write && arg->off_cnt != 1)
/* multi-offset write cannot set stack_def */
return 0;
for (i = 0; i < arg->off_cnt; i++) {
- err = record_stack_access_off(instance, arg->off[i], access_bytes, frame, insn_idx);
+ err = record_stack_access_off(instance, arg->off[i], info, frame, insn_idx);
if (err)
return err;
}
@@ -1533,9 +1534,10 @@ static int record_load_store_access(struct bpf_verifier_env *env,
struct arg_track *at, int insn_idx)
{
struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
+ struct arg_access_info info = {};
int depth = instance->depth;
- s32 sz = bpf_size_to_bytes(BPF_SIZE(insn->code));
u8 class = BPF_CLASS(insn->code);
+ bool read = false, write = false;
struct arg_track resolved, *ptr;
int oi;
@@ -1552,23 +1554,26 @@ static int record_load_store_access(struct bpf_verifier_env *env,
switch (class) {
case BPF_LDX:
ptr = &at[insn->src_reg];
+ read = true;
break;
case BPF_STX:
if (BPF_MODE(insn->code) == BPF_ATOMIC) {
if (insn->imm == BPF_STORE_REL)
- sz = -sz;
+ write = true;
+ else
+ read = true;
if (insn->imm == BPF_LOAD_ACQ)
ptr = &at[insn->src_reg];
else
ptr = &at[insn->dst_reg];
} else {
ptr = &at[insn->dst_reg];
- sz = -sz;
+ write = true;
}
break;
case BPF_ST:
ptr = &at[insn->dst_reg];
- sz = -sz;
+ write = true;
break;
default:
return 0;
@@ -1587,32 +1592,53 @@ static int record_load_store_access(struct bpf_verifier_env *env,
ptr = &resolved;
}
+ info.may_read = read;
+ info.may_write = write;
+ info.must_write = write;
+ info.size = bpf_size_to_bytes(BPF_SIZE(insn->code));
if (ptr->frame >= 0 && ptr->frame <= depth)
- return record_stack_access(env, instance, ptr, sz, ptr->frame, insn_idx);
+ return record_stack_access(env, instance, ptr, info, ptr->frame, insn_idx);
if (ptr->frame == ARG_IMPRECISE)
return record_imprecise(env, instance, ptr->mask, insn_idx);
/* ARG_NONE: not derived from any frame pointer, skip */
return 0;
}
+/* Adapt the signed helper/kfunc access size until both producers are converted. */
+static struct arg_access_info stack_access_info(s64 bytes)
+{
+ bool write = bytes < 0 && bytes != S64_MIN && -bytes < U32_MAX;
+
+ return (struct arg_access_info) {
+ .size = bytes == S64_MIN
+ ? U32_MAX
+ : min_t(u64, bytes < 0 ? -bytes : bytes, U32_MAX),
+ .may_read = bytes > 0 || bytes == S64_MIN,
+ .may_write = write,
+ .must_write = write,
+ };
+}
+
static int record_arg_access(struct bpf_verifier_env *env,
struct func_instance *instance,
struct bpf_insn *insn,
struct arg_track *at, int arg_idx,
int insn_idx)
{
+ struct arg_access_info info;
int depth = instance->depth;
int frame = at->frame;
int err = 0;
- s64 bytes;
if (!arg_is_fp(at))
return 0;
if (bpf_helper_call(insn)) {
- bytes = bpf_helper_stack_access_bytes(env, insn, arg_idx, insn_idx);
+ info = stack_access_info(bpf_helper_stack_access_bytes(env, insn,
+ arg_idx, insn_idx));
} else if (bpf_pseudo_kfunc_call(insn)) {
- bytes = bpf_kfunc_stack_access_bytes(env, insn, arg_idx, insn_idx);
+ info = stack_access_info(bpf_kfunc_stack_access_bytes(env, insn,
+ arg_idx, insn_idx));
} else {
for (int f = 0; f <= depth; f++) {
err = mark_stack_read_all(env, instance, f, insn_idx);
@@ -1621,11 +1647,11 @@ static int record_arg_access(struct bpf_verifier_env *env,
}
return 0;
}
- if (bytes == 0)
+ if (!info.size)
return 0;
if (frame >= 0 && frame <= depth)
- err = record_stack_access(env, instance, at, bytes, frame, insn_idx);
+ err = record_stack_access(env, instance, at, info, frame, insn_idx);
else if (frame == ARG_IMPRECISE)
err = record_imprecise(env, instance, at->mask, insn_idx);
return err;