Skip to content

Commit f96da09

Browse files
borkmanndavem330
authored andcommitted
bpf: simplify narrower ctx access
This work tries to make the semantics and code around the narrower ctx access a bit easier to follow. Right now everything is done inside the .is_valid_access(). Offset matching is done differently for read/write types, meaning writes don't support narrower access and thus matching only on offsetof(struct foo, bar) is enough whereas for read case that supports narrower access we must check for offsetof(struct foo, bar) + offsetof(struct foo, bar) + sizeof(<bar>) - 1 for each of the cases. For read cases of individual members that don't support narrower access (like packet pointers or skb->cb[] case which has its own narrow access logic), we check as usual only offsetof(struct foo, bar) like in write case. Then, for the case where narrower access is allowed, we also need to set the aux info for the access. Meaning, ctx_field_size and converted_op_size have to be set. First is the original field size e.g. sizeof(<bar>) as in above example from the user facing ctx, and latter one is the target size after actual rewrite happened, thus for the kernel facing ctx. Also here we need the range match and we need to keep track changing convert_ctx_access() and converted_op_size from is_valid_access() as both are not at the same location. We can simplify the code a bit: check_ctx_access() becomes simpler in that we only store ctx_field_size as a meta data and later in convert_ctx_accesses() we fetch the target_size right from the location where we do convert. Should the verifier be misconfigured we do reject for BPF_WRITE cases or target_size that are not provided. For the subsystems, we always work on ranges in is_valid_access() and add small helpers for ranges and narrow access, convert_ctx_accesses() sets target_size for the relevant instruction. Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Acked-by: John Fastabend <john.fastabend@gmail.com> Cc: Yonghong Song <yhs@fb.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent 2be7e21 commit f96da09

File tree

5 files changed

+209
-195
lines changed

5 files changed

+209
-195
lines changed

include/linux/bpf.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,14 @@ struct bpf_prog;
156156
struct bpf_insn_access_aux {
157157
enum bpf_reg_type reg_type;
158158
int ctx_field_size;
159-
int converted_op_size;
160159
};
161160

161+
static inline void
162+
bpf_ctx_record_field_size(struct bpf_insn_access_aux *aux, u32 size)
163+
{
164+
aux->ctx_field_size = size;
165+
}
166+
162167
struct bpf_verifier_ops {
163168
/* return eBPF function prototype for verification */
164169
const struct bpf_func_proto *(*get_func_proto)(enum bpf_func_id func_id);
@@ -173,7 +178,7 @@ struct bpf_verifier_ops {
173178
u32 (*convert_ctx_access)(enum bpf_access_type type,
174179
const struct bpf_insn *src,
175180
struct bpf_insn *dst,
176-
struct bpf_prog *prog);
181+
struct bpf_prog *prog, u32 *target_size);
177182
int (*test_run)(struct bpf_prog *prog, const union bpf_attr *kattr,
178183
union bpf_attr __user *uattr);
179184
};

include/linux/filter.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,22 @@ struct bpf_prog_aux;
337337
bpf_size; \
338338
})
339339

340+
#define bpf_size_to_bytes(bpf_size) \
341+
({ \
342+
int bytes = -EINVAL; \
343+
\
344+
if (bpf_size == BPF_B) \
345+
bytes = sizeof(u8); \
346+
else if (bpf_size == BPF_H) \
347+
bytes = sizeof(u16); \
348+
else if (bpf_size == BPF_W) \
349+
bytes = sizeof(u32); \
350+
else if (bpf_size == BPF_DW) \
351+
bytes = sizeof(u64); \
352+
\
353+
bytes; \
354+
})
355+
340356
#define BPF_SIZEOF(type) \
341357
({ \
342358
const int __size = bytes_to_bpf_size(sizeof(type)); \
@@ -351,6 +367,13 @@ struct bpf_prog_aux;
351367
__size; \
352368
})
353369

