]> granicus.if.org Git - python/commitdiff
Issue #14001: CVE-2012-0845: xmlrpc: Fix an endless loop in SimpleXMLRPCServer
authorCharles-François Natali <neologix@free.fr>
Sat, 18 Feb 2012 13:53:41 +0000 (14:53 +0100)
committerCharles-François Natali <neologix@free.fr>
Sat, 18 Feb 2012 13:53:41 +0000 (14:53 +0100)
upon malformed POST request.

1  2 
Lib/test/test_xmlrpc.py
Lib/xmlrpc/server.py
Misc/NEWS

index e97420b8a8d70429bd5bbbac9c17be76fbaaff7d,9833f2674ff558398fead12603d970ba41109de1..38141912a12b13ff9853de20ae8673ffc410b362
@@@ -474,12 -347,11 +474,7 @@@ class BaseServerTestCase(unittest.TestC
  
      def tearDown(self):
          # wait on the server thread to terminate
--        self.evt.wait(4.0)
-         # XXX this code does not work, and in fact stop_serving doesn't exist.
--        if not self.evt.is_set():
--            self.evt.set()
--            stop_serving()
--            raise RuntimeError("timeout reached, test has failed")
++        self.evt.wait()
  
          # disable traceback reporting
          xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False
@@@ -622,193 -491,6 +617,200 @@@ class SimpleServerTestCase(BaseServerTe
          # This avoids waiting for the socket timeout.
          self.test_simple1()
  
 +    def test_unicode_host(self):
 +        server = xmlrpclib.ServerProxy("http://%s:%d/RPC2" % (ADDR, PORT))
 +        self.assertEqual(server.add("a", "\xe9"), "a\xe9")
 +
++    def test_partial_post(self):
++        # Check that a partial POST doesn't make the server loop: issue #14001.
++        conn = http.client.HTTPConnection(ADDR, PORT)
++        conn.request('POST', '/RPC2 HTTP/1.0\r\nContent-Length: 100\r\n\r\nbye')
++        conn.close()
++
++
 +class MultiPathServerTestCase(BaseServerTestCase):
 +    threadFunc = staticmethod(http_multi_server)
 +    request_count = 2
 +    def test_path1(self):
 +        p = xmlrpclib.ServerProxy(URL+"/foo")
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        self.assertRaises(xmlrpclib.Fault, p.add, 6, 8)
 +
 +    def test_path2(self):
 +        p = xmlrpclib.ServerProxy(URL+"/foo/bar")
 +        self.assertEqual(p.add(6,8), 6+8)
 +        self.assertRaises(xmlrpclib.Fault, p.pow, 6, 8)
 +
 +    def test_path3(self):
 +        p = xmlrpclib.ServerProxy(URL+"/is/broken")
 +        self.assertRaises(xmlrpclib.Fault, p.add, 6, 8)
 +
 +#A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism
 +#does indeed serve subsequent requests on the same connection
 +class BaseKeepaliveServerTestCase(BaseServerTestCase):
 +    #a request handler that supports keep-alive and logs requests into a
 +    #class variable
 +    class RequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
 +        parentClass = xmlrpc.server.SimpleXMLRPCRequestHandler
 +        protocol_version = 'HTTP/1.1'
 +        myRequests = []
 +        def handle(self):
 +            self.myRequests.append([])
 +            self.reqidx = len(self.myRequests)-1
 +            return self.parentClass.handle(self)
 +        def handle_one_request(self):
 +            result = self.parentClass.handle_one_request(self)
 +            self.myRequests[self.reqidx].append(self.raw_requestline)
 +            return result
 +
 +    requestHandler = RequestHandler
 +    def setUp(self):
 +        #clear request log
 +        self.RequestHandler.myRequests = []
 +        return BaseServerTestCase.setUp(self)
 +
 +#A test case that verifies that a server using the HTTP/1.1 keep-alive mechanism
 +#does indeed serve subsequent requests on the same connection
 +class KeepaliveServerTestCase1(BaseKeepaliveServerTestCase):
 +    def test_two(self):
 +        p = xmlrpclib.ServerProxy(URL)
 +        #do three requests.
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        p("close")()
 +
 +        #they should have all been handled by a single request handler
 +        self.assertEqual(len(self.RequestHandler.myRequests), 1)
 +
 +        #check that we did at least two (the third may be pending append
 +        #due to thread scheduling)
 +        self.assertGreaterEqual(len(self.RequestHandler.myRequests[-1]), 2)
 +
 +
 +#test special attribute access on the serverproxy, through the __call__
 +#function.
 +class KeepaliveServerTestCase2(BaseKeepaliveServerTestCase):
 +    #ask for two keepalive requests to be handled.
 +    request_count=2
 +
 +    def test_close(self):
 +        p = xmlrpclib.ServerProxy(URL)
 +        #do some requests with close.
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        p("close")() #this should trigger a new keep-alive request
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        p("close")()
 +
 +        #they should have all been two request handlers, each having logged at least
 +        #two complete requests
 +        self.assertEqual(len(self.RequestHandler.myRequests), 2)
 +        self.assertGreaterEqual(len(self.RequestHandler.myRequests[-1]), 2)
 +        self.assertGreaterEqual(len(self.RequestHandler.myRequests[-2]), 2)
 +
 +
 +    def test_transport(self):
 +        p = xmlrpclib.ServerProxy(URL)
 +        #do some requests with close.
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        p("transport").close() #same as above, really.
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        p("close")()
 +        self.assertEqual(len(self.RequestHandler.myRequests), 2)
 +
 +#A test case that verifies that gzip encoding works in both directions
 +#(for a request and the response)
 +class GzipServerTestCase(BaseServerTestCase):
 +    #a request handler that supports keep-alive and logs requests into a
 +    #class variable
 +    class RequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler):
 +        parentClass = xmlrpc.server.SimpleXMLRPCRequestHandler
 +        protocol_version = 'HTTP/1.1'
 +
 +        def do_POST(self):
 +            #store content of last request in class
 +            self.__class__.content_length = int(self.headers["content-length"])
 +            return self.parentClass.do_POST(self)
 +    requestHandler = RequestHandler
 +
 +    class Transport(xmlrpclib.Transport):
 +        #custom transport, stores the response length for our perusal
 +        fake_gzip = False
 +        def parse_response(self, response):
 +            self.response_length=int(response.getheader("content-length", 0))
 +            return xmlrpclib.Transport.parse_response(self, response)
 +
 +        def send_content(self, connection, body):
 +            if self.fake_gzip:
 +                #add a lone gzip header to induce decode error remotely
 +                connection.putheader("Content-Encoding", "gzip")
 +            return xmlrpclib.Transport.send_content(self, connection, body)
 +
 +    def setUp(self):
 +        BaseServerTestCase.setUp(self)
 +
 +    def test_gzip_request(self):
 +        t = self.Transport()
 +        t.encode_threshold = None
 +        p = xmlrpclib.ServerProxy(URL, transport=t)
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        a = self.RequestHandler.content_length
 +        t.encode_threshold = 0 #turn on request encoding
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        b = self.RequestHandler.content_length
 +        self.assertTrue(a>b)
 +        p("close")()
 +
 +    def test_bad_gzip_request(self):
 +        t = self.Transport()
 +        t.encode_threshold = None
 +        t.fake_gzip = True
 +        p = xmlrpclib.ServerProxy(URL, transport=t)
 +        cm = self.assertRaisesRegex(xmlrpclib.ProtocolError,
 +                                    re.compile(r"\b400\b"))
 +        with cm:
 +            p.pow(6, 8)
 +        p("close")()
 +
 +    def test_gsip_response(self):
 +        t = self.Transport()
 +        p = xmlrpclib.ServerProxy(URL, transport=t)
 +        old = self.requestHandler.encode_threshold
 +        self.requestHandler.encode_threshold = None #no encoding
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        a = t.response_length
 +        self.requestHandler.encode_threshold = 0 #always encode
 +        self.assertEqual(p.pow(6,8), 6**8)
 +        p("close")()
 +        b = t.response_length
 +        self.requestHandler.encode_threshold = old
 +        self.assertTrue(a>b)
 +
 +#Test special attributes of the ServerProxy object
 +class ServerProxyTestCase(unittest.TestCase):
 +    def setUp(self):
 +        unittest.TestCase.setUp(self)
 +        if threading:
 +            self.url = URL
 +        else:
 +            # Without threading, http_server() and http_multi_server() will not
 +            # be executed and URL is still equal to None. 'http://' is a just
 +            # enough to choose the scheme (HTTP)
 +            self.url = 'http://'
 +
 +    def test_close(self):
 +        p = xmlrpclib.ServerProxy(self.url)
 +        self.assertEqual(p('close')(), None)
 +
 +    def test_transport(self):
 +        t = xmlrpclib.Transport()
 +        p = xmlrpclib.ServerProxy(self.url, transport=t)
 +        self.assertEqual(p('transport'), t)
 +
  # This is a contrived way to make a failure occur on the server side
  # in order to test the _send_traceback_header flag on the server
  class FailingMessageClass(http.client.HTTPMessage):
Simple merge
diff --cc Misc/NEWS
index 754baab6d631563d955d7c5b6fdc798ed871d0b1,6e9569733778e115a572270b68c639c8e0d20308..b9cd7644aa96373323b0cd9beba07805b4d5b3f5
+++ b/Misc/NEWS
@@@ -116,87 -13,9 +116,90 @@@ Core and Builtin
  Library
  -------
  
+ - Issue #14001: CVE-2012-0845: xmlrpc: Fix an endless loop in
+   SimpleXMLRPCServer upon malformed POST request.
 +- Issue #2489: pty.spawn could consume 100% cpu when it encountered an EOF.
 +
 +- Issue #13014: Fix a possible reference leak in SSLSocket.getpeercert().
 +
 +- Issue #13015: Fix a possible reference leak in defaultdict.__repr__.
 +  Patch by Suman Saha.
 +
 +- Issue #1326113: distutils' build_ext command --libraries option now
 +  correctly parses multiple values separated by whitespace or commas.
 +
 +- Issue #10287: nntplib now queries the server's CAPABILITIES first before
 +  sending MODE READER, and only sends it if not already in READER mode.
 +  Patch by Hynek Schlawack.
 +
 +- Issue #13979: A bug in ctypes.util.find_library that caused
 +  the wrong library name to be returned has been fixed.
 +
 +- Issue #13993: HTMLParser is now able to handle broken end tags when
 +  strict=False.
 +
 +- Issue #9750: Fix sqlite3.Connection.iterdump on tables and fields
 +  with a name that is a keyword or contains quotes. Patch by Marko
 +  Kohtala.
 +
 +- Issue #10287: nntplib now queries the server's CAPABILITIES again after
 +  authenticating (since the result may change, according to RFC 4643).
 +  Patch by Hynek Schlawack.
 +
 +- Issue #13989: Document that GzipFile does not support text mode, and give a
 +  more helpful error message when opened with an invalid mode string.
 +
 +- Issue #13590: On OS X 10.7 and 10.6 with Xcode 4.2, building
 +  Distutils-based packages with C extension modules may fail because
 +  Apple has removed gcc-4.2, the version used to build python.org
 +  64-bit/32-bit Pythons.  If the user does not explicitly override
 +  the default C compiler by setting the CC environment variable,
 +  Distutils will now attempt to compile extension modules with clang
 +  if gcc-4.2 is required but not found. Also as a convenience, if
 +  the user does explicitly set CC, substitute its value as the default
 +  compiler in the Distutils LDSHARED configuration variable for OS X.
 +  (Note, the python.org 32-bit-only Pythons use gcc-4.0 and the 10.4u
 +  SDK, neither of which are available in Xcode 4.  This change does not
 +  attempt to override settings to support their use with Xcode 4.)
 +
 +- Issue #13960: HTMLParser is now able to handle broken comments when
 +  strict=False.
 +
 +- Issue #9021: Add an introduction to the copy module documentation.
 +
 +- Issue #6005: Examples in the socket library documentation use sendall, where
 +  relevant, instead send method.
 +
 +- Issue #10811: Fix recursive usage of cursors. Instead of crashing,
 +  raise a ProgrammingError now.
 +
 +- Issue #10881: Fix test_site failure with OS X framework builds.
 +
 +- Issue #964437: Make IDLE help window non-modal.
 +  Patch by Guilherme Polo and Roger Serwy.
 +
 +- Issue #2945: Make the distutils upload command aware of bdist_rpm products.
 +
 +- Issue #13933: IDLE auto-complete did not work with some imported
 +  module, like hashlib.  (Patch by Roger Serwy)
 +
 +- Issue #13901: Prevent test_distutils failures on OS X with --enable-shared.
 +
 +- Issue #13676: Handle strings with embedded zeros correctly in sqlite3.
 +
 +- Issue #13506: Add '' to path for IDLE Shell when started and restarted with Restart Shell.
 +  Original patches by Marco Scataglini and Roger Serwy.
 +
 +- Issue #13848: open() and the FileIO constructor now check for NUL
 +  characters in the file name.  Patch by Hynek Schlawack.
 +
 +- Issue #13806: The size check in audioop decompression functions was too
 +  strict and could reject valid compressed data.  Patch by Oleg Plakhotnyuk.
 +
 +- Issue #13812: When a multiprocessing Process child raises an exception,
 +  flush stderr after printing the exception traceback.
 +
  - Issue #13885: CVE-2011-3389: the _ssl module would always disable the CBC
    IV attack countermeasure.