mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
SimLinux: Stop pre-growing the stack past address zero. (#6806)
SimLinux.state_blank pre-grows the stack by a fixed 0x20 pages without
checking that 0x20 pages exist beneath the stack pointer. When they do
not, the allocation loop wraps past address 0 and hands out the remainder
at the top of the address space.
On x86-64 that is silent: blank_state(stack_end=0x10000) maps sixteen
stack pages from 0x0 up and sixteen more from 0xfffffffffffff000 down.
Where the wrap reaches a page the same call already handed out, the state
fails with SimSegfaultException("stack collided with heap") instead, and
where it reaches the loaded image it replaces it with blank pages.
Skip the pre-grow when that much space does not exist. Clamping it to the
space that does exist is not an option: the pre-allocated pages are not
backed by the loader, so a stack that reaches down to an image beneath it
hides that image. The pages that are skipped are still faulted in on
demand.
Also reject an allocation that does not fit beneath the top of the stack
in allocate_stack_pages() itself, so a caller that asks for one gets an
error rather than pages at the top of the address space.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
503b1be066
commit
55530509ce
4 changed files with 82 additions and 1 deletions
|
|
@ -216,7 +216,9 @@ class SimLinux(SimUserland):
|
|||
|
||||
# pre-grow the stack by 0x20 pages. unsure if this is strictly required or just a hack around a compiler bug
|
||||
if not self._is_core and hasattr(state.memory, "allocate_stack_pages"):
|
||||
state.memory.allocate_stack_pages(state.solver.eval(state.regs.sp) - 1, 0x20 * 0x1000)
|
||||
sp = state.solver.eval(state.regs.sp)
|
||||
if sp >= 0x20 * 0x1000: # ...but only when the stack has that much room beneath it
|
||||
state.memory.allocate_stack_pages(sp - 1, 0x20 * 0x1000)
|
||||
|
||||
if self.project.loader.tls.threads:
|
||||
tls_obj = self.project.loader.tls.threads[thread_idx if thread_idx is not None else 0]
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ class StackAllocationMixin(PagedMemoryMixin):
|
|||
pageno = addr // self.page_size
|
||||
if pageno != self._red_pageno:
|
||||
raise SimMemoryError("Trying to allocate stack space in a place that isn't the top of the stack")
|
||||
if size > addr + 1:
|
||||
# the loop below would wrap past address 0 and hand out pages at the top of memory instead
|
||||
raise SimMemoryError("Trying to allocate more stack space than exists below the top of the stack")
|
||||
num = pageno - ((addr - size + 1) // self.page_size) + 1
|
||||
|
||||
result = []
|
||||
|
|
|
|||
60
tests/simos/test_linux.py
Normal file
60
tests/simos/test_linux.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
# pylint: disable=protected-access
|
||||
from __future__ import annotations
|
||||
|
||||
__package__ = __package__ or "tests.simos" # pylint:disable=redefined-builtin
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import archinfo
|
||||
|
||||
import angr
|
||||
from angr.errors import SimMemoryError
|
||||
from tests.common import bin_location
|
||||
|
||||
test_location = os.path.join(bin_location, "tests")
|
||||
|
||||
|
||||
class TestSimLinuxStateBlank(unittest.TestCase):
|
||||
"""
|
||||
Tests for the stack that SimLinux.state_blank() pre-grows.
|
||||
"""
|
||||
|
||||
binary = os.path.join(test_location, "x86_64", "fauxware")
|
||||
|
||||
def test_stack_is_pre_grown(self):
|
||||
state = angr.Project(self.binary, auto_load_libs=False).factory.blank_state()
|
||||
sp = state.solver.eval(state.regs.sp)
|
||||
|
||||
assert set(state.memory._pages) == set(range((sp - 0x20 * 0x1000) // 0x1000, sp // 0x1000))
|
||||
|
||||
def test_stack_is_not_pre_grown_past_address_zero(self):
|
||||
project = angr.Project(self.binary, auto_load_libs=False)
|
||||
|
||||
# 0x20 pages is more room than this stack has beneath it
|
||||
state = project.factory.blank_state(stack_end=0x10000)
|
||||
|
||||
# none of them are pre-allocated, and in particular the excess does not wrap to the top of the address space
|
||||
assert not state.memory._pages
|
||||
with self.assertRaises(SimMemoryError):
|
||||
state.memory.permissions(0xFFFFFFFFFFFFF000)
|
||||
|
||||
def test_pre_grown_stack_does_not_cover_the_loaded_image(self):
|
||||
# the same stack, with the image beneath it rather than above it
|
||||
project = angr.Project(
|
||||
self.binary,
|
||||
main_opts={"backend": "blob", "arch": "AMD64", "base_addr": 0x1000, "entry_point": 0x1000},
|
||||
auto_load_libs=False,
|
||||
simos="linux",
|
||||
)
|
||||
|
||||
state = project.factory.blank_state(stack_end=0x10000)
|
||||
|
||||
# pre-allocated stack pages are not backed by the loader, so clamping the pre-grow to the room that does
|
||||
# exist would hide the image behind blank pages
|
||||
assert state.solver.eval(state.memory.load(0x1000, 4, endness=archinfo.Endness.BE)) == 0x7F454C46
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -6,10 +6,12 @@ import time
|
|||
import unittest
|
||||
|
||||
import claripy
|
||||
from archinfo import ArchAMD64
|
||||
from claripy.annotation import UninitializedAnnotation
|
||||
|
||||
from angr import SIM_PROCEDURES, SimState
|
||||
from angr import options as o
|
||||
from angr.errors import SimMemoryError
|
||||
from angr.state_plugins import SimLightRegisters, SimSystemPosix
|
||||
from angr.storage.file import SimFile
|
||||
from angr.storage.memory_mixins import (
|
||||
|
|
@ -752,6 +754,20 @@ class TestMemory(unittest.TestCase):
|
|||
state.memory.store(0xFFFFFFFF, symbol)
|
||||
assert state.memory.load(0, 1) is symbol[64 - 8 - 1 : 64 - 16]
|
||||
|
||||
def test_allocate_stack_pages_stops_at_address_zero(self):
|
||||
state = SimState(arch=ArchAMD64(), stack_end=0x1000)
|
||||
|
||||
# only one page exists below the top of this stack, so two of them do not fit under it
|
||||
with self.assertRaises(SimMemoryError):
|
||||
state.memory.allocate_stack_pages(0xFFF, 0x2000)
|
||||
# in particular the second one is not handed out at the top of the address space
|
||||
with self.assertRaises(SimMemoryError):
|
||||
state.memory.permissions(0xFFFFFFFFFFFFF000)
|
||||
|
||||
# the one page that does fit is still handed out
|
||||
assert len(state.memory.allocate_stack_pages(0xFFF, 0x1000)) == 1
|
||||
assert state.memory.permissions(0) is not None
|
||||
|
||||
def test_underconstrained(self):
|
||||
state = SimState(arch="AMD64", add_options={o.UNDER_CONSTRAINED_SYMEXEC})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue