Keywords: use-after-free, dangling pointer, stale pointer, invalid pointer dereference, double free, deleted object
In 2009 Mozilla fixed one of the vulnerability (CVE-2009-2467) I reported them. It had allowed to reference freed object leading to code execution. Some time after this in 2010 I got interested researching this class of security bugs. I pursued dynamic approach to discover them but I wanted different approach than feeding the application with fuzzed data. It was also a requirement to discover these problems in binaries without having source code or debug information available.
I thought it would be nice to make it visible if a pointer is dangling. I knew detecting the pointer that is dangling could come with lots of false positives because some of them cannot be referenced from the execution flow.
Anyway, I came up with the following train of thought.
malloc()
is used to allocate a region of memory on the heap. The pointer referencing to the region of memory can be on the stack or on another region of the heap. So we need to handle the situation in a different way if a pointer is a dangling pointer on the stack or on the heap.
I kept working on this approach and thought we need to maintain a structure what region of memory was allocated and freed. I thought when
free()
is called we could check if there is a reference to the freed memory. Here is a
skeleton of the approach I draw back in 2010 - it's unprofessional and not so important so you might wanna continue reading instead... :)
The solution above wouldn't have worked in practice however. The conceptual problem here is if there is a reference to a freed object it doesn't mean the code would use the pointer is reachable. When a region of memory is freed the reference might exist to the freed object even if you explicitly set it to
NULL
. This is because the compiler optimizes this out if it cannot be reached. Another conceptual problem is the original idea itself that is we depend on the check for the references only when
free()
is called.
I had discussed this idea to people how could this be improved but concluded none of the solutions would be practically applicable. Possible improvements involve timing, and applied static analysis. Static analysis in dynamic approach might be an area to explore further but this would require significant research effort and showed only a little benefit that time.
The low-level constructs are complex so I knew we could detect something that indicates the presence of dangling pointer because in a complex environment it's so big the playground. I suspended working on this for a long time, however, with the fact in my mind that I have a solution that show the sign of working. It was just not optimal enough to use it in practice.
Couple of weeks ago I started working on a debugger extension to place data breakpoint on arbitrary size of the memory. I had a huge success and already built
two functionalities on it - both of them detect possible security problems. Thought why not give it a try to explore the old dangling pointer project further involving this new approach.
From the previous posts, you might know that it's possible to track data access, and to determine the kind of the access that is either read or write access. By applying hook on
malloc()
and on
free()
we can maintain a list of allocated and freed memory blocks. When there is a read data access to a pointer to freed memory we can
issue a notification: pointer to freed memory has been read.
Here is an isolated example code that reads pointer to freed memory.
int *read_freed_ptr(void)
{
int *ptr = (int *)malloc(sizeof(int));
free(ptr);
// read pointer to freed memory
return ptr;
}
When a freed pointer is read it might not cause a crash later on the execution but definitely could do if the pointer is dereferenced. It is possible, for example, a lot of fuzzing cases cause the application to read freed pointer but the bugs remain undetected because they are not dereferenced. I'm particularly interested researching this approach further on JIT emitted code.
Anyway, here is the assembly code for the above C code. I highlighted the area when the pointer to the freed memory is read.
00401000 55 push ebp
00401001 8B EC mov ebp,esp
00401003 51 push ecx
00401004 6A 04 push 4
00401006 FF 15 A4 20 40 00 call dword ptr [__imp__malloc (4020A4h)]
0040100C 83 C4 04 add esp,4
0040100F 89 45 FC mov dword ptr [ptr],eax
00401012 8B 45 FC mov eax,dword ptr [ptr]
00401015 50 push eax
00401016 FF 15 9C 20 40 00 call dword ptr [__imp__free (40209Ch)]
0040101C 83 C4 04 add esp,4
0040101F 8B 45 FC mov eax,dword ptr [ptr]
00401022 8B E5 mov esp,ebp
00401024 5D pop ebp
00401025 C3 ret
Detecting read of pointer to freed memory is possible and straightforward task to do prototype implementation.
--Attila Suszter (
@reon_wi)