Skip to content

Commit 719d87f

Browse files
pchaignoKernel Patches Daemon
authored andcommitted
bpf: Craft non-linear skbs in BPF_PROG_TEST_RUN
This patch adds support for crafting non-linear skbs in BPF test runs for tc programs, via a new flag BPF_F_TEST_SKB_NON_LINEAR. When this flag is set, the size of the linear area is given by ctx->data_end, with a minimum of ETH_HLEN always pulled in the linear area. This is particularly useful to test support for non-linear skbs in large codebases such as Cilium. We've had multiple bugs in the past few years where we were missing calls to bpf_skb_pull_data(). This support in BPF_PROG_TEST_RUN would allow us to automatically cover this case in our BPF tests. In addition to the selftests introduced later in the series, this patch was tested by setting BPF_F_TEST_SKB_NON_LINEAR for all tc selftests programs and checking test failures were expected. Suggested-by: Daniel Borkmann <[email protected]> Signed-off-by: Paul Chaignon <[email protected]>
1 parent a6e38bb commit 719d87f

File tree

3 files changed

+82
-28
lines changed

3 files changed

+82
-28
lines changed

include/uapi/linux/bpf.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,10 @@ enum {
14481448
#define BPF_F_TEST_XDP_LIVE_FRAMES (1U << 1)
14491449
/* If set, apply CHECKSUM_COMPLETE to skb and validate the checksum */
14501450
#define BPF_F_TEST_SKB_CHECKSUM_COMPLETE (1U << 2)
1451+
/* If set, skb will be non-linear with the size of the linear area given
1452+
* by ctx.data_end or at least ETH_HLEN.
1453+
*/
1454+
#define BPF_F_TEST_SKB_NON_LINEAR (1U << 3)
14511455

14521456
/* type for BPF_ENABLE_STATS */
14531457
enum bpf_stats_type {

net/bpf/test_run.c

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -660,20 +660,29 @@ BTF_ID_FLAGS(func, bpf_kfunc_call_memb_release, KF_RELEASE)
660660
BTF_KFUNCS_END(test_sk_check_kfunc_ids)
661661

662662
static void *bpf_test_init(const union bpf_attr *kattr, u32 user_size,
663-
u32 size, u32 headroom, u32 tailroom)
663+
u32 size, u32 headroom, u32 tailroom, bool nonlinear)
664664
{
665665
void __user *data_in = u64_to_user_ptr(kattr->test.data_in);
666-
void *data;
666+
void *data, *dst;
667667

668668
if (user_size < ETH_HLEN || user_size > PAGE_SIZE - headroom - tailroom)
669669
return ERR_PTR(-EINVAL);
670670

671-
size = SKB_DATA_ALIGN(size);
672-
data = kzalloc(size + headroom + tailroom, GFP_USER);
671+
/* In non-linear case, data_in is copied to the paged data */
672+
if (nonlinear) {
673+
data = alloc_page(GFP_USER);
674+
} else {
675+
size = SKB_DATA_ALIGN(size);
676+
data = kzalloc(size + headroom + tailroom, GFP_USER);
677+
}
673678
if (!data)
674679
return ERR_PTR(-ENOMEM);
675680

676-
if (copy_from_user(data + headroom, data_in, user_size)) {
681+
if (nonlinear)
682+
dst = page_address(data);
683+
else
684+
dst = data + headroom;
685+
if (copy_from_user(dst, data_in, user_size)) {
677686
kfree(data);
678687
return ERR_PTR(-EFAULT);
679688
}
@@ -910,6 +919,12 @@ static int convert___skb_to_skb(struct sk_buff *skb, struct __sk_buff *__skb)
910919
/* cb is allowed */
911920

912921
if (!range_is_zero(__skb, offsetofend(struct __sk_buff, cb),
922+
offsetof(struct __sk_buff, data_end)))
923+
return -EINVAL;
924+
925+
/* data_end is allowed, but not copied to skb */
926+
927+
if (!range_is_zero(__skb, offsetofend(struct __sk_buff, data_end),
913928
offsetof(struct __sk_buff, tstamp)))
914929
return -EINVAL;
915930

@@ -984,7 +999,7 @@ static struct proto bpf_dummy_proto = {
984999
int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
9851000
union bpf_attr __user *uattr)
9861001
{
987-
bool is_l2 = false, is_direct_pkt_access = false;
1002+
bool is_l2 = false, is_direct_pkt_access = false, is_nonlinear = false;
9881003
struct net *net = current->nsproxy->net_ns;
9891004
struct net_device *dev = net->loopback_dev;
9901005
u32 size = kattr->test.data_size_in;
@@ -994,25 +1009,14 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
9941009
struct sock *sk = NULL;
9951010
u32 retval, duration;
9961011
int hh_len = ETH_HLEN;
1012+
int linear_size, ret;
9971013
void *data;
998-
int ret;
9991014

1000-
if ((kattr->test.flags & ~BPF_F_TEST_SKB_CHECKSUM_COMPLETE) ||
1015+
if ((kattr->test.flags & ~(BPF_F_TEST_SKB_CHECKSUM_COMPLETE | BPF_F_TEST_SKB_NON_LINEAR)) ||
10011016
kattr->test.cpu || kattr->test.batch_size)
10021017
return -EINVAL;
10031018

1004-
data = bpf_test_init(kattr, kattr->test.data_size_in,
1005-
size, NET_SKB_PAD + NET_IP_ALIGN,
1006-
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)));
1007-
if (IS_ERR(data))
1008-
return PTR_ERR(data);
1009-
1010-
ctx = bpf_ctx_init(kattr, sizeof(struct __sk_buff));
1011-
if (IS_ERR(ctx)) {
1012-
ret = PTR_ERR(ctx);
1013-
ctx = NULL;
1014-
goto out;
1015-
}
1019+
is_nonlinear = kattr->test.flags & BPF_F_TEST_SKB_NON_LINEAR;
10161020

10171021
switch (prog->type) {
10181022
case BPF_PROG_TYPE_SCHED_CLS:
@@ -1029,22 +1033,60 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
10291033
break;
10301034
}
10311035

1036+
if (is_nonlinear && !is_l2)
1037+
return -EINVAL;
1038+
1039+
data = bpf_test_init(kattr, kattr->test.data_size_in,
1040+
size, NET_SKB_PAD + NET_IP_ALIGN,
1041+
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)),
1042+
is_nonlinear);
1043+
if (IS_ERR(data))
1044+
return PTR_ERR(data);
1045+
1046+
ctx = bpf_ctx_init(kattr, sizeof(struct __sk_buff));
1047+
if (IS_ERR(ctx)) {
1048+
ret = PTR_ERR(ctx);
1049+
ctx = NULL;
1050+
goto out;
1051+
}
1052+
1053+
linear_size = hh_len;
1054+
if (is_nonlinear && ctx && ctx->data_end > linear_size)
1055+
linear_size = ctx->data_end;
1056+
10321057
sk = sk_alloc(net, AF_UNSPEC, GFP_USER, &bpf_dummy_proto, 1);
10331058
if (!sk) {
10341059
ret = -ENOMEM;
10351060
goto out;
10361061
}
10371062
sock_init_data(NULL, sk);
10381063

1039-
skb = slab_build_skb(data);
1064+
if (is_nonlinear)
1065+
skb = alloc_skb(NET_SKB_PAD + NET_IP_ALIGN + size +
1066+
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)),
1067+
GFP_USER);
1068+
else
1069+
skb = slab_build_skb(data);
10401070
if (!skb) {
10411071
ret = -ENOMEM;
10421072
goto out;
10431073
}
1074+
10441075
skb->sk = sk;
10451076

