Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

libcurl: Segmentation fault on curl_easy_perform when WriteMemoryCallback is used

I make a json-rpc request using C and libcurl successfully using the following code

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

void post_rpc(CURL *curl_handle)
{
    CURLcode res;

    char JSONRPC_BASE_URL[] = "https://api.betfair.com/exchange/betting/json-rpc/v1";
    char rpc_request[]="{\"jsonrpc\": \"2.0\", \"method\": \"SportsAPING/v1.0/listEventTypes\", \"params\": {\"filter\":{ }}, \"id\": 1}";  
    char session_token[]= "MY_ACTIVE_SESSION_STRING";
    char session_header[100+sizeof(session_token)];

    strcpy(session_header, "X-Authentication:");
    strcat(session_header, session_token);

    struct curl_slist * headers = NULL;
    headers = curl_slist_append(headers, "X-Application: MY_API_KEY");
    headers = curl_slist_append(headers, session_header);
    headers = curl_slist_append(headers, "content-type : application/json");

    /* init the curl session */
    curl_handle = curl_easy_init();

    /*  HEADERS */
    curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
    /* POST FIELD : Json-rpc request */
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, rpc_request);
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, (long)strlen(rpc_request));
    /* stdout the header sent */
    curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, stdout);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, stdout);

    /* specify URL to get */
    curl_easy_setopt(curl_handle, CURLOPT_URL, JSONRPC_BASE_URL);

    /* get it! */
    res = curl_easy_perform(curl_handle);
    /* check for errors */
    if(res != CURLE_OK) {
        fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(res));
    }
}

int main(){

    int res;
    CURL *curl_handle;

    curl_global_init(CURL_GLOBAL_DEFAULT);
    post_rpc(curl_handle);
    curl_global_cleanup();

    return 0;
}

However when I try to integrate the getinmemory.c libcurl example (in order to get the output in a variable instead of stdout), thus coming up with the following code (where I have indented my edits, which basically are adding the above code)

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

#include <curl/curl.h>

struct MemoryStruct {
  char *memory;
  size_t size;
};


static size_t
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
  size_t realsize = size * nmemb;
  struct MemoryStruct *mem = (struct MemoryStruct *)userp;

  mem->memory = realloc(mem->memory, mem->size + realsize + 1);
  if(mem->memory == NULL) {
    /* out of memory! */
    printf("not enough memory (realloc returned NULL)\n");
    return 0;
  }

  memcpy(&(mem->memory[mem->size]), contents, realsize);
  mem->size += realsize;
  mem->memory[mem->size] = 0;

  return realsize;
}


int main(void)
{
  CURL *curl_handle;
  CURLcode res;

  struct MemoryStruct chunk;
  chunk.memory = malloc(1);  /* will be grown as needed by the realloc above */
  chunk.size = 0;    /* no data at this point */

  curl_global_init(CURL_GLOBAL_ALL);

  /* init the curl session */
  curl_handle = curl_easy_init();


        char JSONRPC_BASE_URL[] = "https://api.betfair.com/exchange/betting/json-rpc/v1";
        char rpc_request[]="{\"jsonrpc\": \"2.0\", \"method\": \"SportsAPING/v1.0/listEventTypes\", \"params\": {\"filter\":{ }}, \"id\": 1}";  
        char session_token[]= "MY_ACTIVE_SESSION_STRING";
        char session_header[100+sizeof(session_token)];

        strcpy(session_header, "X-Authentication:");
        strcat(session_header, session_token);

        struct curl_slist * headers = NULL;
        headers = curl_slist_append(headers, "X-Application: MY_API_KEY");
        headers = curl_slist_append(headers, session_header);
        headers = curl_slist_append(headers, "content-type : application/json");

        /*  HEADERS */
        curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
        /* POST FIELD : Json-rpc request */
        curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, rpc_request);
        curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, (long)strlen(rpc_request));
        /* stdout the header sent */
        curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, stdout);
        curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, stdout);

        /* specify URL to get */
        curl_easy_setopt(curl_handle, CURLOPT_URL, JSONRPC_BASE_URL);


  /* send all data to this function  */
  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);

  /* we pass our 'chunk' struct to the callback function */
  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);

  /* some servers don't like requests that are made without a user-agent
     field, so we provide one */
  curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");

        printf("THIS IS PRINTED\n");
        fflush(stdout);

  /* get it! */
  res = curl_easy_perform(curl_handle);   //LINE 114: SEGFAULT

        printf("THIS IS NOT...\n");
        fflush(stdout);

  /* check for errors */
  if(res != CURLE_OK) {
    fprintf(stderr, "curl_easy_perform() failed: %s\n",
            curl_easy_strerror(res));
  }
  else {
    /*
     * Now, our chunk.memory points to a memory block that is chunk.size
     * bytes big and contains the remote file.
     *
     * Do something nice with it!
     */

    printf("%lu bytes retrieved\n", (long)chunk.size);
    printf("Chunk:%s\n", chunk.memory);

  }

  /* cleanup curl stuff */
  curl_easy_cleanup(curl_handle);

  if(chunk.memory)
    free(chunk.memory);

  /* we're done with libcurl, so clean it up */
  curl_global_cleanup();

  return 0;
}

