mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Vineet Gupta <vineet.gupta@linux.dev>
To: Eduard Zingerman <eddyz87@gmail.com>,
	ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
	memxor@gmail.com
Cc: martin.lau@linux.dev, song@kernel.org, yonghong.song@linux.dev,
	jolsa@kernel.org, emil@etsalapatis.com, ihor.solodrai@linux.dev,
	john.fastabend@gmail.com, shuah@kernel.org, bpf@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org
Subject: Re: [RFC bpf-next 5/6] bpf: support low-32 subreg scalar linking for sign-extending movs
Date: Wed, 9 Sep 2026 18:29:35 +0530	[thread overview]
Message-ID: <a2a2d7f0-8869-4d85-95d5-e436f743c163@linux.dev> (raw)
In-Reply-To: <d43e9adedfeced52ccf5eafea9f2aec01b79f413.camel@gmail.com>

On 8/19/26 11:48 AM, Eduard Zingerman wrote:
>> @@ -570,9 +572,18 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
>>   		 * on a path that predates this series, which is a pruning change
>>   		 * that wants measuring on its own; it is deliberately left
>>   		 * alone here.
>> +		 *
>> +		 * Only demand a match when the old state carries a link at all.
>> +		 * These flags are only ever set together with an ->id, so
>> +		 * rold->id == 0 implies none is set, and the only case this
>> +		 * admits is "old knows no low-32 relationship, cur does" -- cur
>> +		 * is then strictly more constrained than old, which is the safe
>> +		 * direction for pruning. The reverse is still rejected. Without
>> +		 * this a register that first acquires a link inside a loop would
>> +		 * never match its pre-loop state and pruning would not converge.
>>   		 */
>>   		if (rold->id &&
>> -		    (rold->flags & BPF_FLAG_SUBREG_ZEXT) != (rcur->flags & BPF_FLAG_SUBREG_ZEXT))
>> +		    (rold->flags & BPF_FLAG_SUBREG) != (rcur->flags & BPF_FLAG_SUBREG))
>>   			return false;
> Same comment as for ZEXT patch.

The prior zext patch compares ->subreg with enum distinguishing zext and 
sext: no sext specific code needed.

>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 8a802d49d0a4..45cb67dc3999 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -14976,6 +14976,8 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
>>   	return 0;
>>   }
>>   
>> +static void reconstruct_sext32(struct bpf_reg_state *reg, struct bpf_reg_state *src);
> Nit: is it possible to avoid forward declaration?

Removed.

