Merge lp:~doctormo/inkscape/fix166371 into lp:~inkscape.dev/inkscape/trunk

Proposed by Martin Owens
Status: Merged
Merged at revision: 12510
Proposed branch: lp:~doctormo/inkscape/fix166371
Merge into: lp:~inkscape.dev/inkscape/trunk
Diff against target: 128 lines (+59/-16)
1 file modified
src/xml/repr-io.cpp (+59/-16)
To merge this branch: bzr merge lp:~doctormo/inkscape/fix166371
Reviewer Review Type Date Requested Status
Inkscape Developers Pending
Review via email: mp+185064@code.launchpad.net

Description of the change

This is a fix for our critical blocker regarding Adobe vs Hackers in bug 166371. I should explain some things:

The security vuln occurs when SYSTEM calls load data from the hard drive, using replacements with entities causes it to open a huge security hole where private data can be stolen. This can be closed by stopping the parsing of ENTITIES. libxml2's response to the vulnerability was very poor IMO, simply killing an entire feature of their library without sufficient protection when it's switched back on.

But Adobe files REQUIRE ENTITY replacements for their namespace attributes.

What this fix does, is firstly attempt a normal open, so most files will not be slowed down by the filtering. If it fails in the specific way Adobe files fail... which is to cause a valid document with no namespace i.e. ns:svg instead of svg:svg then it attempts to reload the document, this time it switches on NOENT /and/ the same switch strips SYSTEM out of the read buffer before it gets to the parser. Thus even an adobe file can't be used to steal data and there is no vuln.

The alternative fix, is to switch NOENT back off and manually replace each of the Adobe namespace entities, but this is longer winded and needs updating in any ways files might differ. Please advise as the code is similar and doesn't require much change between them.

To post a comment you must log in.
lp:~doctormo/inkscape/fix166371 updated
12507. By Martin Owens

Improve code with Kosiński's regex replacement

12508. By Martin Owens

