Commit 68d83a81 authored by Yang Tse's avatar Yang Tse
Browse files

Overhauled test suite getpart() function. Fixing potential out of bounds
stack and memory overwrites triggered with huge test case definitions.
parent b4ff6d30
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -6,6 +6,10 @@

                                  Changelog

Yang Tse (14 Feb 2010)
- Overhauled test suite getpart() function. Fixing potential out of bounds
  stack and memory overwrites triggered with huge test case definitions.

Daniel Stenberg (13 Feb 2010)
- Martin Hager reported and fixed a problem with a missing quote in libcurl.m4

+286 −125
Original line number Diff line number Diff line
@@ -41,11 +41,11 @@ struct SessionHandle {
/* include memdebug.h last */
#include "memdebug.h"

#define EAT_SPACE(ptr) while( ptr && *ptr && ISSPACE(*ptr) ) ptr++
#define EAT_WORD(ptr) while( ptr && *ptr && !ISSPACE(*ptr) && \
                            ('>' != *ptr)) ptr++
#define EAT_SPACE(p) while(*(p) && ISSPACE(*(p))) (p)++

#ifdef DEBUG
#define EAT_WORD(p)  while(*(p) && !ISSPACE(*(p)) && ('>' != *(p))) (p)++

#ifdef DEBUG_GETPART
#define show(x) printf x
#else
#define show(x)
@@ -65,191 +65,352 @@ curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc;
#  pragma warning(default:4232) /* MSVC extension, dllimport identity */
#endif

static
char *appendstring(char *string, /* original string */
                   char *buffer, /* to append */
                   size_t *stringlen, /* length of string */
                   size_t *stralloc,  /* allocated size */
                   char base64) /* 1 if base64 encoded */
/*
 * readline()
 *
 * Reads a complete line from a file into a dynamically allocated buffer.
 *
 * Calling function may call this multiple times with same 'buffer'
 * and 'bufsize' pointers to avoid multiple buffer allocations. Buffer
 * will be reallocated and 'bufsize' increased until whole line fits in
 * buffer before returning it.
 *
 * Calling function is responsible to free allocated buffer.
 *
 * This function may return:
 *   GPE_OUT_OF_MEMORY
 *   GPE_END_OF_FILE
 *   GPE_OK
 */

static int readline(char **buffer, size_t *bufsize, FILE *stream)
{
  size_t offset = 0;
  size_t length;
  char *newptr;

  if(!*buffer) {
    *buffer = malloc(128);
    if(!*buffer)
      return GPE_OUT_OF_MEMORY;
    *bufsize = 128;
  }

  for(;;) {
    if(!fgets(*buffer + offset, (int)(*bufsize - offset), stream))
      return (offset != 0) ? GPE_OK : GPE_END_OF_FILE ;

    length = offset + strlen(*buffer + offset);
    if(*(*buffer + length - 1) == '\n')
      break;
    offset = length;
    if(length < *bufsize - 1)
      continue;

    newptr = realloc(*buffer, *bufsize * 2);
    if(!newptr)
      return GPE_OUT_OF_MEMORY;
    *buffer = newptr;
    *bufsize *= 2;
  }

  return GPE_OK;
}

/*
 * appenddata()
 *
 * This appends data from a given source buffer to the end of the used part of
 * a destination buffer. Arguments relative to the destination buffer are, the
 * address of a pointer to the destination buffer 'dst_buf', the length of data
 * in destination buffer excluding potential null string termination 'dst_len',
 * the allocated size of destination buffer 'dst_alloc'. All three destination
 * buffer arguments may be modified by this function. Arguments relative to the
 * source buffer are, a pointer to the source buffer 'src_buf' and indication
 * whether the source buffer is base64 encoded or not 'src_b64'.
 *
 * If the source buffer is indicated to be base64 encoded, this appends the
 * decoded data, binary or whatever, to the destination. The source buffer
 * may not hold binary data, only a null terminated string is valid content.
 *
 * Destination buffer will be enlarged and relocated as needed.
 *
 * Calling function is responsible to provide preallocated destination
 * buffer and also to deallocate it when no longer needed.
 *
 * This function may return:
 *   GPE_OUT_OF_MEMORY
 *   GPE_OK
 */

