Re: Is it possible to read from a dynamic offset in a packet?

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thank you! That works when I write the opcodes by hand.

When using clang (6.0.0) to compile from C code, however, the register
allocator does not seem to want to reuse the register it used for the
comparison for the read, causing a verifier failure.
Is there a good method for getting clang to reuse the register it used
for the comparison for the read?

===== BEGIN CODE =====
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

static int (*bpf_trace_printk)(const char* fmt, int fmt_size, ...) =
(void *)BPF_FUNC_trace_printk;

int xdp_program(struct xdp_md *context) {
void* frame_end = (void *)(long)context->data_end;

void *frame_start = (void*)(long)context->data;
if (frame_start + sizeof(struct ethhdr) + sizeof(struct iphdr) > frame_end) {
return XDP_PASS;
}

if (__builtin_bswap16(((struct ethhdr*)frame_start)->h_proto) != ETH_P_IP) {
return XDP_PASS;
}

__u16 payload_length = __builtin_bswap16(((struct iphdr*)(frame_start
+ sizeof(struct ethhdr)))->tot_len - sizeof(struct iphdr));
if (payload_length > 1500) {
return XDP_PASS;
}

if (frame_start + sizeof(struct ethhdr) + sizeof(struct iphdr) +
payload_length > frame_end) {
return XDP_PASS;
}
int last_byte_payload_offset = payload_length - 1;
__u8 byte = ((__u8*)frame_start + sizeof(struct ethhdr) +
sizeof(struct iphdr))[last_byte_payload_offset];
char fmt[] = "Packet's last byte was 0x%x\n";
bpf_trace_printk(fmt, sizeof(fmt), byte);

return XDP_TX;
}

char _license[] __attribute__((section("license"), used)) = "GPL";
===== END CODE =====

The above code was compiled with clang -O2 -Wall -Werror -target bpf -c -g.

When attempting to install the program, the verifier complained with

===== BEGIN VERIFIER OUTPUT =====
Verifier analysis:

0: (b7) r0 = 2
1: (61) r2 = *(u32 *)(r1 +4)
2: (61) r3 = *(u32 *)(r1 +0)
3: (bf) r1 = r3
4: (07) r1 += 34
5: (2d) if r1 > r2 goto pc+32
 R0=inv2 R1=pkt(id=0,off=34,r=34,imm=0) R2=pkt_end(id=0,off=0,imm=0)
R3=pkt(id=0,off=0,r=34,imm=0) R10=fp0,call_-1
6: (71) r4 = *(u8 *)(r3 +12)
7: (71) r5 = *(u8 *)(r3 +13)
8: (67) r5 <<= 8
9: (4f) r5 |= r4
10: (55) if r5 != 0x8 goto pc+27
 R0=inv2 R1=pkt(id=0,off=34,r=34,imm=0) R2=pkt_end(id=0,off=0,imm=0)
R3=pkt(id=0,off=0,r=34,imm=0) R4=inv(id=0,umax_value=255,var_off=(0x0;
0xff)) R5=inv8 R10=fp0,call_-1
11: (69) r3 = *(u16 *)(r3 +16)
12: (07) r3 += 65516
13: (dc) r3 = be16 r3
14: (25) if r3 > 0x5dc goto pc+23
 R0=inv2 R1=pkt(id=0,off=34,r=34,imm=0) R2=pkt_end(id=0,off=0,imm=0)
R3=inv(id=0,umax_value=1500,var_off=(0x0; 0x7ff))
R4=inv(id=0,umax_value=255,var_off=(0x0; 0xff)) R5=inv8
R10=fp0,call_-1
15: (bf) r4 = r1
16: (0f) r4 += r3
17: (2d) if r4 > r2 goto pc+20
 R0=inv2 R1=pkt(id=0,off=34,r=34,imm=0) R2=pkt_end(id=0,off=0,imm=0)
R3=inv(id=0,umax_value=1500,var_off=(0x0; 0x7ff))
R4=pkt(id=1,off=34,r=34,umax_value=1500,var_off=(0x0; 0x7ff)) R5=inv8
R10=fp0,call_-1
18: (0f) r1 += r3
19: (71) r3 = *(u8 *)(r1 -1)
invalid access to packet, off=33 size=1, R1(id=2,off=34,r=0)
R1 offset is outside of the packet

Error fetching program/map!
===== END VERIFIER OUTPUT =====

Thank you!
--Zvi


On Thu, Jun 7, 2018 at 6:24 AM, Edward Cree <ecree@xxxxxxxxxxxxxx> wrote:
> On 06/06/18 23:22, Zvi Effron wrote:
>> Hi XDPeople!
>>
>> I've been having great success with reading data from fixed offsets in
>> a packet/ethernet frame. But every time I try to read from a dynamic
>> offset (e.g. read the last byte in the packet by using the packet
>> length in the IP/UDP headers). It looks like once I add a register to
>> a packet pointer, the range is reset to 0, and no amount of
>> comparisons will restore a range.
>>
>> Looking through the verifier code, I believe I found where the range
>> is getting set to 0
>> (https://elixir.bootlin.com/linux/latest/source/kernel/bpf/verifier.c#L2695),
>> and it looks like the risk of overflow
>> (https://elixir.bootlin.com/linux/latest/source/kernel/bpf/verifier.c#L3267)
>> is why the range doesn't get updated by later comparisons.
>>
>> Is there a way to do a read from a packet at a dynamic offset? If not,
>> is that something that could potentially be added to the verifier? It
>> feels like `if (packet_start <= packet_start + offset && packet_end >
>> packet_start + offset)` is something that should be verifiable as
>> safe, even with potential overflow in `packet_start + offset`?
>>
>> Thank you!
>> --Zvi
> I think that to make this work you need to bound the dynamic offset
>  before you add it to the packet pointer.  So for instance if you read
>  a packet length from the IP header, that's a u16 (so max. 0xffff), and
>  it's then added to the offset of L4 (probably 14+20=34), giving a max.
>  larger than MAX_PACKET_OFF.
>
> So you need to do something like
>   if (offset < 0xffff && packet_start + offset < packet_end)
>  because the verifier isn't smart enough to learn anything from an
>   if (ptr_to_packet <= ptr_to_packet + offset)
>  check.  The trouble with the latter is that it's a piece of state that
>  belongs both to the packet-pointer (which in the general case could be
>  variably-offset already) and to the offset, so you'd need n² storage to
>  record which (pointer, offset) pairs had been determined not to overflow.
>
> HTH,
> -Ed




[Index of Archives]     [Linux Networking Development]     [Fedora Linux Users]     [Linux SCTP]     [DCCP]     [Gimp]     [Yosemite Campsites]

  Powered by Linux