Overhauled test suite getpart() function. Fixing potential out of bounds

stack and memory overwrites triggered with huge test case definitions.
This commit is contained in:
Yang Tse 2010-02-14 13:14:17 +00:00
parent b4ff6d3007
commit 68d83a8142
7 changed files with 415 additions and 164 deletions

View File

@ -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

View File

@ -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;
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(needed_len >= *stralloc) {
char *newptr;
size_t newsize = needed_len*2; /* get twice the needed size */
newptr = realloc(string, newsize);
if(newptr) {
string = newptr;
*stralloc = newsize;
}
else {
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 NULL;
return GPE_OUT_OF_MEMORY;
}
}
need_alloc = src_len + *dst_len + 1;
/* 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 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 */
/*
** closing section tag
*/
ptr++;
EAT_SPACE(ptr);
end = ptr;
EAT_WORD(end);
*end = 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;
}
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(!display) {
/* this is the beginning of a section */
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);
}
memcpy(ptag, ptr, length);
ptag[length] = '\0';
if((STATE_INSUB == state) && !strcmp(csub, ptag)) {
/* end of current sub section */
state = STATE_INMAIN;
break;
case STATE_INMAIN:
strcpy(csub, ptr);
state = STATE_INSUB;
break;
default:
break;
}
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;
csub[0] = '\0';
if(in_wanted_part) {
/* end of wanted part */
in_wanted_part = 0;
break;
}
}
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;
}
}
}
if(display) {
string = appendstring(string, buffer, &stringlen, &stralloc, base64);
show(("* %s\n", buffer));
else if(!in_wanted_part) {
/*
** opening section tag
*/
/* get potential tag */
end = ptr;
EAT_WORD(end);
if((length = end - ptr) > MAX_TAG_LEN) {
error = GPE_NO_BUFFER_SPACE;
break;
}
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(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;
}
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;
}

View File

@ -1,3 +1,5 @@
#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 */

View File

@ -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",

View File

@ -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",

View File

@ -35,15 +35,20 @@
int main(int argc, char **argv)
{
int rc;
char *part;
size_t partlen, i;
if(argc< 3) {
printf("./testpart main sub\n");
}
else {
size_t size;
unsigned int i;
const char *buffer = spitout(stdin, argv[1], argv[2], &size);
for(i=0; i< size; i++)
printf("%c", buffer[i]);
rc = getpart(&part, &partlen, argv[1], argv[2], stdin);
if(rc)
return(rc);
for(i = 0; i < partlen; i++)
printf("%c", part[i]);
free(part);
}
return 0;
}

View File

@ -1051,8 +1051,12 @@ static int validate_access(struct testcase *test,
}
else {
size_t count;
test->buffer = (char *)spitout(stream, "reply", partbuf, &count);
error = getpart(&test->buffer, &count, "reply", partbuf, stream);
fclose(stream);
if(error) {
logmsg("getpart() failed with error: %d", error);
return EACCESS;
}
if(test->buffer) {
test->rptr = test->buffer; /* set read pointer */
test->bufsize = count; /* set total count */