370+
#define BPF_LDST_BYTES(insn) \
371+
({ \
372+
const int __size = bpf_size_to_bytes(BPF_SIZE(insn->code)); \
373+
WARN_ON(__size < 0); \
374+
__size; \
375+
})
376+
354377
#define __BPF_MAP_0(m, v, ...) v
355378
#define __BPF_MAP_1(m, v, t, a, ...) m(t, a)
356379
#define __BPF_MAP_2(m, v, t, a, ...) m(t, a), __BPF_MAP_1(m, v, __VA_ARGS__)
@@ -401,6 +424,18 @@ struct bpf_prog_aux;
401424
#define BPF_CALL_4(name, ...) BPF_CALL_x(4, name, __VA_ARGS__)
402425
#define BPF_CALL_5(name, ...) BPF_CALL_x(5, name, __VA_ARGS__)
403426

427+
#define bpf_ctx_range(TYPE, MEMBER) \
428+
offsetof(TYPE, MEMBER) ... offsetofend(TYPE, MEMBER) - 1
429+
#define bpf_ctx_range_till(TYPE, MEMBER1, MEMBER2) \
430+
offsetof(TYPE, MEMBER1) ... offsetofend(TYPE, MEMBER2) - 1
431+
432+
#define bpf_target_off(TYPE, MEMBER, SIZE, PTR_SIZE) \
433+
({ \
434+
BUILD_BUG_ON(FIELD_SIZEOF(TYPE, MEMBER) != (SIZE)); \
435+
*(PTR_SIZE) = (SIZE); \
436+
offsetof(TYPE, MEMBER); \
437+
})
438+
404439
#ifdef CONFIG_COMPAT
405440
/* A struct sock_filter is architecture independent. */
406441
struct compat_sock_fprog {
@@ -564,6 +599,18 @@ static inline bool bpf_prog_was_classic(const struct bpf_prog *prog)
564599
return prog->type == BPF_PROG_TYPE_UNSPEC;
565600
}
566601

602+
static inline bool
603+
bpf_ctx_narrow_access_ok(u32 off, u32 size, const u32 size_default)
604+
{
605+
bool off_ok;
606+
#ifdef __LITTLE_ENDIAN
607+
off_ok = (off & (size_default - 1)) == 0;
608+
#else
609+
off_ok = (off & (size_default - 1)) + size == size_default;
610+
#endif
611+
return off_ok && size <= size_default && (size & (size - 1)) == 0;
612+
}
613+
567614
#define bpf_classic_proglen(fprog) (fprog->len * sizeof(fprog->filter[0]))
568615

569616
#ifdef CONFIG_ARCH_HAS_SET_MEMORY

kernel/bpf/verifier.c

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -546,20 +546,6 @@ static int check_reg_arg(struct bpf_reg_state *regs, u32 regno,
546546
return 0;
547547
}
548548

