ARM:LE:32:v7 and v8 segfault translating a coprocessor STC word (v6 raises BadDataError)
- Linguagem predominante
- C++
- Estrelas
- 214
- Forks
- 30
- Métricas de merge de PRs
- Nenhum PR com merge em 30d
Descrição
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.
Guia de contribuição
Nenhum guia de contribuição indexado para este repositório
Avaliação
Esta issue ainda não foi avaliada.