mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-06-21 15:43:21 +02:00
cd5b460ed1
states.c:range_within() must be updated to properly check if
cnum-based range in an old state is a superset of a range in the cur
state. Currently it makes the decision using min/max accessors:
reg_umin(old) <= reg_umin(cur) <= reg_umax(old)
This is wrong for cnums that cross both UT_MAX/0 and ST_MAX/ST_MIN
boundaries. Consider cnum32{base=0x7FFFFFF0, size=0x80000020},
which represents values [0x7FFFFFF0, ..., U32_MAX, 0, ..., 0x10].
Its projections are u32_min/max=0/U32_MAX, s32_min/max=S32_MIN/MAX.
A register with range [0x100, 0x200] (which lies entirely in the gap
of the wrapping range) would pass the min/max check despite having no
overlap with the actual cnum arc.
This commit replaces min/max comparison with cnum{32,64}_is_subset()
operation. The operation implementation is verified using cbmc model
checker in [1].
[1] https://github.com/eddyz87/cnum-verif/
Fixes: bbc6310855 ("bpf: replace min/max fields with struct cnum{32,64}")
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260425-cnum-range-within-v1-1-2fdca70cb09d@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
83 lines
3.3 KiB
C
83 lines
3.3 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
|
|
|
|
#ifndef _LINUX_CNUM_H
|
|
#define _LINUX_CNUM_H
|
|
|
|
#include <linux/types.h>
|
|
|
|
/*
|
|
* cnum32: a circular number.
|
|
* A unified representation for signed and unsigned ranges.
|
|
*
|
|
* Assume that a 32-bit range is a circle, with 0 being in the 12 o'clock
|
|
* position, numbers placed sequentially in clockwise order and U32_MAX
|
|
* in the 11 o'clock position. Signed values map onto the same circle:
|
|
* S32_MAX sits at 5 o'clock, S32_MIN sits at 6 o'clock (opposite 0),
|
|
* negative values occupy the left half and positive values the right half.
|
|
*
|
|
* @cnum32 represents an arc on this circle drawn clockwise.
|
|
* @base corresponds to the first value of the range.
|
|
* @size corresponds to the number of integers in the range excluding @base.
|
|
* (The @base is excluded to avoid integer overflow when representing the full
|
|
* 0..U32_MAX range, which corresponds to 2^32, which can't be stored in u32).
|
|
*
|
|
* For example: {U32_MAX, 1} corresponds to signed range [-1, 0],
|
|
* {S32_MAX, 1} corresponds to unsigned range [S32_MAX, S32_MIN].
|
|
*/
|
|
struct cnum32 {
|
|
u32 base;
|
|
u32 size;
|
|
};
|
|
|
|
#define CNUM32_UNBOUNDED ((struct cnum32){ .base = 0, .size = U32_MAX })
|
|
#define CNUM32_EMPTY ((struct cnum32){ .base = U32_MAX, .size = U32_MAX })
|
|
|
|
struct cnum32 cnum32_from_urange(u32 min, u32 max);
|
|
struct cnum32 cnum32_from_srange(s32 min, s32 max);
|
|
u32 cnum32_umin(struct cnum32 cnum);
|
|
u32 cnum32_umax(struct cnum32 cnum);
|
|
s32 cnum32_smin(struct cnum32 cnum);
|
|
s32 cnum32_smax(struct cnum32 cnum);
|
|
struct cnum32 cnum32_intersect(struct cnum32 a, struct cnum32 b);
|
|
void cnum32_intersect_with(struct cnum32 *dst, struct cnum32 src);
|
|
void cnum32_intersect_with_urange(struct cnum32 *dst, u32 min, u32 max);
|
|
void cnum32_intersect_with_srange(struct cnum32 *dst, s32 min, s32 max);
|
|
bool cnum32_contains(struct cnum32 cnum, u32 v);
|
|
bool cnum32_is_const(struct cnum32 cnum);
|
|
bool cnum32_is_empty(struct cnum32 cnum);
|
|
struct cnum32 cnum32_add(struct cnum32 a, struct cnum32 b);
|
|
struct cnum32 cnum32_negate(struct cnum32 a);
|
|
bool cnum32_is_subset(struct cnum32 outer, struct cnum32 inner);
|
|
|
|
/* Same as cnum32 but for 64-bit ranges */
|
|
struct cnum64 {
|
|
u64 base;
|
|
u64 size;
|
|
};
|
|
|
|
#define CNUM64_UNBOUNDED ((struct cnum64){ .base = 0, .size = U64_MAX })
|
|
#define CNUM64_EMPTY ((struct cnum64){ .base = U64_MAX, .size = U64_MAX })
|
|
|
|
struct cnum64 cnum64_from_urange(u64 min, u64 max);
|
|
struct cnum64 cnum64_from_srange(s64 min, s64 max);
|
|
u64 cnum64_umin(struct cnum64 cnum);
|
|
u64 cnum64_umax(struct cnum64 cnum);
|
|
s64 cnum64_smin(struct cnum64 cnum);
|
|
s64 cnum64_smax(struct cnum64 cnum);
|
|
struct cnum64 cnum64_intersect(struct cnum64 a, struct cnum64 b);
|
|
void cnum64_intersect_with(struct cnum64 *dst, struct cnum64 src);
|
|
void cnum64_intersect_with_urange(struct cnum64 *dst, u64 min, u64 max);
|
|
void cnum64_intersect_with_srange(struct cnum64 *dst, s64 min, s64 max);
|
|
bool cnum64_contains(struct cnum64 cnum, u64 v);
|
|
bool cnum64_is_const(struct cnum64 cnum);
|
|
bool cnum64_is_empty(struct cnum64 cnum);
|
|
struct cnum64 cnum64_add(struct cnum64 a, struct cnum64 b);
|
|
struct cnum64 cnum64_negate(struct cnum64 a);
|
|
bool cnum64_is_subset(struct cnum64 outer, struct cnum64 inner);
|
|
|
|
struct cnum32 cnum32_from_cnum64(struct cnum64 cnum);
|
|
struct cnum64 cnum64_cnum32_intersect(struct cnum64 a, struct cnum32 b);
|
|
|
|
#endif /* _LINUX_CNUM_H */
|