3

Consider the following code:

#include <stdio.h>

void badidea(int**);

int main(void) {
        int* p;
        badidea(&p);
        printf("%d\n", *p); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
        return 0;
}

void badidea(int** p) {
        int x = 5;
        *p = &x;
}

The intent seems to be that it will print 5, but it actually invokes undefined behavior, due to dereferencing a pointer to an out-of-scope local variable in main. How can I find instances of this problem in a codebase? Here's what I've tried so far:

  • Compiling with gcc -Wall -Wextra -pedantic
  • Compiling with clang -Weverything
  • Running having compiled with clang -fsanitize=undefined
  • Running under valgrind

None of the above produced any warnings.

  • Most tools will not find illegal references to stack based variables unless something has overwritten the stack frame. – cup Oct 02 '18 at 03:27
  • 2
    The static analyzer I use ([PVS Studio](https://www.viva64.com/)) finds the problem – Jabberwocky Oct 02 '18 at 06:49
  • Diagnostics are issued at `-Wall` for *returning* a pointer to a local variable because it is clearly meaningless. – Antti Haapala -- Слава Україні Oct 02 '18 at 07:25
  • 1
    @n.m. Just because some bugs cannot be detected by static analysis, doesn't mean that all bugs (or this one) cannot be detected by static analysis. – Ira Baxter Oct 02 '18 at 16:16
  • @IraBaxter **All** bugs cannot be detected by static analysis. **Some** bugs might be, but the question isn't about finding **some** instances of this problem. – n. m. could be an AI Oct 02 '18 at 16:25
  • @n.m. This question was about detecting *this* bug and presumably other bugs like it related to errors with pointers to dead stack frames. Many of these can be detected by static analysis. That's pretty useful. Your original remark strongly implies that one cannot usefully detect that, and that's simply wrong. – Ira Baxter Oct 02 '18 at 16:31
  • undefined behaviour is not invoked.... it simply happens. You don't know if it is invoked or it invoked something on behalf of you. It's undefined, by definition. – Luis Colorado Oct 05 '18 at 18:10

3 Answers3

2

Compiling first with GCC 7.2 and without -fsanitize=address and then running under Valgrind produces the following:

==25751== Conditional jump or move depends on uninitialised value(s)
==25751==    at 0x4E988DA: vfprintf (vfprintf.c:1642)
==25751==    by 0x4EA0F25: printf (printf.c:33)
==25751==    by 0x1086E5: main (in ./a.out)

followed by other warnings.

0

Our CheckPointer tool can detect this using dynamic analysis, along with a wide variety of other memory access errors. The tool tracks all allocations, accesses and assignments involving explicit or implicit pointers, and complains at the earliest moment when such an access is illegal or undefined.

Saving OP's example code as "buggy.c", and running CheckPointer produces the following output (some lines removed for pedagogical reasons):

C~GCC4 CheckPointer Version 1.2.1001
Copyright (C) 2011-2016 Semantic Designs, Inc; All Rights Reserved; SD Confidential Powered by DMS (R) Software Reengineering Toolkit
Parsing source file "E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
  Grouping top level declarations ...
  Creating object meta data initializers ...
  Normalizing syntax tree ...
  Instrumenting syntax tree ...
  Ungrouping top level declarations ...
Writing target file "E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Target/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
*** Compiling sources with memory access checking code gcc.exe -I"e:\DMS\Domains\C\GCC4\Tools\CheckPointer" -I.\Target -obuggy.exe Target\buggy.c Target\check-pointer-data-initializers.c "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer.c" "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-splay-tree.c" "e:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-wrappers.c"
*** Executing instrumented application
*** Error: CWE-465 Pointer Issue (subcategory CWE-476, CWE-587, CWE-824, or CWE-825)
       Dereference of dangling pointer.
in function: main, line: 8, file E:/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c

The specific type of error is reported using codes defined by the Common Weakness Enumeration standard.

NIST offers a "torture" test for Java and C errors called Juliet. Of the 14,195 Juliet test cases that are relevant to the C language, CheckPointer detected 13257 expected memory-access errors. 908 test cases were not diagnosed, but these include ones that contain undefined behavior not related to pointer usage errors (which CheckPointer is not intended to detect), or pointer usage errors that were not exposed by the actual execution (e.g. uninitialized variable contained 0 in actual execution). [We modified some of these examples to ensure that the actual execution did not contain 0 for such variables, and afterwards CheckPointer gave an error message as expected.]

CheckPointer works with GCC and MSVisualStudio.

=======================================

@n.m. made a number of comments to various answers in this thread. He issued a kind of challenge problem where he demonstrated that valgrind can't find a bug in the following code, similar to OPs but more deeply nested:

#include <stdio.h>

void badidea(int**);
void worseidea(int**);

int main(void) {
    int* p;
    badidea(&p);
//        printf("%d\n", *p); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
    worseidea(&p);
    return 0;
}

void worseidea(int **p) {
    int x = 42;
    printf("%d %d\n", **p, x); /* undefined behavior happens here: p points to x from badidea, which is now out of scope */
}

void badidea(int** p) {
    int x = 5;
    *p = &x;
}

Here's the Checkpointer run, which does diagnose the pointer problem in n.m's code:

C~GCC4 CheckPointer Version 1.2.1001
Copyright (C) 2011-2016 Semantic Designs, Inc; All Rights Reserved; SD Confidential
...
Parsing source file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
  ...
Writing target file "C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Target/buggy.c" using encoding CP-1252 +CRLF $^J $^M $^e -1 +8 ...
*** Compiling sources with memory access checking code
gcc.exe -I"c:\DMS\Domains\C\GCC4\Tools\CheckPointer" -I.\Target -obuggy.exe Target\buggy.c Target\check-pointer-data-initializers.c "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-
pointer.c" "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-splay-tree.c" "c:\DMS\Domains\C\GCC4\Tools\CheckPointer\check-pointer-wrappers.c"
*** Executing instrumented application
*** Error: CWE-465 Pointer Issue (subcategory CWE-476, CWE-587, CWE-824, or CWE-825)
       Dereference of dangling pointer.
in function: worseidea, line: 16, file C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
called in function: main, line: 10, file: C:/Users/idbaxter/AppData/Local/Temp/DMS/Domains/C/GCC4/Tools/CheckPointer/Example/Source/buggy.c
Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • Amazing. This answer's OP's questions, and it even answers a harder problem posed by a commenter, and somebody downvotes. – Ira Baxter Oct 10 '18 at 15:36
-2

I don't think such mechanism exists in the C language, in the end pointers are simply variables holding addresses. When you give them a type it simply tells the compiler what kind of variable lies in the address space pointed by the pointer.Thus in theory pointer can hold any address values as long as it is in the defined address space.

And actually this is what makes C language really powerful. Espacially in data transferring mechanisms because you can send any type of data even the user defined structueres etc. in any order and receive/typecast in the other end easily without any concern of endiannes and such.

Though in your case , hopefully ,assuming you know your program's stack size and beginning address, You can check to see if the address content pointed by the pointer is in the area reserved for the Stack or not. Thus knowing if you are pointing to a local variable or not.

+If you must point to a local variable, you can define it as static, which place the variable outside of the stack in RAM. (You probably know it, but you know some might not.)

İlkerK
  • 76
  • 4
  • 2
    Although many dialects of C usefully support some pointer actions beyond those mandated by the Standard, there is no possible calling code for which the Standard would require the statement `*p = &x;` within `badidea` to have any effect. If an implementation happens to go into more detail than the Standard requires about how objects are stored, and allows pointers's addresses to be compared with other pointers, printed, or otherwise used *as addresses* beyond the lifetime of the objects identified thereby, a function like `badidea` could be useful as means of e.g. making note of... – supercat Oct 02 '18 at 15:39
  • 1
    ...the stack pointer. Such an implementation could thus have no way of knowing whether the function might actually be serving a useful purpose. If an implementation guarantees nothing beyond what's required by the Standard, it could omit the assignment in this particular case (and perhaps warn about it), but in most situations where code might store a pointer in an externally-visible place that would never be used within the pointer's lifetime, a compiler wouldn't be able to tell that the pointer could never be used, and the value of warning about those few cases where it can would be... – supercat Oct 02 '18 at 15:45
  • 1
    ...insufficient to justify the effort. – supercat Oct 02 '18 at 15:45
  • @supercat Minor nitpick: doesn't the standard require that `p` in main not be a null pointer after that statement runs? – Joseph Sible-Reinstate Monica Oct 02 '18 at 21:50
  • 1
    @JosephSible: The value of `p` becomes Indeterminate when code exits `badidea`. The Standard imposes no requirements upon what happens if an object holding Indeterminate Value is accessed using a non-character type, and imposes no requirements upon the values yielded if it is examined via character type. There would be no Standard-defined way for a program to determine, after the function exits, whether `p` had been set to a non-null value within it. – supercat Oct 02 '18 at 21:55
  • @JosephSible: Implementations intended for low-level or systems programming should specify what `p` will mean as an address after the function returns, but there's no requirement that implementations be suitable for such purposes (or any other purpose, for that matter). – supercat Oct 02 '18 at 21:58