Search code examples
cif-statementrealloc

Failing to reallocate memory when in if statement


When trying to reallocate memory I crash when using this code:

//value of i is currently 4096
while((c = recv(sock, htmlbff + q, MAXDATASIZE, 0)) > 0)
{
    if((i - q) < MAXDATASIZE)
    {
            i *= 2;
            if(!(tmp = realloc(htmlbff, i)))
            {
                free(htmlbff);
                printf("\nError! Memory allocation failed!");
                return 0x00;
            }
            htmlbff = tmp;
    }
    q += c;
}

it crashes because of a memory leak.... HOWEVER the following code does not crash:

while((c = recv(sock, htmlbff + q, MAXDATASIZE, 0)) > 0)
{
    if((i - q) < MAXDATASIZE)
    {
        i *= 2;
        if(!(tmp = realloc(htmlbff, i)))
        {
            free(htmlbff);
            printf("\nError! Memory allocation failed!");
            return 0x00;
        }
    }
    htmlbff = tmp;
    q += c;
}

How can moving htmlbff = tmp; outside of the if statement be fixing this problem? It doesn't seem to set htmlbff to tmp when inside the if statement... I am extremely confused.


Solution

  • /*
     * I assume these exist, and more or less fit the requirements described.
     * They don't have to be these specific numbers, but they do need to have
     * the specified relationships in order for the code to work properly.
     */
    #define MAXDATASIZE 4096    /* any number here is ok, subject to rules below */
    int i = 4096;               // i >= MAXDATASIZE, or the first recv can trigger UB
    char *htmlbff = malloc(i);  // ITYK you can't realloc memory that wasn't malloc'd
    int q = 0;                  // q <= i-MAXDATASIZE
    
    /* maybe other code */
    
    while((c = recv(sock, htmlbff + q, MAXDATASIZE, 0)) > 0)
    {
        /*
         * You've already just read up to MAXDATASIZE bytes.
         * if (i-q) < MAXDATASIZE, then **your buffer is already too small**
         * and that last `recv` may have overrun it.
         */
        if((i - q) < MAXDATASIZE)
        {
            ... reallocate htmlbff ...
        }
        /* Then you bump q...but too late.  lemme explain in a sec */
        q += c;
    }
    

    Let's say you recv 4096 bytes twice in a row. What happens is:

    1. The first read reads 4096 bytes, starting at htmlbff + 0.
    2. Since q is still 0, i - q == 4096, so no allocation is done.
    3. q is bumped up by 4096.
    4. The second read gets 4096 bytes, starting at htmlbff + 4096. But wait, since we didn't resize it last iteration, htmlbff is only 4096 bytes big, and the entire read spills out of the buffer!
    5. If you're lucky, the overrun causes a segfault and the program dies. If you're not, then the CPU just soldiers on, and any behavior from here on is undefined. There's very little point in even diagnosing further issues at this point, since $DEITY knows what the code just trashed.

    Try this instead...

    while((c = recv(sock, htmlbff + q, MAXDATASIZE, 0)) > 0)
    {
        /* **First**, bump `q` past the stuff you just read */
        q += c;
    
        /*
         * **Now** check the buffer.  If i-q is too small at this point, the buffer is
         * legitimately too small for the next read, and also hasn't been overrun yet.
         */
        if((i - q) < MAXDATASIZE)
        {
            /* This temp pointer **really** should be limited in scope */
            char *double_sized;
    
            /* Note that otherwise, i'm using the "broken" resize code.
             * It should work fine.
             */
            i *= 2;
            if(!(double_sized = realloc(htmlbff, i)))
            {
                free(htmlbff);
                printf("\nError! Memory allocation failed!");
                return 0x00;
            }
            htmlbff = double_sized;
        }
    }