mirror of
https://github.com/angr/pyvex
synced 2026-06-21 13:47:01 +00:00
pyvex_c: widen HashHW value slot to ULong (fix 64-bit guest-pointer truncation on 32-bit hosts) (#541)
* pyvex_c: widen HashHW value slot to ULong HashHW (lifted from libvex/ir_opt.c) caches (IR-temp -> resolved constant) bindings during data_ref extraction. Values were typed HWord (= unsigned long, host-pointer-width). On 32-bit hosts that's 4 bytes, but the guest can be 64-bit (AMD64, AArch64), so cached guest pointers were silently truncated. Symptom on wasm32-emscripten with an AMD64 guest: pyvex.lift returns data_addr=0x6320 for a load whose actual target is 0x100006320 (high 32 bits dropped). Native x86-64 SysV hides the bug because long is 64-bit there. Fix: store HashHW values as ULong unconditionally. Keys are pyvex-internal IR-temp identifiers and can stay HWord. Adds tests/test_data_refs_64bit_target.py to lock in the expected value. Fixes #539 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests: drop archinfo dep, add class docstring archinfo isn't a pyvex test dependency, so the new test failed to collect on all CI runners. Use the bundled pyvex.ARCH_AMD64 constant instead. Also add the class docstring that the ecosystem lint flagged. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Brian Caswell <brian@demoray.net>
This commit is contained in:
co-authored by
pre-commit-ci[bot]
Brian Caswell
parent
5a3760552b
commit
6ef0032cf4
+6
-6
@@ -210,7 +210,7 @@ typedef
|
||||
struct {
|
||||
Bool* inuse;
|
||||
HWord* key;
|
||||
HWord* val;
|
||||
ULong* val;
|
||||
Int size;
|
||||
Int used;
|
||||
}
|
||||
@@ -223,7 +223,7 @@ static HashHW* newHHW()
|
||||
h->used = 0;
|
||||
h->inuse = (Bool*)malloc(h->size * sizeof(Bool));
|
||||
h->key = (HWord*)malloc(h->size * sizeof(HWord));
|
||||
h->val = (HWord*)malloc(h->size * sizeof(HWord));
|
||||
h->val = (ULong*)malloc(h->size * sizeof(ULong));
|
||||
return h;
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ static void freeHHW(HashHW* h)
|
||||
|
||||
/* Look up key in the map. */
|
||||
|
||||
static Bool lookupHHW(HashHW* h, /*OUT*/HWord* val, HWord key)
|
||||
static Bool lookupHHW(HashHW* h, /*OUT*/ULong* val, HWord key)
|
||||
{
|
||||
Int i;
|
||||
|
||||
@@ -255,7 +255,7 @@ static Bool lookupHHW(HashHW* h, /*OUT*/HWord* val, HWord key)
|
||||
|
||||
/* Add key->val to the map. Replaces any existing binding for key. */
|
||||
|
||||
static void addToHHW(HashHW* h, HWord key, HWord val)
|
||||
static void addToHHW(HashHW* h, HWord key, ULong val)
|
||||
{
|
||||
Int i, j;
|
||||
|
||||
@@ -272,7 +272,7 @@ static void addToHHW(HashHW* h, HWord key, HWord val)
|
||||
/* Copy into arrays twice the size. */
|
||||
Bool* inuse2 = malloc(2 * h->size * sizeof(Bool));
|
||||
HWord* key2 = malloc(2 * h->size * sizeof(HWord));
|
||||
HWord* val2 = malloc(2 * h->size * sizeof(HWord));
|
||||
ULong* val2 = malloc(2 * h->size * sizeof(ULong));
|
||||
for (i = j = 0; i < h->size; i++) {
|
||||
if (!h->inuse[i]) continue;
|
||||
inuse2[j] = True;
|
||||
@@ -811,7 +811,7 @@ void execute_irsb(
|
||||
case Iex_Get:
|
||||
{
|
||||
UInt key = mk_key_GetPut(data->Iex.Get.offset, data->Iex.Get.ty);
|
||||
HWord val;
|
||||
ULong val;
|
||||
if (lookupHHW(env, &val, key) == True) {
|
||||
tmps[stmt->Ist.WrTmp.tmp].used = 1;
|
||||
tmps[stmt->Ist.WrTmp.tmp].value = val;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Regression test for HashHW value-slot truncation on 32-bit hosts.
|
||||
|
||||
See pyvex_c/analysis.c::HashHW. Before the fix, the cached register
|
||||
value flowed through a `HWord` (= `unsigned long`, 32-bit on
|
||||
wasm32-emscripten and other ILP32 hosts), silently dropping the high
|
||||
32 bits of any guest pointer. The PoC below uses an AMD64 guest LEA +
|
||||
memory load to land a 64-bit pointer in the HashHW.
|
||||
|
||||
https://github.com/angr/pyvex/issues/539
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
import pyvex
|
||||
|
||||
|
||||
class TestDataRefs64BitTarget(unittest.TestCase):
|
||||
"""Regression tests for HashHW value-slot width on 32-bit hosts."""
|
||||
|
||||
def test_64bit_guest_pointer_survives_hashhw_roundtrip(self):
|
||||
# 5 AMD64 instructions, 25 bytes. The mov at +0xe loads from
|
||||
# rdi - 4 where rdi was set by the previous lea to 0x100006324,
|
||||
# so the load target should be 0x100006320.
|
||||
blob = bytes.fromhex(
|
||||
"488d3d39b0feff" # lea rdi, [rip - 0x14fc7]
|
||||
"bd14000000" # mov ebp, 0x14
|
||||
"488d542420" # lea rdx, [rsp + 0x20]
|
||||
"8b4ffc" # mov ecx, dword ptr [rdi - 4]
|
||||
"e873300100" # call rel32
|
||||
)
|
||||
irsb = pyvex.lift(
|
||||
blob,
|
||||
0x10001B2E4,
|
||||
pyvex.ARCH_AMD64,
|
||||
max_inst=5,
|
||||
collect_data_refs=True,
|
||||
opt_level=1,
|
||||
cross_insn_opt=False,
|
||||
)
|
||||
sized_refs = [r for r in (irsb.data_refs or []) if r.ins_addr == 0x10001B2F5 and r.data_size == 4]
|
||||
assert sized_refs, "expected one size=4 ref from `mov ecx, [rdi - 4]`"
|
||||
self.assertEqual(
|
||||
sized_refs[0].data_addr,
|
||||
0x100006320,
|
||||
f"data_addr=0x{sized_refs[0].data_addr:x}; "
|
||||
"high 32 bits dropped — see pyvex_c/analysis.c HashHW value width",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user