549-
static int bpf_size_to_bytes(int bpf_size)
550-
{
551-
if (bpf_size == BPF_W)
552-
return 4;
553-
else if (bpf_size == BPF_H)
554-
return 2;
555-
else if (bpf_size == BPF_B)
556-
return 1;
557-
else if (bpf_size == BPF_DW)
558-
return 8;
559-
else
560-
return -EINVAL;
561-
}
562-
563549
static bool is_spillable_regtype(enum bpf_reg_type type)
564550
{
565551
switch (type) {
@@ -761,33 +747,24 @@ static int check_packet_access(struct bpf_verifier_env *env, u32 regno, int off,
761747
static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off, int size,
762748
enum bpf_access_type t, enum bpf_reg_type *reg_type)
763749
{
764-
struct bpf_insn_access_aux info = { .reg_type = *reg_type };
750+
struct bpf_insn_access_aux info = {
751+
.reg_type = *reg_type,
752+
};
765753

766754
/* for analyzer ctx accesses are already validated and converted */
767755
if (env->analyzer_ops)
768756
return 0;
769757

770758
if (env->prog->aux->ops->is_valid_access &&
771759
env->prog->aux->ops->is_valid_access(off, size, t, &info)) {
772-
/* a non zero info.ctx_field_size indicates:
773-
* . For this field, the prog type specific ctx conversion algorithm
774-
* only supports whole field access.
775-
* . This ctx access is a candiate for later verifier transformation
776-
* to load the whole field and then apply a mask to get correct result.
777-
* a non zero info.converted_op_size indicates perceived actual converted
778-
* value width in convert_ctx_access.
760+
/* A non zero info.ctx_field_size indicates that this field is a
761+
* candidate for later verifier transformation to load the whole
762+
* field and then apply a mask when accessed with a narrower
763+
* access than actual ctx access size. A zero info.ctx_field_size
764+
* will only allow for whole field access and rejects any other
765+
* type of narrower access.
779766
*/
780-
if ((info.ctx_field_size && !info.converted_op_size) ||
781-
(!info.ctx_field_size && info.converted_op_size)) {
782-
verbose("verifier bug in is_valid_access prog type=%u off=%d size=%d\n",
783-
env->prog->type, off, size);
784-
return -EACCES;
785-
}
786-
787-
if (info.ctx_field_size) {
788-
env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
789-
env->insn_aux_data[insn_idx].converted_op_size = info.converted_op_size;
790-
}
767+
env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
791768
*reg_type = info.reg_type;
792769

793770
/* remember the offset of last byte accessed in ctx */
@@ -3401,11 +3378,13 @@ static struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 of
34013378
static int convert_ctx_accesses(struct bpf_verifier_env *env)
34023379
{
34033380
const struct bpf_verifier_ops *ops = env->prog->aux->ops;
3381+
int i, cnt, size, ctx_field_size, delta = 0;
34043382
const int insn_cnt = env->prog->len;
34053383
struct bpf_insn insn_buf[16], *insn;
34063384
struct bpf_prog *new_prog;
34073385
enum bpf_access_type type;
3408-
int i, cnt, off, size, ctx_field_size, converted_op_size, is_narrower_load, delta = 0;
3386+
bool is_narrower_load;
3387+
u32 target_size;
34093388

34103389
if (ops->gen_prologue) {
34113390
cnt = ops->gen_prologue(insn_buf, env->seen_direct_write,
@@ -3445,39 +3424,50 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
34453424
if (env->insn_aux_data[i + delta].ptr_type != PTR_TO_CTX)
34463425
continue;
34473426

3448-
off = insn->off;
3449-
size = bpf_size_to_bytes(BPF_SIZE(insn->code));
34503427
ctx_field_size = env->insn_aux_data[i + delta].ctx_field_size;
3451-
converted_op_size = env->insn_aux_data[i + delta].converted_op_size;
3452-
is_narrower_load = type == BPF_READ && size < ctx_field_size;
3428+
size = BPF_LDST_BYTES(insn);
34533429

34543430
/* If the read access is a narrower load of the field,
34553431
* convert to a 4/8-byte load, to minimum program type specific
34563432
* convert_ctx_access changes. If conversion is successful,
34573433
* we will apply proper mask to the result.
34583434
*/
3435+
is_narrower_load = size < ctx_field_size;
34593436
if (is_narrower_load) {
3460-
int size_code = BPF_H;
3437+
u32 off = insn->off;
3438+
u8 size_code;
3439+
3440+
if (type == BPF_WRITE) {
3441+
verbose("bpf verifier narrow ctx access misconfigured\n");
3442+
return -EINVAL;
3443+
}
34613444

3445+
size_code = BPF_H;
34623446
if (ctx_field_size == 4)
34633447
size_code = BPF_W;
34643448
else if (ctx_field_size == 8)
34653449
size_code = BPF_DW;
3450+
34663451
insn->off = off & ~(ctx_field_size - 1);
34673452
insn->code = BPF_LDX | BPF_MEM | size_code;
34683453
}
3469-
cnt = ops->convert_ctx_access(type, insn, insn_buf, env->prog);
3470-
if (cnt == 0 || cnt >= ARRAY_SIZE(insn_buf)) {
3454+
3455+
target_size = 0;
3456+
cnt = ops->convert_ctx_access(type, insn, insn_buf, env->prog,
3457+
&target_size);
3458+
if (cnt == 0 || cnt >= ARRAY_SIZE(insn_buf) ||
3459+
(ctx_field_size && !target_size)) {
34713460
verbose("bpf verifier is misconfigured\n");
34723461
return -EINVAL;
34733462
}
3474-
if (is_narrower_load && size < converted_op_size) {
3463+
3464+
if (is_narrower_load && size < target_size) {
34753465
if (ctx_field_size <= 4)
34763466
insn_buf[cnt++] = BPF_ALU32_IMM(BPF_AND, insn->dst_reg,
3477-
(1 << size * 8) - 1);
3467+
(1 << size * 8) - 1);
34783468
else
34793469
insn_buf[cnt++] = BPF_ALU64_IMM(BPF_AND, insn->dst_reg,
3480-
(1 << size * 8) - 1);
3470+
(1 << size * 8) - 1);
34813471
}
34823472

34833473
new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);

kernel/trace/bpf_trace.c

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,8 @@ const struct bpf_verifier_ops tracepoint_prog_ops = {
583583
static bool pe_prog_is_valid_access(int off, int size, enum bpf_access_type type,
584584
struct bpf_insn_access_aux *info)
585585
{
586-
int sample_period_off;
586+
const int size_sp = FIELD_SIZEOF(struct bpf_perf_event_data,
587+
sample_period);
587588

588589
if (off < 0 || off >= sizeof(struct bpf_perf_event_data))
589590
return false;
@@ -592,43 +593,35 @@ static bool pe_prog_is_valid_access(int off, int size, enum bpf_access_type type
592593
if (off % size != 0)
593594
return false;
594595

595-
/* permit 1, 2, 4 byte narrower and 8 normal read access to sample_period */
596-
sample_period_off = offsetof(struct bpf_perf_event_data, sample_period);
597-
if (off >= sample_period_off && off < sample_period_off + sizeof(__u64)) {
598-
int allowed;
599-
600-
#ifdef __LITTLE_ENDIAN
601-
allowed = (off & 0x7) == 0 && size <= 8 && (size & (size - 1)) == 0;
602-
#else
603-
allowed = ((off & 0x7) + size) == 8 && size <= 8 && (size & (size - 1)) == 0;
604-
#endif
605-
if (!allowed)
596+
switch (off) {
597+
case bpf_ctx_range(struct bpf_perf_event_data, sample_period):
598+
bpf_ctx_record_field_size(info, size_sp);
599+
if (!bpf_ctx_narrow_access_ok(off, size, size_sp))
606600
return false;
607-
info->ctx_field_size = 8;
608-
info->converted_op_size = 8;
609-
} else {
601+
break;
602+
default:
610603
if (size != sizeof(long))
611604
return false;
612605
}
606+
613607
return true;
614608
}
615609

616610
static u32 pe_prog_convert_ctx_access(enum bpf_access_type type,
617611
const struct bpf_insn *si,
618612
struct bpf_insn *insn_buf,
619-
struct bpf_prog *prog)
613+
struct bpf_prog *prog, u32 *target_size)
620614
{
621615
struct bpf_insn *insn = insn_buf;
622616

623617
switch (si->off) {
624618
case offsetof(struct bpf_perf_event_data, sample_period):
625-
BUILD_BUG_ON(FIELD_SIZEOF(struct perf_sample_data, period) != sizeof(u64));
626-
627619
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct bpf_perf_event_data_kern,
628620
data), si->dst_reg, si->src_reg,
629621
offsetof(struct bpf_perf_event_data_kern, data));
630622
*insn++ = BPF_LDX_MEM(BPF_DW, si->dst_reg, si->dst_reg,
631-
offsetof(struct perf_sample_data, period));
623+
bpf_target_off(struct perf_sample_data, period, 8,
624+
target_size));
632625
break;
633626
default:
634627
*insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct bpf_perf_event_data_kern,

0 commit comments

Comments
 (0)