>>   /* check validity of 32-bit and 64-bit arithmetic operations */
>>   static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
>>   {
>> @@ -15052,15 +15054,65 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
>>   							insn->src_reg);
>>   						return -EACCES;
>>   					} else if (src_reg->type == SCALAR_VALUE) {
>> +						int sz = insn->off >> 3;
>>   						bool no_sext;
>> +						bool subreg_link;
>>   
>>   						no_sext = reg_umax(src_reg) < (1ULL << (insn->off - 1));
>> -						if (no_sext)
>> +						/*
>> +						 * When no_sext, dst == src exactly, so link them
>> +						 * (existing behavior). When !no_sext for a 32-bit sign
>> +						 * extension the low 32 bits are still identical (sext
>> +						 * preserves them), so form a BPF_FLAG_SUBREG_SEXT
>> +						 * link: a later narrowing of the low 32 bits
>> +						 * propagates here, and sync_linked_regs() rebuilds
>> +						 * the high half via reconstruct_sext32().
>> +						 *
>> +						 * An ADD_CONST-linked src is excluded for the same
>> +						 * reason as in the zero-extending arm below:
>> +						 * assign_scalar_id_before_mov() would clear its
>> +						 * base+delta link, and a combined subreg+delta link
>> +						 * isn't modeled anyway. Unlike that arm a self-mov is
>> +						 * NOT excluded -- r0 = (s32)r0 is the case this is
>> +						 * here for.
>> +						 */
>> +						subreg_link = (sz == 4) &&
>> +							      !(src_reg->flags & BPF_FLAG_ADD_CONST);
>> +
>> +						if (no_sext || subreg_link)
>>   							assign_scalar_id_before_mov(env, src_reg);
>>   						*dst_reg = *src_reg;
>> -						if (!no_sext)
>> -							clear_scalar_id(dst_reg);
>> -						coerce_reg_to_size_sx(dst_reg, insn->off >> 3);
>> +						if (!no_sext) {
>> +							if (subreg_link && src_reg->id) {
>> +								/* ->id already copied above */
>> +								dst_reg->flags = (dst_reg->flags & ~BPF_FLAG_SUBREG) |
>> +										 BPF_FLAG_SUBREG_SEXT;
>> +							} else {
>> +								clear_scalar_id(dst_reg);
>> +							}
>> +						}
>> +						/*
>> +						 * coerce_reg_to_size_sx() falls back to the full sext
>> +						 * range when smin/smax straddle the sign boundary (e.g.
>> +						 * an errno-or-zero value clamped to [-4095, 0]). For a
>> +						 * register tracked as the sign-extension of its low 32
>> +						 * bits the high half IS that sign-extension, so rebuild
>> +						 * the tighter 64-bit range from the low bounds, taken
>> +						 * from a snapshot because coerce overwrites them.
>> +						 *
>> +						 * Gated on sz == 4, not on the flag alone: an (s8)/(s16)
>> +						 * mov whose src is already SEXT-linked copies the flag
>> +						 * across in the *dst_reg = *src_reg above, and a 32-bit
>> +						 * reconstruction must not run for a narrower operation.
>> +						 */
>> +						if (sz == 4 && (dst_reg->flags & BPF_FLAG_SUBREG_SEXT)) {
>> +							struct bpf_reg_state sext_src = *dst_reg;
>> +
>> +							coerce_reg_to_size_sx(dst_reg, sz);
>> +							reconstruct_sext32(dst_reg, &sext_src);
> It does not make sense to maintain two functions that do register sign
> extension. If coerce_reg_to_size_sx() is not precise enough for the
> 32-bit case, then it should be adapted instead of special-cased.

Adapting coerce_reg_to_size_sx () update is now a standalone patch, with 
it's own test.

+       if (size == 1) {
+               field_smin = S8_MIN;
+               field_smax = S8_MAX;
+       } else if (size == 2) {
+               field_smin = S16_MIN;
+               field_smax = S16_MAX;
+       } else {
+               /* size == 4 */
+               field_smin = S32_MIN;
+               field_smax = S32_MAX;
+       }
+
+       /*
+        * The range already fits the field, so (sN)v == v for every 
value the
+        * register can hold and the sign extension changes nothing. The 
tests
+        * below cannot reach this case once smin is negative: a 
negative smin
+        * and a non-negative smax never share their high bits.
+        */
+       if (reg_smin(reg) >= field_smin && reg_smax(reg) <= field_smax)
+               return;


It existing usage is retained in check_alu_op, w/o any reconstruct business.

However in sync routine reconstruct_sext32 is needed, see below....

>> +						} else {
>> +							coerce_reg_to_size_sx(dst_reg, sz);
>> +						}
>>   					} else {
>>   						mark_reg_unknown(env, regs, insn->dst_reg);
>>   					}
>> @@ -15107,7 +15159,15 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
>>   						if (!is_src_reg_u32) {
>>   							if (wide_subreg_link && src_reg->id) {
>>   								/* ->id already copied above */
>> -								dst_reg->flags |= BPF_FLAG_SUBREG_ZEXT;
>> +								/*
>> +								 * Zero-extension: high bits are 0, not a
>> +								 * sign-extension of the low field. Drop any
>> +								 * SUBREG_SEXT copied from a sext-linked src
>> +								 * so sync_linked_regs() rebuilds dst by
>> +								 * zero-extension, not reconstruct_sext32().
>> +								 */
>> +								dst_reg->flags = (dst_reg->flags & ~BPF_FLAG_SUBREG) |
>> +										 BPF_FLAG_SUBREG_ZEXT;
> Nit: please find a way to reduce indentation (e.g. less if-nesting, or a utility function).

Flattened.

>>   							} else {
>>   								clear_scalar_id(dst_reg);
>>   							}
>> @@ -15961,6 +16021,32 @@ static void collect_linked_regs(struct bpf_verifier_env *env,
>>   	}
>>   }
>>   
>> +/*
>> + * Set @reg to the sign-extension of the low 32 bits currently held by @src.
>> + * A BPF_FLAG_SUBREG_SEXT-linked register came from a 32-bit sign
>> + * extension (r0 = (s32)r0): it shares @src's low 32 bits and its high bits are
>> + * the sign-extension of that low field. Only the value fields are written;
>> + * @reg's linkage fields (id, delta, flags) are left intact by
>> + * the caller (___mark_reg_known touches only var_off/r64/r32). Callers must
>> + * ensure no ADD_CONST delta is involved (see sync_linked_regs()).
>> + */
>> +static void reconstruct_sext32(struct bpf_reg_state *reg, struct bpf_reg_state *src)
> Nit: let's rename src -> known_reg, to make reading sync_linked_regs() simpler.

Done.