10461077
skb_reserve(skb, NET_SKB_PAD + NET_IP_ALIGN);
1047-
__skb_put(skb, size);
1078+
1079+
if (is_nonlinear) {
1080+
skb_fill_page_desc(skb, 0, data, 0, size);
1081+
skb->truesize += PAGE_SIZE;
1082+
skb->data_len = size;
1083+
skb->len = size;
1084+
1085+
/* eth_type_trans expects the Ethernet header in the linear area. */
1086+
__pskb_pull_tail(skb, linear_size);
1087+
} else {
1088+
__skb_put(skb, size);
1089+
}
10481090

10491091
data = NULL; /* data released via kfree_skb */
10501092

@@ -1127,9 +1169,11 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
11271169
convert_skb_to___skb(skb, ctx);
11281170

11291171
size = skb->len;
1130-
/* bpf program can never convert linear skb to non-linear */
1131-
if (WARN_ON_ONCE(skb_is_nonlinear(skb)))
1172+
if (skb_is_nonlinear(skb)) {
1173+
/* bpf program can never convert linear skb to non-linear */
1174+
WARN_ON_ONCE(!is_nonlinear);
11321175
size = skb_headlen(skb);
1176+
}
11331177
ret = bpf_test_finish(kattr, uattr, skb->data, NULL, size, retval,
11341178
duration);
11351179
if (!ret)
@@ -1139,7 +1183,8 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
11391183
if (dev && dev != net->loopback_dev)
11401184
dev_put(dev);
11411185
kfree_skb(skb);
1142-
kfree(data);
1186+
if (data)
1187+
is_nonlinear ? __free_page(data) : kfree(data);
11431188
if (sk)
11441189
sk_free(sk);
11451190
kfree(ctx);
@@ -1265,7 +1310,7 @@ int bpf_prog_test_run_xdp(struct bpf_prog *prog, const union bpf_attr *kattr,
12651310
size = max_data_sz;
12661311
}
12671312

