diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-11-06 08:28:16 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-11-06 08:28:16 +0100 |
| commit | 83cd3bb631131a116f024cfacfd687e8c4855fd1 (patch) | |
| tree | d8d75c1eef9a36ffd77cca825fd8be3df33342a3 | |
| parent | 96b6ad283bf63ab447a3a974a4cba8c04b01df01 (diff) | |
Bound what load_state will accept for an age
| -rw-r--r-- | src/opentracker.c | 8 | ||||
| -rw-r--r-- | src/trackerlogic.c | 2 | ||||
| -rw-r--r-- | tests/integration/test_statefile.py | 40 |
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 | |||
| 57 | class 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 | |||
| 93 | if __name__ == "__main__": | ||
| 94 | unittest.main() | ||
