Fix heap corruption on instructions that outgrow the parse tree - #289
Fix heap corruption on instructions that outgrow the parse tree#289zardus wants to merge 1 commit into
Conversation
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head
Reproducers, one instruction per process,
Which glibc check fires, and whether the process aborts or faults, depends on heap layout and varies between builds; the PowerPC and ARM deaths happen after Tree sizes, from a build carrying a counter in
The operand counts come from the shipped specs:
Caveats:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #289 +/- ##
=======================================
Coverage 86.82% 86.82%
=======================================
Files 5 5
Lines 516 516
Branches 82 82
=======================================
Hits 448 448
Misses 26 26
Partials 42 42 ☔ View full report in Codecov by Harness. |
ParserContext holds the parse tree of the instruction being decoded in three
fixed arrays: a node array and a per-node operand array, both sized once by
ParserContext::initialize, and the walker's breadcrumb trail. allocateOperand
indexed all three without checking any of them, so an instruction whose tree
did not fit wrote past the end of two heap blocks and into the walker's own
context member. Translation returned normally with correct p-code and the
process died later, in an unrelated malloc or free, with a glibc abort or a
segfault.
Ordinary encodings reach every one of the three. PowerPC AltiVec spells
arithmetic out one operand per vector lane, so vaddubm declares 24 operands and
vadduhm and vsubuhm declare 27, against an allotment of 20. A register list
takes one Constructor per register, so ARM vldmia r0,{s0-s19} needs 76 nodes
against 75, and vldmia r0,{s0-s30} needs 109 and nests 36 deep against a
32-entry breadcrumb trail. NDS32 register-list instructions reach 120 nodes.
Give the node array room for 512 nodes and the breadcrumb trail room for 128,
have setConstructor size a node's operand array to the Constructor it is
attached to, and have allocateOperand raise BadDataError rather than allocate a
node the arrays cannot hold. BadDataError is already how oneInstruction reports
undecodable input, so a tree that genuinely has no bound of its own -- a JVM
lookupswitch, which nests one level per table entry and takes the entry count
from the instruction stream -- now reaches the caller as a catchable exception.
Refusing abandons a half-built tree in a cached ParserContext, so the tests
cover that the Context stays usable afterwards.
Over 20,000 random inputs against each of the 187 shipped languages, no
instruction outside JVM reaches either limit, and the largest tree any of them
builds is 120 nodes at 36 levels.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
478e663 to
54d3ad5
Compare
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Context.translatecorrupts the heap on ordinary encodings.ParserContextkeeps an instruction's parse tree in three fixed arrays andallocateOperandindexes all three without checking any of them, so PowerPC AltiVec operands and ARM and NDS32 register lists write past the end. Translation still returns correct p-code, and the process dies later in an unrelatedmallocorfree.A node's operand array is now sized to the constructor attached to it, the node array and breadcrumb trail hold the largest tree the shipped languages build, and
allocateOperandraisesBadDataErrorinstead of overrunning them, which is already howoneInstructionreports undecodable input.The regression decodes the PowerPC, ARM and NDS32 instructions in child interpreters, since master crashes rather than fails. These sources are vendored from Ghidra unchanged, so the defect is upstream too.
Validation: #289 (comment)