Skip to content

Commit

Permalink
Remove test_slow_retrieval expected failure test
Browse files Browse the repository at this point in the history
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:
- theupdateframework/specification#111
- theupdateframework#1156

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
  • Loading branch information
MVrachev committed Nov 4, 2020
1 parent a88a5bd commit 304b2ae
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 136 deletions.
42 changes: 7 additions & 35 deletions tests/slow_retrieval_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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()
132 changes: 31 additions & 101 deletions tests/test_slow_retrieval_attack.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,62 +68,20 @@
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:
# <urlopen error [Errno 111] Connection refused>
# 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.
unittest_toolbox.Modified_TestCase.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/'.
Expand Down Expand Up @@ -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:
# <urlopen error [Errno 111] Connection refused>
# 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.
Expand All @@ -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')
Expand All @@ -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__':
Expand Down

0 comments on commit 304b2ae

Please sign in to comment.