summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-06 08:28:16 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-06 08:28:16 +0100
commit83cd3bb631131a116f024cfacfd687e8c4855fd1 (patch)
treed8d75c1eef9a36ffd77cca825fd8be3df33342a3
parent96b6ad283bf63ab447a3a974a4cba8c04b01df01 (diff)
Bound what load_state will accept for an age
-rw-r--r--src/opentracker.c8
-rw-r--r--src/trackerlogic.c2
-rw-r--r--tests/integration/test_statefile.py40
3 files changed, 45 insertions, 5 deletions
diff --git a/src/opentracker.c b/src/opentracker.c
index 65fe382..e467ce6 100644
--- a/src/opentracker.c
+++ b/src/opentracker.c
@@ -300,7 +300,7 @@ void load_state(const char *const state_filename) {
300 FILE *state_filehandle; 300 FILE *state_filehandle;
301 char inbuf[512]; 301 char inbuf[512];
302 ot_hash infohash; 302 ot_hash infohash;
303 uint64_t base, downcount; 303 uint64_t age, downcount;
304 int consumed; 304 int consumed;
305 305
306 state_filehandle = fopen(state_filename, "r"); 306 state_filehandle = fopen(state_filename, "r");
@@ -318,12 +318,12 @@ void load_state(const char *const state_filename) {
318 continue; 318 continue;
319 i = 2 * sizeof(ot_hash); 319 i = 2 * sizeof(ot_hash);
320 320
321 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX))) 321 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &age, OT_TORRENT_TIMEOUT)))
322 continue; 322 continue;
323 i += consumed; 323 i += consumed;
324 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX))) 324 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, SIZE_MAX)))
325 continue; 325 continue;
326 add_torrent_from_saved_state(infohash, base, downcount); 326 add_torrent_from_saved_state(infohash, (ot_time)age, (size_t)downcount);
327 } 327 }
328 328
329 fclose(state_filehandle); 329 fclose(state_filehandle);
diff --git a/src/trackerlogic.c b/src/trackerlogic.c
index 2313379..9fd6114 100644
--- a/src/trackerlogic.c
+++ b/src/trackerlogic.c
@@ -45,7 +45,7 @@ void add_torrent_from_saved_state(ot_hash const hash, ot_time age, size_t down_c
45 ot_torrent *torrent; 45 ot_torrent *torrent;
46 ot_vector *torrents_list; 46 ot_vector *torrents_list;
47 47
48 if (age > OT_TORRENT_TIMEOUT) 48 if (age < 0 || age > OT_TORRENT_TIMEOUT)
49 return; 49 return;
50 50
51 torrents_list = mutex_bucket_lock_by_hash(hash); 51 torrents_list = mutex_bucket_lock_by_hash(hash);
diff --git a/tests/integration/test_statefile.py b/tests/integration/test_statefile.py
index 9f6db01..21eefae 100644
--- a/tests/integration/test_statefile.py
+++ b/tests/integration/test_statefile.py
@@ -52,3 +52,43 @@ class StateFileRoundTrip(TrackerTestCase):
52 restarted.stop() 52 restarted.stop()
53 finally: 53 finally:
54 restarted._cleanup() 54 restarted._cleanup()
55
56
57class StateFileWithNonsenseAges(TrackerTestCase):
58 """The age column is read as unsigned and handed on as a signed ot_time.
59 A value at or above 2^63 arrived negative, cleared the timeout check, and
60 put the torrent's base in the future, where the cleaner never reaches it.
61 At exactly 2^63 the subtraction itself overflowed."""
62
63 AGES = {
64 "overflow": 2**63,
65 "wrapped": 2**63 + 60 * 24,
66 "minus-one": 2**64 - 1,
67 "int64-max": 2**63 - 1,
68 "past-timeout": 60 * 24 + 1,
69 }
70
71 def test_no_age_outside_the_timeout_builds_a_torrent(self):
72 lines = ["%s:%d:7" % (self.hash_for(name).hex(), age) for name, age in self.AGES.items()]
73 lines.append("%s:5:7" % self.hash_for("honest").hex())
74
75 path = os.path.join(self.tracker.dir, "nonsense")
76 with open(path, "w") as handle:
77 handle.write("\n".join(lines) + "\n")
78
79 restarted = Tracker(extra_args=["-l", path])
80 try:
81 for name in self.AGES:
82 self.assertNotIn(self.hash_for(name), restarted.scrape(self.hash_for(name))[b"files"],
83 "the line with age %d was loaded" % self.AGES[name])
84 self.assertIn(self.hash_for("honest"), restarted.scrape(self.hash_for("honest"))[b"files"],
85 "the state file was not read at all")
86 self.assertEqual(restarted.torrent_count(), 1)
87 complaints = restarted.stop()
88 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
89 finally:
90 restarted._cleanup()
91
92
93if __name__ == "__main__":
94 unittest.main()