Fix regex so it effects the buffer and ban PUBLIC entities too.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/xml/repr-io.cpp'
--- src/xml/repr-io.cpp 2013-08-05 21:07:35 +0000
+++ src/xml/repr-io.cpp 2013-09-12 21:07:18 +0000
@@ -100,6 +100,9 @@
100100
101 int setFile( char const * filename );101 int setFile( char const * filename );
102102
103 xmlDocPtr readXml();
104 bool SystemCheck; // Checks for SYSTEM Entities
105
103 static int readCb( void * context, char * buffer, int len );106 static int readCb( void * context, char * buffer, int len );
104 static int closeCb( void * context );107 static int closeCb( void * context );
105108
@@ -121,6 +124,7 @@
121{124{
122 int retVal = -1;125 int retVal = -1;
123126
127 this->SystemCheck = false;
124 this->filename = filename;128 this->filename = filename;
125129
126 fp = Inkscape::IO::fopen_utf8name(filename, "r");130 fp = Inkscape::IO::fopen_utf8name(filename, "r");
@@ -178,13 +182,53 @@
178 return retVal;182 return retVal;
179}183}
180184
185xmlDocPtr XmlSource::readXml()
186{
187 int parse_options = XML_PARSE_HUGE | XML_PARSE_RECOVER;
188
189 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
190 bool allowNetAccess = prefs->getBool("/options/externalresources/xml/allow_net_access", false);
191 if (!allowNetAccess) parse_options |= XML_PARSE_NONET;
192
193 // Allow NOENT only if we're filtering out SYSTEM and PUBLIC entities
194 if (SystemCheck) parse_options |= XML_PARSE_NOENT;
195
196 return xmlReadIO( readCb, closeCb, this,
197 filename, getEncoding(), parse_options);
198}
181199
182int XmlSource::readCb( void * context, char * buffer, int len )200int XmlSource::readCb( void * context, char * buffer, int len )
183{201{
184 int retVal = -1;202 int retVal = -1;
203
185 if ( context ) {204 if ( context ) {
186 XmlSource* self = static_cast<XmlSource*>(context);205 XmlSource* self = static_cast<XmlSource*>(context);
187 retVal = self->read( buffer, len );206 retVal = self->read( buffer, len );
207
208 if(self->SystemCheck) {
209 GMatchInfo *info;
210 gint start, end;
211
212 GRegex *regex = g_regex_new(
213 "<!ENTITY\\s+[^>\\s]+\\s+(SYSTEM|PUBLIC\\s+\"[^>\"]+\")\\s+\"[^>\"]+\"\\s*>",
214 G_REGEX_CASELESS, G_REGEX_MATCH_NEWLINE_ANY, NULL);
215
216 // Check for SYSTEM or PUBLIC entities and kill them with spaces
217 // Note: g_regex_replace does not modify buffer in place, this
218 // logic is used instead because we can just blank out the offending
219 // charicters in the right place without hurting the length.
220 g_regex_match (regex, buffer, G_REGEX_MATCH_NEWLINE_ANY, &info);
221
222 while (g_match_info_matches (info)) {
223 if (g_match_info_fetch_pos (info, 1, &start, &end)) {
224 for (int x=start; x<end; x++)
225 buffer[x] = 0x20;
226 }
227 g_match_info_next (info, NULL);
228 }
229 g_match_info_unref(info);
230 g_regex_unref(regex);
231 }
188 }232 }
189 return retVal;233 return retVal;
190}234}
@@ -299,22 +343,21 @@
299 XmlSource src;343 XmlSource src;
300344
301 if ( (src.setFile(filename) == 0) ) {345 if ( (src.setFile(filename) == 0) ) {
302 int parse_options = XML_PARSE_HUGE; // do not use XML_PARSE_NOENT ! see bug lp:1025185346 doc = src.readXml();
303 Inkscape::Preferences *prefs = Inkscape::Preferences::get();347 rdoc = sp_repr_do_read( doc, default_ns );
304 bool allowNetAccess = prefs->getBool("/options/externalresources/xml/allow_net_access", false);348 // For some reason, failed ns loading results in this
305 if (!allowNetAccess) {349 // We try a system check version of load with NOENT for adobe
306 parse_options |= XML_PARSE_NONET;350 if(rdoc && strcmp(rdoc->root()->name(), "ns:svg") == 0) {
351 xmlFreeDoc( doc );
352 src.setFile(filename);
353 src.SystemCheck = true;
354 doc = src.readXml();
355 rdoc = sp_repr_do_read( doc, default_ns );
307 }356 }
308 doc = xmlReadIO( XmlSource::readCb,
309 XmlSource::closeCb,
310 &src,
311 localFilename,
312 src.getEncoding(),
313 parse_options);
314 }357 }
315 }358 }
316359
317 rdoc = sp_repr_do_read( doc, default_ns );360
318 if ( doc ) {361 if ( doc ) {
319 xmlFreeDoc( doc );362 xmlFreeDoc( doc );
320 }363 }
@@ -947,15 +990,15 @@
947 // THIS DOESN'T APPEAR TO DO ANYTHING. Can it be commented out or deleted?990 // THIS DOESN'T APPEAR TO DO ANYTHING. Can it be commented out or deleted?
948 {991 {
949 GQuark const href_key = g_quark_from_static_string("xlink:href");992 GQuark const href_key = g_quark_from_static_string("xlink:href");
950 GQuark const absref_key = g_quark_from_static_string("sodipodi:absref");993 //GQuark const absref_key = g_quark_from_static_string("sodipodi:absref");
951994
952 gchar const *xxHref = 0;995 gchar const *xxHref = 0;
953 gchar const *xxAbsref = 0;996 //gchar const *xxAbsref = 0;
954 for ( List<AttributeRecord const> ai(attributes); ai; ++ai ) {997 for ( List<AttributeRecord const> ai(attributes); ai; ++ai ) {
955 if ( ai->key == href_key ) {998 if ( ai->key == href_key ) {
956 xxHref = ai->value;999 xxHref = ai->value;
957 } else if ( ai->key == absref_key ) {1000 //} else if ( ai->key == absref_key ) {
958 xxAbsref = ai->value;1001 //xxAbsref = ai->value;
959 }1002 }
960 }1003 }
9611004