X

Improve application security, performance, and scalability

  • October 15, 2015

Solving Trickier Problems - Detecting Dynamic Memory Access Errors Using Discover

Raj Prakash
Architect
Although a subset of memory access errors can be detected at compile time, the majority can only be detected at runtime. Some memory error conditions, like double free, can be detected using a light veneer of functionality over the existing library functions. Other memory access errors can only be detected through a deep instrumentation of the target application.



Studio provides the tool discover to do both light and deep analysis of memory access errors in a target application.



Light instrumentation of library calls has a very small impact on the performance of an application, most applications continue to run at essentially normal speeds. The weakness of this level of analysis is that it only catches problems at the function call level. Consider the following code:

#include <stdlib.h>
void main()
{
char * string = (char*)malloc(1024);
free(string);
free(string);
}

This code has a double free() of allocated memory. Of course, given that both calls are in the same scope a static check of the code would also pick up the error. However, most errors of this kind occur in more complex code sequences.



Double free is one of the errors that can be picked up with a light instrumentation of the applications library calls. To perform a light instrumentation we compile the binary, then process the resulting binary with the tool discover, this produces an instrumented version of the application which can then be run. If the -l command line option is passed to discover then it only instruments the library calls. The results of this instrumentation are:

$ cc -g double.c
$ discover -l -w - ./a.out
$ ./a.out
ERROR 1 (DFM): double freeing memory "*string" at address 0x40560 at:
main() + 0x20 <double.c:7>
4: {
5: char * string = (char*)malloc(1024);
6: free(string);
7:=> free(string);
8: }
9:
_start() + 0x108
was allocated at (1024 bytes):
main() + 0x4 <double.c:5>
2:
3: void main()
4: {
5:=> char * string = (char*)malloc(1024);
6: free(string);
7: free(string);
8: }
_start() + 0x108
freed at:
main() + 0x14 <double.c:6>
3: void main()
4: {
5: char * string = (char*)malloc(1024);
6:=> free(string);
7: free(string);
8: }
9:
_start() + 0x108
***************** Discover Memory Report *****************
No allocated memory left on program exit.
DISCOVER SUMMARY:
unique errors : 1 (1 total)
unique warnings : 0 (0 total)

The report produced for the double free indicates where the second free occurred. It also helpfully points out where the memory was allocated, and where it was first freed. This should enable most developers to reconstruct the program flow that lead to the double free error.



In the example we passed the option “-w -” to discover. This tells discover to produce a report to stderr. Without this, the default is to produce an html report that can be examined in a web browser.



If the application has been compiled with Solaris Studio, then discover can do a far deeper analysis of memory access errors. The tool actually adds instrumentation code into every memory operation, and this instrumentation checks whether the memory access is valid or not. The downside of this degree of instrumentation is that it can have considerable impact on runtime.

Uninitialized memory


As an example of this consider the earlier code example with accessed uninitialized memory. Using discover we get a report that shows the location of the invalid memory access and the call stack that got us to that point.

$ cc -g uninit.c
$ discover -w - ./a.out
$ ./a.out
ERROR 1 (UMR): accessing uninitialized data at address 0xffbffba4 (4 bytes) on the stack
at:
printint() + 0xa8 <uninit.c:5>
2:
3: void printint(int *i)
4: {
5:=> printf("Integer = %i\n",*i);
6: }
7:
8: void main()
main() + 0x20 <uninit.c:11>
8: void main()
9: {
10: int i;
11:=> printint(&i);
12: }
13:
_start() + 0x108
***************** Discover Memory Report *****************
No allocated memory left on program exit.
DISCOVER SUMMARY:
unique errors : 1 (1 total)
unique warnings : 0 (0 total)

Access past array bounds


Earlier we showed an example where the code accessed beyond the end of an array.





Discover can diagnose using deep memory access checking, as shown in the example below.

-bash-4.1$ cc -g array.c
-bash-4.1$ discover -w - ./a.out
-bash-4.1$ ./a.out
ERROR 1 (ABR): reading memory beyond array bounds at address 0x40590 (4 bytes) on the
heap at:
printarray() + 0x144 <array.c:8>
5: {
6: for (int i=0; i<len; i++)
7: {
8:=> printf("Index %i = %i\n", i, array[i]);
9: }
10: }
11:
main() + 0x74 <array.c:15>
12: void main()
13: {
14: int * array = (int*) calloc(sizeof(int), 10);
15:=> printarray(array,11);
16: }
17:
18:
_start() + 0x108
was allocated at (40 bytes):
main() + 0x20 <array.c:14>
11:
12: void main()
13: {
14:=> int * array = (int*) calloc(sizeof(int), 10);
15: printarray(array,11);
16: }
17:
_start() + 0x108
***************** Discover Memory Report *****************
1 allocation at 1 location left on the heap with a total size of 40 bytes
LEAK 1: 1 allocation with total size of 40 bytes
main() + 0x20 <array.c:14>
11:
12: void main()
13: {
14:=> int * array = (int*) calloc(sizeof(int), 10);
15: printarray(array,11);
16: }
17:
_start() + 0x108
DISCOVER SUMMARY:
unique errors : 1 (1 total)
unique warnings : 0 (0 total)

Discover is also able to report memory leaks. In this instance discover is reporting that 40 bytes were left on the heap.

Access to previously freed memory


Instrumentation can easily detect situations where there is an access to previously freed memory.






Since discover instruments the binary it can point out the exact place in the code where the previously freed memory is accessed, and it can indicate where the memory was allocated, and where it was freed.

-bash-4.1$ cc -g freed.c
-bash-4.1$ discover -w - ./a.out
-bash-4.1$ ./a.out
ERROR 1 (FMR): reading from freed memory at address 0x50010 (4 bytes) on the heap at:
printint() + 0xa8 <freed.c:6>
3:
4: void printint(int *i)
5: {
6:=> printf("Integer = %i\n", *i);
7: }
8:
9: void main()
main() + 0x78 <freed.c:13>
10: {
11: int * i = malloc(sizeof(int));
12: free(i);
13:=> printint(i);
14: }
15:
_start() + 0x108
was allocated at (4 bytes):
main() + 0x1c <freed.c:11>
8:
9: void main()
10: {
11:=> int * i = malloc(sizeof(int));
12: free(i);
13: printint(i);
14: }
_start() + 0x108
freed at:
main() + 0x6c <freed.c:12>
9: void main()
10: {
11: int * i = malloc(sizeof(int));
12:=> free(i);
13: printint(i);
14: }
15:
_start() + 0x108
Integer = 327704
***************** Discover Memory Report *****************
No allocated memory left on program exit.
DISCOVER SUMMARY:
unique errors : 1 (1 total)
unique warnings : 0 (0 total)

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.