Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f1174f77 authored by Edward Cree's avatar Edward Cree Committed by David S. Miller
Browse files

bpf/verifier: rework value tracking



Unifies adjusted and unadjusted register value types (e.g. FRAME_POINTER is
 now just a PTR_TO_STACK with zero offset).
Tracks value alignment by means of tracking known & unknown bits.  This
 also replaces the 'reg->imm' (leading zero bits) calculations for (what
 were) UNKNOWN_VALUEs.
If pointer leaks are allowed, and adjust_ptr_min_max_vals returns -EACCES,
 treat the pointer as an unknown scalar and try again, because we might be
 able to conclude something about the result (e.g. pointer & 0x40 is either
 0 or 0x40).
Verifier hooks in the netronome/nfp driver were changed to match the new
 data structures.

Signed-off-by: default avatarEdward Cree <ecree@solarflare.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e1cb90f2
Loading
Loading
Loading
Loading
+14 −10
Original line number Diff line number Diff line
@@ -79,28 +79,32 @@ nfp_bpf_check_exit(struct nfp_prog *nfp_prog,
		   const struct bpf_verifier_env *env)
{
	const struct bpf_reg_state *reg0 = &env->cur_state.regs[0];
	u64 imm;

	if (nfp_prog->act == NN_ACT_XDP)
		return 0;

	if (reg0->type != CONST_IMM) {
		pr_info("unsupported exit state: %d, imm: %llx\n",
			reg0->type, reg0->imm);
	if (!(reg0->type == SCALAR_VALUE && tnum_is_const(reg0->var_off))) {
		char tn_buf[48];

		tnum_strn(tn_buf, sizeof(tn_buf), reg0->var_off);
		pr_info("unsupported exit state: %d, var_off: %s\n",
			reg0->type, tn_buf);
		return -EINVAL;
	}

	if (nfp_prog->act != NN_ACT_DIRECT &&
	    reg0->imm != 0 && (reg0->imm & ~0U) != ~0U) {
	imm = reg0->var_off.value;
	if (nfp_prog->act != NN_ACT_DIRECT && imm != 0 && (imm & ~0U) != ~0U) {
		pr_info("unsupported exit state: %d, imm: %llx\n",
			reg0->type, reg0->imm);
			reg0->type, imm);
		return -EINVAL;
	}

	if (nfp_prog->act == NN_ACT_DIRECT && reg0->imm <= TC_ACT_REDIRECT &&
	    reg0->imm != TC_ACT_SHOT && reg0->imm != TC_ACT_STOLEN &&
	    reg0->imm != TC_ACT_QUEUED) {
	if (nfp_prog->act == NN_ACT_DIRECT && imm <= TC_ACT_REDIRECT &&
	    imm != TC_ACT_SHOT && imm != TC_ACT_STOLEN &&
	    imm != TC_ACT_QUEUED) {
		pr_info("unsupported exit state: %d, imm: %llx\n",
			reg0->type, reg0->imm);
			reg0->type, imm);
		return -EINVAL;
	}

+12 −22
Original line number Diff line number Diff line
@@ -117,35 +117,25 @@ enum bpf_access_type {
};

/* types of values stored in eBPF registers */
/* Pointer types represent:
 * pointer
 * pointer + imm
 * pointer + (u16) var
 * pointer + (u16) var + imm
 * if (range > 0) then [ptr, ptr + range - off) is safe to access
 * if (id > 0) means that some 'var' was added
 * if (off > 0) means that 'imm' was added
 */
enum bpf_reg_type {
	NOT_INIT = 0,		 /* nothing was written into register */
	UNKNOWN_VALUE,		 /* reg doesn't contain a valid pointer */
	SCALAR_VALUE,		 /* reg doesn't contain a valid pointer */
	PTR_TO_CTX,		 /* reg points to bpf_context */
	CONST_PTR_TO_MAP,	 /* reg points to struct bpf_map */
	PTR_TO_MAP_VALUE,	 /* reg points to map element value */
	PTR_TO_MAP_VALUE_OR_NULL,/* points to map elem value or NULL */
	FRAME_PTR,		 /* reg == frame_pointer */
	PTR_TO_STACK,		 /* reg == frame_pointer + imm */
	CONST_IMM,		 /* constant integer value */

	/* PTR_TO_PACKET represents:
	 * skb->data
	 * skb->data + imm
	 * skb->data + (u16) var
	 * skb->data + (u16) var + imm
	 * if (range > 0) then [ptr, ptr + range - off) is safe to access
	 * if (id > 0) means that some 'var' was added
	 * if (off > 0) menas that 'imm' was added
	 */
	PTR_TO_PACKET,
	PTR_TO_STACK,		 /* reg == frame_pointer + offset */
	PTR_TO_PACKET,		 /* reg points to skb->data */
	PTR_TO_PACKET_END,	 /* skb->data + headlen */

	/* PTR_TO_MAP_VALUE_ADJ is used for doing pointer math inside of a map
	 * elem value.  We only allow this if we can statically verify that
	 * access from this register are going to fall within the size of the
	 * map element.
	 */
	PTR_TO_MAP_VALUE_ADJ,
};

struct bpf_prog;
+21 −13
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@

#include <linux/bpf.h> /* for enum bpf_reg_type */
#include <linux/filter.h> /* for MAX_BPF_STACK */
#include <linux/tnum.h>

 /* Just some arbitrary values so we can safely do math without overflowing and
  * are obviously wrong for any sort of memory access.
@@ -19,30 +20,37 @@
struct bpf_reg_state {
	enum bpf_reg_type type;
	union {
		/* valid when type == CONST_IMM | PTR_TO_STACK | UNKNOWN_VALUE */
		s64 imm;

		/* valid when type == PTR_TO_PACKET* */
		struct {
			u16 off;
		/* valid when type == PTR_TO_PACKET */
		u16 range;
		};

		/* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |
		 *   PTR_TO_MAP_VALUE_OR_NULL
		 */
		struct bpf_map *map_ptr;
	};
	/* Fixed part of pointer offset, pointer types only */
	s32 off;
	/* For PTR_TO_PACKET, used to find other pointers with the same variable
	 * offset, so they can share range knowledge.
	 * For PTR_TO_MAP_VALUE_OR_NULL this is used to share which map value we
	 * came from, when one is tested for != NULL.
	 */
	u32 id;
	/* These three fields must be last.  See states_equal() */
	/* For scalar types (SCALAR_VALUE), this represents our knowledge of
	 * the actual value.
	 * For pointer types, this represents the variable part of the offset
	 * from the pointed-to object, and is shared with all bpf_reg_states
	 * with the same id as us.
	 */
	struct tnum var_off;
	/* Used to determine if any memory access using this register will
	 * result in a bad access. These two fields must be last.
	 * See states_equal()
	 * result in a bad access.
	 * These refer to the same value as var_off, not necessarily the actual
	 * contents of the register.
	 */
	s64 min_value;
	u64 max_value;
	u32 min_align;
	u32 aux_off;
	u32 aux_off_align;
	bool value_from_signed;
};

