Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #7635: do a better job checking for infinite loops in multi-par…

…t MIME parsing. Thanks, Mike Axiak.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@7905 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit c121ff4046263b37862f9aed5a6d7463794abb81 1 parent 8e852a9
Jacob Kaplan-Moss authored July 12, 2008

Showing 1 changed file with 24 additions and 33 deletions. Show diff stats Hide diff stats

  1. 57  django/http/multipartparser.py
57  django/http/multipartparser.py
@@ -270,24 +270,9 @@ def __init__(self, producer, length=None):
270 270
         self._empty = False
271 271
         self._leftover = ''
272 272
         self.length = length
273  
-        self._position = 0
  273
+        self.position = 0
274 274
         self._remaining = length
275  
-
276  
-        # These fields are to do sanity checking to make sure we don't
277  
-        # have infinite loops getting/ungetting from the stream. The
278  
-        # purpose overall is to raise an exception if we perform lots
279  
-        # of stream get/unget gymnastics without getting
280  
-        # anywhere. Naturally this is not sound, but most probably
281  
-        # would indicate a bug if the exception is raised.
282  
-
283  
-        # largest position tell us how far this lazystream has ever
284  
-        # been advanced
285  
-        self._largest_position = 0
286  
-
287  
-        # "modifications since" will start at zero and increment every
288  
-        # time the position is modified but a new largest position is
289  
-        # not achieved.
290  
-        self._modifications_since = 0
  275
+        self._unget_history = []
291 276
 
292 277
     def tell(self):
293 278
         return self.position
@@ -329,6 +314,7 @@ def next(self):
329 314
             self._leftover = ''
330 315
         else:
331 316
             output = self._producer.next()
  317
+            self._unget_history = []
332 318
         self.position += len(output)
333 319
         return output
334 320
 
@@ -351,25 +337,30 @@ def unget(self, bytes):
351 337
         Future calls to read() will return those bytes first. The
352 338
         stream position and thus tell() will be rewound.
353 339
         """
  340
+        if not bytes:
  341
+            return
  342
+        self._update_unget_history(len(bytes))
354 343
         self.position -= len(bytes)
355 344
         self._leftover = ''.join([bytes, self._leftover])
356 345
 
357  
-    def _set_position(self, value):
358  
-        if value > self._largest_position:
359  
-            self._modifications_since = 0
360  
-            self._largest_position = value
361  
-        else:
362  
-            self._modifications_since += 1
363  
-            if self._modifications_since > 500:
364  
-                raise SuspiciousOperation(
365  
-                    "The multipart parser got stuck, which shouldn't happen with"
366  
-                    " normal uploaded files. Check for malicious upload activity;"
367  
-                    " if there is none, report this to the Django developers."
368  
-                )
369  
-
370  
-        self._position = value
371  
-
372  
-    position = property(lambda self: self._position, _set_position)
  346
+    def _update_unget_history(self, num_bytes):
  347
+        """
  348
+        Updates the unget history as a sanity check to see if we've pushed
  349
+        back the same number of bytes in one chunk. If we keep ungetting the
  350
+        same number of bytes many times (here, 50), we're mostly likely in an
  351
+        infinite loop of some sort. This is usually caused by a
  352
+        maliciously-malformed MIME request.
  353
+        """
  354
+        self._unget_history = [num_bytes] + self._unget_history[:49]
  355
+        number_equal = len([current_number for current_number in self._unget_history
  356
+                            if current_number == num_bytes])
  357
+
  358
+        if number_equal > 40:
  359
+            raise SuspiciousOperation(
  360
+                "The multipart parser got stuck, which shouldn't happen with"
  361
+                " normal uploaded files. Check for malicious upload activity;"
  362
+                " if there is none, report this to the Django developers."
  363
+            )
373 364
 
374 365
 class ChunkIter(object):
375 366
     """

0 notes on commit c121ff4

Please sign in to comment.
Something went wrong with that request. Please try again.