static int appenddata(char  **dst_buf,   /* dest buffer */
                      size_t *dst_len,   /* dest buffer data length */
                      size_t *dst_alloc, /* dest buffer allocated size */
                      char   *src_buf,   /* source buffer */
                      int     src_b64)   /* != 0 if source is base64 encoded */
{
  size_t need_alloc, src_len;
  union {
    unsigned char *as_uchar;
             char *as_char;
  } buf64;

  size_t len = strlen(buffer);
  size_t needed_len = len + *stringlen + 1;
  src_len = strlen(src_buf);
  if(!src_len)
    return GPE_OK;

  buf64.as_char = NULL;

  if(base64) {
    /* decode the given buffer first */
    len = Curl_base64_decode(buffer, &buf64.as_uchar); /* updated len */
    buffer = buf64.as_char;
    needed_len = len + *stringlen + 1; /* recalculate */
  if(src_b64) {
    /* base64 decode the given buffer */
    src_len = Curl_base64_decode(src_buf, &buf64.as_uchar);
    src_buf = buf64.as_char;
    if(!src_len || !src_buf) {
      /*
      ** currently there is no way to tell apart an OOM condition in
      ** Curl_base64_decode() from zero length decoded data. For now,
      ** let's just assume it is an OOM condition, currently we have
      ** no input for this function that decodes to zero length data.
      */
      if(buf64.as_char)
        free(buf64.as_char);
      return GPE_OUT_OF_MEMORY;
    }
  }

  if(needed_len >= *stralloc) {
    char *newptr;
    size_t newsize = needed_len*2; /* get twice the needed size */
  need_alloc = src_len + *dst_len + 1;

    newptr = realloc(string, newsize);
    if(newptr) {
      string = newptr;
      *stralloc = newsize;
    }
    else {
  /* enlarge destination buffer if required */
  if(need_alloc > *dst_alloc) {
    size_t newsize = need_alloc * 2;
    char *newptr = realloc(*dst_buf, newsize);
    if(!newptr) {
      if(buf64.as_char)
        free(buf64.as_char);
      return NULL;
      return GPE_OUT_OF_MEMORY;
    }
    *dst_alloc = newsize;
    *dst_buf = newptr;
  }

  /* memcpy to support binary blobs */
  memcpy(&string[*stringlen], buffer, len);
  *stringlen += len;
  string[*stringlen]=0;
  memcpy(*dst_buf + *dst_len, src_buf, src_len);
  *dst_len += src_len;
  *(*dst_buf + *dst_len) = '\0';

  if(buf64.as_char)
    free(buf64.as_char);

  return string;
  return GPE_OK;
}

const char *spitout(FILE *stream,
                    const char *main,
                    const char *sub, size_t *size)
/*
 * getpart()
 *
 * This returns whole contents of specified XML-like section and subsection
 * from the given file. This is mostly used to retrieve a specific part from
 * a test definition file for consumption by test suite servers.
 *
 * Data is returned in a dynamically allocated buffer, a pointer to this data
 * and the size of the data is stored at the addresses that caller specifies.
 *
 * If the returned data is a string the returned size will be the length of
 * the string excluding null termination. Otherwise it will just be the size
 * of the returned binary data.
 *
 * Calling function is responsible to free returned buffer.
 *
 * This function may return:
 *   GPE_NO_BUFFER_SPACE
 *   GPE_OUT_OF_MEMORY
 *   GPE_OK
 */

int getpart(char **outbuf, size_t *outlen,
            const char *main, const char *sub, FILE *stream)
{
  char buffer[8192]; /* big enough for anything */
  char cmain[128]=""; /* current main section */
  char csub[128]="";  /* current sub section */
# define MAX_TAG_LEN 79
  char couter[MAX_TAG_LEN+1]; /* current outermost section */
  char cmain[MAX_TAG_LEN+1];  /* current main section */
  char csub[MAX_TAG_LEN+1];   /* current sub section */
  char ptag[MAX_TAG_LEN+1];   /* potential tag */
  char patt[MAX_TAG_LEN+1];   /* potential attributes */
  char *buffer = NULL;
  char *ptr;
  char *end;
  char display = 0;

  char *string;
  size_t stringlen=0;
  size_t stralloc=256;
  char base64 = 0; /* set to 1 if true */
  size_t length;
  size_t bufsize = 0;
  size_t outalloc = 256;
  int in_wanted_part = 0;
  int base64 = 0;
  int error;

  enum {
    STATE_OUTSIDE,
    STATE_OUTER,
    STATE_INMAIN,
    STATE_INSUB,
    STATE_ILLEGAL
    STATE_OUTSIDE = 0,
    STATE_OUTER   = 1,
    STATE_INMAIN  = 2,
    STATE_INSUB   = 3,
    STATE_ILLEGAL = 4
  } state = STATE_OUTSIDE;

  string = malloc(stralloc);
  if(!string)
    return NULL;
  *outlen = 0;
  *outbuf = malloc(outalloc);
  if(!*outbuf)
    return GPE_OUT_OF_MEMORY;
  *(*outbuf) = '\0';

  string[0] = 0; /* zero first byte in case of no data */
  couter[0] = cmain[0] = csub[0] = ptag[0] = patt[0] = '\0';

  while(fgets(buffer, sizeof(buffer), stream)) {
  while((error = readline(&buffer, &bufsize, stream)) == GPE_OK) {

    ptr = buffer;

    /* pass white spaces */
    EAT_SPACE(ptr);

    if('<' != *ptr) {
      if(display) {
      if(in_wanted_part) {
        show(("=> %s", buffer));
        string = appendstring(string, buffer, &stringlen, &stralloc, base64);
        show(("* %s\n", buffer));
        error = appenddata(outbuf, outlen, &outalloc, buffer, base64);
        if(error)
          break;
      }
      continue;
    }

    ptr++;
    EAT_SPACE(ptr);

    if('/' == *ptr) {
      /* end of a section */
      ptr++;
      EAT_SPACE(ptr);
      /*
      ** closing section tag
      */

      ptr++;
      end = ptr;
      EAT_WORD(end);
      *end = 0;
      if((length = end - ptr) > MAX_TAG_LEN) {
        error = GPE_NO_BUFFER_SPACE;
        break;
      }
      memcpy(ptag, ptr, length);
      ptag[length] = '\0';

      if((state == STATE_INSUB) &&
         !strcmp(csub, ptr)) {
        /* this is the end of the currently read sub section */
        state--;
        csub[0]=0; /* no sub anymore */
        display=0;
      if((STATE_INSUB == state) && !strcmp(csub, ptag)) {
        /* end of current sub section */
        state = STATE_INMAIN;
        csub[0] = '\0';
        if(in_wanted_part) {
          /* end of wanted part */
          in_wanted_part = 0;
          break;
        }
      else if((state == STATE_INMAIN) &&
              !strcmp(cmain, ptr)) {
        /* this is the end of the currently read main section */
        state--;
        cmain[0]=0; /* no main anymore */
        display=0;
      }
      else if(state == STATE_OUTER) {
        /* this is the end of the outermost file section */
        state--;
      else if((STATE_INMAIN == state) && !strcmp(cmain, ptag)) {
        /* end of current main section */
        state = STATE_OUTER;
        cmain[0] = '\0';
        if(in_wanted_part) {
          /* end of wanted part */
          in_wanted_part = 0;
          break;
        }
      }
      else if((STATE_OUTER == state) && !strcmp(couter, ptag)) {
        /* end of outermost file section */
        state = STATE_OUTSIDE;
        couter[0] = '\0';
        if(in_wanted_part) {
          /* end of wanted part */
          in_wanted_part = 0;
          break;
        }
      }
    else if(!display) {
      /* this is the beginning of a section */

    }
    else if(!in_wanted_part) {
      /*
      ** opening section tag
      */

      /* get potential tag */
      end = ptr;
      EAT_WORD(end);

      *end = 0;
      switch(state) {
      case STATE_OUTSIDE:
        /* Skip over the outermost element (<testcase>), but if it turns out
           to be a comment, completely ignore it below */
        strcpy(cmain, ptr);
        state = STATE_OUTER;
      if((length = end - ptr) > MAX_TAG_LEN) {
        error = GPE_NO_BUFFER_SPACE;
        break;
      case STATE_OUTER:
        strcpy(cmain, ptr);
        state = STATE_INMAIN;
        break;
      case STATE_INMAIN:
        strcpy(csub, ptr);
        state = STATE_INSUB;
        break;
      default:
      }
      memcpy(ptag, ptr, length);
      ptag[length] = '\0';

      /* ignore comments, doctypes and xml declarations */
      if(('!' == ptag[0]) || ('?' == ptag[0])) {
        show(("* ignoring (%s)", buffer));
        continue;
      }

      /* get all potential attributes */
      ptr = end;
      EAT_SPACE(ptr);
      end = ptr;
      while(*end && ('>' != *end))
        end++;
      if((length = end - ptr) > MAX_TAG_LEN) {
        error = GPE_NO_BUFFER_SPACE;
        break;
      }
      memcpy(patt, ptr, length);
      patt[length] = '\0';

      if(!end[1] != '>') {
        /* There might be attributes here. Check for those we know of and care
           about. */
        if(strstr(&end[1], "base64=")) {
          /* rough and dirty, but "mostly" functional */
          /* Treat all data as base64 encoded */
          base64 = 1;
      if(STATE_OUTSIDE == state) {
        /* outermost element (<testcase>) */
        strcpy(couter, ptag);
        state = STATE_OUTER;
        continue;
      }
      else if(STATE_OUTER == state) {
        /* start of a main section */
        strcpy(cmain, ptag);
        state = STATE_INMAIN;
        continue;
      }
      else if(STATE_INMAIN == state) {
        /* start of a sub section */
        strcpy(csub, ptag);
        state = STATE_INSUB;
        if(!strcmp(cmain, main) && !strcmp(csub, sub)) {
          /* start of wanted part */
          in_wanted_part = 1;
          if(strstr(patt, "base64="))
              /* bit rough test, but "mostly" functional, */
              /* treat wanted part data as base64 encoded */
              base64 = 1;
        }
    if(display) {
      string = appendstring(string, buffer, &stringlen, &stralloc, base64);
      show(("* %s\n", buffer));
        continue;
      }

    if((STATE_INSUB == state) &&
       !strcmp(cmain, main) &&
       !strcmp(csub, sub)) {
      show(("* (%d bytes) %s\n", stringlen, buffer));
      display = 1; /* start displaying */
    }
    else if ((*cmain == '?') || (*cmain == '!') || (*csub == '!')) {
        /* Ignore comments, DOCTYPEs and XML declarations */
        show(("%d ignoring (%s/%s)\n", state, cmain, csub));
        state--;

    if(in_wanted_part) {
      show(("=> %s", buffer));
      error = appenddata(outbuf, outlen, &outalloc, buffer, base64);
      if(error)
        break;
    }

  } /* while */

  if(buffer)
    free(buffer);

  if(error != GPE_OK) {
    if(error == GPE_END_OF_FILE)
      error = GPE_OK;
    else {
      show(("%d (%s/%s): %s\n", state, cmain, csub, buffer));
      display = 0; /* no display */
      if(*outbuf)
        free(*outbuf);
      *outbuf = NULL;
      *outlen = 0;
    }
  }

  *size = stringlen;
  return string;
  return error;
}
+13 −6
Original line number Diff line number Diff line
#ifndef HEADER_SERVER_GETPART_H
#define HEADER_SERVER_GETPART_H
/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
@@ -5,7 +7,7 @@
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2009, Daniel Stenberg, <daniel@haxx.se>, et al.
 * Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
@@ -20,8 +22,13 @@
 *
 * $Id$
 ***************************************************************************/
const char *
spitout(FILE *stream,
        const char *main,
        const char *sub,
        size_t *size);

#define GPE_NO_BUFFER_SPACE -2
#define GPE_OUT_OF_MEMORY   -1
#define GPE_OK               0
#define GPE_END_OF_FILE      1

int getpart(char **outbuf, size_t *outlen,
            const char *main, const char *sub, FILE *stream);

#endif /* HEADER_SERVER_GETPART_H */
+44 −9
Original line number Diff line number Diff line
@@ -423,9 +423,14 @@ static int ProcessRequest(struct httprequest *req)
        char *rtp_scratch = NULL;

        /* get the custom server control "commands" */
        cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize);
        ptr = cmd;
        error = getpart(&cmd, &cmdsize, "reply", "servercmd", stream);
        fclose(stream);
        if(error) {
          logmsg("getpart() failed with error: %d", error);
          req->open = FALSE; /* closes connection */
          return 1; /* done */
        }
        ptr = cmd;

        if(cmdsize) {
          logmsg("Found a reply-servercmd section!");
@@ -505,6 +510,8 @@ static int ProcessRequest(struct httprequest *req)
          } while(ptr && *ptr);
          logmsg("Done parsing server commands");
        }
        if(cmd)
          free(cmd);
      }
    }
    else {
@@ -950,13 +957,20 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
      return 0;
    }
    else {
      buffer = spitout(stream, "reply", partbuf, &count);
      ptr = (char *)buffer;
      error = getpart(&buffer, &count, "reply", partbuf, stream);
      fclose(stream);
      if(error) {
        logmsg("getpart() failed with error: %d", error);
        return 0;
      }
      ptr = (char *)buffer;
    }

    if(got_exit_signal)
    if(got_exit_signal) {
      if(ptr)
        free(ptr);
      return -1;
    }

    /* re-open the same file again */
    stream=fopen(filename, "rb");
@@ -965,17 +979,30 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
      logmsg("fopen() failed with error: %d %s", error, strerror(error));
      logmsg("Error opening file: %s", filename);
      logmsg("Couldn't open test file");
      if(ptr)
        free(ptr);
      return 0;
    }
    else {
      /* get the custom server control "commands" */
      cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize);
      error = getpart(&cmd, &cmdsize, "reply", "postcmd", stream);
      fclose(stream);
      if(error) {
        logmsg("getpart() failed with error: %d", error);
        if(ptr)
          free(ptr);
        return 0;
      }
    }
  }

  if(got_exit_signal)
  if(got_exit_signal) {
    if(ptr)
      free(ptr);
    if(cmd)
      free(cmd);
    return -1;
  }

  /* If the word 'swsclose' is present anywhere in the reply chunk, the
     connection will be closed after the data has been sent to the requesting
@@ -997,6 +1024,10 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
    logmsg("fopen() failed with error: %d %s", error, strerror(error));
    logmsg("Error opening file: %s", RESPONSE_DUMP);
    logmsg("couldn't create logfile: " RESPONSE_DUMP);
    if(ptr)
      free(ptr);
    if(cmd)
      free(cmd);
    return -1;
  }

@@ -1045,7 +1076,6 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
    req->rtp_buffersize = 0;
  }


  do {
    res = fclose(dump);
  } while(res && ((error = ERRNO) == EINTR));
@@ -1053,8 +1083,13 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
    logmsg("Error closing file %s error: %d %s",
           RESPONSE_DUMP, error, strerror(error));

  if(got_exit_signal)
  if(got_exit_signal) {
    if(ptr)
      free(ptr);
    if(cmd)
      free(cmd);
    return -1;
  }

  if(sendfailure) {
    logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) were sent",
+43 −8
Original line number Diff line number Diff line
@@ -389,8 +389,13 @@ static int ProcessRequest(struct httprequest *req)
        int num=0;

        /* get the custom server control "commands" */
        cmd = (char *)spitout(stream, "reply", "servercmd", &cmdsize);
        error = getpart(&cmd, &cmdsize, "reply", "servercmd", stream);
        fclose(stream);
        if(error) {
          logmsg("getpart() failed with error: %d", error);
          req->open = FALSE; /* closes connection */
          return 1; /* done */
        }

        if(cmdsize) {
          logmsg("Found a reply-servercmd section!");
@@ -423,8 +428,9 @@ static int ProcessRequest(struct httprequest *req)
          else {
            logmsg("funny instruction found: %s", cmd);
          }
          free(cmd);
        }
        if(cmd)
          free(cmd);
      }
    }
    else {
@@ -863,13 +869,20 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
      return 0;
    }
    else {
      buffer = spitout(stream, "reply", partbuf, &count);
      ptr = (char *)buffer;
      error = getpart(&ptr, &count, "reply", partbuf, stream);
      fclose(stream);
      if(error) {
        logmsg("getpart() failed with error: %d", error);
        return 0;
      }
      buffer = ptr;
    }

    if(got_exit_signal)
    if(got_exit_signal) {
      if(ptr)
        free(ptr);
      return -1;
    }

    /* re-open the same file again */
    stream=fopen(filename, "rb");
@@ -878,17 +891,30 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
      logmsg("fopen() failed with error: %d %s", error, strerror(error));
      logmsg("Error opening file: %s", filename);
      logmsg("Couldn't open test file");
      if(ptr)
        free(ptr);
      return 0;
    }
    else {
      /* get the custom server control "commands" */
      cmd = (char *)spitout(stream, "reply", "postcmd", &cmdsize);
      error = getpart(&cmd, &cmdsize, "reply", "postcmd", stream);
      fclose(stream);
      if(error) {
        logmsg("getpart() failed with error: %d", error);
        if(ptr)
          free(ptr);
        return 0;
      }
    }
  }

  if(got_exit_signal)
  if(got_exit_signal) {
    if(ptr)
      free(ptr);
    if(cmd)
      free(cmd);
    return -1;
  }

  /* If the word 'swsclose' is present anywhere in the reply chunk, the
     connection will be closed after the data has been sent to the requesting
@@ -910,6 +936,10 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
    logmsg("fopen() failed with error: %d %s", error, strerror(error));
    logmsg("Error opening file: %s", RESPONSE_DUMP);
    logmsg("couldn't create logfile: " RESPONSE_DUMP);
    if(ptr)
      free(ptr);
    if(cmd)
      free(cmd);
    return -1;
  }

@@ -945,8 +975,13 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
    logmsg("Error closing file %s error: %d %s",
           RESPONSE_DUMP, error, strerror(error));

  if(got_exit_signal)
  if(got_exit_signal) {
    if(ptr)
      free(ptr);
    if(cmd)
      free(cmd);
    return -1;
  }

  if(sendfailure) {
    logmsg("Sending response failed. Only (%zu bytes) of (%zu bytes) were sent",
Loading