Skip to content

Add an ARMEL fixture for zero-size normalization anchors - #201

Open
zardus wants to merge 1 commit into
masterfrom
fixture/normalize-zero-size-anchor
Open

Add an ARMEL fixture for zero-size normalization anchors#201
zardus wants to merge 1 commit into
masterfrom
fixture/normalize-zero-size-anchor

Conversation

@zardus

@zardus zardus commented Aug 26, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

angr's CFG normalization has a defect that needs a zero-size node as the anchor of an end-address group, and no fixture here can produce one. A node reaches size zero when the ARM undefined-instruction recognizer accepts an address and the lifter returns an empty IRSB for it, which takes the permanently-undefined word 0xe7f000f0 sitting where a block ends. Searching every ARM object under tests/:

searched 735 ELF objects under tests/ for: EM_ARM object whose .text holds the permanently-undefined word 0xe7f000f0
0 match

Without an input, the consumer test can only assert on a graph it assembles itself, which is the shape the test-inputs rule exists to prevent.

Root cause

A compiler does not emit UDF in the middle of reachable code, and it does not emit two blocks that overlap and end at the same address. The arrangement has to be written in assembly.

Fix

Add tests/armel/normalize_zero_size_anchor, an ARMEL ET_EXEC of 852 bytes. entangle at 0x10084 branches into itself twice, so the blocks entered at 0x10098 and 0x1009c overlap and both end at 0x100a0, where the word is 0xe7f000f0:

   10084: 010050e3   cmp r0, #1   <entangle>
   10088: 0200000a   beq #0x10098
   1008c: 000050e3   cmp r0, #0
   10090: 0100000a   beq #0x1009c
   10094: 1eff2fe1   bx lr
   10098: 010080e2   add r0, r0, #1
   1009c: ffffffea   b #0x100a0
   100a0: f000f0e7   udf #0

Source and a build script are in tests_src/cfg_tests. Nothing existing is touched.

Testing

tests_src/cfg_tests/build_normalize_zero_size_anchor.sh, run under nix shell nixpkgs#pkgsCross.armv7l-hf-multiplatform.buildPackages.gcc (GCC 15.3.0), reproduces the committed file byte for byte, so the recipe and the artifact agree. This repository has no suite of its own; the consumer is the angr change to CFGBase normalization, whose new test fails until this merges.

Validation: #201 (comment)

session: sharpen

@zardus

zardus commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 848156756b3c48dcd39e43b6ff182fb3ba569158.

  • tests/armel/normalize_zero_size_anchor — 852 bytes, sha256 f311799c292b38064abf7e882a2b1abce58a782cfbd890f23399da2d3b43ff8a; ELFCLASS32, little-endian, EM_ARM, ET_EXEC
  • Shape: entangle starts at 0x10084 and contains two beq branches into itself whose blocks overlap on [0x1009c, 0x100a0) and share the end address 0x100a0. The word at 0x100a0 is e7f000f0 (UDF), stored as f0 00 f0 e7, with a second at 0x100a4 — so both blocks end immediately before an undefined instruction, which is the zero-size node the defect needs
  • Rebuild: tests_src/cfg_tests/build_normalize_zero_size_anchor.sh run offline under nix shell nixpkgs#pkgsCross.armv7l-hf-multiplatform.buildPackages.gcc (GCC 15.3.0) reproduces the committed file byte for byte, same sha256
  • Load: cle.Loader(path, auto_load_libs=False) on cle b58ea02a446106647cdaae32bdf91b7062404cc1 gives <ELF Object normalize_zero_size_anchor, maps [0x10000:0x100ab]>, <Arch ARMEL (LE)>

Caveats: this repository has no test suite, so the record is header and byte verification of the committed artifact, the rebuild above, and the load. The consumer test that fails without this fixture lives in the angr change; it was not run here.

@zardus

zardus commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Searching every EM_ARM object under tests/ for the ARM permanently-undefined word 0xe7f000f0, before and after this change.

Before — no ARM fixture holds one, so no fixture can produce the zero-size CFG node the defect needs:

angr/binaries at the merge base
angr/binaries at baseline 8646be4
searched 735 ELF objects under tests/ for: EM_ARM object whose .text holds the permanently-undefined word 0xe7f000f0
0 match

Afternormalize_zero_size_anchor holds two, at the end address two overlapping blocks share:

with this change
angr/binaries at head 8481567
searched 736 ELF objects under tests/ for: EM_ARM object whose .text holds the permanently-undefined word 0xe7f000f0
1 match
    tests/armel/normalize_zero_size_anchor  --  2 occurrences of 0xe7f000f0

disassembly of tests/armel/normalize_zero_size_anchor .text:
   10074: 0200a0e3   mov r0, #2   <_start>
   10078: 010000eb   bl #0x10084
   1007c: 0170a0e3   mov r7, #1
   10080: 000000ef   svc #0
   10084: 010050e3   cmp r0, #1   <entangle>
   10088: 0200000a   beq #0x10098
   1008c: 000050e3   cmp r0, #0
   10090: 0100000a   beq #0x1009c
   10094: 1eff2fe1   bx lr
   10098: 010080e2   add r0, r0, #1
   1009c: ffffffea   b #0x100a0
   100a0: f000f0e7   udf #0
   100a4: f000f0e7   udf #0
   100a8: 1eff2fe1   bx lr

CFGBase.normalize() groups nodes by end address to decide which
overlapping blocks to split. A zero-size node lands in the group of
every node that ends where it starts, wins the "highest address"
selection, and splitting the group at its address shortens nothing.

No existing fixture produces a non-SimProcedure zero-size CFGNode. This
one does: a UDF instruction that VEX decodes to a zero-byte IRSB, placed
at the end of two overlapping blocks inside one function.
@zardus

zardus commented Aug 29, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

angr#6960, the consumer this fixture was added for, is closed: angr#6839 repairs
the producer, so CFGFast no longer records the extentless node the fixture is
built around and CFGBase.normalize() never sees one. Nothing depends on this
pull request now.

It is left open rather than closed because it is still the smallest input that
exercises the ARM UND half of angr#6839, which has no regression of its own —
see angr/angr#6839 for the test that would use it.

The description's premise needs correcting either way. The search behind it
looked for the literal word 0xe7f000f0, but the ARM UND recognizer in
_generate_cfgnode matches a family of encodings, so objects already in this
repository reach the same state. Measured over tests/{armel,armhf,i386} — 216
objects under 4 MB, 202 of which load and complete — with
CFGFast(normalize=True) on angr at df4f8ba7281a76bdb212f3ca89e7e2f3a30f569b:
eight objects carry an extentless non-SimProcedure CFGNode, 456 in total,
every one ARM. tests/armel/btrfs.ko carries 443 of them and also exhibits the
whole defect, leaving 20 blocks starting at a non-leading instruction of another
block, which angr#6960's change took to 1. So a public reproducer for that
consumer already existed here; this fixture made it small, which is a good
reason for a fixture and not the reason the description gives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant