angr / angr/pypcode

ARM:LE:32:v7 and v8 segfault translating a coprocessor STC word (v6 raises BadDataError)

未关闭
#294 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
C++
星标
214
派生
30
PR 合并指标
30 天内没有已合并 PR

描述

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

`ARM:LE:32:v7` and `ARM:LE:32:v8` segfault translating a single 4-byte word.
`ARM:LE:32:v6` rejects the same word cleanly with `BadDataError`, so the correct
failure mode already exists next door in the same processor family.

## Reproducer

```python
import pypcode
pypcode.Context("ARM:LE:32:v7").translate(bytes.fromhex("746b69cd"), 0x1000)
```

Four bytes, one call, no flags, no other state. Deterministic: 10/10 runs,
`SIGSEGV` (exit 139). Same for `ARM:LE:32:v8`.

## Expected behaviour

A raised exception, not a crash. `BadDataError` is the right outcome here — the
word is not valid code for this language, and `ARM:LE:32:v6` already returns
exactly that for the identical input:

```
BadDataError: r0x00001000: Unable to resolve constructor
```

The distinction matters to a caller, not just aesthetically. An exception is
catchable: a tool sweeping an image can skip the offending block and carry on.
`SIGSEGV` takes the process out with no opportunity to recover, no partial
results, and no indication of which address was responsible. So the ask is that
`v7`/`v8` fail the way `v6` does, whatever the underlying cause turns out to be.

## Behaviour across the ARM variants

Each in its own child process, so the matrix survives:

```python
import subprocess, sys, pypcode

WORD = "746b69cd" # little-endian 0xcd696b74
CHILD = "import pypcode,sys; pypcode.Context(sys.argv[1]).translate(bytes.fromhex(sys.argv[2]), 0x1000)"

print(f"pypcode {pypcode.__version__}")
for lang in ("ARM:LE:32:v5", "ARM:LE:32:v6", "ARM:LE:32:v7",
"ARM:LE:32:v8", "ARM:LE:32:Cortex", "ARM:BE:32:v7"):
rc = subprocess.run([sys.executable, "-c", CHILD, lang, WORD], capture_output=True).returncode
print(f" {lang:18} {'SIGSEGV' if rc in (-11, 139) else f'ok (rc={rc})'}")
```

| language | result |
| --- | --- |
| `ARM:LE:32:v5` | ok — decodes as `stclgt p11,cr6,[r9,#-0x1d0]!` |
| `ARM:LE:32:v6` | `BadDataError: r0x00001000: Unable to resolve constructor` |
| `ARM:LE:32:v7` | **SIGSEGV** |
| `ARM:LE:32:v8` | **SIGSEGV** |
| `ARM:LE:32:Cortex` | ok — decodes as Thumb (`ldr` / `ldmia`) |
| `ARM:BE:32:v7` | ok — different word under the other byte order |

## The word

`0xcd696b74` decodes (per `ARM:LE:32:v5`) as:

```
stclgt p11, cr6, [r9, #-0x1d0]!
```

A coprocessor store, predicated `GT`, pre-indexed with writeback, targeting
**p11** — the VFP double-precision coprocessor. Field breakdown:

```
cond=0xc(GT) op=0b110 P=1 U=0 D=1 W=1 L=0
Rn=r9 CRd=c6 coproc=p11 imm8=116 (byte offset 464)
```

The split lines up with VFP modelling: `v5` has no VFP model and takes it as a
plain `STCL`; `v6` refuses it; `v7`/`v8` model VFP/NEON and die. That `v6` raises
where `v7` crashes is the part I would weight most — it suggests something in the
newer VFP/NEON decode path rather than a general gap in coprocessor handling.

## Where the bytes came from

Not synthesised. The word occurs five times inside **`kallsyms_names`** — the
compressed kallsyms symbol-name table — in Debian's `linux-image-2.6.37-2-versatile`
(armel). The crashing decode is at `0xc02b6410`, `kallsyms_names + 0x8e70`. That
build's `System.map` types `kallsyms_names` as `T`, so the table sits in the text
region, and a sweep of executable ranges decodes it as instructions.

To be exact about the artifact: Debian's regular (non-dbg) armel package ships a
compressed `/boot/vmlinuz`, not an ELF, so the bytes were read from the
decompressed kernel image out of that zImage — stock Debian content, in a
container rebuilt locally. The word does not appear in the armhf `-dbg` images I
also have (`4.9.0-19-armmp`, `4.18.0-3-armmp`), so this is specific to that
build, and I would not want to overstate how common it is.

That is the practical impact: a consumer pointed at an arbitrary ARM binary
cannot know whether a given word is code before lifting it, and a segfault gives
no opportunity to skip the block. Refusing invalid input is fine — `v6` already
does exactly that.

## Environment

```
pypcode 4.0.0 (PyPI wheel, cp313-cp313-manylinux_2_27_x86_64)
python 3.13.7
platform Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.35
machine x86_64
```

Tested against the 4.0.0 release wheel, **not** against `master` — happy to
retest there or against a candidate fix if that is useful.

## Related

Same class as #293 (segfaults on short inputs for JVM, NDS32, SPARC) and #292
(PowerPC `vaddubm` heap corruption), but a different language and not covered by
either, so filing separately. Unlike the three in #293, this word is not a
synthetic sequence — it occurs in a real distribution kernel.

贡献指南

这个仓库没有索引到贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。