>> +{
>> +	s32 s32min = reg_s32_min(src);
>> +	s32 s32max = reg_s32_max(src);
>> +
>> +	if (s32min == s32max) {
>> +		/* Low 32 bits are constant -> the whole value is the sext constant. */
>> +		___mark_reg_known(reg, (u64)(s64)s32min);
>> +	} else {
>> +		/* Sign-extension is monotonic over the signed-32 range. */
>> +		reg_set_srange64(reg, (s64)s32min, (s64)s32max);
>> +		reg_set_srange32(reg, s32min, s32max);
>> +		reg->var_off = tnum_range((u64)(s64)s32min, (u64)(s64)s32max);
> Note that known lower 32-bits of the known_reg->var_off are lost,
> we might benefit from adding a dedicated tnum_sext().

Added.

>> +		reg_bounds_sync(reg);
>> +	}
>> +}
>> +
>>   /* For all R in linked_regs, copy known_reg range into R
>>    * if R->id == known_reg->id.
>>    */
>> @@ -15984,17 +16070,21 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
>>   		 * A low-32 linked register shares only the base's low 32 bits;
>>   		 * the flag says how its high bits are derived. For
>>   		 * BPF_FLAG_SUBREG_ZEXT they are zero (32-bit zero-extending mov).
>> +		 * For BPF_FLAG_SUBREG_SEXT they are the sign-extension of the low
>> +		 * field (32-bit sign extension).
>>   		 * Rebuild it from known_reg's low 32 bits accordingly, but only
>>   		 * when neither side carries an ADD_CONST delta -- with a delta
>>   		 * the low bits differ from the base by that delta and the combined
>>   		 * subreg+ADD_CONST reconstruction isn't modeled here, so leave reg
>>   		 * unchanged (sound, just less precise).
>>   		 */
>> -		if (reg->flags & BPF_FLAG_SUBREG_ZEXT) {
>> +		if (reg->flags & BPF_FLAG_SUBREG) {
>>   			if (!((reg->flags | known_reg->flags) & BPF_FLAG_ADD_CONST)) {
>> -				{
>> +				if (reg->flags & BPF_FLAG_SUBREG_SEXT) {
>> +					reconstruct_sext32(reg, known_reg);
>> +				} else {
> Let's move this branch to a dedicated utility function as well.

This is all simplified in-place now.

-                       reconstruct_zext32(reg, known_reg);
+                       if (reg->subreg == SUBREG_ZEXT)
+                               reconstruct_zext32(reg, known_reg);
+                       else
+                               reconstruct_sext32(reg, known_reg);



coerce_reg_to_size_sx() reads the 64-bit smin/smax, while a 32-bit 
compare narrows the 32-bit range.

For a simple test (actually added)

call %[bpf_get_prandom_u32];
r6 = r0;
r7 = (s32)r6;         /* forms the link */
if w6 != -1 goto 1f;  /* narrows r6's low 32 to all ones */
if r7 == -1 goto 1f;  /* r7 must follow, sign-extended */
r0 /= 0;              /* reachable only if it didn't */
1:
r0 = 0;
exit;

With coerce_reg_to_size_sx(reg, 4) it fails

2: (bf) r7 = (s32)r6   ; 
R7=scalar(id=1.lo32sx,smin=0xffffffff80000000,smax=0x7fffffff)
3: (56) if w6 != 0xffffffff goto pc+2
  R6=scalar(id=1,smin=0x80000000ffffffff,smin32=-1,smax32=-1,var_off=(0xffffffff; 
0xffffffff00000000))
      R7=scalar(id=1.lo32sx,smin=0xffffffff80000000,smax=0x7fffffff) 
  <-- unchanged

With sext_32_to_64() it passes:

3: (56) if w6 != 0xffffffff goto pc+2
  R6=scalar(id=1,smin=0x80000000ffffffff,smin32=-1,smax32=-1,var_off=(0xffffffff; 
0xffffffff00000000))
      R7=-1   <-- reconstructed

Thx,
-Vineet

  reply	other threads:[~2026-09-09 12:59 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-14 23:19 [RFC bpf-next 0/6] bpf: track scalar equality across the low 32 bits Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 1/6] bpf: turn bpf_reg_state->precise into a flags field [NFC] Vineet Gupta
2026-08-18 21:38   ` Eduard Zingerman
2026-08-14 23:19 ` [RFC bpf-next 2/6] bpf: move the linked-scalar flags into bpf_reg_state->flags [NFC] Vineet Gupta
2026-08-18 22:51   ` Eduard Zingerman
2026-09-04  2:54     ` Vineet Gupta
2026-09-04  2:56     ` Vineet Gupta
2026-09-04  2:58     ` Vineet Gupta
2026-09-04  3:29     ` Vineet Gupta
2026-09-04  3:33       ` Mailer snafu (was Re: [RFC bpf-next 2/6] bpf: move the linked-scalar flags into bpf_reg_state->flags [NFC]) Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 3/6] bpf: support low-32 subreg scalar linking for zero-extending movs Vineet Gupta
2026-08-19  3:39   ` Eduard Zingerman
2026-09-08  9:48     ` Vineet Gupta
2026-08-19  4:07   ` Eduard Zingerman
2026-09-04  8:44     ` Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 4/6] selftests/bpf: cover low-32 subreg-equal link " Vineet Gupta
2026-08-19  5:05   ` Eduard Zingerman
2026-09-03  5:49     ` Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 5/6] bpf: support low-32 subreg scalar linking for sign-extending movs Vineet Gupta
2026-08-19  6:18   ` Eduard Zingerman
2026-09-09 12:59     ` Vineet Gupta [this message]
2026-08-14 23:19 ` [RFC bpf-next 6/6] selftests/bpf: cover 32-bit sign-extension low-32 links Vineet Gupta
2026-08-19  4:35 ` [RFC bpf-next 0/6] bpf: track scalar equality across the low 32 bits Eduard Zingerman

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=a2a2d7f0-8869-4d85-95d5-e436f743c163@linux.dev \
    --to=vineet.gupta@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.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=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=shuah@kernel.org \
    --cc=song@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®