Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: specify expected instructions in test_verifier tests

Allows to specify expected and unexpected instruction sequences in
test_verifier test cases. The instructions are requested from kernel
after BPF program loading, thus allowing to check some of the
transformations applied by BPF verifier.

- `expected_insn` field specifies a sequence of instructions expected
to be found in the program;
- `unexpected_insn` field specifies a sequence of instructions that
are not expected to be found in the program;
- `INSN_OFF_MASK` and `INSN_IMM_MASK` values could be used to mask
`off` and `imm` fields.
- `SKIP_INSNS` could be used to specify that some instructions in the
(un)expected pattern are not important (behavior similar to usage of
`\t` in `errstr` field).

The intended usage is as follows:

{
"inline simple bpf_loop call",
.insns = {
/* main */
BPF_ALU64_IMM(BPF_MOV, BPF_REG_1, 1),
BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, BPF_REG_2,
BPF_PSEUDO_FUNC, 0, 6),
...
BPF_EXIT_INSN(),
/* callback */
BPF_ALU64_IMM(BPF_MOV, BPF_REG_0, 1),
BPF_EXIT_INSN(),
},
.expected_insns = {
BPF_ALU64_IMM(BPF_MOV, BPF_REG_1, 1),
SKIP_INSNS(),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 8, 1)
},
.unexpected_insns = {
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0,
INSN_OFF_MASK, INSN_IMM_MASK),
},
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
.result = ACCEPT,
.runs = 0,
},

Here it is expected that move of 1 to register 1 would remain in place
and helper function call instruction would be replaced by a relative
call instruction.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Acked-by: Song Liu <songliubraving@fb.com>
Link: https://lore.kernel.org/r/20220620235344.569325-2-eddyz87@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Eduard Zingerman and committed by
Alexei Starovoitov
933ff531 aca80dd9

