summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorEduard Zingerman <eddyz87@gmail.com>2026-10-09 03:47:22 -0700
committerAlexei Starovoitov <ast@kernel.org>2026-10-09 20:29:12 +0000
commitac43e0393f44931bd2d84ab682e78b1f08f9f1ec (patch)
tree8e2fece5d0084137f560b4344a3cf55882b57a5d /kernel
parent8f3bd4537faad487a1aed4df24c1af4587904568 (diff)
bpf: Track may_write flags in liveness
Track stack slots that instructions may modify, in order to account for such writes when constructing SCEV expressions. Consume the may_write flag in arg_access_info separately from reads and definite writes. Include partially covered slots and all candidate offsets; imprecise writes mark whole candidate frames. Possible writes do not kill liveness. Merge analyzed instances as: may_write(dst) |= may_write(src) must_write(dst) &= must_write(src) For each ancestor frame f, summarize callee writes at the callsite: may_write(callsite, f) |= OR_{i in callee} may_write(i, f) 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-4-bcba9bc53ac9@gmail.com
Diffstat (limited to 'kernel')
-rw-r--r--kernel/bpf/liveness.c207
1 files changed, 142 insertions, 65 deletions
diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c
index d4089a488784..e18d5d86c301 100644
--- a/kernel/bpf/liveness.c
+++ b/kernel/bpf/liveness.c
@@ -23,6 +23,7 @@ enum {
FM_MAY_READ, /* stack slots that may be read by this instruction */
FM_MUST_WRITE, /* stack slots written by this instruction */
FM_LIVE_BEFORE, /* stack slots that may be read by this insn and its successors */
+ FM_MAY_WRITE, /* stack slots that may be written by this instruction */
FM_MASK_CNT,
};
@@ -262,6 +263,12 @@ static int mark_stack_write(struct func_instance *instance, u32 frame, u32 insn_
return mark_stack_range(instance, frame, insn_idx, FM_MUST_WRITE, lo, hi);
}
+static int mark_stack_may_write(struct func_instance *instance, u32 frame, u32 insn_idx,
+ s32 lo, s32 hi)
+{
+ return mark_stack_range(instance, frame, insn_idx, FM_MAY_WRITE, lo, hi);
+}
+
/*
* Mark every half-slot of @frame as possibly read by @insn_idx. This widens
* the masks to the program's stack budget: a full read recorded at a narrower
@@ -277,9 +284,16 @@ static int mark_stack_read_all(struct bpf_verifier_env *env, struct func_instanc
env->stack_limit / BPF_HALF_REG_SIZE - 1);
}
-/* Accumulate @src, a mask @src_words wide, into may_read of @frame at @insn_idx */
-static int mark_stack_read_mask(struct func_instance *instance, u32 frame, u32 insn_idx,
- const unsigned long *src, u32 src_words)
+static int mark_stack_may_write_all(struct bpf_verifier_env *env, struct func_instance *instance,
+ u32 frame, u32 insn_idx)
+{
+ return mark_stack_may_write(instance, frame, insn_idx, 0,
+ env->stack_limit / BPF_HALF_REG_SIZE - 1);
+}
+
+/* Accumulate @src, a mask @src_words wide, into @kind mask of @frame at @insn_idx */
+static int mark_stack_mask(struct func_instance *instance, u32 frame, u32 insn_idx, u32 kind,
+ const unsigned long *src, u32 src_words)
{
u32 nbits = src_words * BITS_PER_LONG;
struct frame_masks *fm;
@@ -292,7 +306,7 @@ static int mark_stack_read_mask(struct func_instance *instance, u32 frame, u32 i
fm = widen_frame_masks(instance, frame, BITS_TO_LONGS(last + 1));
if (!fm)
return -ENOMEM;
- dst = rel_mask(fm, relative_idx(instance, insn_idx), FM_MAY_READ);
+ dst = rel_mask(fm, relative_idx(instance, insn_idx), kind);
/* @src has no bits set past @last, hence none past @fm->words either */
src_words = min(src_words, fm->words);
for (w = 0; w < src_words; w++)
@@ -300,6 +314,12 @@ static int mark_stack_read_mask(struct func_instance *instance, u32 frame, u32 i
return 0;
}
+static int mark_stack_read_mask(struct func_instance *instance, u32 frame, u32 insn_idx,
+ const unsigned long *src, u32 src_words)
+{
+ return mark_stack_mask(instance, frame, insn_idx, FM_MAY_READ, src, src_words);
+}
+
int bpf_jmp_offset(struct bpf_insn *insn)
{
u8 code = insn->code;
@@ -624,16 +644,41 @@ static char *fmt_spis_mask(struct bpf_verifier_env *env, int frame, bool first,
return env->tmp_str_buf;
}
+/* Print mask @kind of the instruction at relative index @i for every frame, if any bit is set. */
+static bool print_mask(struct bpf_verifier_env *env, struct func_instance *instance, int i,
+ const char *name, u32 kind)
+{
+ struct frame_masks *fm;
+ bool printed = false;
+ unsigned long *mask;
+ int frame;
+ u64 pos;
+
+ pos = env->log.end_pos;
+ verbose(env, "%s", name);
+ for (frame = instance->depth; frame >= 0; --frame) {
+ fm = instance->frames[frame];
+ if (!fm)
+ continue;
+ mask = rel_mask(fm, i, kind);
+ if (bitmap_empty(mask, frame_mask_bits(fm)))
+ continue;
+ verbose(env, "%s", fmt_spis_mask(env, frame, !printed, mask, fm->words));
+ printed = true;
+ }
+ if (!printed)
+ bpf_vlog_reset(&env->log, pos);
+ return printed;
+}
+
static void print_instance(struct bpf_verifier_env *env, struct func_instance *instance)
{
int start = env->subprog_info[instance->subprog].start;
struct bpf_insn *insns = env->prog->insnsi;
- struct frame_masks *fm;
- unsigned long *mask;
int len = instance->insn_cnt;
- int insn_idx, frame, i;
- bool has_use, has_def;
u64 pos, insn_pos;
+ int insn_idx, i;
+ bool printed;
if (!(env->log.level & BPF_LOG_LEVEL2))
return;
@@ -642,41 +687,17 @@ static void print_instance(struct bpf_verifier_env *env, struct func_instance *i
verbose(env, "%s:\n", fmt_instance(env, instance));
for (i = 0; i < len; i++) {
insn_idx = start + i;
- has_use = false;
- has_def = false;
pos = env->log.end_pos;
verbose(env, "%3d: ", insn_idx);
bpf_verbose_insn(env, &insns[insn_idx]);
insn_pos = env->log.end_pos;
verbose(env, "%*c;", bpf_vlog_alignment(insn_pos - pos), ' ');
- pos = env->log.end_pos;
- verbose(env, " use: ");
- for (frame = instance->depth; frame >= 0; --frame) {
- fm = instance->frames[frame];
- if (!fm)
- continue;
- mask = rel_mask(fm, i, FM_MAY_READ);
- if (bitmap_empty(mask, frame_mask_bits(fm)))
- continue;
- verbose(env, "%s", fmt_spis_mask(env, frame, !has_use, mask, fm->words));
- has_use = true;
- }
- if (!has_use)
- bpf_vlog_reset(&env->log, pos);
- pos = env->log.end_pos;
- verbose(env, " def: ");
- for (frame = instance->depth; frame >= 0; --frame) {
- fm = instance->frames[frame];
- if (!fm)
- continue;
- mask = rel_mask(fm, i, FM_MUST_WRITE);
- if (bitmap_empty(mask, frame_mask_bits(fm)))
- continue;
- verbose(env, "%s", fmt_spis_mask(env, frame, !has_def, mask, fm->words));
- has_def = true;
- }
- if (!has_def)
- bpf_vlog_reset(&env->log, has_use ? pos : insn_pos);
+ printed = false;
+ printed |= print_mask(env, instance, i, " use: ", FM_MAY_READ);
+ printed |= print_mask(env, instance, i, " def: ", FM_MUST_WRITE);
+ printed |= print_mask(env, instance, i, " may_def: ", FM_MAY_WRITE);
+ if (!printed)
+ bpf_vlog_reset(&env->log, insn_pos);
verbose(env, "\n");
if (bpf_is_ldimm64(&insns[insn_idx]))
i++;
@@ -1437,12 +1458,14 @@ static void arg_track_xfer(struct bpf_verifier_env *env, struct bpf_insn *insn,
}
/*
- * Record reads for every touched half-slot. A definite
+ * Record possible reads and writes 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,
- struct arg_access_info info, u32 frame, u32 insn_idx)
+static int record_stack_access_off(struct func_instance *instance, const struct arg_track *arg,
+ s64 off_idx, struct arg_access_info info,
+ u32 frame, u32 insn_idx)
{
+ s64 fp_off = arg->off[off_idx];
s32 slot_hi, slot_lo;
int err;
@@ -1454,20 +1477,25 @@ static int record_stack_access_off(struct func_instance *instance, s64 fp_off,
*/
return 0;
- 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);
- }
+ /*
+ * Read and may_write marks include partially covered slots.
+ * An unknown access size may reach from fp_off to the frame top.
+ */
+ slot_hi = (-fp_off - 1) / STACK_SLOT_SZ;
+ slot_lo = info.size == U32_MAX
+ ? 0
+ : max_t(s32, (-fp_off - info.size) / STACK_SLOT_SZ, 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 - 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) {
+ if (info.may_write) {
+ err = mark_stack_may_write(instance, frame, insn_idx, slot_lo, slot_hi);
+ if (err)
+ return err;
+ }
+ if (info.must_write && info.size != U32_MAX && arg->off_cnt == 1) {
/* Mark only fully covered slots as def */
slot_hi = (-fp_off) / STACK_SLOT_SZ - 1;
slot_lo = max_t(s32, (-fp_off - info.size + STACK_SLOT_SZ - 1) / STACK_SLOT_SZ, 0);
@@ -1492,26 +1520,24 @@ static int record_stack_access(struct bpf_verifier_env *env,
if (err)
return err;
}
+ if (info.may_write) {
+ err = mark_stack_may_write_all(env, instance, frame, insn_idx);
+ if (err)
+ return err;
+ }
return 0;
}
- 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], info, frame, insn_idx);
+ err = record_stack_access_off(instance, arg, i, info, frame, insn_idx);
if (err)
return err;
}
return 0;
}
-/*
- * When a pointer is ARG_IMPRECISE, conservatively mark every frame in
- * the bitmask as fully used.
- */
+/* Record possible effects on every candidate frame of an imprecise pointer. */
static int record_imprecise(struct bpf_verifier_env *env, struct func_instance *instance,
- u32 mask, u32 insn_idx)
+ struct arg_access_info info, u32 mask, u32 insn_idx)
{
int depth = instance->depth;
int f, err;
@@ -1520,9 +1546,16 @@ static int record_imprecise(struct bpf_verifier_env *env, struct func_instance *
if (!(mask & 1))
continue;
if (f <= depth) {
- err = mark_stack_read_all(env, instance, f, insn_idx);
- if (err)
- return err;
+ if (info.may_read) {
+ err = mark_stack_read_all(env, instance, f, insn_idx);
+ if (err)
+ return err;
+ }
+ if (info.may_write) {
+ err = mark_stack_may_write_all(env, instance, f, insn_idx);
+ if (err)
+ return err;
+ }
}
}
return 0;
@@ -1599,7 +1632,7 @@ static int record_load_store_access(struct bpf_verifier_env *env,
if (ptr->frame >= 0 && ptr->frame <= depth)
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);
+ return record_imprecise(env, instance, info, ptr->mask, insn_idx);
/* ARG_NONE: not derived from any frame pointer, skip */
return 0;
}
@@ -1627,6 +1660,9 @@ static int record_arg_access(struct bpf_verifier_env *env,
err = mark_stack_read_all(env, instance, f, insn_idx);
if (err)
return err;
+ err = mark_stack_may_write_all(env, instance, f, insn_idx);
+ if (err)
+ return err;
}
return 0;
}
@@ -1636,7 +1672,7 @@ static int record_arg_access(struct bpf_verifier_env *env,
if (frame >= 0 && frame <= depth)
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);
+ err = record_imprecise(env, instance, info, at->mask, insn_idx);
return err;
}
@@ -1996,6 +2032,7 @@ static bool has_fp_args(struct arg_track *args)
/*
* Merge a freshly analyzed instance into the original.
* may_read: union (any pass might read the slot).
+ * may_write: union (slots written on ANY pass).
* must_write: intersection (only slots written on ALL passes are guaranteed).
* live_before is recomputed by a subsequent update_instance() on @dst.
*
@@ -2034,18 +2071,50 @@ static int merge_instances(struct func_instance *dst, struct func_instance *src)
for (i = 0; i < dst->insn_cnt; i++) {
unsigned long *dst_read = rel_mask(d, i, FM_MAY_READ);
unsigned long *dst_write = rel_mask(d, i, FM_MUST_WRITE);
+ unsigned long *dst_may_write = rel_mask(d, i, FM_MAY_WRITE);
unsigned long *src_read = rel_mask(s, i, FM_MAY_READ);
unsigned long *src_write = rel_mask(s, i, FM_MUST_WRITE);
+ unsigned long *src_may_write = rel_mask(s, i, FM_MAY_WRITE);
for (w = 0; w < d->words; w++) {
dst_read[w] |= w < s->words ? src_read[w] : 0;
dst_write[w] &= w < s->words ? src_write[w] : 0;
+ dst_may_write[w] |= w < s->words ? src_may_write[w] : 0;
}
}
}
return 0;
}
+/*
+ * Fold a fully analyzed callee instance writes to upper frames as
+ * may_write marks at callsite in caller's frames.
+ */
+static int merge_may_write(struct func_instance *caller, struct func_instance *callee)
+{
+ DECLARE_BITMAP(acc, FRAME_HALF_SPIS);
+ u32 call_idx = callee->callsite;
+ struct frame_masks *fm;
+ u32 f, i, nbits;
+ int err;
+
+ for (f = 0; f < callee->depth; f++) {
+ fm = callee->frames[f];
+ if (!fm)
+ continue;
+ nbits = frame_mask_bits(fm);
+ bitmap_zero(acc, nbits);
+ for (i = 0; i < callee->insn_cnt; i++)
+ bitmap_or(acc, acc, rel_mask(fm, i, FM_MAY_WRITE), nbits);
+ if (bitmap_empty(acc, nbits))
+ continue;
+ err = mark_stack_mask(caller, f, call_idx, FM_MAY_WRITE, acc, fm->words);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
static struct func_instance *fresh_instance(struct func_instance *src)
{
struct func_instance *f;
@@ -2161,6 +2230,9 @@ static int analyze_subprog(struct bpf_verifier_env *env,
err = mark_stack_read_all(env, instance, f, idx);
if (err)
goto out_free;
+ err = mark_stack_may_write_all(env, instance, f, idx);
+ if (err)
+ goto out_free;
}
continue;
}
@@ -2213,6 +2285,11 @@ static int analyze_subprog(struct bpf_verifier_env *env,
goto out_free;
}
}
+
+ /* Summarize callee's writes to ancestor frames onto the callsite */
+ err = merge_may_write(instance, callee_instance);
+ if (err)
+ goto out_free;
}
if (prev_instance) {