summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-11-06 21:17:54 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-11-06 21:17:54 +0100
commita29e606b6f97c00d0b7e80cb0592bca54f92f2c9 (patch)
tree3907ef58492c459bd64e1381117f16083c4968cf
parent1d7ed6b295a5a9509d8194ce2a03255f0fb5a5fc (diff)
Fix a theoretical bug where a short request packet yields an oob read
-rw-r--r--src/ot_http.c6
-rw-r--r--tests/integration/test_keepalive.py16
2 files changed, 19 insertions, 3 deletions
diff --git a/src/ot_http.c b/src/ot_http.c
index fc49333..1b5281c 100644
--- a/src/ot_http.c
+++ b/src/ot_http.c
@@ -762,8 +762,8 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
762 } 762 }
763 } 763 }
764 764
765 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */ 765 /* The request need not be NUL terminated and can be shorter than the literal */
766 if (memcmp(read_ptr, "GET /", 5)) 766 if (ws->request_size < 5 || memcmp(read_ptr, "GET /", 5))
767 HTTPERROR_400; 767 HTTPERROR_400;
768 768
769 /* Skip leading '/' */ 769 /* Skip leading '/' */
@@ -786,7 +786,7 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
786 if ((*write_ptr == 'a') || (*write_ptr == '?')) 786 if ((*write_ptr == 'a') || (*write_ptr == '?'))
787 http_handle_announce(sock, ws, read_ptr); 787 http_handle_announce(sock, ws, read_ptr);
788#ifdef WANT_FULLSCRAPE 788#ifdef WANT_FULLSCRAPE
789 else if (!memcmp(write_ptr, "scrape HTTP/", 12)) 789 else if (ws->request + ws->request_size - write_ptr >= 12 && !memcmp(write_ptr, "scrape HTTP/", 12))
790 http_handle_fullscrape(sock, ws); 790 http_handle_fullscrape(sock, ws);
791#endif 791#endif
792 /* This is the hardcore match for scrape */ 792 /* This is the hardcore match for scrape */
diff --git a/tests/integration/test_keepalive.py b/tests/integration/test_keepalive.py
index 2c724fc..f9aa468 100644
--- a/tests/integration/test_keepalive.py
+++ b/tests/integration/test_keepalive.py
@@ -107,6 +107,22 @@ class KeepAlive(TrackerTestCase):
107 finally: 107 finally:
108 c.close() 108 c.close()
109 109
110 def test_a_tiny_split_request_does_not_overread(self):
111 """A request that arrives in pieces and completes as a buffer shorter
112 than the dispatch literals (\"GET /\" etc.) used to read past the
113 exactly sized buffer. It must be rejected without a sanitizer hit."""
114 c = self.tracker.connect()
115 try:
116 c.sock.sendall(b"\r\n")
117 time.sleep(0.05)
118 c.sock.sendall(b"\r\n") # completes as a 4-byte buffer
119 time.sleep(0.1)
120 finally:
121 c.close()
122 # the tracker must still answer; tearDown's stop() surfaces any sanitizer abort
123 answer = self.tracker.announce(self.hash_for("after-tiny"), port=6881, left=0)
124 self.assertNotIn(b"failure reason", answer, "the tracker stopped serving after a tiny request")
125
110 def test_a_split_request_answered_by_a_worker_does_not_spin(self): 126 def test_a_split_request_answered_by_a_worker_does_not_spin(self):
111 """A reply that a worker produces does not go through the send path, 127 """A reply that a worker produces does not go through the send path,
112 so the request stays in the buffer the read loop is reading. The loop 128 so the request stays in the buffer the read loop is reading. The loop