include/linux/tnum.h

0 → 100644
+79 −0
Original line number Diff line number Diff line
/* tnum: tracked (or tristate) numbers
 *
 * A tnum tracks knowledge about the bits of a value.  Each bit can be either
 * known (0 or 1), or unknown (x).  Arithmetic operations on tnums will
 * propagate the unknown bits such that the tnum result represents all the
 * possible results for possible values of the operands.
 */
#include <linux/types.h>

struct tnum {
	u64 value;
	u64 mask;
};

/* Constructors */
/* Represent a known constant as a tnum. */
struct tnum tnum_const(u64 value);
/* A completely unknown value */
extern const struct tnum tnum_unknown;

/* Arithmetic and logical ops */
/* Shift a tnum left (by a fixed shift) */
struct tnum tnum_lshift(struct tnum a, u8 shift);
/* Shift a tnum right (by a fixed shift) */
struct tnum tnum_rshift(struct tnum a, u8 shift);
/* Add two tnums, return @a + @b */
struct tnum tnum_add(struct tnum a, struct tnum b);
/* Subtract two tnums, return @a - @b */
struct tnum tnum_sub(struct tnum a, struct tnum b);
/* Bitwise-AND, return @a & @b */
struct tnum tnum_and(struct tnum a, struct tnum b);
/* Bitwise-OR, return @a | @b */
struct tnum tnum_or(struct tnum a, struct tnum b);
/* Bitwise-XOR, return @a ^ @b */
struct tnum tnum_xor(struct tnum a, struct tnum b);
/* Multiply two tnums, return @a * @b */
struct tnum tnum_mul(struct tnum a, struct tnum b);

/* Return a tnum representing numbers satisfying both @a and @b */
struct tnum tnum_intersect(struct tnum a, struct tnum b);

/* Return @a with all but the lowest @size bytes cleared */
struct tnum tnum_cast(struct tnum a, u8 size);

/* Returns true if @a is a known constant */
static inline bool tnum_is_const(struct tnum a)
{
	return !a.mask;
}

/* Returns true if @a == tnum_const(@b) */
static inline bool tnum_equals_const(struct tnum a, u64 b)
{
	return tnum_is_const(a) && a.value == b;
}

/* Returns true if @a is completely unknown */
static inline bool tnum_is_unknown(struct tnum a)
{
	return !~a.mask;
}

/* Returns true if @a is known to be a multiple of @size.
 * @size must be a power of two.
 */
bool tnum_is_aligned(struct tnum a, u64 size);

/* Returns true if @b represents a subset of @a. */
bool tnum_in(struct tnum a, struct tnum b);

/* Formatting functions.  These have snprintf-like semantics: they will write
 * up to @size bytes (including the terminating NUL byte), and return the number
 * of bytes (excluding the terminating NUL) which would have been written had
 * sufficient space been available.  (Thus tnum_sbin always returns 64.)
 */
/* Format a tnum as a pair of hex numbers (value; mask) */
int tnum_strn(char *str, size_t size, struct tnum a);
/* Format a tnum as tristate binary expansion */
int tnum_sbin(char *str, size_t size, struct tnum a);
+1 −1
Original line number Diff line number Diff line
obj-y := core.o

obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o
obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o
obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o
ifeq ($(CONFIG_NET),y)
obj-$(CONFIG_BPF_SYSCALL) += devmap.o
Loading