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

perf dwarf-aux: Check allowed DWARF Ops

The DWARF location expression can be fairly complex and it'd be hard
to match it with the condition correctly. So let's be conservative
and only allow simple expressions. For now it just checks the first
operation in the list. The following operations looks ok:

* DW_OP_stack_value
* DW_OP_deref_size
* DW_OP_deref
* DW_OP_piece

To refuse complex (and unsupported) location expressions, add
check_allowed_ops() to compare the rest of the list. It seems earlier
result contained those unsupported expressions. For example, I found
some local struct variable is placed like below.

<2><43d1517>: Abbrev Number: 62 (DW_TAG_variable)
<43d1518> DW_AT_location : 15 byte block: 91 50 93 8 91 78 93 4 93 84 8 91 68 93 4
(DW_OP_fbreg: -48; DW_OP_piece: 8;
DW_OP_fbreg: -8; DW_OP_piece: 4;
DW_OP_piece: 1028;
DW_OP_fbreg: -24; DW_OP_piece: 4)

Another example is something like this.

0057c8be ffffffffffffffff ffffffff812109f0 (base address)
0057c8ce ffffffff812112b5 ffffffff812112c8 (DW_OP_breg3 (rbx): 0;
DW_OP_constu: 18446744073709551612;
DW_OP_and;
DW_OP_stack_value)

It should refuse them. After the change, the stat shows:

Annotate data type stats:
total 294, ok 158 (53.7%), bad 136 (46.3%)
-----------------------------------------------------------
30 : no_sym
32 : no_mem_ops
53 : no_var
14 : no_typeinfo
7 : bad_offset

Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Reviewed-by: Ian Rogers <irogers@google.com>
Cc: Stephane Eranian <eranian@google.com>
Link: https://lore.kernel.org/r/20240117062657.985479-10-namhyung@kernel.org
Signed-off-by: Namhyung Kim <namhyung@kernel.org>

+40 -4
+40 -4
tools/perf/util/dwarf-aux.c
··· 1305 1305 return true; 1306 1306 } 1307 1307 1308 + static bool check_allowed_ops(Dwarf_Op *ops, size_t nops) 1309 + { 1310 + /* The first op is checked separately */ 1311 + ops++; 1312 + nops--; 1313 + 1314 + /* 1315 + * It needs to make sure if the location expression matches to the given 1316 + * register and offset exactly. Thus it rejects any complex expressions 1317 + * and only allows a few of selected operators that doesn't change the 1318 + * location. 1319 + */ 1320 + while (nops) { 1321 + switch (ops->atom) { 1322 + case DW_OP_stack_value: 1323 + case DW_OP_deref_size: 1324 + case DW_OP_deref: 1325 + case DW_OP_piece: 1326 + break; 1327 + default: 1328 + return false; 1329 + } 1330 + ops++; 1331 + nops--; 1332 + } 1333 + return true; 1334 + } 1335 + 1308 1336 /* Only checks direct child DIEs in the given scope. */ 1309 1337 static int __die_find_var_reg_cb(Dwarf_Die *die_mem, void *arg) 1310 1338 { ··· 1360 1332 /* Local variables accessed using frame base register */ 1361 1333 if (data->is_fbreg && ops->atom == DW_OP_fbreg && 1362 1334 data->offset >= (int)ops->number && 1335 + check_allowed_ops(ops, nops) && 1363 1336 match_var_offset(die_mem, data, data->offset, ops->number)) 1364 1337 return DIE_FIND_CB_END; 1365 1338 1366 1339 /* Only match with a simple case */ 1367 1340 if (data->reg < DWARF_OP_DIRECT_REGS) { 1368 - if (ops->atom == (DW_OP_reg0 + data->reg) && nops == 1) 1341 + /* pointer variables saved in a register 0 to 31 */ 1342 + if (ops->atom == (DW_OP_reg0 + data->reg) && 1343 + check_allowed_ops(ops, nops)) 1369 1344 return DIE_FIND_CB_END; 1370 1345 1371 1346 /* Local variables accessed by a register + offset */ 1372 1347 if (ops->atom == (DW_OP_breg0 + data->reg) && 1348 + check_allowed_ops(ops, nops) && 1373 1349 match_var_offset(die_mem, data, data->offset, ops->number)) 1374 1350 return DIE_FIND_CB_END; 1375 1351 } else { 1352 + /* pointer variables saved in a register 32 or above */ 1376 1353 if (ops->atom == DW_OP_regx && ops->number == data->reg && 1377 - nops == 1) 1354 + check_allowed_ops(ops, nops)) 1378 1355 return DIE_FIND_CB_END; 1379 1356 1380 1357 /* Local variables accessed by a register + offset */ 1381 1358 if (ops->atom == DW_OP_bregx && data->reg == ops->number && 1359 + check_allowed_ops(ops, nops) && 1382 1360 match_var_offset(die_mem, data, data->offset, ops->number2)) 1383 1361 return DIE_FIND_CB_END; 1384 1362 } ··· 1446 1412 if (data->addr < ops->number) 1447 1413 continue; 1448 1414 1449 - if (match_var_offset(die_mem, data, data->addr, ops->number)) 1415 + if (check_allowed_ops(ops, nops) && 1416 + match_var_offset(die_mem, data, data->addr, ops->number)) 1450 1417 return DIE_FIND_CB_END; 1451 1418 } 1452 1419 return DIE_FIND_CB_SIBLING; ··· 1538 1503 return -1; 1539 1504 1540 1505 if (!dwarf_cfi_addrframe(cfi, pc, &frame) && 1541 - !dwarf_frame_cfa(frame, &ops, &nops) && nops == 1) { 1506 + !dwarf_frame_cfa(frame, &ops, &nops) && 1507 + check_allowed_ops(ops, nops)) { 1542 1508 *preg = reg_from_dwarf_op(ops); 1543 1509 *poffset = offset_from_dwarf_op(ops); 1544 1510 return 0;