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