2022-05-23 14:07:12 -07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/* Copyright (c) 2022 Facebook */
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
2023-05-16 21:04:04 -07:00
|
|
|
#include <stdbool.h>
|
2022-05-23 14:07:12 -07:00
|
|
|
#include <linux/bpf.h>
|
|
|
|
#include <bpf/bpf_helpers.h>
|
selftests/bpf: tests for using dynptrs to parse skb and xdp buffers
Test skb and xdp dynptr functionality in the following ways:
1) progs/test_cls_redirect_dynptr.c
* Rewrite "progs/test_cls_redirect.c" test to use dynptrs to parse
skb data
* This is a great example of how dynptrs can be used to simplify a
lot of the parsing logic for non-statically known values.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t cls_redirect"):
original version: 0.092 sec
with dynptrs: 0.078 sec
2) progs/test_xdp_dynptr.c
* Rewrite "progs/test_xdp.c" test to use dynptrs to parse xdp data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t xdp_attach"):
original version: 0.118 sec
with dynptrs: 0.094 sec
3) progs/test_l4lb_noinline_dynptr.c
* Rewrite "progs/test_l4lb_noinline.c" test to use dynptrs to parse
skb data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t l4lb_all"):
original version: 0.062 sec
with dynptrs: 0.081 sec
For number of processed verifier instructions:
original version: 6268 insns
with dynptrs: 2588 insns
4) progs/test_parse_tcp_hdr_opt_dynptr.c
* Add sample code for parsing tcp hdr opt lookup using dynptrs.
This logic is lifted from a real-world use case of packet parsing
in katran [0], a layer 4 load balancer. The original version
"progs/test_parse_tcp_hdr_opt.c" (not using dynptrs) is included
here as well, for comparison.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t parse_tcp_hdr_opt"):
original version: 0.031 sec
with dynptrs: 0.045 sec
5) progs/dynptr_success.c
* Add test case "test_skb_readonly" for testing attempts at writes
on a prog type with read-only skb ctx.
* Add "test_dynptr_skb_data" for testing that bpf_dynptr_data isn't
supported for skb progs.
6) progs/dynptr_fail.c
* Add test cases "skb_invalid_data_slice{1,2,3,4}" and
"xdp_invalid_data_slice{1,2}" for testing that helpers that modify the
underlying packet buffer automatically invalidate the associated
data slice.
* Add test cases "skb_invalid_ctx" and "xdp_invalid_ctx" for testing
that prog types that do not support bpf_dynptr_from_skb/xdp don't
have access to the API.
* Add test case "dynptr_slice_var_len{1,2}" for testing that
variable-sized len can't be passed in to bpf_dynptr_slice
* Add test case "skb_invalid_slice_write" for testing that writes to a
read-only data slice are rejected by the verifier.
* Add test case "data_slice_out_of_bounds_skb" for testing that
writes to an area outside the slice are rejected.
* Add test case "invalid_slice_rdwr_rdonly" for testing that prog
types that don't allow writes to packet data don't accept any calls
to bpf_dynptr_slice_rdwr.
[0] https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230301154953.641654-11-joannelkoong@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-03-01 07:49:53 -08:00
|
|
|
#include <linux/if_ether.h>
|
2022-05-23 14:07:12 -07:00
|
|
|
#include "bpf_misc.h"
|
selftests/bpf: tests for using dynptrs to parse skb and xdp buffers
Test skb and xdp dynptr functionality in the following ways:
1) progs/test_cls_redirect_dynptr.c
* Rewrite "progs/test_cls_redirect.c" test to use dynptrs to parse
skb data
* This is a great example of how dynptrs can be used to simplify a
lot of the parsing logic for non-statically known values.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t cls_redirect"):
original version: 0.092 sec
with dynptrs: 0.078 sec
2) progs/test_xdp_dynptr.c
* Rewrite "progs/test_xdp.c" test to use dynptrs to parse xdp data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t xdp_attach"):
original version: 0.118 sec
with dynptrs: 0.094 sec
3) progs/test_l4lb_noinline_dynptr.c
* Rewrite "progs/test_l4lb_noinline.c" test to use dynptrs to parse
skb data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t l4lb_all"):
original version: 0.062 sec
with dynptrs: 0.081 sec
For number of processed verifier instructions:
original version: 6268 insns
with dynptrs: 2588 insns
4) progs/test_parse_tcp_hdr_opt_dynptr.c
* Add sample code for parsing tcp hdr opt lookup using dynptrs.
This logic is lifted from a real-world use case of packet parsing
in katran [0], a layer 4 load balancer. The original version
"progs/test_parse_tcp_hdr_opt.c" (not using dynptrs) is included
here as well, for comparison.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t parse_tcp_hdr_opt"):
original version: 0.031 sec
with dynptrs: 0.045 sec
5) progs/dynptr_success.c
* Add test case "test_skb_readonly" for testing attempts at writes
on a prog type with read-only skb ctx.
* Add "test_dynptr_skb_data" for testing that bpf_dynptr_data isn't
supported for skb progs.
6) progs/dynptr_fail.c
* Add test cases "skb_invalid_data_slice{1,2,3,4}" and
"xdp_invalid_data_slice{1,2}" for testing that helpers that modify the
underlying packet buffer automatically invalidate the associated
data slice.
* Add test cases "skb_invalid_ctx" and "xdp_invalid_ctx" for testing
that prog types that do not support bpf_dynptr_from_skb/xdp don't
have access to the API.
* Add test case "dynptr_slice_var_len{1,2}" for testing that
variable-sized len can't be passed in to bpf_dynptr_slice
* Add test case "skb_invalid_slice_write" for testing that writes to a
read-only data slice are rejected by the verifier.
* Add test case "data_slice_out_of_bounds_skb" for testing that
writes to an area outside the slice are rejected.
* Add test case "invalid_slice_rdwr_rdonly" for testing that prog
types that don't allow writes to packet data don't accept any calls
to bpf_dynptr_slice_rdwr.
[0] https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230301154953.641654-11-joannelkoong@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-03-01 07:49:53 -08:00
|
|
|
#include "bpf_kfuncs.h"
|
2022-05-23 14:07:12 -07:00
|
|
|
|
|
|
|
char _license[] SEC("license") = "GPL";
|
|
|
|
|
|
|
|
struct test_info {
|
|
|
|
int x;
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct {
|
|
|
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
|
|
__uint(max_entries, 1);
|
|
|
|
__type(key, __u32);
|
|
|
|
__type(value, struct bpf_dynptr);
|
|
|
|
} array_map1 SEC(".maps");
|
|
|
|
|
|
|
|
struct {
|
|
|
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
|
|
__uint(max_entries, 1);
|
|
|
|
__type(key, __u32);
|
|
|
|
__type(value, struct test_info);
|
|
|
|
} array_map2 SEC(".maps");
|
|
|
|
|
|
|
|
struct {
|
|
|
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
|
|
__uint(max_entries, 1);
|
|
|
|
__type(key, __u32);
|
|
|
|
__type(value, __u32);
|
|
|
|
} array_map3 SEC(".maps");
|
|
|
|
|
2023-01-21 05:52:38 +05:30
|
|
|
struct {
|
|
|
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
|
|
|
__uint(max_entries, 1);
|
|
|
|
__type(key, __u32);
|
|
|
|
__type(value, __u64);
|
|
|
|
} array_map4 SEC(".maps");
|
|
|
|
|
2022-05-23 14:07:12 -07:00
|
|
|
struct sample {
|
|
|
|
int pid;
|
|
|
|
long value;
|
|
|
|
char comm[16];
|
|
|
|
};
|
|
|
|
|
|
|
|
struct {
|
|
|
|
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
2022-12-07 12:16:48 -08:00
|
|
|
__uint(max_entries, 4096);
|
2022-05-23 14:07:12 -07:00
|
|
|
} ringbuf SEC(".maps");
|
|
|
|
|
|
|
|
int err, val;
|
|
|
|
|
|
|
|
static int get_map_val_dynptr(struct bpf_dynptr *ptr)
|
|
|
|
{
|
|
|
|
__u32 key = 0, *map_val;
|
|
|
|
|
|
|
|
bpf_map_update_elem(&array_map3, &key, &val, 0);
|
|
|
|
|
|
|
|
map_val = bpf_map_lookup_elem(&array_map3, &key);
|
|
|
|
if (!map_val)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
bpf_dynptr_from_mem(map_val, sizeof(*map_val), 0, ptr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Every bpf_ringbuf_reserve_dynptr call must have a corresponding
|
|
|
|
* bpf_ringbuf_submit/discard_dynptr call
|
|
|
|
*/
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
bpf: Invalidate slices on destruction of dynptrs on stack
The previous commit implemented destroy_if_dynptr_stack_slot. It
destroys the dynptr which given spi belongs to, but still doesn't
invalidate the slices that belong to such a dynptr. While for the case
of referenced dynptr, we don't allow their overwrite and return an error
early, we still allow it and destroy the dynptr for unreferenced dynptr.
To be able to enable precise and scoped invalidation of dynptr slices in
this case, we must be able to associate the source dynptr of slices that
have been obtained using bpf_dynptr_data. When doing destruction, only
slices belonging to the dynptr being destructed should be invalidated,
and nothing else. Currently, dynptr slices belonging to different
dynptrs are indistinguishible.
Hence, allocate a unique id to each dynptr (CONST_PTR_TO_DYNPTR and
those on stack). This will be stored as part of reg->id. Whenever using
bpf_dynptr_data, transfer this unique dynptr id to the returned
PTR_TO_MEM_OR_NULL slice pointer, and store it in a new per-PTR_TO_MEM
dynptr_id register state member.
Finally, after establishing such a relationship between dynptrs and
their slices, implement precise invalidation logic that only invalidates
slices belong to the destroyed dynptr in destroy_if_dynptr_stack_slot.
Acked-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20230121002241.2113993-5-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-01-21 05:52:33 +05:30
|
|
|
__failure __msg("Unreleased reference id=2")
|
2022-05-23 14:07:12 -07:00
|
|
|
int ringbuf_missing_release1(void *ctx)
|
|
|
|
{
|
2024-05-10 19:38:50 +01:00
|
|
|
struct bpf_dynptr ptr = {};
|
2022-05-23 14:07:12 -07:00
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
/* missing a call to bpf_ringbuf_discard/submit_dynptr */
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
bpf: Invalidate slices on destruction of dynptrs on stack
The previous commit implemented destroy_if_dynptr_stack_slot. It
destroys the dynptr which given spi belongs to, but still doesn't
invalidate the slices that belong to such a dynptr. While for the case
of referenced dynptr, we don't allow their overwrite and return an error
early, we still allow it and destroy the dynptr for unreferenced dynptr.
To be able to enable precise and scoped invalidation of dynptr slices in
this case, we must be able to associate the source dynptr of slices that
have been obtained using bpf_dynptr_data. When doing destruction, only
slices belonging to the dynptr being destructed should be invalidated,
and nothing else. Currently, dynptr slices belonging to different
dynptrs are indistinguishible.
Hence, allocate a unique id to each dynptr (CONST_PTR_TO_DYNPTR and
those on stack). This will be stored as part of reg->id. Whenever using
bpf_dynptr_data, transfer this unique dynptr id to the returned
PTR_TO_MEM_OR_NULL slice pointer, and store it in a new per-PTR_TO_MEM
dynptr_id register state member.
Finally, after establishing such a relationship between dynptrs and
their slices, implement precise invalidation logic that only invalidates
slices belong to the destroyed dynptr in destroy_if_dynptr_stack_slot.
Acked-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20230121002241.2113993-5-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-01-21 05:52:33 +05:30
|
|
|
__failure __msg("Unreleased reference id=4")
|
2022-05-23 14:07:12 -07:00
|
|
|
int ringbuf_missing_release2(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr1, ptr2;
|
|
|
|
struct sample *sample;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr1);
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr2);
|
|
|
|
|
|
|
|
sample = bpf_dynptr_data(&ptr1, 0, sizeof(*sample));
|
|
|
|
if (!sample) {
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr1, 0);
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr2, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr1, 0);
|
|
|
|
|
|
|
|
/* missing a call to bpf_ringbuf_discard/submit_dynptr on ptr2 */
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int missing_release_callback_fn(__u32 index, void *data)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
/* missing a call to bpf_ringbuf_discard/submit_dynptr */
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Any dynptr initialized within a callback must have bpf_dynptr_put called */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("Unreleased reference id")
|
2022-05-23 14:07:12 -07:00
|
|
|
int ringbuf_missing_release_callback(void *ctx)
|
|
|
|
{
|
|
|
|
bpf_loop(10, missing_release_callback_fn, NULL, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't call bpf_ringbuf_submit/discard_dynptr on a non-initialized dynptr */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("arg 1 is an unacquired reference")
|
2022-05-23 14:07:12 -07:00
|
|
|
int ringbuf_release_uninit_dynptr(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A dynptr can't be used after it has been invalidated */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("Expected an initialized dynptr as arg #3")
|
2022-05-23 14:07:12 -07:00
|
|
|
int use_after_invalid(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
char read_data[64];
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(read_data), 0, &ptr);
|
|
|
|
|
2022-07-06 16:25:47 -07:00
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0, 0);
|
2022-05-23 14:07:12 -07:00
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
2022-07-06 16:25:47 -07:00
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0, 0);
|
2022-05-23 14:07:12 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't call non-dynptr ringbuf APIs on a dynptr ringbuf sample */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("type=mem expected=ringbuf_mem")
|
2022-05-23 14:07:12 -07:00
|
|
|
int ringbuf_invalid_api(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct sample *sample;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr);
|
|
|
|
sample = bpf_dynptr_data(&ptr, 0, sizeof(*sample));
|
|
|
|
if (!sample)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
sample->pid = 123;
|
|
|
|
|
|
|
|
/* invalid API use. need to use dynptr API to submit/discard */
|
|
|
|
bpf_ringbuf_submit(sample, 0);
|
|
|
|
|
|
|
|
done:
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't add a dynptr to a map */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid indirect read from stack")
|
2022-05-23 14:07:12 -07:00
|
|
|
int add_dynptr_to_map1(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
int key = 0;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_map_update_elem(&array_map1, &key, &ptr, 0);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't add a struct with an embedded dynptr to a map */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid indirect read from stack")
|
2022-05-23 14:07:12 -07:00
|
|
|
int add_dynptr_to_map2(void *ctx)
|
|
|
|
{
|
|
|
|
struct test_info x;
|
|
|
|
int key = 0;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &x.ptr);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_map_update_elem(&array_map2, &key, &x, 0);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&x.ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A data slice can't be accessed out of bounds */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("value is outside of the allowed memory range")
|
2022-05-23 14:07:12 -07:00
|
|
|
int data_slice_out_of_bounds_ringbuf(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
void *data;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 8, 0, &ptr);
|
|
|
|
|
|
|
|
data = bpf_dynptr_data(&ptr, 0, 8);
|
|
|
|
if (!data)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
/* can't index out of bounds of the data slice */
|
|
|
|
val = *((char *)data + 8);
|
|
|
|
|
|
|
|
done:
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
selftests/bpf: tests for using dynptrs to parse skb and xdp buffers
Test skb and xdp dynptr functionality in the following ways:
1) progs/test_cls_redirect_dynptr.c
* Rewrite "progs/test_cls_redirect.c" test to use dynptrs to parse
skb data
* This is a great example of how dynptrs can be used to simplify a
lot of the parsing logic for non-statically known values.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t cls_redirect"):
original version: 0.092 sec
with dynptrs: 0.078 sec
2) progs/test_xdp_dynptr.c
* Rewrite "progs/test_xdp.c" test to use dynptrs to parse xdp data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t xdp_attach"):
original version: 0.118 sec
with dynptrs: 0.094 sec
3) progs/test_l4lb_noinline_dynptr.c
* Rewrite "progs/test_l4lb_noinline.c" test to use dynptrs to parse
skb data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t l4lb_all"):
original version: 0.062 sec
with dynptrs: 0.081 sec
For number of processed verifier instructions:
original version: 6268 insns
with dynptrs: 2588 insns
4) progs/test_parse_tcp_hdr_opt_dynptr.c
* Add sample code for parsing tcp hdr opt lookup using dynptrs.
This logic is lifted from a real-world use case of packet parsing
in katran [0], a layer 4 load balancer. The original version
"progs/test_parse_tcp_hdr_opt.c" (not using dynptrs) is included
here as well, for comparison.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t parse_tcp_hdr_opt"):
original version: 0.031 sec
with dynptrs: 0.045 sec
5) progs/dynptr_success.c
* Add test case "test_skb_readonly" for testing attempts at writes
on a prog type with read-only skb ctx.
* Add "test_dynptr_skb_data" for testing that bpf_dynptr_data isn't
supported for skb progs.
6) progs/dynptr_fail.c
* Add test cases "skb_invalid_data_slice{1,2,3,4}" and
"xdp_invalid_data_slice{1,2}" for testing that helpers that modify the
underlying packet buffer automatically invalidate the associated
data slice.
* Add test cases "skb_invalid_ctx" and "xdp_invalid_ctx" for testing
that prog types that do not support bpf_dynptr_from_skb/xdp don't
have access to the API.
* Add test case "dynptr_slice_var_len{1,2}" for testing that
variable-sized len can't be passed in to bpf_dynptr_slice
* Add test case "skb_invalid_slice_write" for testing that writes to a
read-only data slice are rejected by the verifier.
* Add test case "data_slice_out_of_bounds_skb" for testing that
writes to an area outside the slice are rejected.
* Add test case "invalid_slice_rdwr_rdonly" for testing that prog
types that don't allow writes to packet data don't accept any calls
to bpf_dynptr_slice_rdwr.
[0] https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230301154953.641654-11-joannelkoong@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-03-01 07:49:53 -08:00
|
|
|
/* A data slice can't be accessed out of bounds */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("value is outside of the allowed memory range")
|
|
|
|
int data_slice_out_of_bounds_skb(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*(__u8*)(hdr + 1) = 1;
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("value is outside of the allowed memory range")
|
2022-05-23 14:07:12 -07:00
|
|
|
int data_slice_out_of_bounds_map_value(void *ctx)
|
|
|
|
{
|
2023-03-08 21:40:14 -08:00
|
|
|
__u32 map_val;
|
2022-05-23 14:07:12 -07:00
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
void *data;
|
|
|
|
|
|
|
|
get_map_val_dynptr(&ptr);
|
|
|
|
|
|
|
|
data = bpf_dynptr_data(&ptr, 0, sizeof(map_val));
|
|
|
|
if (!data)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* can't index out of bounds of the data slice */
|
|
|
|
val = *((char *)data + (sizeof(map_val) + 1));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A data slice can't be used after it has been released */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
2022-08-09 14:40:55 -07:00
|
|
|
int data_slice_use_after_release1(void *ctx)
|
2022-05-23 14:07:12 -07:00
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct sample *sample;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr);
|
|
|
|
sample = bpf_dynptr_data(&ptr, 0, sizeof(*sample));
|
|
|
|
if (!sample)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
sample->pid = 123;
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
val = sample->pid;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
done:
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-08-09 14:40:55 -07:00
|
|
|
/* A data slice can't be used after it has been released.
|
|
|
|
*
|
|
|
|
* This tests the case where the data slice tracks a dynptr (ptr2)
|
|
|
|
* that is at a non-zero offset from the frame pointer (ptr1 is at fp,
|
|
|
|
* ptr2 is at fp - 16).
|
|
|
|
*/
|
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
2022-08-09 14:40:55 -07:00
|
|
|
int data_slice_use_after_release2(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr1, ptr2;
|
|
|
|
struct sample *sample;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr1);
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, sizeof(*sample), 0, &ptr2);
|
|
|
|
|
|
|
|
sample = bpf_dynptr_data(&ptr2, 0, sizeof(*sample));
|
|
|
|
if (!sample)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
sample->pid = 23;
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr2, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
sample->pid = 23;
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr1, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
done:
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr2, 0);
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr1, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-05-23 14:07:12 -07:00
|
|
|
/* A data slice must be first checked for NULL */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid mem access 'mem_or_null'")
|
2022-05-23 14:07:12 -07:00
|
|
|
int data_slice_missing_null_check1(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
void *data;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 8, 0, &ptr);
|
|
|
|
|
|
|
|
data = bpf_dynptr_data(&ptr, 0, 8);
|
|
|
|
|
|
|
|
/* missing if (!data) check */
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*(__u8 *)data = 3;
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A data slice can't be dereferenced if it wasn't checked for null */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid mem access 'mem_or_null'")
|
2022-05-23 14:07:12 -07:00
|
|
|
int data_slice_missing_null_check2(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
__u64 *data1, *data2;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 16, 0, &ptr);
|
|
|
|
|
|
|
|
data1 = bpf_dynptr_data(&ptr, 0, 8);
|
|
|
|
data2 = bpf_dynptr_data(&ptr, 0, 8);
|
|
|
|
if (data1)
|
|
|
|
/* this should fail */
|
|
|
|
*data2 = 3;
|
|
|
|
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't pass in a dynptr as an arg to a helper function that doesn't take in a
|
|
|
|
* dynptr argument
|
|
|
|
*/
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid indirect read from stack")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_helper1(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
get_map_val_dynptr(&ptr);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_strncmp((const char *)&ptr, sizeof(ptr), "hello!");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A dynptr can't be passed into a helper function at a non-zero offset */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
bpf: Fix missing var_off check for ARG_PTR_TO_DYNPTR
Currently, the dynptr function is not checking the variable offset part
of PTR_TO_STACK that it needs to check. The fixed offset is considered
when computing the stack pointer index, but if the variable offset was
not a constant (such that it could not be accumulated in reg->off), we
will end up a discrepency where runtime pointer does not point to the
actual stack slot we mark as STACK_DYNPTR.
It is impossible to precisely track dynptr state when variable offset is
not constant, hence, just like bpf_timer, kptr, bpf_spin_lock, etc.
simply reject the case where reg->var_off is not constant. Then,
consider both reg->off and reg->var_off.value when computing the stack
pointer index.
A new helper dynptr_get_spi is introduced to hide over these details
since the dynptr needs to be located in multiple places outside the
process_dynptr_func checks, hence once we know it's a PTR_TO_STACK, we
need to enforce these checks in all places.
Note that it is disallowed for unprivileged users to have a non-constant
var_off, so this problem should only be possible to trigger from
programs having CAP_PERFMON. However, its effects can vary.
Without the fix, it is possible to replace the contents of the dynptr
arbitrarily by making verifier mark different stack slots than actual
location and then doing writes to the actual stack address of dynptr at
runtime.
Fixes: 97e03f521050 ("bpf: Add verifier support for dynptrs")
Acked-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20230121002241.2113993-3-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-01-21 05:52:31 +05:30
|
|
|
__failure __msg("cannot pass in dynptr at an offset=-8")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_helper2(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
char read_data[64];
|
|
|
|
|
|
|
|
get_map_val_dynptr(&ptr);
|
|
|
|
|
|
|
|
/* this should fail */
|
2022-07-06 16:25:47 -07:00
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), (void *)&ptr + 8, 0, 0);
|
2022-05-23 14:07:12 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A bpf_dynptr is invalidated if it's been written into */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("Expected an initialized dynptr as arg #1")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_write1(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
void *data;
|
|
|
|
__u8 x = 0;
|
|
|
|
|
|
|
|
get_map_val_dynptr(&ptr);
|
|
|
|
|
|
|
|
memcpy(&ptr, &x, sizeof(x));
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
data = bpf_dynptr_data(&ptr, 0, 1);
|
2023-03-08 21:40:14 -08:00
|
|
|
__sink(data);
|
2022-05-23 14:07:12 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A bpf_dynptr can't be used as a dynptr if it has been written into at a fixed
|
|
|
|
* offset
|
|
|
|
*/
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
bpf: Fix partial dynptr stack slot reads/writes
Currently, while reads are disallowed for dynptr stack slots, writes are
not. Reads don't work from both direct access and helpers, while writes
do work in both cases, but have the effect of overwriting the slot_type.
While this is fine, handling for a few edge cases is missing. Firstly,
a user can overwrite the stack slots of dynptr partially.
Consider the following layout:
spi: [d][d][?]
2 1 0
First slot is at spi 2, second at spi 1.
Now, do a write of 1 to 8 bytes for spi 1.
This will essentially either write STACK_MISC for all slot_types or
STACK_MISC and STACK_ZERO (in case of size < BPF_REG_SIZE partial write
of zeroes). The end result is that slot is scrubbed.
Now, the layout is:
spi: [d][m][?]
2 1 0
Suppose if user initializes spi = 1 as dynptr.
We get:
spi: [d][d][d]
2 1 0
But this time, both spi 2 and spi 1 have first_slot = true.
Now, when passing spi 2 to dynptr helper, it will consider it as
initialized as it does not check whether second slot has first_slot ==
false. And spi 1 should already work as normal.
This effectively replaced size + offset of first dynptr, hence allowing
invalid OOB reads and writes.
Make a few changes to protect against this:
When writing to PTR_TO_STACK using BPF insns, when we touch spi of a
STACK_DYNPTR type, mark both first and second slot (regardless of which
slot we touch) as STACK_INVALID. Reads are already prevented.
Second, prevent writing to stack memory from helpers if the range may
contain any STACK_DYNPTR slots. Reads are already prevented.
For helpers, we cannot allow it to destroy dynptrs from the writes as
depending on arguments, helper may take uninit_mem and dynptr both at
the same time. This would mean that helper may write to uninit_mem
before it reads the dynptr, which would be bad.
PTR_TO_MEM: [?????dd]
Depending on the code inside the helper, it may end up overwriting the
dynptr contents first and then read those as the dynptr argument.
Verifier would only simulate destruction when it does byte by byte
access simulation in check_helper_call for meta.access_size, and
fail to catch this case, as it happens after argument checks.
The same would need to be done for any other non-trivial objects created
on the stack in the future, such as bpf_list_head on stack, or
bpf_rb_root on stack.
A common misunderstanding in the current code is that MEM_UNINIT means
writes, but note that writes may also be performed even without
MEM_UNINIT in case of helpers, in that case the code after handling meta
&& meta->raw_mode will complain when it sees STACK_DYNPTR. So that
invalid read case also covers writes to potential STACK_DYNPTR slots.
The only loophole was in case of meta->raw_mode which simulated writes
through instructions which could overwrite them.
A future series sequenced after this will focus on the clean up of
helper access checks and bugs around that.
Fixes: 97e03f521050 ("bpf: Add verifier support for dynptrs")
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20230121002241.2113993-4-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-01-21 05:52:32 +05:30
|
|
|
__failure __msg("cannot overwrite referenced dynptr")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_write2(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
char read_data[64];
|
|
|
|
__u8 x = 0;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
|
|
|
|
|
|
|
|
memcpy((void *)&ptr + 8, &x, sizeof(x));
|
|
|
|
|
|
|
|
/* this should fail */
|
2022-07-06 16:25:47 -07:00
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0, 0);
|
2022-05-23 14:07:12 -07:00
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A bpf_dynptr can't be used as a dynptr if it has been written into at a
|
|
|
|
* non-const offset
|
|
|
|
*/
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
bpf: Fix partial dynptr stack slot reads/writes
Currently, while reads are disallowed for dynptr stack slots, writes are
not. Reads don't work from both direct access and helpers, while writes
do work in both cases, but have the effect of overwriting the slot_type.
While this is fine, handling for a few edge cases is missing. Firstly,
a user can overwrite the stack slots of dynptr partially.
Consider the following layout:
spi: [d][d][?]
2 1 0
First slot is at spi 2, second at spi 1.
Now, do a write of 1 to 8 bytes for spi 1.
This will essentially either write STACK_MISC for all slot_types or
STACK_MISC and STACK_ZERO (in case of size < BPF_REG_SIZE partial write
of zeroes). The end result is that slot is scrubbed.
Now, the layout is:
spi: [d][m][?]
2 1 0
Suppose if user initializes spi = 1 as dynptr.
We get:
spi: [d][d][d]
2 1 0
But this time, both spi 2 and spi 1 have first_slot = true.
Now, when passing spi 2 to dynptr helper, it will consider it as
initialized as it does not check whether second slot has first_slot ==
false. And spi 1 should already work as normal.
This effectively replaced size + offset of first dynptr, hence allowing
invalid OOB reads and writes.
Make a few changes to protect against this:
When writing to PTR_TO_STACK using BPF insns, when we touch spi of a
STACK_DYNPTR type, mark both first and second slot (regardless of which
slot we touch) as STACK_INVALID. Reads are already prevented.
Second, prevent writing to stack memory from helpers if the range may
contain any STACK_DYNPTR slots. Reads are already prevented.
For helpers, we cannot allow it to destroy dynptrs from the writes as
depending on arguments, helper may take uninit_mem and dynptr both at
the same time. This would mean that helper may write to uninit_mem
before it reads the dynptr, which would be bad.
PTR_TO_MEM: [?????dd]
Depending on the code inside the helper, it may end up overwriting the
dynptr contents first and then read those as the dynptr argument.
Verifier would only simulate destruction when it does byte by byte
access simulation in check_helper_call for meta.access_size, and
fail to catch this case, as it happens after argument checks.
The same would need to be done for any other non-trivial objects created
on the stack in the future, such as bpf_list_head on stack, or
bpf_rb_root on stack.
A common misunderstanding in the current code is that MEM_UNINIT means
writes, but note that writes may also be performed even without
MEM_UNINIT in case of helpers, in that case the code after handling meta
&& meta->raw_mode will complain when it sees STACK_DYNPTR. So that
invalid read case also covers writes to potential STACK_DYNPTR slots.
The only loophole was in case of meta->raw_mode which simulated writes
through instructions which could overwrite them.
A future series sequenced after this will focus on the clean up of
helper access checks and bugs around that.
Fixes: 97e03f521050 ("bpf: Add verifier support for dynptrs")
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20230121002241.2113993-4-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-01-21 05:52:32 +05:30
|
|
|
__failure __msg("cannot overwrite referenced dynptr")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_write3(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
char stack_buf[16];
|
|
|
|
unsigned long len;
|
|
|
|
__u8 x = 0;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 8, 0, &ptr);
|
|
|
|
|
|
|
|
memcpy(stack_buf, &val, sizeof(val));
|
|
|
|
len = stack_buf[0] & 0xf;
|
|
|
|
|
|
|
|
memcpy((void *)&ptr + len, &x, sizeof(x));
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int invalid_write4_callback(__u32 index, void *data)
|
|
|
|
{
|
|
|
|
*(__u32 *)data = 123;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the dynptr is written into in a callback function, it should
|
|
|
|
* be invalidated as a dynptr
|
|
|
|
*/
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
bpf: Fix partial dynptr stack slot reads/writes
Currently, while reads are disallowed for dynptr stack slots, writes are
not. Reads don't work from both direct access and helpers, while writes
do work in both cases, but have the effect of overwriting the slot_type.
While this is fine, handling for a few edge cases is missing. Firstly,
a user can overwrite the stack slots of dynptr partially.
Consider the following layout:
spi: [d][d][?]
2 1 0
First slot is at spi 2, second at spi 1.
Now, do a write of 1 to 8 bytes for spi 1.
This will essentially either write STACK_MISC for all slot_types or
STACK_MISC and STACK_ZERO (in case of size < BPF_REG_SIZE partial write
of zeroes). The end result is that slot is scrubbed.
Now, the layout is:
spi: [d][m][?]
2 1 0
Suppose if user initializes spi = 1 as dynptr.
We get:
spi: [d][d][d]
2 1 0
But this time, both spi 2 and spi 1 have first_slot = true.
Now, when passing spi 2 to dynptr helper, it will consider it as
initialized as it does not check whether second slot has first_slot ==
false. And spi 1 should already work as normal.
This effectively replaced size + offset of first dynptr, hence allowing
invalid OOB reads and writes.
Make a few changes to protect against this:
When writing to PTR_TO_STACK using BPF insns, when we touch spi of a
STACK_DYNPTR type, mark both first and second slot (regardless of which
slot we touch) as STACK_INVALID. Reads are already prevented.
Second, prevent writing to stack memory from helpers if the range may
contain any STACK_DYNPTR slots. Reads are already prevented.
For helpers, we cannot allow it to destroy dynptrs from the writes as
depending on arguments, helper may take uninit_mem and dynptr both at
the same time. This would mean that helper may write to uninit_mem
before it reads the dynptr, which would be bad.
PTR_TO_MEM: [?????dd]
Depending on the code inside the helper, it may end up overwriting the
dynptr contents first and then read those as the dynptr argument.
Verifier would only simulate destruction when it does byte by byte
access simulation in check_helper_call for meta.access_size, and
fail to catch this case, as it happens after argument checks.
The same would need to be done for any other non-trivial objects created
on the stack in the future, such as bpf_list_head on stack, or
bpf_rb_root on stack.
A common misunderstanding in the current code is that MEM_UNINIT means
writes, but note that writes may also be performed even without
MEM_UNINIT in case of helpers, in that case the code after handling meta
&& meta->raw_mode will complain when it sees STACK_DYNPTR. So that
invalid read case also covers writes to potential STACK_DYNPTR slots.
The only loophole was in case of meta->raw_mode which simulated writes
through instructions which could overwrite them.
A future series sequenced after this will focus on the clean up of
helper access checks and bugs around that.
Fixes: 97e03f521050 ("bpf: Add verifier support for dynptrs")
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20230121002241.2113993-4-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-01-21 05:52:32 +05:30
|
|
|
__failure __msg("cannot overwrite referenced dynptr")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_write4(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_loop(10, invalid_write4_callback, &ptr, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A globally-defined bpf_dynptr can't be used (it must reside as a stack frame) */
|
|
|
|
struct bpf_dynptr global_dynptr;
|
2022-12-07 12:16:48 -08:00
|
|
|
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("type=map_value expected=fp")
|
2022-05-23 14:07:12 -07:00
|
|
|
int global(void *ctx)
|
|
|
|
{
|
|
|
|
/* this should fail */
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 16, 0, &global_dynptr);
|
|
|
|
|
|
|
|
bpf_ringbuf_discard_dynptr(&global_dynptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A direct read should fail */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid read from stack")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_read1(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
val = *(int *)&ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A direct read at an offset should fail */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("cannot pass in dynptr at an offset")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_read2(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
char read_data[64];
|
|
|
|
|
|
|
|
get_map_val_dynptr(&ptr);
|
|
|
|
|
|
|
|
/* this should fail */
|
2022-07-06 16:25:47 -07:00
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), (void *)&ptr + 1, 0, 0);
|
2022-05-23 14:07:12 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A direct read at an offset into the lower stack slot should fail */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid read from stack")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_read3(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr1, ptr2;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 16, 0, &ptr1);
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 16, 0, &ptr2);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
memcpy(&val, (void *)&ptr1 + 8, sizeof(val));
|
|
|
|
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr1, 0);
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr2, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int invalid_read4_callback(__u32 index, void *data)
|
|
|
|
{
|
|
|
|
/* this should fail */
|
|
|
|
val = *(__u32 *)data;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A direct read within a callback function should fail */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("invalid read from stack")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_read4(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_loop(10, invalid_read4_callback, &ptr, 0);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initializing a dynptr on an offset should fail */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
bpf: Fix missing var_off check for ARG_PTR_TO_DYNPTR
Currently, the dynptr function is not checking the variable offset part
of PTR_TO_STACK that it needs to check. The fixed offset is considered
when computing the stack pointer index, but if the variable offset was
not a constant (such that it could not be accumulated in reg->off), we
will end up a discrepency where runtime pointer does not point to the
actual stack slot we mark as STACK_DYNPTR.
It is impossible to precisely track dynptr state when variable offset is
not constant, hence, just like bpf_timer, kptr, bpf_spin_lock, etc.
simply reject the case where reg->var_off is not constant. Then,
consider both reg->off and reg->var_off.value when computing the stack
pointer index.
A new helper dynptr_get_spi is introduced to hide over these details
since the dynptr needs to be located in multiple places outside the
process_dynptr_func checks, hence once we know it's a PTR_TO_STACK, we
need to enforce these checks in all places.
Note that it is disallowed for unprivileged users to have a non-constant
var_off, so this problem should only be possible to trigger from
programs having CAP_PERFMON. However, its effects can vary.
Without the fix, it is possible to replace the contents of the dynptr
arbitrarily by making verifier mark different stack slots than actual
location and then doing writes to the actual stack address of dynptr at
runtime.
Fixes: 97e03f521050 ("bpf: Add verifier support for dynptrs")
Acked-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20230121002241.2113993-3-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-01-21 05:52:31 +05:30
|
|
|
__failure __msg("cannot pass in dynptr at an offset=0")
|
2022-05-23 14:07:12 -07:00
|
|
|
int invalid_offset(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr + 1);
|
|
|
|
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't release a dynptr twice */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("arg 1 is an unacquired reference")
|
2022-05-23 14:07:12 -07:00
|
|
|
int release_twice(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 16, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
/* this second release should fail */
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int release_twice_callback_fn(__u32 index, void *data)
|
|
|
|
{
|
|
|
|
/* this should fail */
|
|
|
|
bpf_ringbuf_discard_dynptr(data, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Test that releasing a dynptr twice, where one of the releases happens
|
2023-02-16 17:55:37 +09:00
|
|
|
* within a callback function, fails
|
2022-05-23 14:07:12 -07:00
|
|
|
*/
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("arg 1 is an unacquired reference")
|
2022-05-23 14:07:12 -07:00
|
|
|
int release_twice_callback(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 32, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
bpf_loop(10, release_twice_callback_fn, &ptr, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reject unsupported local mem types for dynptr_from_mem API */
|
2022-08-05 10:14:05 -07:00
|
|
|
SEC("?raw_tp")
|
2022-12-07 12:16:48 -08:00
|
|
|
__failure __msg("Unsupported reg type fp for bpf_dynptr_from_mem data")
|
2022-05-23 14:07:12 -07:00
|
|
|
int dynptr_from_mem_invalid_api(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
int x = 0;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_from_mem(&x, sizeof(x), 0, &ptr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2023-01-21 05:52:38 +05:30
|
|
|
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("cannot overwrite referenced dynptr") __log_level(2)
|
|
|
|
int dynptr_pruning_overwrite(struct __sk_buff *ctx)
|
|
|
|
{
|
|
|
|
asm volatile (
|
|
|
|
"r9 = 0xeB9F; \
|
|
|
|
r6 = %[ringbuf] ll; \
|
|
|
|
r1 = r6; \
|
|
|
|
r2 = 8; \
|
|
|
|
r3 = 0; \
|
|
|
|
r4 = r10; \
|
|
|
|
r4 += -16; \
|
|
|
|
call %[bpf_ringbuf_reserve_dynptr]; \
|
|
|
|
if r0 == 0 goto pjmp1; \
|
|
|
|
goto pjmp2; \
|
|
|
|
pjmp1: \
|
|
|
|
*(u64 *)(r10 - 16) = r9; \
|
|
|
|
pjmp2: \
|
|
|
|
r1 = r10; \
|
|
|
|
r1 += -16; \
|
|
|
|
r2 = 0; \
|
|
|
|
call %[bpf_ringbuf_discard_dynptr]; "
|
|
|
|
:
|
|
|
|
: __imm(bpf_ringbuf_reserve_dynptr),
|
|
|
|
__imm(bpf_ringbuf_discard_dynptr),
|
|
|
|
__imm_addr(ringbuf)
|
|
|
|
: __clobber_all
|
|
|
|
);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SEC("?tc")
|
|
|
|
__success __msg("12: safe") __log_level(2)
|
|
|
|
int dynptr_pruning_stacksafe(struct __sk_buff *ctx)
|
|
|
|
{
|
|
|
|
asm volatile (
|
|
|
|
"r9 = 0xeB9F; \
|
|
|
|
r6 = %[ringbuf] ll; \
|
|
|
|
r1 = r6; \
|
|
|
|
r2 = 8; \
|
|
|
|
r3 = 0; \
|
|
|
|
r4 = r10; \
|
|
|
|
r4 += -16; \
|
|
|
|
call %[bpf_ringbuf_reserve_dynptr]; \
|
|
|
|
if r0 == 0 goto stjmp1; \
|
|
|
|
goto stjmp2; \
|
|
|
|
stjmp1: \
|
|
|
|
r9 = r9; \
|
|
|
|
stjmp2: \
|
|
|
|
r1 = r10; \
|
|
|
|
r1 += -16; \
|
|
|
|
r2 = 0; \
|
|
|
|
call %[bpf_ringbuf_discard_dynptr]; "
|
|
|
|
:
|
|
|
|
: __imm(bpf_ringbuf_reserve_dynptr),
|
|
|
|
__imm(bpf_ringbuf_discard_dynptr),
|
|
|
|
__imm_addr(ringbuf)
|
|
|
|
: __clobber_all
|
|
|
|
);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("cannot overwrite referenced dynptr") __log_level(2)
|
|
|
|
int dynptr_pruning_type_confusion(struct __sk_buff *ctx)
|
|
|
|
{
|
|
|
|
asm volatile (
|
|
|
|
"r6 = %[array_map4] ll; \
|
|
|
|
r7 = %[ringbuf] ll; \
|
|
|
|
r1 = r6; \
|
|
|
|
r2 = r10; \
|
|
|
|
r2 += -8; \
|
|
|
|
r9 = 0; \
|
|
|
|
*(u64 *)(r2 + 0) = r9; \
|
|
|
|
r3 = r10; \
|
|
|
|
r3 += -24; \
|
|
|
|
r9 = 0xeB9FeB9F; \
|
|
|
|
*(u64 *)(r10 - 16) = r9; \
|
|
|
|
*(u64 *)(r10 - 24) = r9; \
|
|
|
|
r9 = 0; \
|
|
|
|
r4 = 0; \
|
|
|
|
r8 = r2; \
|
|
|
|
call %[bpf_map_update_elem]; \
|
|
|
|
r1 = r6; \
|
|
|
|
r2 = r8; \
|
|
|
|
call %[bpf_map_lookup_elem]; \
|
|
|
|
if r0 != 0 goto tjmp1; \
|
|
|
|
exit; \
|
|
|
|
tjmp1: \
|
|
|
|
r8 = r0; \
|
|
|
|
r1 = r7; \
|
|
|
|
r2 = 8; \
|
|
|
|
r3 = 0; \
|
|
|
|
r4 = r10; \
|
|
|
|
r4 += -16; \
|
|
|
|
r0 = *(u64 *)(r0 + 0); \
|
|
|
|
call %[bpf_ringbuf_reserve_dynptr]; \
|
|
|
|
if r0 == 0 goto tjmp2; \
|
|
|
|
r8 = r8; \
|
|
|
|
r8 = r8; \
|
|
|
|
r8 = r8; \
|
|
|
|
r8 = r8; \
|
|
|
|
r8 = r8; \
|
|
|
|
r8 = r8; \
|
|
|
|
r8 = r8; \
|
|
|
|
goto tjmp3; \
|
|
|
|
tjmp2: \
|
|
|
|
*(u64 *)(r10 - 8) = r9; \
|
|
|
|
*(u64 *)(r10 - 16) = r9; \
|
|
|
|
r1 = r8; \
|
|
|
|
r1 += 8; \
|
|
|
|
r2 = 0; \
|
|
|
|
r3 = 0; \
|
|
|
|
r4 = r10; \
|
|
|
|
r4 += -16; \
|
|
|
|
call %[bpf_dynptr_from_mem]; \
|
|
|
|
tjmp3: \
|
|
|
|
r1 = r10; \
|
|
|
|
r1 += -16; \
|
|
|
|
r2 = 0; \
|
|
|
|
call %[bpf_ringbuf_discard_dynptr]; "
|
|
|
|
:
|
|
|
|
: __imm(bpf_map_update_elem),
|
|
|
|
__imm(bpf_map_lookup_elem),
|
|
|
|
__imm(bpf_ringbuf_reserve_dynptr),
|
|
|
|
__imm(bpf_dynptr_from_mem),
|
|
|
|
__imm(bpf_ringbuf_discard_dynptr),
|
|
|
|
__imm_addr(array_map4),
|
|
|
|
__imm_addr(ringbuf)
|
|
|
|
: __clobber_all
|
|
|
|
);
|
|
|
|
return 0;
|
|
|
|
}
|
2023-01-21 05:52:39 +05:30
|
|
|
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("dynptr has to be at a constant offset") __log_level(2)
|
|
|
|
int dynptr_var_off_overwrite(struct __sk_buff *ctx)
|
|
|
|
{
|
|
|
|
asm volatile (
|
|
|
|
"r9 = 16; \
|
|
|
|
*(u32 *)(r10 - 4) = r9; \
|
|
|
|
r8 = *(u32 *)(r10 - 4); \
|
|
|
|
if r8 >= 0 goto vjmp1; \
|
|
|
|
r0 = 1; \
|
|
|
|
exit; \
|
|
|
|
vjmp1: \
|
|
|
|
if r8 <= 16 goto vjmp2; \
|
|
|
|
r0 = 1; \
|
|
|
|
exit; \
|
|
|
|
vjmp2: \
|
|
|
|
r8 &= 16; \
|
|
|
|
r1 = %[ringbuf] ll; \
|
|
|
|
r2 = 8; \
|
|
|
|
r3 = 0; \
|
|
|
|
r4 = r10; \
|
|
|
|
r4 += -32; \
|
|
|
|
r4 += r8; \
|
|
|
|
call %[bpf_ringbuf_reserve_dynptr]; \
|
|
|
|
r9 = 0xeB9F; \
|
|
|
|
*(u64 *)(r10 - 16) = r9; \
|
|
|
|
r1 = r10; \
|
|
|
|
r1 += -32; \
|
|
|
|
r1 += r8; \
|
|
|
|
r2 = 0; \
|
|
|
|
call %[bpf_ringbuf_discard_dynptr]; "
|
|
|
|
:
|
|
|
|
: __imm(bpf_ringbuf_reserve_dynptr),
|
|
|
|
__imm(bpf_ringbuf_discard_dynptr),
|
|
|
|
__imm_addr(ringbuf)
|
|
|
|
: __clobber_all
|
|
|
|
);
|
|
|
|
return 0;
|
|
|
|
}
|
2023-01-21 05:52:40 +05:30
|
|
|
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("cannot overwrite referenced dynptr") __log_level(2)
|
|
|
|
int dynptr_partial_slot_invalidate(struct __sk_buff *ctx)
|
|
|
|
{
|
|
|
|
asm volatile (
|
|
|
|
"r6 = %[ringbuf] ll; \
|
|
|
|
r7 = %[array_map4] ll; \
|
|
|
|
r1 = r7; \
|
|
|
|
r2 = r10; \
|
|
|
|
r2 += -8; \
|
|
|
|
r9 = 0; \
|
|
|
|
*(u64 *)(r2 + 0) = r9; \
|
|
|
|
r3 = r2; \
|
|
|
|
r4 = 0; \
|
|
|
|
r8 = r2; \
|
|
|
|
call %[bpf_map_update_elem]; \
|
|
|
|
r1 = r7; \
|
|
|
|
r2 = r8; \
|
|
|
|
call %[bpf_map_lookup_elem]; \
|
|
|
|
if r0 != 0 goto sjmp1; \
|
|
|
|
exit; \
|
|
|
|
sjmp1: \
|
|
|
|
r7 = r0; \
|
|
|
|
r1 = r6; \
|
|
|
|
r2 = 8; \
|
|
|
|
r3 = 0; \
|
|
|
|
r4 = r10; \
|
|
|
|
r4 += -24; \
|
|
|
|
call %[bpf_ringbuf_reserve_dynptr]; \
|
|
|
|
*(u64 *)(r10 - 16) = r9; \
|
|
|
|
r1 = r7; \
|
|
|
|
r2 = 8; \
|
|
|
|
r3 = 0; \
|
|
|
|
r4 = r10; \
|
|
|
|
r4 += -16; \
|
|
|
|
call %[bpf_dynptr_from_mem]; \
|
|
|
|
r1 = r10; \
|
|
|
|
r1 += -512; \
|
|
|
|
r2 = 488; \
|
|
|
|
r3 = r10; \
|
|
|
|
r3 += -24; \
|
|
|
|
r4 = 0; \
|
|
|
|
r5 = 0; \
|
|
|
|
call %[bpf_dynptr_read]; \
|
|
|
|
r8 = 1; \
|
|
|
|
if r0 != 0 goto sjmp2; \
|
|
|
|
r8 = 0; \
|
|
|
|
sjmp2: \
|
|
|
|
r1 = r10; \
|
|
|
|
r1 += -24; \
|
|
|
|
r2 = 0; \
|
|
|
|
call %[bpf_ringbuf_discard_dynptr]; "
|
|
|
|
:
|
|
|
|
: __imm(bpf_map_update_elem),
|
|
|
|
__imm(bpf_map_lookup_elem),
|
|
|
|
__imm(bpf_ringbuf_reserve_dynptr),
|
|
|
|
__imm(bpf_ringbuf_discard_dynptr),
|
|
|
|
__imm(bpf_dynptr_from_mem),
|
|
|
|
__imm(bpf_dynptr_read),
|
|
|
|
__imm_addr(ringbuf),
|
|
|
|
__imm_addr(array_map4)
|
|
|
|
: __clobber_all
|
|
|
|
);
|
|
|
|
return 0;
|
|
|
|
}
|
2023-01-21 05:52:41 +05:30
|
|
|
|
|
|
|
/* Test that it is allowed to overwrite unreferenced dynptr. */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__success
|
|
|
|
int dynptr_overwrite_unref(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
return 0;
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
return 0;
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Test that slices are invalidated on reinitializing a dynptr. */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int dynptr_invalidate_slice_reinit(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
__u8 *p;
|
|
|
|
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
return 0;
|
|
|
|
p = bpf_dynptr_data(&ptr, 0, 1);
|
|
|
|
if (!p)
|
|
|
|
return 0;
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
return 0;
|
|
|
|
/* this should fail */
|
|
|
|
return *p;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidation of dynptr slices on destruction of dynptr should not miss
|
|
|
|
* mem_or_null pointers.
|
|
|
|
*/
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("R1 type=scalar expected=percpu_ptr_")
|
|
|
|
int dynptr_invalidate_slice_or_null(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
__u8 *p;
|
|
|
|
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
p = bpf_dynptr_data(&ptr, 0, 1);
|
|
|
|
*(__u8 *)&ptr = 0;
|
|
|
|
/* this should fail */
|
|
|
|
bpf_this_cpu_ptr(p);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Destruction of dynptr should also any slices obtained from it */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("R7 invalid mem access 'scalar'")
|
|
|
|
int dynptr_invalidate_slice_failure(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr1;
|
|
|
|
struct bpf_dynptr ptr2;
|
|
|
|
__u8 *p1, *p2;
|
|
|
|
|
|
|
|
if (get_map_val_dynptr(&ptr1))
|
|
|
|
return 0;
|
|
|
|
if (get_map_val_dynptr(&ptr2))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
p1 = bpf_dynptr_data(&ptr1, 0, 1);
|
|
|
|
if (!p1)
|
|
|
|
return 0;
|
|
|
|
p2 = bpf_dynptr_data(&ptr2, 0, 1);
|
|
|
|
if (!p2)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
*(__u8 *)&ptr1 = 0;
|
|
|
|
/* this should fail */
|
|
|
|
return *p1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidation of slices should be scoped and should not prevent dereferencing
|
|
|
|
* slices of another dynptr after destroying unrelated dynptr
|
|
|
|
*/
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__success
|
|
|
|
int dynptr_invalidate_slice_success(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr1;
|
|
|
|
struct bpf_dynptr ptr2;
|
|
|
|
__u8 *p1, *p2;
|
|
|
|
|
|
|
|
if (get_map_val_dynptr(&ptr1))
|
|
|
|
return 1;
|
|
|
|
if (get_map_val_dynptr(&ptr2))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
p1 = bpf_dynptr_data(&ptr1, 0, 1);
|
|
|
|
if (!p1)
|
|
|
|
return 1;
|
|
|
|
p2 = bpf_dynptr_data(&ptr2, 0, 1);
|
|
|
|
if (!p2)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
*(__u8 *)&ptr1 = 0;
|
|
|
|
return *p2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Overwriting referenced dynptr should be rejected */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("cannot overwrite referenced dynptr")
|
|
|
|
int dynptr_overwrite_ref(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
|
|
|
|
/* this should fail */
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
bpf_ringbuf_discard_dynptr(&ptr, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reject writes to dynptr slot from bpf_dynptr_read */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("potential write to dynptr at off=-16")
|
|
|
|
int dynptr_read_into_slot(void *ctx)
|
|
|
|
{
|
|
|
|
union {
|
|
|
|
struct {
|
|
|
|
char _pad[48];
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
};
|
|
|
|
char buf[64];
|
|
|
|
} data;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &data.ptr);
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_read(data.buf, sizeof(data.buf), &data.ptr, 0, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
selftests/bpf: tests for using dynptrs to parse skb and xdp buffers
Test skb and xdp dynptr functionality in the following ways:
1) progs/test_cls_redirect_dynptr.c
* Rewrite "progs/test_cls_redirect.c" test to use dynptrs to parse
skb data
* This is a great example of how dynptrs can be used to simplify a
lot of the parsing logic for non-statically known values.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t cls_redirect"):
original version: 0.092 sec
with dynptrs: 0.078 sec
2) progs/test_xdp_dynptr.c
* Rewrite "progs/test_xdp.c" test to use dynptrs to parse xdp data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t xdp_attach"):
original version: 0.118 sec
with dynptrs: 0.094 sec
3) progs/test_l4lb_noinline_dynptr.c
* Rewrite "progs/test_l4lb_noinline.c" test to use dynptrs to parse
skb data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t l4lb_all"):
original version: 0.062 sec
with dynptrs: 0.081 sec
For number of processed verifier instructions:
original version: 6268 insns
with dynptrs: 2588 insns
4) progs/test_parse_tcp_hdr_opt_dynptr.c
* Add sample code for parsing tcp hdr opt lookup using dynptrs.
This logic is lifted from a real-world use case of packet parsing
in katran [0], a layer 4 load balancer. The original version
"progs/test_parse_tcp_hdr_opt.c" (not using dynptrs) is included
here as well, for comparison.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t parse_tcp_hdr_opt"):
original version: 0.031 sec
with dynptrs: 0.045 sec
5) progs/dynptr_success.c
* Add test case "test_skb_readonly" for testing attempts at writes
on a prog type with read-only skb ctx.
* Add "test_dynptr_skb_data" for testing that bpf_dynptr_data isn't
supported for skb progs.
6) progs/dynptr_fail.c
* Add test cases "skb_invalid_data_slice{1,2,3,4}" and
"xdp_invalid_data_slice{1,2}" for testing that helpers that modify the
underlying packet buffer automatically invalidate the associated
data slice.
* Add test cases "skb_invalid_ctx" and "xdp_invalid_ctx" for testing
that prog types that do not support bpf_dynptr_from_skb/xdp don't
have access to the API.
* Add test case "dynptr_slice_var_len{1,2}" for testing that
variable-sized len can't be passed in to bpf_dynptr_slice
* Add test case "skb_invalid_slice_write" for testing that writes to a
read-only data slice are rejected by the verifier.
* Add test case "data_slice_out_of_bounds_skb" for testing that
writes to an area outside the slice are rejected.
* Add test case "invalid_slice_rdwr_rdonly" for testing that prog
types that don't allow writes to packet data don't accept any calls
to bpf_dynptr_slice_rdwr.
[0] https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230301154953.641654-11-joannelkoong@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-03-01 07:49:53 -08:00
|
|
|
/* bpf_dynptr_slice()s are read-only and cannot be written to */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("R0 cannot write into rdonly_mem")
|
|
|
|
int skb_invalid_slice_write(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
hdr = bpf_dynptr_slice(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
hdr->h_proto = 1;
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The read-only data slice is invalidated whenever a helper changes packet data */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int skb_invalid_data_slice1(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
hdr = bpf_dynptr_slice(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
val = hdr->h_proto;
|
|
|
|
|
|
|
|
if (bpf_skb_pull_data(skb, skb->len))
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
val = hdr->h_proto;
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The read-write data slice is invalidated whenever a helper changes packet data */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int skb_invalid_data_slice2(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
hdr->h_proto = 123;
|
|
|
|
|
|
|
|
if (bpf_skb_pull_data(skb, skb->len))
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
hdr->h_proto = 1;
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The read-only data slice is invalidated whenever bpf_dynptr_write() is called */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int skb_invalid_data_slice3(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
char write_data[64] = "hello there, world!!";
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
hdr = bpf_dynptr_slice(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
val = hdr->h_proto;
|
|
|
|
|
|
|
|
bpf_dynptr_write(&ptr, 0, write_data, sizeof(write_data), 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
val = hdr->h_proto;
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The read-write data slice is invalidated whenever bpf_dynptr_write() is called */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int skb_invalid_data_slice4(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
char write_data[64] = "hello there, world!!";
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
hdr->h_proto = 123;
|
|
|
|
|
|
|
|
bpf_dynptr_write(&ptr, 0, write_data, sizeof(write_data), 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
hdr->h_proto = 1;
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The read-only data slice is invalidated whenever a helper changes packet data */
|
|
|
|
SEC("?xdp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int xdp_invalid_data_slice1(struct xdp_md *xdp)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_xdp(xdp, 0, &ptr);
|
|
|
|
hdr = bpf_dynptr_slice(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
val = hdr->h_proto;
|
|
|
|
|
|
|
|
if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(*hdr)))
|
|
|
|
return XDP_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
val = hdr->h_proto;
|
|
|
|
|
|
|
|
return XDP_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The read-write data slice is invalidated whenever a helper changes packet data */
|
|
|
|
SEC("?xdp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int xdp_invalid_data_slice2(struct xdp_md *xdp)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_xdp(xdp, 0, &ptr);
|
|
|
|
hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, sizeof(buffer));
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
hdr->h_proto = 9;
|
|
|
|
|
|
|
|
if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(*hdr)))
|
|
|
|
return XDP_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
hdr->h_proto = 1;
|
|
|
|
|
|
|
|
return XDP_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only supported prog type can create skb-type dynptrs */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("calling kernel function bpf_dynptr_from_skb is not allowed")
|
|
|
|
int skb_invalid_ctx(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_from_skb(ctx, 0, &ptr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-01-21 05:52:41 +05:30
|
|
|
/* Reject writes to dynptr slot for uninit arg */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("potential write to dynptr at off=-16")
|
|
|
|
int uninit_write_into_slot(void *ctx)
|
|
|
|
{
|
|
|
|
struct {
|
|
|
|
char buf[64];
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
} data;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 80, 0, &data.ptr);
|
|
|
|
/* this should fail */
|
|
|
|
bpf_get_current_comm(data.buf, 80);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
selftests/bpf: tests for using dynptrs to parse skb and xdp buffers
Test skb and xdp dynptr functionality in the following ways:
1) progs/test_cls_redirect_dynptr.c
* Rewrite "progs/test_cls_redirect.c" test to use dynptrs to parse
skb data
* This is a great example of how dynptrs can be used to simplify a
lot of the parsing logic for non-statically known values.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t cls_redirect"):
original version: 0.092 sec
with dynptrs: 0.078 sec
2) progs/test_xdp_dynptr.c
* Rewrite "progs/test_xdp.c" test to use dynptrs to parse xdp data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t xdp_attach"):
original version: 0.118 sec
with dynptrs: 0.094 sec
3) progs/test_l4lb_noinline_dynptr.c
* Rewrite "progs/test_l4lb_noinline.c" test to use dynptrs to parse
skb data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t l4lb_all"):
original version: 0.062 sec
with dynptrs: 0.081 sec
For number of processed verifier instructions:
original version: 6268 insns
with dynptrs: 2588 insns
4) progs/test_parse_tcp_hdr_opt_dynptr.c
* Add sample code for parsing tcp hdr opt lookup using dynptrs.
This logic is lifted from a real-world use case of packet parsing
in katran [0], a layer 4 load balancer. The original version
"progs/test_parse_tcp_hdr_opt.c" (not using dynptrs) is included
here as well, for comparison.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t parse_tcp_hdr_opt"):
original version: 0.031 sec
with dynptrs: 0.045 sec
5) progs/dynptr_success.c
* Add test case "test_skb_readonly" for testing attempts at writes
on a prog type with read-only skb ctx.
* Add "test_dynptr_skb_data" for testing that bpf_dynptr_data isn't
supported for skb progs.
6) progs/dynptr_fail.c
* Add test cases "skb_invalid_data_slice{1,2,3,4}" and
"xdp_invalid_data_slice{1,2}" for testing that helpers that modify the
underlying packet buffer automatically invalidate the associated
data slice.
* Add test cases "skb_invalid_ctx" and "xdp_invalid_ctx" for testing
that prog types that do not support bpf_dynptr_from_skb/xdp don't
have access to the API.
* Add test case "dynptr_slice_var_len{1,2}" for testing that
variable-sized len can't be passed in to bpf_dynptr_slice
* Add test case "skb_invalid_slice_write" for testing that writes to a
read-only data slice are rejected by the verifier.
* Add test case "data_slice_out_of_bounds_skb" for testing that
writes to an area outside the slice are rejected.
* Add test case "invalid_slice_rdwr_rdonly" for testing that prog
types that don't allow writes to packet data don't accept any calls
to bpf_dynptr_slice_rdwr.
[0] https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230301154953.641654-11-joannelkoong@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-03-01 07:49:53 -08:00
|
|
|
/* Only supported prog type can create xdp-type dynptrs */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("calling kernel function bpf_dynptr_from_xdp is not allowed")
|
|
|
|
int xdp_invalid_ctx(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_from_xdp(ctx, 0, &ptr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
__u32 hdr_size = sizeof(struct ethhdr);
|
|
|
|
/* Can't pass in variable-sized len to bpf_dynptr_slice */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("unbounded memory access")
|
|
|
|
int dynptr_slice_var_len1(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
char buffer[sizeof(*hdr)] = {};
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
hdr = bpf_dynptr_slice(&ptr, 0, buffer, hdr_size);
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't pass in variable-sized len to bpf_dynptr_slice */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("must be a known constant")
|
|
|
|
int dynptr_slice_var_len2(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
char buffer[sizeof(struct ethhdr)] = {};
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
if (hdr_size <= sizeof(buffer)) {
|
|
|
|
/* this should fail */
|
|
|
|
hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, hdr_size);
|
|
|
|
if (!hdr)
|
|
|
|
return SK_DROP;
|
|
|
|
hdr->h_proto = 12;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SK_PASS;
|
|
|
|
}
|
|
|
|
|
2023-01-21 05:52:41 +05:30
|
|
|
static int callback(__u32 index, void *data)
|
|
|
|
{
|
|
|
|
*(__u32 *)data = 123;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the dynptr is written into in a callback function, its data
|
|
|
|
* slices should be invalidated as well.
|
|
|
|
*/
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int invalid_data_slices(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
__u32 *slice;
|
|
|
|
|
|
|
|
if (get_map_val_dynptr(&ptr))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
slice = bpf_dynptr_data(&ptr, 0, sizeof(__u32));
|
|
|
|
if (!slice)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bpf_loop(10, callback, &ptr, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*slice = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
selftests/bpf: tests for using dynptrs to parse skb and xdp buffers
Test skb and xdp dynptr functionality in the following ways:
1) progs/test_cls_redirect_dynptr.c
* Rewrite "progs/test_cls_redirect.c" test to use dynptrs to parse
skb data
* This is a great example of how dynptrs can be used to simplify a
lot of the parsing logic for non-statically known values.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t cls_redirect"):
original version: 0.092 sec
with dynptrs: 0.078 sec
2) progs/test_xdp_dynptr.c
* Rewrite "progs/test_xdp.c" test to use dynptrs to parse xdp data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t xdp_attach"):
original version: 0.118 sec
with dynptrs: 0.094 sec
3) progs/test_l4lb_noinline_dynptr.c
* Rewrite "progs/test_l4lb_noinline.c" test to use dynptrs to parse
skb data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t l4lb_all"):
original version: 0.062 sec
with dynptrs: 0.081 sec
For number of processed verifier instructions:
original version: 6268 insns
with dynptrs: 2588 insns
4) progs/test_parse_tcp_hdr_opt_dynptr.c
* Add sample code for parsing tcp hdr opt lookup using dynptrs.
This logic is lifted from a real-world use case of packet parsing
in katran [0], a layer 4 load balancer. The original version
"progs/test_parse_tcp_hdr_opt.c" (not using dynptrs) is included
here as well, for comparison.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t parse_tcp_hdr_opt"):
original version: 0.031 sec
with dynptrs: 0.045 sec
5) progs/dynptr_success.c
* Add test case "test_skb_readonly" for testing attempts at writes
on a prog type with read-only skb ctx.
* Add "test_dynptr_skb_data" for testing that bpf_dynptr_data isn't
supported for skb progs.
6) progs/dynptr_fail.c
* Add test cases "skb_invalid_data_slice{1,2,3,4}" and
"xdp_invalid_data_slice{1,2}" for testing that helpers that modify the
underlying packet buffer automatically invalidate the associated
data slice.
* Add test cases "skb_invalid_ctx" and "xdp_invalid_ctx" for testing
that prog types that do not support bpf_dynptr_from_skb/xdp don't
have access to the API.
* Add test case "dynptr_slice_var_len{1,2}" for testing that
variable-sized len can't be passed in to bpf_dynptr_slice
* Add test case "skb_invalid_slice_write" for testing that writes to a
read-only data slice are rejected by the verifier.
* Add test case "data_slice_out_of_bounds_skb" for testing that
writes to an area outside the slice are rejected.
* Add test case "invalid_slice_rdwr_rdonly" for testing that prog
types that don't allow writes to packet data don't accept any calls
to bpf_dynptr_slice_rdwr.
[0] https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230301154953.641654-11-joannelkoong@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-03-01 07:49:53 -08:00
|
|
|
|
|
|
|
/* Program types that don't allow writes to packet data should fail if
|
|
|
|
* bpf_dynptr_slice_rdwr is called
|
|
|
|
*/
|
|
|
|
SEC("cgroup_skb/ingress")
|
|
|
|
__failure __msg("the prog does not allow writes to packet data")
|
|
|
|
int invalid_slice_rdwr_rdonly(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
char buffer[sizeof(struct ethhdr)] = {};
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
/* this should fail since cgroup_skb doesn't allow
|
|
|
|
* changing packet data
|
|
|
|
*/
|
|
|
|
hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, sizeof(buffer));
|
2023-03-08 21:40:14 -08:00
|
|
|
__sink(hdr);
|
selftests/bpf: tests for using dynptrs to parse skb and xdp buffers
Test skb and xdp dynptr functionality in the following ways:
1) progs/test_cls_redirect_dynptr.c
* Rewrite "progs/test_cls_redirect.c" test to use dynptrs to parse
skb data
* This is a great example of how dynptrs can be used to simplify a
lot of the parsing logic for non-statically known values.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t cls_redirect"):
original version: 0.092 sec
with dynptrs: 0.078 sec
2) progs/test_xdp_dynptr.c
* Rewrite "progs/test_xdp.c" test to use dynptrs to parse xdp data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t xdp_attach"):
original version: 0.118 sec
with dynptrs: 0.094 sec
3) progs/test_l4lb_noinline_dynptr.c
* Rewrite "progs/test_l4lb_noinline.c" test to use dynptrs to parse
skb data
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t l4lb_all"):
original version: 0.062 sec
with dynptrs: 0.081 sec
For number of processed verifier instructions:
original version: 6268 insns
with dynptrs: 2588 insns
4) progs/test_parse_tcp_hdr_opt_dynptr.c
* Add sample code for parsing tcp hdr opt lookup using dynptrs.
This logic is lifted from a real-world use case of packet parsing
in katran [0], a layer 4 load balancer. The original version
"progs/test_parse_tcp_hdr_opt.c" (not using dynptrs) is included
here as well, for comparison.
When measuring the user + system time between the original version
vs. using dynptrs, and averaging the time for 10 runs (using
"time ./test_progs -t parse_tcp_hdr_opt"):
original version: 0.031 sec
with dynptrs: 0.045 sec
5) progs/dynptr_success.c
* Add test case "test_skb_readonly" for testing attempts at writes
on a prog type with read-only skb ctx.
* Add "test_dynptr_skb_data" for testing that bpf_dynptr_data isn't
supported for skb progs.
6) progs/dynptr_fail.c
* Add test cases "skb_invalid_data_slice{1,2,3,4}" and
"xdp_invalid_data_slice{1,2}" for testing that helpers that modify the
underlying packet buffer automatically invalidate the associated
data slice.
* Add test cases "skb_invalid_ctx" and "xdp_invalid_ctx" for testing
that prog types that do not support bpf_dynptr_from_skb/xdp don't
have access to the API.
* Add test case "dynptr_slice_var_len{1,2}" for testing that
variable-sized len can't be passed in to bpf_dynptr_slice
* Add test case "skb_invalid_slice_write" for testing that writes to a
read-only data slice are rejected by the verifier.
* Add test case "data_slice_out_of_bounds_skb" for testing that
writes to an area outside the slice are rejected.
* Add test case "invalid_slice_rdwr_rdonly" for testing that prog
types that don't allow writes to packet data don't accept any calls
to bpf_dynptr_slice_rdwr.
[0] https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20230301154953.641654-11-joannelkoong@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
2023-03-01 07:49:53 -08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2023-04-20 00:14:14 -07:00
|
|
|
|
|
|
|
/* bpf_dynptr_adjust can only be called on initialized dynptrs */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #1")
|
|
|
|
int dynptr_adjust_invalid(void *ctx)
|
|
|
|
{
|
2024-05-10 19:38:50 +01:00
|
|
|
struct bpf_dynptr ptr = {};
|
2023-04-20 00:14:14 -07:00
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_adjust(&ptr, 1, 2);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bpf_dynptr_is_null can only be called on initialized dynptrs */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #1")
|
|
|
|
int dynptr_is_null_invalid(void *ctx)
|
|
|
|
{
|
2024-05-10 19:38:50 +01:00
|
|
|
struct bpf_dynptr ptr = {};
|
2023-04-20 00:14:14 -07:00
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_is_null(&ptr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bpf_dynptr_is_rdonly can only be called on initialized dynptrs */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #1")
|
|
|
|
int dynptr_is_rdonly_invalid(void *ctx)
|
|
|
|
{
|
2024-05-10 19:38:50 +01:00
|
|
|
struct bpf_dynptr ptr = {};
|
2023-04-20 00:14:14 -07:00
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_is_rdonly(&ptr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bpf_dynptr_size can only be called on initialized dynptrs */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #1")
|
|
|
|
int dynptr_size_invalid(void *ctx)
|
|
|
|
{
|
2024-05-10 19:38:50 +01:00
|
|
|
struct bpf_dynptr ptr = {};
|
2023-04-20 00:14:14 -07:00
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_size(&ptr);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only initialized dynptrs can be cloned */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #1")
|
|
|
|
int clone_invalid1(void *ctx)
|
|
|
|
{
|
2024-05-10 19:38:50 +01:00
|
|
|
struct bpf_dynptr ptr1 = {};
|
2023-04-20 00:14:14 -07:00
|
|
|
struct bpf_dynptr ptr2;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_clone(&ptr1, &ptr2);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't overwrite an existing dynptr when cloning */
|
|
|
|
SEC("?xdp")
|
|
|
|
__failure __msg("cannot overwrite referenced dynptr")
|
|
|
|
int clone_invalid2(struct xdp_md *xdp)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr1;
|
|
|
|
struct bpf_dynptr clone;
|
|
|
|
|
|
|
|
bpf_dynptr_from_xdp(xdp, 0, &ptr1);
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &clone);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_clone(&ptr1, &clone);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&clone, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidating a dynptr should invalidate its clones */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #3")
|
|
|
|
int clone_invalidate1(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr clone;
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
char read_data[64];
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), &clone, 0, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidating a dynptr should invalidate its parent */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #3")
|
|
|
|
int clone_invalidate2(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct bpf_dynptr clone;
|
|
|
|
char read_data[64];
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&clone, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), &ptr, 0, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidating a dynptr should invalidate its siblings */
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("Expected an initialized dynptr as arg #3")
|
|
|
|
int clone_invalidate3(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct bpf_dynptr clone1;
|
|
|
|
struct bpf_dynptr clone2;
|
|
|
|
char read_data[64];
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone1);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone2);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&clone2, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
bpf_dynptr_read(read_data, sizeof(read_data), &clone1, 0, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidating a dynptr should invalidate any data slices
|
|
|
|
* of its clones
|
|
|
|
*/
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int clone_invalidate4(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct bpf_dynptr clone;
|
|
|
|
int *data;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone);
|
|
|
|
data = bpf_dynptr_data(&clone, 0, sizeof(val));
|
|
|
|
if (!data)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&ptr, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*data = 123;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidating a dynptr should invalidate any data slices
|
|
|
|
* of its parent
|
|
|
|
*/
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int clone_invalidate5(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct bpf_dynptr clone;
|
|
|
|
int *data;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
data = bpf_dynptr_data(&ptr, 0, sizeof(val));
|
|
|
|
if (!data)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone);
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&clone, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*data = 123;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidating a dynptr should invalidate any data slices
|
|
|
|
* of its sibling
|
|
|
|
*/
|
|
|
|
SEC("?raw_tp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int clone_invalidate6(void *ctx)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct bpf_dynptr clone1;
|
|
|
|
struct bpf_dynptr clone2;
|
|
|
|
int *data;
|
|
|
|
|
|
|
|
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone1);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone2);
|
|
|
|
|
|
|
|
data = bpf_dynptr_data(&clone1, 0, sizeof(val));
|
|
|
|
if (!data)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bpf_ringbuf_submit_dynptr(&clone2, 0);
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*data = 123;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A skb clone's data slices should be invalid anytime packet data changes */
|
|
|
|
SEC("?tc")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int clone_skb_packet_data(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
char buffer[sizeof(__u32)] = {};
|
|
|
|
struct bpf_dynptr clone;
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
__u32 *data;
|
|
|
|
|
|
|
|
bpf_dynptr_from_skb(skb, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone);
|
|
|
|
data = bpf_dynptr_slice_rdwr(&clone, 0, buffer, sizeof(buffer));
|
|
|
|
if (!data)
|
|
|
|
return XDP_DROP;
|
|
|
|
|
|
|
|
if (bpf_skb_pull_data(skb, skb->len))
|
|
|
|
return SK_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*data = 123;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A xdp clone's data slices should be invalid anytime packet data changes */
|
|
|
|
SEC("?xdp")
|
|
|
|
__failure __msg("invalid mem access 'scalar'")
|
|
|
|
int clone_xdp_packet_data(struct xdp_md *xdp)
|
|
|
|
{
|
|
|
|
char buffer[sizeof(__u32)] = {};
|
|
|
|
struct bpf_dynptr clone;
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
struct ethhdr *hdr;
|
|
|
|
__u32 *data;
|
|
|
|
|
|
|
|
bpf_dynptr_from_xdp(xdp, 0, &ptr);
|
|
|
|
|
|
|
|
bpf_dynptr_clone(&ptr, &clone);
|
|
|
|
data = bpf_dynptr_slice_rdwr(&clone, 0, buffer, sizeof(buffer));
|
|
|
|
if (!data)
|
|
|
|
return XDP_DROP;
|
|
|
|
|
|
|
|
if (bpf_xdp_adjust_head(xdp, 0 - (int)sizeof(*hdr)))
|
|
|
|
return XDP_DROP;
|
|
|
|
|
|
|
|
/* this should fail */
|
|
|
|
*data = 123;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2023-05-05 18:31:32 -07:00
|
|
|
|
|
|
|
/* Buffers that are provided must be sufficiently long */
|
|
|
|
SEC("?cgroup_skb/egress")
|
|
|
|
__failure __msg("memory, len pair leads to invalid memory access")
|
|
|
|
int test_dynptr_skb_small_buff(struct __sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct bpf_dynptr ptr;
|
|
|
|
char buffer[8] = {};
|
|
|
|
__u64 *data;
|
|
|
|
|
|
|
|
if (bpf_dynptr_from_skb(skb, 0, &ptr)) {
|
|
|
|
err = 1;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This may return NULL. SKB may require a buffer */
|
|
|
|
data = bpf_dynptr_slice(&ptr, 0, buffer, 9);
|
|
|
|
|
|
|
|
return !!data;
|
|
|
|
}
|