Tuesday 27 May 2014

Security vulnerability in gcc optimised code?

I've been toying recently with a simple code-coverage and
inspection mechanism for C/C++ code. If we compile to assembler,
we can inject code into the assembler and create an instrumented binary.
Its a very simply technique offering some interesting insights into
what clang (-faddress=sanitize) and valgrind can do - without the
hardwork of writing a compiler or a JIT emulator.

The first implementation worked - about 30 lines of Perl, and
the same for the runtime library. This lets you trace every function
call/return and line executed - stored to a log file for later
analysis.

I got stuck, because a piece of my code would run in an infinite loop.
Examination of the C code and assembler kept telling me to "save the registers" -
any attempt to inject a CALL instruction into the body of the compiled
output would be sensitive to any register corruptions or flags register
issues. However, despite tediously poring over my code and looking for
insights, I couldnt get the bug to go away.

After a lot of investigation, and a definite "the bug is exactly NOT
where you think it is", I found the culprit. It surprised me, but should not
have.

Given any form of assembler, like this:


instr1
// inject code here
call __cdebug
// end of injection
instr2


(Above is simplified), the call to __cdebug, should not affect the
execution state. But it did. In fact, the above code looks more like this:


instr1
// inject code here
pushf
push %rax
push %rbx
...
call __cdebug
...
pop %rbx
pop %rax
popf
// end of injection
instr2


The above would break the application. But how?

Well, the compiler can do something like this:


....
subq $nnn,%rsp
// code to use the buffer referenced by %rsp
....
// restore the stack pointer
addq $nnn,%rsp


By moving the stack pointer and reserving space, something strange happens.
The above code can be generated by this:


void func(..args...)
{ char buf[nnn];

....
}


The buffer - which is uninitialised, but *will* be used, can be affected
by gregarious push/pops writing to the stack. I think this is interesting
because it suggests that something like a signal, could do the same thing -
writing random results to a part of the stack which is needed, yet not
carefully guarded against. This could be a security hole in any
application where timers or other signals arise. That would be near
impossible to find.

The solution for me is simple .. before doing anything, move the %rsp
register out of harms way:


subq $bignum,%rsp
....
addq $bignum,%rsp


and that fixed my problem. Now the question is, what is "bignum"? Heres
a simple way to find the potential worst case scenario in your code:


/tmp@dixxy: objdump -d ~/crisp/bin/cr| grep sub.*0x....,%rsp
42bd0a: 48 81 ec 18 20 00 00 sub $0x2018,%rsp
42d07a: 48 81 ec 00 20 00 00 sub $0x2000,%rsp
43b200: 48 81 ec 28 20 00 00 sub $0x2028,%rsp
43b645: 48 81 ec 08 20 00 00 sub $0x2008,%rsp
43c5a2: 48 81 ec 18 40 00 00 sub $0x4018,%rsp
43da8b: 48 81 ec 10 20 00 00 sub $0x2010,%rsp
...


Those 0xNNN numbers represent likely similar scenarios to having buffers
on the stack and potentially dangerous signal holes in an application.
You either want the max() of these numbers, or something which knows
the max stack area for a call frame.

Interesting. I dont recall anyone playing with this area before.


Post created by CRiSP v11.0.32a-b6744


No comments:

Post a Comment