+234
+234
tools/testing/selftests/bpf/test_verifier.c
··· 51 51 #endif 52 52 53 53 #define MAX_INSNS BPF_MAXINSNS 54 + #define MAX_EXPECTED_INSNS 32 55 + #define MAX_UNEXPECTED_INSNS 32 54 56 #define MAX_TEST_INSNS 1000000 55 57 #define MAX_FIXUPS 8 56 58 #define MAX_NR_MAPS 23 57 59 #define MAX_TEST_RUNS 8 58 60 #define POINTER_VALUE 0xcafe4all 59 61 #define TEST_DATA_LEN 64 62 + 63 + #define INSN_OFF_MASK ((__s16)0xFFFF) 64 + #define INSN_IMM_MASK ((__s32)0xFFFFFFFF) 65 + #define SKIP_INSNS() BPF_RAW_INSN(0xde, 0xa, 0xd, 0xbeef, 0xdeadbeef) 60 66 61 67 #define F_NEEDS_EFFICIENT_UNALIGNED_ACCESS (1 << 0) 62 68 #define F_LOAD_WITH_STRICT_ALIGNMENT (1 << 1) ··· 85 79 const char *descr; 86 80 struct bpf_insn insns[MAX_INSNS]; 87 81 struct bpf_insn *fill_insns; 82 + /* If specified, test engine looks for this sequence of 83 + * instructions in the BPF program after loading. Allows to 84 + * test rewrites applied by verifier. Use values 85 + * INSN_OFF_MASK and INSN_IMM_MASK to mask `off` and `imm` 86 + * fields if content does not matter. The test case fails if 87 + * specified instructions are not found. 88 + * 89 + * The sequence could be split into sub-sequences by adding 90 + * SKIP_INSNS instruction at the end of each sub-sequence. In 91 + * such case sub-sequences are searched for one after another. 92 + */ 93 + struct bpf_insn expected_insns[MAX_EXPECTED_INSNS]; 94 + /* If specified, test engine applies same pattern matching 95 + * logic as for `expected_insns`. If the specified pattern is 96 + * matched test case is marked as failed. 97 + */ 98 + struct bpf_insn unexpected_insns[MAX_UNEXPECTED_INSNS]; 88 99 int fixup_map_hash_8b[MAX_FIXUPS]; 89 100 int fixup_map_hash_48b[MAX_FIXUPS]; 90 101 int fixup_map_hash_16b[MAX_FIXUPS]; ··· 1149 1126 return true; 1150 1127 } 1151 1128 1129 + static int get_xlated_program(int fd_prog, struct bpf_insn **buf, int *cnt) 1130 + { 1131 + struct bpf_prog_info info = {}; 1132 + __u32 info_len = sizeof(info); 1133 + __u32 xlated_prog_len; 1134 + __u32 buf_element_size = sizeof(struct bpf_insn); 1135 + 1136 + if (bpf_obj_get_info_by_fd(fd_prog, &info, &info_len)) { 1137 + perror("bpf_obj_get_info_by_fd failed"); 1138 + return -1; 1139 + } 1140 + 1141 + xlated_prog_len = info.xlated_prog_len; 1142 + if (xlated_prog_len % buf_element_size) { 1143 + printf("Program length %d is not multiple of %d\n", 1144 + xlated_prog_len, buf_element_size); 1145 + return -1; 1146 + } 1147 + 1148 + *cnt = xlated_prog_len / buf_element_size; 1149 + *buf = calloc(*cnt, buf_element_size); 1150 + if (!buf) { 1151 + perror("can't allocate xlated program buffer"); 1152 + return -ENOMEM; 1153 + } 1154 + 1155 + bzero(&info, sizeof(info)); 1156 + info.xlated_prog_len = xlated_prog_len; 1157 + info.xlated_prog_insns = (__u64)*buf; 1158 + if (bpf_obj_get_info_by_fd(fd_prog, &info, &info_len)) { 1159 + perror("second bpf_obj_get_info_by_fd failed"); 1160 + goto out_free_buf; 1161 + } 1162 + 1163 + return 0; 1164 + 1165 + out_free_buf: 1166 + free(*buf); 1167 + return -1; 1168 + } 1169 + 1170 + static bool is_null_insn(struct bpf_insn *insn) 1171 + { 1172 + struct bpf_insn null_insn = {}; 1173 + 1174 + return memcmp(insn, &null_insn, sizeof(null_insn)) == 0; 1175 + } 1176 + 1177 + static bool is_skip_insn(struct bpf_insn *insn) 1178 + { 1179 + struct bpf_insn skip_insn = SKIP_INSNS(); 1180 + 1181 + return memcmp(insn, &skip_insn, sizeof(skip_insn)) == 0; 1182 + } 1183 + 1184 + static int null_terminated_insn_len(struct bpf_insn *seq, int max_len) 1185 + { 1186 + int i; 1187 + 1188 + for (i = 0; i < max_len; ++i) { 1189 + if (is_null_insn(&seq[i])) 1190 + return i; 1191 + } 1192 + return max_len; 1193 + } 1194 + 1195 + static bool compare_masked_insn(struct bpf_insn *orig, struct bpf_insn *masked) 1196 + { 1197 + struct bpf_insn orig_masked; 1198 + 1199 + memcpy(&orig_masked, orig, sizeof(orig_masked)); 1200 + if (masked->imm == INSN_IMM_MASK) 1201 + orig_masked.imm = INSN_IMM_MASK; 1202 + if (masked->off == INSN_OFF_MASK) 1203 + orig_masked.off = INSN_OFF_MASK; 1204 + 1205 + return memcmp(&orig_masked, masked, sizeof(orig_masked)) == 0; 1206 + } 1207 + 1208 + static int find_insn_subseq(struct bpf_insn *seq, struct bpf_insn *subseq, 1209 + int seq_len, int subseq_len) 1210 + { 1211 + int i, j; 1212 + 1213 + if (subseq_len > seq_len) 1214 + return -1; 1215 + 1216 + for (i = 0; i < seq_len - subseq_len + 1; ++i) { 1217 + bool found = true; 1218 + 1219 + for (j = 0; j < subseq_len; ++j) { 1220 + if (!compare_masked_insn(&seq[i + j], &subseq[j])) { 1221 + found = false; 1222 + break; 1223 + } 1224 + } 1225 + if (found) 1226 + return i; 1227 + } 1228 + 1229 + return -1; 1230 + } 1231 + 1232 + static int find_skip_insn_marker(struct bpf_insn *seq, int len) 1233 + { 1234 + int i; 1235 + 1236 + for (i = 0; i < len; ++i) 1237 + if (is_skip_insn(&seq[i])) 1238 + return i; 1239 + 1240 + return -1; 1241 + } 1242 + 1243 + /* Return true if all sub-sequences in `subseqs` could be found in 1244 + * `seq` one after another. Sub-sequences are separated by a single 1245 + * nil instruction. 1246 + */ 1247 + static bool find_all_insn_subseqs(struct bpf_insn *seq, struct bpf_insn *subseqs, 1248 + int seq_len, int max_subseqs_len) 1249 + { 1250 + int subseqs_len = null_terminated_insn_len(subseqs, max_subseqs_len); 1251 + 1252 + while (subseqs_len > 0) { 1253 + int skip_idx = find_skip_insn_marker(subseqs, subseqs_len); 1254 + int cur_subseq_len = skip_idx < 0 ? subseqs_len : skip_idx; 1255 + int subseq_idx = find_insn_subseq(seq, subseqs, 1256 + seq_len, cur_subseq_len); 1257 + 1258 + if (subseq_idx < 0) 1259 + return false; 1260 + seq += subseq_idx + cur_subseq_len; 1261 + seq_len -= subseq_idx + cur_subseq_len; 1262 + subseqs += cur_subseq_len + 1; 1263 + subseqs_len -= cur_subseq_len + 1; 1264 + } 1265 + 1266 + return true; 1267 + } 1268 + 1269 + static void print_insn(struct bpf_insn *buf, int cnt) 1270 + { 1271 + int i; 1272 + 1273 + printf(" addr op d s off imm\n"); 1274 + for (i = 0; i < cnt; ++i) { 1275 + struct bpf_insn *insn = &buf[i]; 1276 + 1277 + if (is_null_insn(insn)) 1278 + break; 1279 + 1280 + if (is_skip_insn(insn)) 1281 + printf(" ...\n"); 1282 + else 1283 + printf(" %04x: %02x %1x %x %04hx %08x\n", 1284 + i, insn->code, insn->dst_reg, 1285 + insn->src_reg, insn->off, insn->imm); 1286 + } 1287 + } 1288 + 1289 + static bool check_xlated_program(struct bpf_test *test, int fd_prog) 1290 + { 1291 + struct bpf_insn *buf; 1292 + int cnt; 1293 + bool result = true; 1294 + bool check_expected = !is_null_insn(test->expected_insns); 1295 + bool check_unexpected = !is_null_insn(test->unexpected_insns); 1296 + 1297 + if (!check_expected && !check_unexpected) 1298 + goto out; 1299 + 1300 + if (get_xlated_program(fd_prog, &buf, &cnt)) { 1301 + printf("FAIL: can't get xlated program\n"); 1302 + result = false; 1303 + goto out; 1304 + } 1305 + 1306 + if (check_expected && 1307 + !find_all_insn_subseqs(buf, test->expected_insns, 1308 + cnt, MAX_EXPECTED_INSNS)) { 1309 + printf("FAIL: can't find expected subsequence of instructions\n"); 1310 + result = false; 1311 + if (verbose) { 1312 + printf("Program:\n"); 1313 + print_insn(buf, cnt); 1314 + printf("Expected subsequence:\n"); 1315 + print_insn(test->expected_insns, MAX_EXPECTED_INSNS); 1316 + } 1317 + } 1318 + 1319 + if (check_unexpected && 1320 + find_all_insn_subseqs(buf, test->unexpected_insns, 1321 + cnt, MAX_UNEXPECTED_INSNS)) { 1322 + printf("FAIL: found unexpected subsequence of instructions\n"); 1323 + result = false; 1324 + if (verbose) { 1325 + printf("Program:\n"); 1326 + print_insn(buf, cnt); 1327 + printf("Un-expected subsequence:\n"); 1328 + print_insn(test->unexpected_insns, MAX_UNEXPECTED_INSNS); 1329 + } 1330 + } 1331 + 1332 + free(buf); 1333 + out: 1334 + return result; 1335 + } 1336 + 1152 1337 static void do_test_single(struct bpf_test *test, bool unpriv, 1153 1338 int *passes, int *errors) 1154 1339 { ··· 1492 1261 1493 1262 if (verbose) 1494 1263 printf(", verifier log:\n%s", bpf_vlog); 1264 + 1265 + if (!check_xlated_program(test, fd_prog)) 1266 + goto fail_log; 1495 1267 1496 1268 run_errs = 0; 1497 1269 run_successes = 0;