I get a segmentation fault in

res = curl_easy_perform(curl_handle);

which happens in realloc call inside writeMemoryCallback.

Moreover I should mention that the headers printed on stdout from the above example are not printed here. The only thing that's printed is

$ ./dafuq
THIS IS PRINTED
Segmentation fault

This is the gdb dump

(gdb) break 114
Breakpoint 1 at 0x40116d: file getinmemory.c, line 114.
(gdb) break 115
Breakpoint 2 at 0x40117c: file getinmemory.c, line 115.
(gdb) run
Starting program: dafuq 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
THIS IS PRINTED

Breakpoint 1, main () at getinmemory.c:114
114   res = curl_easy_perform(curl_handle);
(gdb) step
[New Thread 0x7ffff341e700 (LWP 6774)]
[Thread 0x7ffff341e700 (LWP 6774) exited]

Program received signal SIGSEGV, Segmentation fault.
__GI___libc_realloc (oldmem=0xfbad2a84, bytes=140737354092562) at malloc.c:2977
2977    malloc.c: No such file or directory.
(gdb) 

The valgrind dump is:

$ valgrind ./dafuq --tool memcheck
==7002== Memcheck, a memory error detector
==7002== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==7002== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==7002== Command: ./dafuq --tool memcheck
==7002== 
THIS IS PRINTED
==7002== Invalid free() / delete / delete[] / realloc()
==7002==    at 0x4C2AF2E: realloc (vg_replace_malloc.c:692)
==7002==    by 0x400D27: WriteMemoryCallback (getinmemory.c:44)
==7002==    by 0x4E4BD89: ??? (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.3.0)
==7002==    by 0x4E4A353: ??? (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.3.0)
==7002==    by 0x4E605AF: ??? (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.3.0)
==7002==    by 0x4E6ACE8: ??? (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.3.0)
==7002==    by 0x4E6B560: curl_multi_perform (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.3.0)
==7002==    by 0x4E6215A: curl_easy_perform (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.3.0)
==7002==    by 0x401178: main (getinmemory.c:114)
==7002==  Address 0xfbad2a84 is not stack'd, malloc'd or (recently) free'd
==7002== 
not enough memory (realloc returned NULL)
THIS IS NOT...
curl_easy_perform() failed: Failed writing received data to disk/application
==7002== 
==7002== HEAP SUMMARY:
==7002==     in use at exit: 238 bytes in 8 blocks
==7002==   total heap usage: 7,171 allocs, 7,163 frees, 67,842,654 bytes allocated
==7002== 
==7002== LEAK SUMMARY:
==7002==    definitely lost: 16 bytes in 1 blocks
==7002==    indirectly lost: 158 bytes in 5 blocks
==7002==      possibly lost: 0 bytes in 0 blocks
==7002==    still reachable: 64 bytes in 2 blocks
==7002==         suppressed: 0 bytes in 0 blocks
==7002== Rerun with --leak-check=full to see details of leaked memory
==7002== 
==7002== For counts of detected and suppressed errors, rerun with: -v
==7002== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Any thoughts?

PS: I also tried using stream like sth's code from here,, coming up with the exact same behavior.

like image 531
stelios Avatar asked Oct 28 '14 13:10

stelios


1 Answers

Stupid mistake, I have override CURLOPT_WRITEDATA with both chunk and stdout. Thus removing

curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, stdout);

does the job...

PS: Segmentation fault in curl_easy_perform also seems to happen when authentication data is false (e.g: expired session)

like image 52
stelios Avatar answered Oct 21 '22 01:10

stelios