1268-
data = bpf_test_init(kattr, size, max_data_sz, headroom, tailroom);
1313+
data = bpf_test_init(kattr, size, max_data_sz, headroom, tailroom, false);
12691314
if (IS_ERR(data)) {
12701315
ret = PTR_ERR(data);
12711316
goto free_ctx;
@@ -1388,7 +1433,7 @@ int bpf_prog_test_run_flow_dissector(struct bpf_prog *prog,
13881433
if (size < ETH_HLEN)
13891434
return -EINVAL;
13901435

1391-
data = bpf_test_init(kattr, kattr->test.data_size_in, size, 0, 0);
1436+
data = bpf_test_init(kattr, kattr->test.data_size_in, size, 0, 0, false);
13921437
if (IS_ERR(data))
13931438
return PTR_ERR(data);
13941439

@@ -1661,7 +1706,8 @@ int bpf_prog_test_run_nf(struct bpf_prog *prog,
16611706

16621707
data = bpf_test_init(kattr, kattr->test.data_size_in, size,
16631708
NET_SKB_PAD + NET_IP_ALIGN,
1664-
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)));
1709+
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)),
1710+
false);
16651711
if (IS_ERR(data))
16661712
return PTR_ERR(data);
16671713

tools/include/uapi/linux/bpf.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,10 @@ enum {
14481448
#define BPF_F_TEST_XDP_LIVE_FRAMES (1U << 1)
14491449
/* If set, apply CHECKSUM_COMPLETE to skb and validate the checksum */
14501450
#define BPF_F_TEST_SKB_CHECKSUM_COMPLETE (1U << 2)
1451+
/* If set, skb will be non-linear with the size of the linear area given
1452+
* by ctx.data_end or at least ETH_HLEN.
1453+
*/
1454+
#define BPF_F_TEST_SKB_NON_LINEAR (1U << 3)
14511455

14521456
/* type for BPF_ENABLE_STATS */
14531457
enum bpf_stats_type {

0 commit comments

Comments
 (0)