5

I am debugging a crash where we have a code snippet similar to -

1184 static void
1185 xyz_delete (<struct type1> *c, <struct type2> **a)
1186 {
...
...
...
...
1196    b = *a;
1197    if (!b) {
1198        return;
1199    }
...
...
1203   prev = b->next;
1204   b->next = NULL;
...
...
1245    free_timer(b->active_timer);
...
...
...
     }  

And we happened to see a crash - segmentation fault; whose callstack is shown below -

#1  0x456789123 in __free [__be___free] (ptr=<optimized out>, saved_caller_pc=0x123456789 , attr=0x0) at free.c:1234
#2  0x345678912 in xyz_delete  [__be_xyz_delete...] (c=c@entry=0x234567891, a=a@entry=0x0) at myfile.c:1245
#3  0x455678912 in abc (apple=0x52453545, a=<optimized out>, hello=12) at myfile:1312 

From the call stack, we can notice the 2nd argument a passed to function xyz_delete is NULL. However, when we dereferenced a at line# 1196, there is no crash - which is really surprising! And there are few read and write operations being performed on b at line# 1203 and 1204. But a segmentation fault is seen when free_timer is called on b->active_timer at line# 1245. free_timer inturn calls free.

How could a NULL pointer be dereferenced without causing a crash?

Any logical explanation for what could be happening here?

ks1322
  • 33,961
  • 14
  • 109
  • 164
  • 6
    Compiler optimization. – Eric Postpischil Jun 24 '22 at 10:44
  • If your program has been compiled with compiler optimizations on, then you cannot say that a program is crashing on a particular line, because the program's code cannot be clearly mapped to individual lines. In that case, you will have to inspect the program's code at the assembly language level to see what is actually going on. I believe that GDB allows you to do this. – Andreas Wenzel Jun 24 '22 at 10:46
  • Memory corruption also often produces "surprises". The non-null pointers member could "point" to a function (random address), which eventually produces the later crash. – BitTickler Jun 24 '22 at 10:47
  • 6
    Dereferencing a null pointer is undefined behaviour. It may crash or it may work as expected. It may even create a mini black hole that swallows the universe :-) – paxdiablo Jun 24 '22 at 10:47
  • @BitTickler Could you explain a bit more on how a memory corruption could happen? And a memory corruption is in this scenario? – Darshan L Jun 24 '22 at 11:00
  • It could be that you corrupt `b` (accidentally set it to NULL) between lines 1196 and 1245. – Jonathan Leffler Jun 24 '22 at 11:29
  • 1
    @DarshanL The code, the author looks at is not the only code in the system he works on. Literally anywhere in the code, currently not under investigation, there could be faulty code, leading to memory corruption. And he keeps staring at the code, showing the surprising behavior. – BitTickler Jun 24 '22 at 20:12
  • Thanks @JonathanLeffler for the explanation. I think you meant to say, the argument `a` was corrupted to hold value NULL after the execution of line #1196. Because OP mentioned `a` being NULL and not `b`. The crash at #1245 is not due to `b` being NULL. It could be due to double freeing of already freed `b->active_timer` – Darshan L Jun 27 '22 at 05:27
  • @BitTickler I can imagine how a heap mem could get corrupted; like when we access already freed memory or Out of Bound access of dynamic array. As the parameter `b` is corrupted, that corruption should be on stack memory space. What are the possibilities of a stack memory corruption? One I could think of is again an local array out of bound access. Could you help think about what other possibility of memory corruption in this case? – Darshan L Jun 27 '22 at 05:29
  • 1
    @DarshanL Heap memory and stack memory "see" each other. Any (maybe uninitialized or messed up) pointer, which happens to point into the stack area and is used for writes can corrupt the stack as easily as any other bit of memory within the process. C helps you code bugs :) Its friendly that way. Maybe a struct member was not initialized (no RAAI or other assistance from C language to prevent that), maybe a pointer to the stack stored in a heap based structure and later written to when the frame is gone... So many possibilities. – BitTickler Jun 27 '22 at 08:49

2 Answers2

3

The C11 standard states that :

The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type "pointer to type", the result has type "type". If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

[...]

Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, [...]

[6.5.3.2 Address and indirection operators, C11]

Dereferencing a null pointer is thus an undefined behaviour. Note that undefined behaviours won't necessarily cause a crash, and can be ignored by the compiler.

Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

[3.4.3 undefined behavior, C11]

For example, some optimisations could skip the pointer indirection and prevent the program from crashing.

You need to check if a pointer is invalid before dereferencing it, otherwise your program invoke undefined behaviour and can be unpredictable. Enabling -Wnull-dereference can help you with that, but it may not catch everything.

Elzaidir
  • 891
  • 6
  • 13
2

Dereferencing NULL is undefined, thus the compiler is free to do anything. Literally anything, including removing the always undefined piece of code (some versions of clang did this IIRC). Crashing on null pointer is a reasonable termination, but it's not guaranteed in any way. Moreover, the compiler is also free to assume the UB won't happen and perform some optmizations basing on that assumption, see this article.

alagner
  • 3,448
  • 1
  • 13
  • 25