angr / angr/pypcode

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

Abierto
#294 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
C++
Estrellas
214
Forks
30
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

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.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.