-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
symbolic
: Modeling the stack
#430
Comments
Does Crucible's memory model have the ability to segment a chunk of memory? (As in, distinguish regions that are adjacent to each other and addressable from the same base pointer?) Then you could prepare a stack frame that will be resistant to such attacks... given some assumptions about when you should specialize a pointer to a segmented chunk to a pointer to a segment, which may turn out to be not so easy. (just speculating, feel free to ignore) |
I don't believe so. Another idea, not sure how feasible this one is... We could create an
That would at least prevent inter-frame memory corruption, though it wouldn't do anything to stop code from overwriting adjacent stack variables nor the return address. Perhaps for the return address, the same execution feature could check that it is unmodified at function returns. |
An addendum: I believe that RISC-V also initializes the end of its stack with slots for stack-spilled arguments, just as AArch32 does (although you should make sure that you adjust the offset on the stack pointer |
Prior art:
|
-- Allocate a stack and insert it into memory | |
-- | |
-- The stack allocation uses the SMT array backed memory model (rather than | |
-- the Haskell-level memory model) | |
stackArrayStorage <- WI.freshConstant sym (WSym.safeSymbol "stack_array") WI.knownRepr | |
stackSize <- WI.bvLit sym WI.knownRepr (BVS.mkBV WI.knownRepr (2 * 1024 * 1024)) |
macaw/symbolic/src/Data/Macaw/Symbolic/Testing.hs
Lines 530 to 531 in 429df8e
(stackBasePtr, mem1) <- CLM.doMalloc bak CLM.StackAlloc CLM.Mutable "stack_alloc" initMem stackSize CLD.noAlignment | |
mem2 <- CLM.doArrayStore bak mem1 stackBasePtr CLD.noAlignment stackArrayStorage stackSize |
macaw/symbolic/src/Data/Macaw/Symbolic/Testing.hs
Lines 533 to 538 in 429df8e
-- Make initial registers, including setting up a stack pointer (which points | |
-- into the middle of the stack allocation, to allow accesses into the caller | |
-- frame if needed (though it generally should not be) | |
initialRegs <- MS.macawAssignToCrucM (mkInitialRegVal symArchFns sym) (MS.crucGenRegAssignment symArchFns) | |
stackInitialOffset <- WI.bvLit sym WI.knownRepr (BVS.mkBV WI.knownRepr (1024 * 1024)) | |
sp <- CLM.ptrAdd sym WI.knownRepr stackBasePtr stackInitialOffset |
pate
Also seems array-backed
saw-script
Seems like it's just a fresh, symbolic pointer?
Potentially with negative consequences?
ambient-verifier
Also does an SMT array:
Conclusions
- Indeed, virtually every client of Macaw wants to model the stack, and most of them do
- Whatever we implement in Macaw should at least have the option to use an SMT array allocation
In a downstream project, we're using
macaw-symbolic
to symbolically execute binaries.macaw-symbolic
encodes functions in the binary as Crucible CFGs that take and return a struct that represents the values of all the registers. To execute such CFGs, we must set initial values for all registers. Most registers can be set to zeroes or symbolic values and plenty of code will run just fine. However, machine code has a lot of expectations about one particular register: the stack pointer.Each ABI1 specifies a stack layout, and our project attempts to model this layout at a low level of abstraction. In particular, we create a large-ish (1MiB) Crucible-LLVM allocation, and:
On AArch32, the very end is initialized with a number of pointer-sized stack slocks, which are reserved for stack-spilled arguments.2
On PPC, the very end is initialized with (in order):
On x86_64, the very end is initialized with (in order):
The contents of the stack pointer register are then initialized to be a pointer to this allocation, offset to where the above data begin.
I'd wager that since a lot of code uses the stack, most clients of
macaw-symbolic
want to model the stack somehow. Furthermore, sincemacaw-symbolic
fairly directly translates manipulations of the stack pointer register in binaries into the Crucible CFGs, I'd argue that this is likely the only strategy that works "out of the box". Therefore, I think we should upstream this code (or something like it) intomacaw-*-symbolic
.One notable upside of this kind of model of the stack is that we can examine what happens in the case of certain kinds of common stack-based exploits, such as stack buffer overruns that clobber adjacent variables, as in this C program:
One notable downside is that such exploits are not automatically detected by the Crucible-LLVM memory model, and so care would be required by users of such an API if they intend to perform safety-oriented analysis.
Footnotes
at a first approximation, each (OS, architecture) pair ↩
One difficulty is that it's hard to tell in advance of executing a function how many stack slots should be reserved for spilled arguments. DWARF information can serve as a source of truth here, when it's available. ↩
The text was updated successfully, but these errors were encountered: