From: Charles-François Natali Date: Sat, 18 Feb 2012 13:53:41 +0000 (+0100) Subject: Issue #14001: CVE-2012-0845: xmlrpc: Fix an endless loop in SimpleXMLRPCServer X-Git-Tag: v3.2.3rc1~43 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=cd96b4f1ff4dd3a97eedbcea8a837388c0cb8345;p=python Issue #14001: CVE-2012-0845: xmlrpc: Fix an endless loop in SimpleXMLRPCServer upon malformed POST request. --- cd96b4f1ff4dd3a97eedbcea8a837388c0cb8345 diff --cc Lib/test/test_xmlrpc.py index e97420b8a8,9833f2674f..38141912a1 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@@ -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): diff --cc Misc/NEWS index 754baab6d6,6e95697337..b9cd7644aa --- a/Misc/NEWS +++ 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.