1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (c) 2021 SUSE LLC <rpalethorpe@suse.com>
4 */
5
6/*\
7 * [Description]
8 *
9 * Compare the effects of 32-bit div/mod by zero with the "expected"
10 * behaviour.
11 *
12 * The commit "bpf: fix subprog verifier bypass by div/mod by 0
13 * exception", changed div/mod by zero from exiting the current
14 * program to setting the destination register to zero (div) or
15 * leaving it untouched (mod).
16 *
17 * This solved one verfier bug which allowed dodgy pointer values, but
18 * it turned out that the source register was being 32-bit truncated
19 * when it should not be. Also the destination register for mod was
20 * not being truncated when it should be.
21 *
22 * So then we have the following two fixes:
23 * "bpf: Fix 32 bit src register truncation on div/mod"
24 * "bpf: Fix truncation handling for mod32 dst reg wrt zero"
25 *
26 * Testing for all of these issues is a problem. Not least because
27 * division by zero is undefined, so in theory any result is
28 * acceptable so long as the verifier and runtime behaviour
29 * match.
30 *
31 * However to keep things simple we just check if the source and
32 * destination register runtime values match the current upstream
33 * behaviour at the time of writing.
34 *
35 * If the test fails you may have one or more of the above patches
36 * missing. In this case it is possible that you are not vulnerable
37 * depending on what other backports and fixes have been applied. If
38 * upstream changes the behaviour of division by zero, then the test
39 * will need updating.
40 *
41 * Note that we use r6 as the src register and r7 as the dst. w6 and
42 * w7 are the same registers treated as 32bit.
43 */
44
45#include <stdio.h>
46#include <string.h>
47#include <inttypes.h>
48
49#include "config.h"
50#include "tst_test.h"
51#include "tst_taint.h"
52#include "tst_capability.h"
53#include "bpf_common.h"
54
55static const char MSG[] = "Ahoj!";
56static char *msg;
57
58static int map_fd;
59static uint32_t *key;
60static uint64_t *val;
61static char *log;
62static union bpf_attr *attr;
63
64static void ensure_ptr_arithmetic(void)
65{
66	const struct bpf_insn prog_insn[] = {
67		/* r2 = r10
68		 * r3 = -1
69		 * r2 += r3
70		 * *(char *)r2 = 0
71		 */
72		BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
73		BPF_MOV64_IMM(BPF_REG_3, -1),
74		BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_3),
75		BPF_ST_MEM(BPF_B, BPF_REG_2, 0, 0),
76
77		/* exit(0) */
78		BPF_MOV64_IMM(BPF_REG_0, 0),
79		BPF_EXIT_INSN()
80	};
81	int ret;
82
83	bpf_init_prog_attr(attr, prog_insn, sizeof(prog_insn), log, BUFSIZE);
84
85	ret = TST_RETRY_FUNC(bpf(BPF_PROG_LOAD, attr, sizeof(*attr)),
86				       TST_RETVAL_GE0);
87
88	if (ret >= 0) {
89		tst_res(TINFO, "Have pointer arithmetic");
90		SAFE_CLOSE(ret);
91		return;
92	}
93
94	if (ret != -1)
95		tst_brk(TBROK, "Invalid bpf() return value: %d", ret);
96
97	if (log[0] != 0)
98		tst_brk(TCONF | TERRNO, "No pointer arithmetic:\n %s", log);
99
100	tst_brk(TBROK | TERRNO, "Failed to load program");
101}
102
103static int load_prog(void)
104{
105	const struct bpf_insn prog_insn[] = {
106		/* r6 = 1 << 32
107		 * r7 = -1
108		 */
109		BPF_LD_IMM64(BPF_REG_6, 1ULL << 32),
110		BPF_MOV64_IMM(BPF_REG_7, -1LL),
111
112		/* w7 /= w6 */
113		BPF_ALU32_REG(BPF_DIV, BPF_REG_7, BPF_REG_6),
114
115		/* map[1] = r6
116		 * map[2] = r7
117		 */
118		BPF_MAP_ARRAY_STX(map_fd, 0, BPF_REG_6),
119		BPF_MAP_ARRAY_STX(map_fd, 1, BPF_REG_7),
120
121		/* r6 = 1 << 32
122		 * r7 = -1
123		 */
124		BPF_LD_IMM64(BPF_REG_6, 1ULL << 32),
125		BPF_MOV64_IMM(BPF_REG_7, -1LL),
126
127		/* w7 %= w6 */
128		BPF_ALU32_REG(BPF_MOD, BPF_REG_7, BPF_REG_6),
129
130		/* map[3] = r6
131		 * map[4] = r7
132		 */
133		BPF_MAP_ARRAY_STX(map_fd, 2, BPF_REG_6),
134		BPF_MAP_ARRAY_STX(map_fd, 3, BPF_REG_7),
135
136		/* exit(0) */
137		BPF_MOV64_IMM(BPF_REG_0, 0),
138		BPF_EXIT_INSN()
139	};
140
141	bpf_init_prog_attr(attr, prog_insn, sizeof(prog_insn), log, BUFSIZE);
142
143	return bpf_load_prog(attr, log);
144}
145
146static void expect_reg_val(const char *const reg_name,
147			   const uint64_t expected_val)
148{
149	bpf_map_array_get(map_fd, key, val);
150
151	(*key)++;
152
153	if (*val != expected_val) {
154		tst_res(TFAIL,
155			"%s = %"PRIu64", but should be %"PRIu64,
156			reg_name, *val, expected_val);
157	} else {
158		tst_res(TPASS, "%s = %"PRIu64, reg_name, *val);
159	}
160}
161
162static void setup(void)
163{
164	rlimit_bump_memlock();
165	memcpy(msg, MSG, sizeof(MSG));
166}
167
168static void run(void)
169{
170	int prog_fd;
171
172	map_fd = bpf_map_array_create(8);
173
174	ensure_ptr_arithmetic();
175	prog_fd = load_prog();
176	bpf_run_prog(prog_fd, msg, sizeof(MSG));
177	SAFE_CLOSE(prog_fd);
178
179	*key = 0;
180
181	tst_res(TINFO, "Check w7(-1) /= w6(0) [r7 = -1, r6 = 1 << 32]");
182	expect_reg_val("src(r6)", 1ULL << 32);
183	expect_reg_val("dst(r7)", 0);
184
185	tst_res(TINFO, "Check w7(-1) %%= w6(0) [r7 = -1, r6 = 1 << 32]");
186	expect_reg_val("src(r6)", 1ULL << 32);
187	expect_reg_val("dst(r7)", (uint32_t)-1);
188
189	SAFE_CLOSE(map_fd);
190}
191
192static struct tst_test test = {
193	.setup = setup,
194	.test_all = run,
195	.min_kver = "3.18",
196	.taint_check = TST_TAINT_W | TST_TAINT_D,
197	.caps = (struct tst_cap []) {
198		TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
199		TST_CAP(TST_CAP_DROP, CAP_BPF),
200		{}
201	},
202	.bufs = (struct tst_buffers []) {
203		{&key, .size = sizeof(*key)},
204		{&val, .size = sizeof(*val)},
205		{&log, .size = BUFSIZE},
206		{&attr, .size = sizeof(*attr)},
207		{&msg, .size = sizeof(MSG)},
208		{}
209	},
210	.tags = (const struct tst_tag[]) {
211		{"linux-git", "f6b1b3bf0d5f"},
212		{"linux-git", "468f6eafa6c4"},
213		{"linux-git", "e88b2c6e5a4d"},
214		{"linux-git", "9b00f1b78809"},
215		{"CVE", "CVE-2021-3444"},
216		{}
217	}
218};
219