GNOME Bugzilla – Bug 752902
signed integer overflow and index out of bound in uri.c and parser.c
Last modified: 2016-05-24 03:39:25 UTC
Created attachment 308184 [details] inputs to reproduce the undefined behaviors Hey, after fuzzing libxml2 with afl for a night, I found one signed integer overflow and several out of bound access to array xmlChar*. To reproduce the bug, you need to build the source code with flag '-fsanitize=undefined' Then execute command: ./xmllint $file (see attachment) You will see the following error information: uri.c:334:25: runtime error: signed integer overflow: 555555555 * 10 cannot be represented in type 'int' parser.c:1273:5: runtime error: index 5 out of bounds for type 'const xmlChar *[5]' parser.c:1274:5: runtime error: index 6 out of bounds for type 'const xmlChar *[5]' parser.c:1278:5: runtime error: index 7 out of bounds for type 'const xmlChar *[5]' parser.c:1279:5: runtime error: index 8 out of bounds for type 'const xmlChar *[5]' parser.c:1283:9: runtime error: index 9 out of bounds for type 'const xmlChar *[5]' parser.c:9584:20: runtime error: index 5 out of bounds for type 'const xmlChar *[5]' parser.c:9585:13: runtime error: index 6 out of bounds for type 'const xmlChar *[5]' parser.c:9643:24: runtime error: index 7 out of bounds for type 'const xmlChar *[5]' parser.c:9644:24: runtime error: index 8 out of bounds for type 'const xmlChar *[5]' parser.c:9600:21: runtime error: index 7 out of bounds for type 'const xmlChar *[5]' parser.c:9602:15: runtime error: index 7 out of bounds for type 'const xmlChar *[5]'
Version information: ./local/bin/xmllint: using libxml version 20902-GITv2.9.2-30-g3eaedba compiled with: Threads Tree Output Push Reader Patterns Writer SAXv1 FTP HTTP DTDValid HTML Legacy C14N Catalog XPath XPointer XInclude Iconv ISO8859X Unicode Regexps Automata Expr Schemas Schematron Modules Debug Zlib
Malloc is done for "defaults": 1233 defaults = (xmlDefAttrsPtr) xmlMalloc(sizeof(xmlDefAttrs) + 1234 (4 * 5) * sizeof(const xmlChar *)); But accessed is default->values which is : const xmlChar *values[5]; /* array of localname/prefix/values/external */ It is accessed at positions 5 & above. 1273 defaults->values[5 * defaults->nbAttrs] = name; 1274 defaults->values[5 * defaults->nbAttrs + 1] = prefix; 1275 /* intern the string and precompute the end */ 1276 len = xmlStrlen(value); 1277 value = xmlDictLookup(ctxt->dict, value, len); 1278 defaults->values[5 * defaults->nbAttrs + 2] = value; 1279 defaults->values[5 * defaults->nbAttrs + 3] = value + len; 1280 if (ctxt->external) 1281 defaults->values[5 * defaults->nbAttrs + 4] = BAD_CAST "external"; 1282 else 1283 defaults->values[5 * defaults->nbAttrs + 4] = NULL; Is this correct?
(In reply to Dingbao Xie from comment #0) > Created attachment 308184 [details] > inputs to reproduce the undefined behaviors > > Hey, after fuzzing libxml2 with afl for a night, I found one signed integer > overflow and several out of bound access to array xmlChar*. > > To reproduce the bug, you need to build the source code with flag > '-fsanitize=undefined' > Then execute command: > ./xmllint $file (see attachment) > You will see the following error information: > uri.c:334:25: runtime error: signed integer overflow: 555555555 * 10 cannot > be represented in type 'int' This issue is tracked here: Bug 765566: Integer overflow parsing port number in uri.c
I don't think it's a bug, we use struct _xmlDefAttrs { int nbAttrs; /* number of defaulted attributes on that element */ int maxAttrs; /* the size of the array */ const xmlChar *values[5]; /* array of localname/prefix/values/external */ }; as the base struct but store more than 5 strings and the allocation is done accordingly as Gaurav pointed. Your compiler may barf but the memory access and allocations are a priori correct. Daniel
(In reply to Gaurav from comment #2) > Malloc is done for "defaults": > 1233 defaults = (xmlDefAttrsPtr) xmlMalloc(sizeof(xmlDefAttrs) + > 1234 (4 * 5) * sizeof(const xmlChar *)); > But accessed is default->values which is : > const xmlChar *values[5]; /* array of localname/prefix/values/external */ > It is accessed at positions 5 & above. > 1273 defaults->values[5 * defaults->nbAttrs] = name; > 1274 defaults->values[5 * defaults->nbAttrs + 1] = prefix; > 1275 /* intern the string and precompute the end */ > 1276 len = xmlStrlen(value); > 1277 value = xmlDictLookup(ctxt->dict, value, len); > 1278 defaults->values[5 * defaults->nbAttrs + 2] = value; > 1279 defaults->values[5 * defaults->nbAttrs + 3] = value + len; > 1280 if (ctxt->external) > 1281 defaults->values[5 * defaults->nbAttrs + 4] = BAD_CAST > "external"; > 1282 else > 1283 defaults->values[5 * defaults->nbAttrs + 4] = NULL; > > Is this correct? TL;DR: This is a false positive. In practice, there is no undefined behavior here since enough memory is allocated earlier in the method to store all the values, but the field is not defined the way it's used since it's a one-dimensional array of 5 items, and having more than one attribute means the code will write past the first 5 items off the "end" of the array: typedef struct _xmlDefAttrs xmlDefAttrs; typedef xmlDefAttrs *xmlDefAttrsPtr; struct _xmlDefAttrs { int nbAttrs; /* number of defaulted attributes on that element */ int maxAttrs; /* the size of the array */ const xmlChar *values[5]; /* array of localname/prefix/values/external */ }; What you really want is a two-dimensional array with 5 items for each row: typedef struct _xmlDefAttrs xmlDefAttrs; typedef xmlDefAttrs *xmlDefAttrsPtr; struct _xmlDefAttrs { int nbAttrs; /* number of defaulted attributes on that element */ int maxAttrs; /* the size of the array */ const xmlChar *values[][5]; /* array of localname/prefix/values/external */ }; Making this change forces one to clean up the code in xmlAddDefAttrs() and xmlParseStartTag2(). In fact, making this change and cleaning up the code leads to a fix for the following buggy code: nsname = xmlGetNamespace(ctxt, attname); if (nsname != defaults->values[2]) { if (nsPush(ctxt, attname, defaults->values[5 * i + 2]) > 0) nbNs++; } The second line above is missing the calculation to get the correct value out of defaults->values. The correct version is this: nsname = xmlGetNamespace(ctxt, attname); if (nsname != defaults->values[i][2]) { if (nsPush(ctxt, attname, defaults->values[i][2]) > 0) nbNs++; } Sadly, there doesn't appear to be any test coverage for this code as it doesn't cause any existing tests to change their results. I can open a new bug for the issue noted above.
I filed: Bug 766828: xmlParseStartTag2() contains typo when checking for default definitions for an attribute in a namespace <https://bugzilla.gnome.org/show_bug.cgi?id=766828>