From 79f4110365fe85f4377c50f0893134a8d5e45575 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Fri, 30 Oct 2026 01:00:52 +0100 Subject: Consolidate all tcp/http tear down into a single http_close function to prevent a rare UAF for expiring connections --- tests/integration/test_expiry.py | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/integration/test_expiry.py (limited to 'tests/integration/test_expiry.py') diff --git a/tests/integration/test_expiry.py b/tests/integration/test_expiry.py new file mode 100644 index 0000000..4466724 --- /dev/null +++ b/tests/integration/test_expiry.py @@ -0,0 +1,42 @@ +"""What the expiry sweep finds after a connection has already gone. + +A plain request is answered and the connection closed by http_senddata, not +by the sweep. If that path leaves the descriptor's slot behind, the sweep +walks into it once the client timeout has passed and frees a cookie that is +already gone. Under the sanitizer build that is reported, and teardown turns +it into a failure. + +The two numbers below mirror OT_CLIENT_TIMEOUT and +OT_CLIENT_TIMEOUT_CHECKINTERVAL in FEATURES_integration. +""" + +import time +import unittest + +from harness import TrackerTestCase + +CLIENT_TIMEOUT = 5 +CHECK_INTERVAL = 1 + + +class ExpirySweep(TrackerTestCase): + def test_an_answered_request_leaves_nothing_behind(self): + info_hash = self.hash_for("expiry-sweep") + self.assertEqual(self.tracker.announce(info_hash)[b"complete"], 1) + + # Long enough for the slot to expire and for a sweep to visit it + time.sleep(CLIENT_TIMEOUT + 3 * CHECK_INTERVAL) + + self.assertEqual(self.tracker.torrent_count(), 1, "the tracker stopped serving while sweeping") + + def test_a_connection_that_never_speaks_is_swept(self): + """The other side of it: a slot that really is live must still go.""" + import socket + + idle = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5) + try: + time.sleep(CLIENT_TIMEOUT + 3 * CHECK_INTERVAL) + idle.settimeout(5) + self.assertEqual(idle.recv(64), b"", "the tracker held a connection that never sent a request") + finally: + idle.close() -- cgit v1.3