Search code examples
clinuxvalgrind

Valgrind reports memory leak with asprintf


Here is a piece of code that writes an HTTP response on a socket based on a simple struct

void write_response(request *req, response *resp, int socket) {
  char *raw_resp;
  int bytes = 0;

  asprintf(&raw_resp, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);

  bytes += strlen(raw_resp);
  for (int i = 0; i < resp->header_count; i++) {
    asprintf(&raw_resp, "%s%s", raw_resp, resp->headers[i]);
    bytes += strlen(resp->headers[i]);
  }

  if (resp->content != NULL) {
    asprintf(&raw_resp, "%s\r\n", raw_resp);
    raw_resp = realloc(raw_resp, bytes + 2 + resp->content->size);
    memcpy(&raw_resp[strlen(raw_resp)], resp->content->data,
           resp->content->size);
    bytes += (resp->content->size + 2);
  }
  write(socket, raw_resp, bytes);
  free(raw_resp);
}

Basically, it adds the HTTP request line first, then the headers and finally if needed the body.

However, valgrind is reporting a Invalid free() / delete / delete[] / realloc() and 18 bytes in 1 blocks are definitely lost in loss record 2 of 4 (memory leaks) on the first 2 asprintf but weirdly not on the 3rd one.

Am I using asprintf right?


Solution

  • Your code does not free all the strings allocated by asprintf. Given how you use asprintf to perform dynamic string concatenation, fixing this problem is a bit cumbersome. Note that you do not handle memory allocation failure either. You could use asprintf return value to detect that and update bytes without the need for an extra strlen() call.

    /* return the number of bytes written or -1 in case of error */
    int write_response(request *req, response *resp, int socket) {
        char *raw_resp, *new_p;
        int bytes;
    
        bytes = asprintf(&raw_resp, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
        if (bytes < 0)
            return -1;
    
        for (int i = 0; i < resp->header_count; i++) {
            bytes = asprintf(&new_p, "%s%s", raw_resp, resp->headers[i]);
            free(raw_resp);
            raw_resp = newp;
            if (bytes < 0)
                return -1;
        }
    
        if (resp->content != NULL) {
            bytes = asprintf(&new_p, "%s\r\n", raw_resp);
            free(raw_resp);
            raw_resp = newp;
            if (bytes < 0)
                return -1;
            new_p = realloc(raw_resp, bytes + resp->content->size);
            if (new_p == NULL) {
                free(raw_resp);
                return -1;
            }
            raw_resp = new_p;
            memcpy(raw_resp + bytes, resp->content->data, resp->content->size);
            bytes += resp->content->size;
        }
        bytes = write(socket, raw_resp, bytes);
        free(raw_resp);
        return bytes;
    }
    

    Remarks:

    • Using asprintf to perform string concatenation with allocation seems inefficient, simply use strlen, realloc and memcpy.

    • asprintf() is non-standard, it is not be available on all platforms.

    • Unless you are requested to issue a single call to write, it might be more efficient to write the contents separately to avoid the extra call to realloc() for a potentially large amount of memory.

    • It might also be more efficient to compute the length of the headers in a initial phase with snprintf and strlen and allocate the space for the headers directly to the full size, or not even allocate if below a reasonable threshold (4K) using a local array.

    Here is a modified version:

    int write_response(request *req, response *resp, int socket) {
        char buffer[4096];
        char *raw_resp, *allocated = NULL;
        int bytes, pos;
    
        bytes = snprintf(NULL, 0, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
        for (int i = 0; i < resp->header_count; i++)
            bytes += strlen(resp->headers[i]);
        if (resp->content != NULL)
            bytes += 2 + resp->content->size;
    
        /* need an extra byte for `snprintf` null terminator 
           if no headers and no contents */
        if (bytes < sizeof(buffer)) {
            raw_resp = buffer;
        } else {
            raw_resp = allocated = malloc(bytes + 1): 
            if (raw_resp == NULL)
                return -1;
        }
        pos = snprintf(raw_resp, bytes, "HTTP/1.1 %d %s\r\n", resp->code, resp->reason_phrase);
        for (int i = 0; i < resp->header_count; i++) {
            int len = strlen(resp->headers[i]);
            memcpy(raw_resp + pos, resp->headers[i], len);
            pos += len;
        }
    
        if (resp->content != NULL) {
            raw_resp[pos++] = '\r';
            raw_resp[pos++] = '\n';
            memcpy(raw_resp + pos, resp->content->data, resp->content->size);
            pos += resp->content->size;
        }
        bytes = write(socket, raw_resp, bytes);
        free(allocated);
        return bytes;
    }