paul lanken
paul lanken

Reputation: 197

safety not guaranteed in C buffer calloc or malloc within a function?

This is a question about how to have "belt and suspenders" safety within a simple bit of C code. The old and somewhat beaten to death issue being how to ensure that one may move data into a buffer within some called function without worry that the heap memory is corrupted after return. There have been a few great things written on this site about the topic and, at least for me, it still isn't clear where we get real total safety. So I wrote the following :

/*********************************************************************
 * The Open Group Base Specifications Issue 6
 * IEEE Std 1003.1, 2004 Edition
 *********************************************************************/
#define _XOPEN_SOURCE 600

#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int use_buffer( const char *strin, char **strout, size_t bufsize ) {

    size_t len;
    int result = -1;

    printf ( "in use_buffer() we have address of strout = %p\n", &strout );
    printf ( "            and that contains an address of %p\n", strout );
    printf ( "           which points to a buffer address %p\n", *strout );

    /* check for null data */
    if ( ( strin == NULL ) || ( *strout == NULL ) )
        return result;

    /* check for zero length data */
    if ( strlen(strin) == 0 )
        return result;

    /* ensure we have a non-zero size buffer to write to ?
     * belt and suspenders safety here is not assured. We have
     * no way to know if the calling routine actually did 
     * allocate memory of size bufsize. 
     */
    len = strlen(strin);
    if ( bufsize < len )
        return result;

    strncpy ( *strout, strin, len );

    return len;

}

int main ( int argc, char *argv[] ) {

    char *some_buffer;
    int retval;
    size_t buflen;

    if ( argc < 2 ) { 
        printf ( "usage: %s somestring\n", argv[0] );
        return ( EXIT_FAILURE );
    }

    buflen = (size_t) ( 4 * 4096 );
    some_buffer = calloc( buflen, sizeof( unsigned char) );
    if ( some_buffer == NULL ) { 
        perror ( "Could not calloc a 16Kb byte buffer." );
        return ( EXIT_FAILURE );
    }

    printf ( "main() has a 16Kb buffer ready at address = %p\n",
                                                      &some_buffer );
    retval = use_buffer( argv[1], &some_buffer, buflen );
    if ( retval > 0 )
        printf ( "Maybe we have %i bytes copied into a buffer.\n", retval );

    free ( some_buffer );
    some_buffer = NULL; /* belt and suspenders */

    return ( EXIT_SUCCESS );

}

Compile and run that and I see this :

$ ./use_buffer "foo of the bar"
main() has a 16Kb buffer ready at address = ffffffff7ffff710
in use_buffer() we have address of strout = ffffffff7ffff630
            and that contains an address of ffffffff7ffff710
           which points to a buffer address 100101440
Maybe we have 14 bytes copied into a buffer.

One of those addresses really does not look right. One of those is just not the same.

Really, it is hard to know why the first three addresses are way off wildly in some other memory region whereas the last one looks to be some local heap memory perhaps?

Is the above method absolutely belt and suspenders safe? I mean that the function is going to be working with a buffer that we know has been pre-allocated and that there is no way for the function to screw it up. I doubt that the function can call free() on the adress stored within strout. That would be like checking into a hotel with a room key and then setting fire to the room. While standing in it. I guess it could be done .. but would be crazy.

So there are two questions here : (1) is there a way for the function to verify the allocated buffer size? Even if the method is to trigger a memory violation. And then (2) is there any safety in passing a null pointer to the function and then allowing the function to calloc/malloc the buffer as required and pass back the address ?

I suspect that (2) has been beaten to death and the answer is "safety not guaranteed". ( sidenote : damn good movie by the way. )

Consider this code bit :

/*********************************************************************
 * The Open Group Base Specifications Issue 6
 * IEEE Std 1003.1, 2004 Edition
 *********************************************************************/
#define _XOPEN_SOURCE 600

