Skip to content

Conversation

@kernel-patches-daemon-bpf
Copy link

Pull request for series with
subject: bpf: Add value tracking for BPF_DIV
version: 1
url: https://patchwork.kernel.org/project/netdevbpf/list/?series=1035457

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: 3d60306
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1035457
version: 1

@kernel-patches-review-bot
Copy link

commit eeca3ca42ac176e41389a95902389f85bc70c977
Author: Yazhou Tang <tangyazhou518@outlook.com>

bpf: Add interval and tnum analysis for signed and unsigned BPF_DIV

This patch adds interval and tnum analysis for BPF_DIV operations to
reduce false positive verification failures for BPF programs using
division instructions.

> diff --git a/kernel/bpf/tnum.c b/kernel/bpf/tnum.c
> index f8e70e9c3998..d115528da6a6 100644

[ ... ]

> +/* __tnum_neg_width: tnum negation with given bit width.
> + * @a: the tnum to be negated.
> + * @width: the bit width to perform negation, 32 or 64.
> + */
> +static struct tnum __tnum_neg_width(struct tnum a, int width)
> +{
> +	if (width == 32)
> +		return tnum_sub(TNUM(U32_MAX, 0), a);
                                ^^^^^^^

For 32-bit negation, is subtracting from U32_MAX correct? This computes
the bitwise NOT (~a) rather than arithmetic negation (-a).

For example, for a = -10 (0xFFFFFFF6 in 32-bit):
  U32_MAX - 0xFFFFFFF6 = 9, but |-10| = 10

The 64-bit case correctly uses tnum_sub(TNUM(0, 0), a) for negation.
Should the 32-bit case also subtract from zero (with appropriate 32-bit
masking)?

This function is called by __tnum_abs() which is used by tnum_sdiv() for
32-bit signed division, so incorrect absolute values would lead to
incorrect tnum results.

> +	else if (width == 64)
> +		return tnum_sub(TNUM(0, 0), a);
> +	else
> +		return tnum_unknown;
> +}

[ ... ]

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index d6b8a77fbe3b..df04a35153ef 100644

[ ... ]

> +static void scalar32_min_max_sdiv(struct bpf_reg_state *dst_reg,
> +				struct bpf_reg_state *src_reg)
> +{
> +	u32 *dst_smin = &dst_reg->s32_min_value;
> +	u32 *dst_smax = &dst_reg->s32_max_value;
> +	u32 smin_val = src_reg->s32_min_value;
> +	u32 smax_val = src_reg->s32_max_value;
        ^^^

Should these be s32 rather than u32? The fields s32_min_value and
s32_max_value are declared as s32 in struct bpf_reg_state.

Using u32 causes the signed comparisons below to behave incorrectly:

> +	if (smin_val <= 0 && smax_val >= 0) {
            ^^^^^^^^^^^^

When s32_min_value contains a negative value like -10 (stored as
0xFFFFFFF6), reading it as u32 gives 4294967286, and the comparison
"smin_val <= 0" becomes "4294967286 <= 0" which is always false.

Similarly:

> +		if (smin_val < 0) {
                    ^^^^^^^^^^^^

This condition can never be true when smin_val is u32.

The 64-bit version scalar_min_max_sdiv() correctly uses s64 types.

[ ... ]



AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

In-Reply-To-Subject: bpf: Add interval and tnum analysis for signed and unsigned BPF_DIV
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/20408116978

@kernel-patches-daemon-bpf
Copy link
Author

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: d2749ae
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1035457
version: 1

@kernel-patches-daemon-bpf
Copy link
Author

Upstream branch: f785a31
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1035457
version: 1

This patch introduces interval analysis (range tracking) and tnum
analysis (bitwise tracking) for both signed and unsigned division
operations in the BPF verifier.

The BPF verifier currently lacks support for value tracking on BPF_DIV
instructions, which can lead to false positives during verification of
BPF programs that utilize division instructions.

According to the BPF instruction set[1], the instruction's offset field
(`insn->off`) is used to distinguish between signed (`off == 1`) and
unsigned division (`off == 0`). Moreover, we also follow the BPF division
semantics to handle special cases, such as division by zero and signed
division overflow.

- UDIV: dst = (src != 0) ? (dst / src) : 0
- SDIV: dst = (src == 0) ? 0 : ((src == -1 && dst == LLONG_MIN) ? LLONG_MIN : (dst / src))

Here is the overview of the changes made in this patch:

1. For interval analysis:
   - Added `scalar_min_max_udiv` and `scalar32_min_max_udiv` to update
     umin/umax bounds, which is straightforward.
   - Added `scalar_min_max_sdiv` and `scalar32_min_max_sdiv` to update
     smin/smax bounds. It handles non-monotonic intervals by decomposing
     the divisor range into negative, zero, and positive sub-ranges, and
     computing the result range for each sub-range separately. Finally,
     it combines the results to get the final smin/smax bounds.
2. For tnum analysis, we referred to LLVM's KnownBits implementation[2]
   and the recent research on abstract interpretation of division[3]:
   - Added `tnum_udiv` to compute the tnum for unsigned division. It
     calculates the maximum possible result based on the maximum values
     of the dividend tnum and the minimum values of the divisor tnum,
     then constructs the resulting tnum accordingly. We have prove its
     soundness using Rocq Prover[4].
   - Added `tnum_sdiv` to compute the tnum for signed division. It splits
     the operands into positive and negative components, then performs
     calculation on absolute values using `tnum_udiv`, finally unions
     the results to ensure soundness.
3. Also updated existing selftests based on the expected BPF_DIV behavior.

[1] https://www.kernel.org/doc/Documentation/bpf/standardization/instruction-set.rst
[2] https://llvm.org/doxygen/KnownBits_8cpp_source.html
[3] https://dl.acm.org/doi/10.1145/3728905
[4] https://github.com/shenghaoyuan/open-verified-artifacts/tree/main/tnum

Co-developed-by: Shenghao Yuan <shenghaoyuan0928@163.com>
Signed-off-by: Shenghao Yuan <shenghaoyuan0928@163.com>
Co-developed-by: Tianci Cao <ziye@zju.edu.cn>
Signed-off-by: Tianci Cao <ziye@zju.edu.cn>
Signed-off-by: Yazhou Tang <tangyazhou518@outlook.com>
Now BPF_DIV has value tracking support via interval and tnum analysis.
This patch adds selftests to cover various cases of signed and
unsigned division operations, including edge cases like division by
zero and signed division overflow.

Specifically, these selftests are based on dead code elimination: If
the BPF verifier can precisely analyze the result of a division
operation, it can prune the path that leads to an error (here we use
invalid memory access as the error case), allowing the program to pass
verification.

Co-developed-by: Shenghao Yuan <shenghaoyuan0928@163.com>
Signed-off-by: Shenghao Yuan <shenghaoyuan0928@163.com>
Co-developed-by: Tianci Cao <ziye@zju.edu.cn>
Signed-off-by: Tianci Cao <ziye@zju.edu.cn>
Signed-off-by: Yazhou Tang <tangyazhou518@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants