summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/progs/sock_ops_get_sk.c
blob: 3a0689f8ce7ca25a27e920c7269c1b123cf0e78c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// SPDX-License-Identifier: GPL-2.0

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"

/*
 * Test the SOCK_OPS_GET_SK() and SOCK_OPS_GET_FIELD() macros in
 * sock_ops_convert_ctx_access() when dst_reg == src_reg.
 *
 * When dst_reg == src_reg, the macros borrow a temporary register to load
 * is_fullsock / is_locked_tcp_sock, because dst_reg holds the ctx pointer
 * and cannot be clobbered before ctx->sk / ctx->field is read. If
 * is_fullsock == 0 (e.g., TCP_NEW_SYN_RECV with a request_sock), the macro
 * must still zero dst_reg so the verifier's PTR_TO_SOCKET_OR_NULL /
 * SCALAR_VALUE type is correct at runtime. A missing clear leaves a stale
 * ctx pointer in dst_reg that passes NULL checks (GET_SK) or leaks a kernel
 * address as a scalar (GET_FIELD).
 *
 * When dst_reg != src_reg, dst_reg itself is used to load is_fullsock, so
 * the JEQ (dst_reg == 0) naturally leaves it zeroed on the !fullsock path.
 */

int bug_detected;
int null_seen;

SEC("sockops")
__naked void sock_ops_get_sk_same_reg(void)
{
	asm volatile (
		"r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
		"r1 = *(u64 *)(r1 + %[sk_off]);"
		"if r7 != 0 goto 2f;"
		"if r1 == 0 goto 1f;"
		"r1 = %[bug_detected] ll;"
		"r2 = 1;"
		"*(u32 *)(r1 + 0) = r2;"
		"goto 2f;"
	"1:"
		"r1 = %[null_seen] ll;"
		"r2 = 1;"
		"*(u32 *)(r1 + 0) = r2;"
	"2:"
		"r0 = 1;"
		"exit;"
		:
		: __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
		  __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
		  __imm_addr(bug_detected),
		  __imm_addr(null_seen)
		: __clobber_all);
}

/* SOCK_OPS_GET_FIELD: same-register, is_locked_tcp_sock == 0 path. */
int field_bug_detected;
int field_null_seen;

SEC("sockops")
__naked void sock_ops_get_field_same_reg(void)
{
	asm volatile (
		"r7 = *(u32 *)(r1 + %[is_fullsock_off]);"
		"r1 = *(u32 *)(r1 + %[snd_cwnd_off]);"
		"if r7 != 0 goto 2f;"
		"if r1 == 0 goto 1f;"
		"r1 = %[field_bug_detected] ll;"
		"r2 = 1;"
		"*(u32 *)(r1 + 0) = r2;"
		"goto 2f;"
	"1:"
		"r1 = %[field_null_seen] ll;"
		"r2 = 1;"
		"*(u32 *)(r1 + 0) = r2;"
	"2:"
		"r0 = 1;"
		"exit;"
		:
		: __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
		  __imm_const(snd_cwnd_off, offsetof(struct bpf_sock_ops, snd_cwnd)),
		  __imm_addr(field_bug_detected),
		  __imm_addr(field_null_seen)
		: __clobber_all);
}

/* SOCK_OPS_GET_SK: different-register, is_fullsock == 0 path. */
int diff_reg_bug_detected;
int diff_reg_null_seen;

SEC("sockops")
__naked void sock_ops_get_sk_diff_reg(void)
{
	asm volatile (
		"r7 = r1;"
		"r6 = *(u32 *)(r7 + %[is_fullsock_off]);"
		"r2 = *(u64 *)(r7 + %[sk_off]);"
		"if r6 != 0 goto 2f;"
		"if r2 == 0 goto 1f;"
		"r1 = %[diff_reg_bug_detected] ll;"
		"r3 = 1;"
		"*(u32 *)(r1 + 0) = r3;"
		"goto 2f;"
	"1:"
		"r1 = %[diff_reg_null_seen] ll;"
		"r3 = 1;"
		"*(u32 *)(r1 + 0) = r3;"
	"2:"
		"r0 = 1;"
		"exit;"
		:
		: __imm_const(is_fullsock_off, offsetof(struct bpf_sock_ops, is_fullsock)),
		  __imm_const(sk_off, offsetof(struct bpf_sock_ops, sk)),
		  __imm_addr(diff_reg_bug_detected),
		  __imm_addr(diff_reg_null_seen)
		: __clobber_all);
}

char _license[] SEC("license") = "GPL";