#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int bad_buffer( const char *strin, char **strout ) {

    size_t len;
    int result = -1;
    char *local_buffer;

    /* check for null data */
    if ( strin == NULL )
        return result;

    /* check for zero length data */
    len = strlen(strin);
    if ( len == 0 )
        return result;

    /*
     * safety not guaranteed ?
     */
    local_buffer = calloc( len+1, sizeof( unsigned char) );

    printf ( "  in bad_buffer() we have local_buffer at = %p\n",
                                                    &local_buffer );

    strncpy ( local_buffer, strin, len );
    *strout = local_buffer;

    return len;

}

int main ( int argc, char *argv[] ) {

    char *some_buffer;
    int retval;
    size_t buflen;

    if ( argc < 2 ) { 
        printf ( "usage: %s somestring\n", argv[0] );
        return ( EXIT_FAILURE );
    }

    retval = bad_buffer( argv[1], &some_buffer );
    printf ( "in main() we now have some_buffer at addr %p\n", some_buffer );

    if ( retval > 0 )
        printf ( "Maybe we have %i bytes copied into a buffer.\n", retval );

    printf ( "main() says the buffer contains \"%s\"\n", some_buffer );

    free ( some_buffer ); /* really ?  main() did not allocate this !? */
    some_buffer = NULL;

    return ( EXIT_SUCCESS );

}

when I compile and run that I see :

$ ./bad_buffer foobar
  in bad_buffer() we have local_buffer at = ffffffff7ffff6b0
in main() we now have some_buffer at addr 100101330
Maybe we have 6 bytes copied into a buffer.
main() says the buffer contains "foobar"

Something seems spooky here. The function did the calloc and then stuffed the address of the buffer into the address within strout. So strout was a pointer to a pointer and so I am fine with that. What scares me is that the memory that was allocated by the function has no right or reason to be considered safe after it is done and we are back in main().

So question number (2) stands as "is there any safety in allowing the function to calloc/malloc the buffer needed?"

Upvotes: 1

Views: 1155

Answers (2)

hyde
hyde

Reputation: 62848

$ ./use_buffer "foo of the bar"
main() has a 16Kb buffer ready at address = ffffffff7ffff710
in use_buffer() we have address of strout = ffffffff7ffff630
            and that contains an address of ffffffff7ffff710
           which points to a buffer address 100101440
Maybe we have 14 bytes copied into a buffer.

Here 3 first values are in stack. Looking at your code, the middle one is actually address of the local argument at that function, while 1st and 3rd are addresses of local variables in main(). Note how stack grows down, so it's positioned at a high address, and called function arguments are lower than variables of the calling function.

4rd value is then something of a special case, because it's address of an argv string. Those strings are either global variables (in their own section of program address space), or they can even be at OS specific special addresses not near anything else.


$ ./bad_buffer foobar
  in bad_buffer() we have local_buffer at = ffffffff7ffff6b0
in main() we now have some_buffer at addr 100101330
Maybe we have 6 bytes copied into a buffer.
main() says the buffer contains "foobar"

And here again, first address is in stack, address of local variable in a function. 2nd address is memory in heap, value of a pointer in main().


And yes, at a glance your code is safe. You seem to be confusing pointer value with address of the pointer variable. Consider this:

char *p1 = malloc(10);
char *p2 = p1;
printf("%p %p %p %p\n", &p1, &p2, p1, p2);

Above will print something like

ffffffff7ffff710 ffffffff7ffff702 100101440 100101440

First is address of variable p1, which is local variable here and in stack. 2nd is adddress of p2, also local variable and in stack. Then two last address as return value of malloc call, assigned to p1 and copied to p2. That allocated block will remain until you free it, no matter how many times you pass the address around, or even if you lose the address (in which case you have memory leak). When you free it, any pointers which still point to that area become dangling pointers, and should not be dereferenced.

Upvotes: 1

Cratylus
Cratylus

Reputation: 54084

One of those addresses really does not look right. One of those is just not the same.

Really, it is hard to know why the first three addresses are way off wildly in some other memory region whereas the last one looks to be some local heap memory perhaps?

some_buffer (or its alias strout) is a local variable stored in the stack of main and is pointing to an address in the heap. So they are addresses of different memory areas

Upvotes: 3

Related Questions