Skip to content

Commit

Permalink
selftests/bpf: Add selftest for packet-pointer Spectre v1 gadget
Browse files Browse the repository at this point in the history
When allowing speculative leaks by enabling packet pointer accesses
without CAP_PERFMON (i.e., without having [1] reverted):

  $ tools/testing/selftests/bpf/test_progs --name=tc_bpf
  tc_bpf_non_root:PASS:set_cap_bpf_cap_net_admin 0 nsec
  tc_bpf_non_root:PASS:disable_cap_sys_admin 0 nsec
  tc_bpf_non_root:FAIL:test_tc_bpf__open_and_load unexpected pointer: 0x55bbd81969a0
  Summary: 0/1 PASSED, 0 SKIPPED, 1 FAILED

With [1] reverted:

  $ tools/testing/selftests/bpf/test_progs --name=tc_bpf
  #238/1   tc_bpf/tc_bpf_root:OK
  #238/2   tc_bpf/tc_bpf_non_root:OK
  #238     tc_bpf:OK
  Summary: 1/2 PASSED, 0 SKIPPED, 0 FAILED

  [1] d75e30d ("bpf: Fix issue in verifying allow_ptr_leaks")

Based-on-patch-by: Yafang Shao <laoar.shao@gmail.com>
Signed-off-by: Luis Gerhorst <gerhorst@amazon.de>
Signed-off-by: Luis Gerhorst <gerhorst@cs.fau.de>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/20230913123154.94264-1-gerhorst@amazon.de
  • Loading branch information
Luis Gerhorst authored and borkmann committed Sep 14, 2023
1 parent 45f2aab commit fc7274e
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 1 deletion.
37 changes: 36 additions & 1 deletion tools/testing/selftests/bpf/prog_tests/tc_bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <test_progs.h>
#include <linux/pkt_cls.h>

#include "cap_helpers.h"
#include "test_tc_bpf.skel.h"

#define LO_IFINDEX 1
Expand Down Expand Up @@ -327,7 +328,7 @@ static int test_tc_bpf_api(struct bpf_tc_hook *hook, int fd)
return 0;
}

void test_tc_bpf(void)
void tc_bpf_root(void)
{
DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = LO_IFINDEX,
.attach_point = BPF_TC_INGRESS);
Expand Down Expand Up @@ -393,3 +394,37 @@ void test_tc_bpf(void)
}
test_tc_bpf__destroy(skel);
}

void tc_bpf_non_root(void)
{
struct test_tc_bpf *skel = NULL;
__u64 caps = 0;
int ret;

/* In case CAP_BPF and CAP_PERFMON is not set */
ret = cap_enable_effective(1ULL << CAP_BPF | 1ULL << CAP_NET_ADMIN, &caps);
if (!ASSERT_OK(ret, "set_cap_bpf_cap_net_admin"))
return;
ret = cap_disable_effective(1ULL << CAP_SYS_ADMIN | 1ULL << CAP_PERFMON, NULL);
if (!ASSERT_OK(ret, "disable_cap_sys_admin"))
goto restore_cap;

skel = test_tc_bpf__open_and_load();
if (!ASSERT_ERR_PTR(skel, "test_tc_bpf__open_and_load"))
goto destroy;

goto restore_cap;
destroy:
test_tc_bpf__destroy(skel);
restore_cap:
if (caps)
cap_enable_effective(caps, NULL);
}

void test_tc_bpf(void)
{
if (test__start_subtest("tc_bpf_root"))
tc_bpf_root();
if (test__start_subtest("tc_bpf_non_root"))
tc_bpf_non_root();
}
95 changes: 95 additions & 0 deletions tools/testing/selftests/bpf/progs/test_tc_bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

/* Dummy prog to test TC-BPF API */

Expand All @@ -10,3 +12,96 @@ int cls(struct __sk_buff *skb)
{
return 0;
}

/* Prog to verify tc-bpf without cap_sys_admin and cap_perfmon is rejected as
* required to prevent Spectre v1 using CPU multiplication port contention
* side-channel. This is not a full exploit but rather a PoC for x86_64. With
* extensions to the verifier's mitigations this may become obsolete.
*
* This should compile to the following bytecode if the kernel would allow
* unprivileged packet pointer accesses:
*
0000000000000000 <pkt_ptr>:
0: b4 00 00 00 00 00 00 00 w0 = 0
1: 61 12 50 00 00 00 00 00 r2 = *(u32 *)(r1 + 80)
2: 61 11 4c 00 00 00 00 00 r1 = *(u32 *)(r1 + 76)
3: bf 13 00 00 00 00 00 00 r3 = r1
4: 07 03 00 00 22 00 00 00 r3 += 34
5: bd 23 07 00 00 00 00 00 if r3 <= r2 goto +7 <LBB1_3>
6: 71 10 0e 00 00 00 00 00 r0 = *(u8 *)(r1 + 14)
7: 64 00 00 00 18 00 00 00 w0 <<= 24
8: c4 00 00 00 18 00 00 00 w0 s>>= 24
9: bc 01 00 00 00 00 00 00 w1 = w0
10: 54 01 00 00 01 00 00 00 w1 &= 1
11: 16 01 01 00 00 00 00 00 if w1 == 0 goto +1 <LBB1_3>
12: 24 00 00 00 61 00 00 00 w0 *= 97
0000000000000068 <LBB1_3>:
13: 95 00 00 00 00 00 00 00 exit
*
* Which should in turn translate to this x86_64 assembly with !allow_ptr_leaks
* and !bypass_spec_v1:
*
int pkt_ptr(struct __sk_buff * skb):
bpf_prog_7c3834bad32f2b0f_pkt_ptr:
; int pkt_ptr(struct __sk_buff *skb)
0: endbr64
4: nopl 0x0(%rax,%rax,1)
9: xchg %ax,%ax
b: push %rbp
c: mov %rsp,%rbp
f: endbr64
13: xor %eax,%eax
; if ((long)(iph + 1) > (long)skb->data_end)
15: mov 0x50(%rdi),%rsi
; struct iphdr *iph = (void *)(long)skb->data + sizeof(struct ethhdr);
19: mov 0xc8(%rdi),%rdi
; if ((long)(iph + 1) > (long)skb->data_end)
20: mov %rdi,%rdx
23: add $0x22,%rdx
; if ((long)(iph + 1) > (long)skb->data_end)
27: cmp %rsi,%rdx
2a: ja 0x0000000000000043
; char secret = *((char *) iph);
2c: movzbq 0xe(%rdi),%rax
31: shl $0x18,%eax
34: sar $0x18,%eax
; if (secret & 1) {
37: mov %eax,%edi
39: and $0x1,%edi
; if (secret & 1) {
3c: test %edi,%edi
3e: je 0x0000000000000043
40: imul $0x61,%eax,%eax
; }
43: leaveq
44: retq
*
*/
SEC("tcx/ingress")
int pkt_ptr(struct __sk_buff *skb)
{
struct iphdr *iph = (void *)(long)skb->data + sizeof(struct ethhdr);

/* Branch to be speculatively bypassed. */
if ((long)(iph + 1) > (long)skb->data_end)
return 0;

/* Speculative access to be prevented. */
char secret = *((char *) iph);

/* Leak the first bit of the secret value that lies behind data_end to a
* SMP silbling thread that also executes imul instructions. If the bit
* is 1, the silbling will experience a slowdown. */
long long x = secret;
if (secret & 1) {
x *= 97;
}

/* To prevent optimization. */
return x;
}

0 comments on commit fc7274e

Please sign in to comment.