2

It appears that when the realloc function is called with a pointer to non-heap memory, the program dies with

Error(s):
Invalid memory reference (SIGSEGV)

Is it possible for my str_cat function to detect this case and thus avoid calling realloc

Here is a sample program which demonstrates the problem:

//gcc 5.4.0

#include  <stdio.h>
#include  <string.h>
#include  <stdlib.h>

int str_cat(char *dest, size_t *dest_size, char *src);

int str_cat(char *dest, size_t *dest_size, char *src) {
// if there is sufficient free space in the dest buffer, append src, 
// otherwise keep doubling the allocated size of dest until it is large 
// enough to append src        
    size_t src_len = strlen(src);
    size_t dest_len = strlen(dest);
    if (src_len < *dest_size - dest_len) {
        memcpy(dest+dest_len, src, src_len+1);
        return 0;
    } else {
        char *new_dest = NULL;
        size_t new_size = *dest_size * 2;
        while (src_len >= new_size - dest_len) {
            new_size *= 2;
        }
        if ((new_dest = realloc(dest, new_size)) != NULL) {
           dest = new_dest;
           *dest_size = new_size;
           memcpy(dest+dest_len, src, src_len+1);  
           return 0;
        } else {
            return -1;
        }
    }
}

int main(void)
{
    size_t heap_buf_size=5;
    char *heap_buf = malloc(heap_buf_size);
    size_t stack_buf_size=5;
    char stack_buf[stack_buf_size];

    *heap_buf = '\0';
    if (str_cat(heap_buf, &heap_buf_size, "foo")) return -1;
    printf("1. heap_buf %s\n", heap_buf);
    printf("1. heap_buf_size %zu\n", heap_buf_size);
    if (str_cat(heap_buf, &heap_buf_size, "bar")) return -1;
    printf("2. heap_buf %s\n", heap_buf);
    printf("2. heap_buf_size %zu\n", heap_buf_size);

    stack_buf[0] = '\0';
    if (str_cat(stack_buf, &stack_buf_size, "foo")) return -1;
    printf("3. stack_buf %s\n", stack_buf);
    printf("3. stack_buf_size %zu\n", stack_buf_size);
/* If the line below is uncommented, the program dies
   with Invalid memory reference (SIGSEGV)    */
//    if (str_cat(stack_buf, &stack_buf_size, "bar")) return -1;
    printf("4. stack_buf %s\n", stack_buf);
    printf("4. stack_buf_size %zu\n", stack_buf_size);

    return 0;
}
user2309803
  • 541
  • 5
  • 15
  • If you know where your stack is located and how big it is, you could check if the pointer is in the stack. – Clearer Feb 07 '18 at 10:36
  • 4
    Why would you want to recover from it? It's a bug in your program. Crashing is the best outcome. It allows you to quickly find and fix the problem as close to the root of the problem as you can get. – Art Feb 07 '18 at 10:36
  • 4
    Calling `free` (a part of `realloc` on an object not previously allocated with `malloc, calloc, realloc` invokes *Undefined Behavior* -- your program is over for all reliable purposes at that point. Bail out and fix the problem. – David C. Rankin Feb 07 '18 at 10:39
  • You can make a wrapper around your allocation and free functions that keep a track of all allocated pointers and can tell you if a pointer is valid heap object, but beyond that you can't do anything portably. If you want a solution for a particular platform, many implementations of heaps expose an API to query heap information. – Ajay Brahmakshatriya Feb 07 '18 at 10:41
  • @DavidC.Rankin: That really should be an answer... – DevSolar Feb 07 '18 at 10:43
  • @Art: but can it be relied upon to crash, then? – Jongware Feb 07 '18 at 10:45
  • @Art if str_cat can determine that dest is not pointing to heap memory, then the logic can be adjusted to avoid calling realloc. BTW, I see now that I forgot call free(heap_buf); – user2309803 Feb 07 '18 at 10:48
  • 2
    C is call by value. `dest = new_dest;` :: the dest argument is local to your function. The caller wont be able to see the new memory. – wildplasser Feb 07 '18 at 11:17
  • 1
    @usr2564301 No, in general you can't _rely_ on `realloc()` etc. crashing if they're passed an invalid pointer (otherwise it would be _defined_ behaviour). Many/most _will_ (because, for example, linked-list memory-block pointers just before the pointer won't make sense), – TripeHound Feb 07 '18 at 11:33
  • 1
    ...which means: if the str_cat()funcion actually reallocs, (and the new pointer is different from the old one) main() will call this function the next time with the **old** value of the pointer, which is no longer valid. And: this is not misuse of realloc, this is (main) referring to a **stale pointer**. – wildplasser Feb 07 '18 at 11:33
  • 1
    @usr2564301 No, of course it can't. But it's very plausible that it will and this is pretty much the best case scenario for debugging. – Art Feb 07 '18 at 11:34
  • Possible duplicate of [How to write a signal handler to catch SIGSEGV?](https://stackoverflow.com/questions/2663456/how-to-write-a-signal-handler-to-catch-sigsegv) – cdarke Feb 07 '18 at 12:01
  • @wildplasser good catch! I will have to think of a solution to get a pointer to the new buffer back to the caller. – user2309803 Feb 07 '18 at 12:54

1 Answers1

4

No, it's not possible to catch or handle SIGSEGV on most systems in a meaningful way. This is because at the point when the program causes unmapped memory access it probably have damaged its stack or data or code already, so can't be trusted and is beyond saving. You're better off crashing immediately if you detect such situation so you can analyze dumped core in debugger.

To track invalid access valgrind is a nice tool, try valgrind ./a.out, will run slow but track many things, including misuses of malloc/realloc/free.

Edit: (since OP edited the question) No, it's not possible to detect if pointer is a correct argument for realloc/free, unless you track all (de)allocations (say using malloc hooks).

Uprooted
  • 941
  • 8
  • 21
  • It is possible to handle SIGSEGV on some systems, including Linux, but whether you can do anything useful after that is another matter. See also https://stackoverflow.com/questions/2663456/how-to-write-a-signal-handler-to-catch-sigsegv – cdarke Feb 07 '18 at 11:59
  • Yeah - you can catch a Windows structured exception, eg an Access Violation, in a similar manner, and with the same risks if ignored.. – Martin James Feb 07 '18 at 12:06
  • @cdrake, improved wording a bit. I do remember having problems with catching SIGSEGV on linux via sigaction, say process can't catch it's own SIGSEGV unless traced? In any case, what OP wants is improper use of the signal, even if possible. – Uprooted Feb 07 '18 at 12:34
  • @user7231 I have clarified my question - I wanted to know if there is a way detect if `dest` is pointer to non-heap memory before calling realloc. That way the program can stay in control and issue an error message (for example). – user2309803 Feb 07 '18 at 12:45