From 10157c3b5052ec59baa4044f9e8234d198a8c1bc Mon Sep 17 00:00:00 2001 From: Martin Vrachev Date: Thu, 8 Oct 2020 12:24:04 +0300 Subject: [PATCH] Remove test_slow_retrieval expected failure test Remove the test with mode 2 ('mode_2': During the download process, the server blocks the download by sending just several characters every few seconds.) from test_slow_retrieval. This test is marked as "expected failure" with the purpose of rewriting it one day, but slow retrievals have been removed from the specification and soon it will be removed from the tuf reference implementation as a whole. That means that the chances of making this test useful are close to 0 if not none. The other test (with mode 1) in test_slow_retrieval is not removed. For reference: - https://github.com/theupdateframework/specification/pull/111 - https://github.com/theupdateframework/tuf/pull/1156 Signed-off-by: Martin Vrachev --- tests/slow_retrieval_server.py | 42 ++------- tests/test_slow_retrieval_attack.py | 132 +++++++--------------------- 2 files changed, 38 insertions(+), 136 deletions(-) diff --git a/tests/slow_retrieval_server.py b/tests/slow_retrieval_server.py index 61e5c4745a..e2c525d2da 100755 --- a/tests/slow_retrieval_server.py +++ b/tests/slow_retrieval_server.py @@ -37,14 +37,6 @@ import six -# Modify the HTTPServer class to pass the 'test_mode' argument to -# do_GET() function. -class HTTPServer_Test(six.moves.BaseHTTPServer.HTTPServer): - def __init__(self, server_address, Handler, test_mode): - six.moves.BaseHTTPServer.HTTPServer.__init__(self, server_address, Handler) - self.test_mode = test_mode - - # HTTP request handler. class Handler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler): @@ -62,38 +54,18 @@ def do_GET(self): self.send_header('Content-length', str(len(data))) self.end_headers() - if self.server.test_mode == 'mode_1': - # Before sending any data, the server does nothing for a long time. - DELAY = 40 - time.sleep(DELAY) - self.wfile.write(data) - - return - - # 'mode_2' - else: - DELAY = 1 - # Throttle the file by sending a character every DELAY seconds. - for i in range(len(data)): - self.wfile.write(data[i].encode('utf-8')) - time.sleep(DELAY) - - return + # Before sending any data, the server does nothing for a long time. + DELAY = 40 + time.sleep(DELAY) + self.wfile.write((data.encode('utf-8'))) except IOError as e: self.send_error(404, 'File Not Found!') -def run(port, test_mode): - server_address = ('localhost', port) - httpd = HTTPServer_Test(server_address, Handler, test_mode) - httpd.handle_request() - - - if __name__ == '__main__': port = int(sys.argv[1]) - test_mode = sys.argv[2] - assert test_mode in ('mode_1', 'mode_2') - run(port, test_mode) + server_address = ('localhost', port) + httpd = six.moves.BaseHTTPServer.HTTPServer(server_address, Handler) + httpd.handle_request() diff --git a/tests/test_slow_retrieval_attack.py b/tests/test_slow_retrieval_attack.py index 4407b59ffc..0fbec8bd85 100755 --- a/tests/test_slow_retrieval_attack.py +++ b/tests/test_slow_retrieval_attack.py @@ -68,55 +68,8 @@ repo_tool.disable_console_log_messages() -class TestSlowRetrievalAttack(unittest_toolbox.Modified_TestCase): - - @classmethod - def setUpClass(cls): - # Create a temporary directory to store the repository, metadata, and target - # files. 'temporary_directory' must be deleted in TearDownModule() so that - # temporary files are always removed, even when exceptions occur. - cls.temporary_directory = tempfile.mkdtemp(dir=os.getcwd()) - cls.SERVER_PORT = random.randint(30000, 45000) - - - - @classmethod - def tearDownClass(cls): - # Remove the temporary repository directory, which should contain all the - # metadata, targets, and key files generated of all the test cases. - shutil.rmtree(cls.temporary_directory) - - - - def _start_slow_server(self, mode): - # Launch a SimpleHTTPServer (serves files in the current directory). - # Test cases will request metadata and target files that have been - # pre-generated in 'tuf/tests/repository_data', which will be served by the - # SimpleHTTPServer launched here. The test cases of this unit test assume - # the pre-generated metadata files have a specific structure, such - # as a delegated role 'targets/role1', three target files, five key files, - # etc. - self.server_process_handler = utils.TestServerProcess(log=logger, - server='slow_retrieval_server.py', port=self.SERVER_PORT, - timeout=0, extra_cmd_args=[mode]) - - logger.info('Slow Retrieval Server process started.') - - # NOTE: Following error is raised if a delay is not long enough: - # - # or, on Windows: - # Failed to establish a new connection: [Errno 111] Connection refused' - # 1s led to occasional failures in automated builds on AppVeyor, so - # increasing this to 3s, sadly. - time.sleep(3) - - - - def _stop_slow_server(self): - # Logs stdout and stderr from the server subprocess and then it - # kills it and closes the temp file used for logging. - self.server_process_handler.clean() +class TestSlowRetrieval(unittest_toolbox.Modified_TestCase): def setUp(self): # We are inheriting from custom class. @@ -124,6 +77,11 @@ def setUp(self): self.repository_name = 'test_repository1' + # Create a temporary directory to store the repository, metadata, and target + # files. 'temporary_directory' must be deleted in TearDownModule() so that + # temporary files are always removed, even when exceptions occur. + self.temporary_directory = tempfile.mkdtemp(dir=os.getcwd()) + # Copy the original repository files provided in the test folder so that # any modifications made to repository files are restricted to the copies. # The 'repository_data' directory is expected to exist in 'tuf/tests/'. @@ -209,8 +167,22 @@ def setUp(self): # Set the url prefix required by the 'tuf/client/updater.py' updater. # 'path/to/tmp/repository' -> 'localhost:8001/tmp/repository'. repository_basepath = self.repository_directory[len(os.getcwd()):] - url_prefix = \ - 'http://localhost:' + str(self.SERVER_PORT) + repository_basepath + + self.server_process_handler = utils.TestServerProcess(log=logger, + server='slow_retrieval_server.py', timeout=0) + + logger.info('Slow Retrieval Server process started.') + + # NOTE: Following error is raised if a delay is not long enough: + # + # or, on Windows: + # Failed to establish a new connection: [Errno 111] Connection refused' + # 1s led to occasional failures in automated builds on AppVeyor, so + # increasing this to 3s, sadly. + time.sleep(3) + + url_prefix = 'http://localhost:' \ + + str(self.server_process_handler.port) + repository_basepath # Setting 'tuf.settings.repository_directory' with the temporary client # directory copied from the original repository files. @@ -233,16 +205,21 @@ def tearDown(self): tuf.roledb.clear_roledb(clear_all=True) tuf.keydb.clear_keydb(clear_all=True) + # Logs stdout and stderr from the server subprocess and then it + # kills it and closes the temp file used for logging. + self.server_process_handler.clean() + # Remove the temporary repository directory, which should contain all the + # metadata, targets, and key files generated of all the test cases. + shutil.rmtree(self.temporary_directory) - def test_with_tuf_mode_1(self): + + def test_delay_before_send(self): # Simulate a slow retrieval attack. - # 'mode_1': When download begins,the server blocks the download for a long + # When download begins,the server blocks the download for a long # time by doing nothing before it sends the first byte of data. - self._start_slow_server('mode_1') - # Verify that the TUF client detects replayed metadata and refuses to # continue the update process. client_filepath = os.path.join(self.client_directory, 'file1.txt') @@ -264,53 +241,6 @@ def test_with_tuf_mode_1(self): else: self.fail('TUF did not prevent a slow retrieval attack.') - finally: - self._stop_slow_server() - - - - # The following test fails as a result of a change to TUF's download code. - # Rather than constructing urllib2 requests, we now use the requests library. - # This solves an HTTPS proxy issue, but has for the moment deprived us of a - # way to prevent certain this kind of slow retrieval attack. - # See conversation in PR: https://github.com/theupdateframework/tuf/pull/781 - # TODO: Update download code to resolve the slow retrieval vulnerability. - @unittest.expectedFailure - def test_with_tuf_mode_2(self): - # Simulate a slow retrieval attack. - # 'mode_2': During the download process, the server blocks the download - # by sending just several characters every few seconds. - - self._start_slow_server('mode_2') - client_filepath = os.path.join(self.client_directory, 'file1.txt') - original_average_download_speed = tuf.settings.MIN_AVERAGE_DOWNLOAD_SPEED - tuf.settings.MIN_AVERAGE_DOWNLOAD_SPEED = 3 - - try: - file1_target = self.repository_updater.get_one_valid_targetinfo('file1.txt') - self.repository_updater.download_target(file1_target, self.client_directory) - - # Verify that the specific 'tuf.exceptions.SlowRetrievalError' exception is - # raised by each mirror. 'file1.txt' should be large enough to trigger a - # slow retrieval attack, otherwise the expected exception may not be - # consistently raised. - except tuf.exceptions.NoWorkingMirrorError as exception: - for mirror_url, mirror_error in six.iteritems(exception.mirror_errors): - url_prefix = self.repository_mirrors['mirror1']['url_prefix'] - url_file = os.path.join(url_prefix, 'targets', 'file1.txt') - - # Verify that 'file1.txt' is the culprit. - self.assertEqual(url_file.replace('\\', '/'), mirror_url) - self.assertTrue(isinstance(mirror_error, tuf.exceptions.SlowRetrievalError)) - - else: - # Another possibility is to check for a successfully downloaded - # 'file1.txt' at this point. - self.fail('TUF did not prevent a slow retrieval attack.') - - finally: - self._stop_slow_server() - tuf.settings.MIN_AVERAGE_DOWNLOAD_SPEED = original_average_download_speed if __name__ == '__main__':