Skip to content

Commit 88d7bd1

Browse files
touilleManpgjones
authored andcommitted
Add early detection of invalid http data when request line starts with binary
1 parent 889a564 commit 88d7bd1

File tree

4 files changed

+54
-0
lines changed

4 files changed

+54
-0
lines changed

h11/_readers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ def _decode_header_lines(lines):
6464
def maybe_read_from_IDLE_client(buf):
6565
lines = buf.maybe_extract_lines()
6666
if lines is None:
67+
if buf.is_next_line_obviously_invalid_request_line():
68+
raise LocalProtocolError("illegal request line")
6769
return None
6870
if not lines:
6971
raise LocalProtocolError("no request line received")
@@ -81,6 +83,8 @@ def maybe_read_from_IDLE_client(buf):
8183
def maybe_read_from_SEND_RESPONSE_server(buf):
8284
lines = buf.maybe_extract_lines()
8385
if lines is None:
86+
if buf.is_next_line_obviously_invalid_request_line():
87+
raise LocalProtocolError("illegal request line")
8488
return None
8589
if not lines:
8690
raise LocalProtocolError("no response line received")

h11/_receivebuffer.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,20 @@ def maybe_extract_lines(self):
133133
del lines[-2:]
134134

135135
return lines
136+
137+
# In theory we should wait until `\r\n` before starting to validate
138+
# incoming data. However it's interesting to detect (very) invalid data
139+
# early given they might not even contain `\r\n` at all (hence only
140+
# timeout will get rid of them).
141+
# This is not a 100% effective detection but more of a cheap sanity check
142+
# allowing for early abort in some useful cases.
143+
# This is especially interesting when peer is messing up with HTTPS and
144+
# sent us a TLS stream where we were expecting plain HTTP given all
145+
# versions of TLS so far start handshake with a 0x16 message type code.
146+
def is_next_line_obviously_invalid_request_line(self):
147+
try:
148+
# HTTP header line must not contain non-printable characters
149+
# and should not start with a space
150+
return self._data[0] < 0x21
151+
except IndexError:
152+
return False

h11/tests/test_connection.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,38 @@ def test_empty_response():
969969
c.next_event()
970970

971971

972+
@pytest.mark.parametrize(
973+
"data",
974+
[
975+
b"\x00",
976+
b"\x20",
977+
b"\x16\x03\x01\x00\xa5", # Typical start of a TLS Client Hello
978+
],
979+
)
980+
def test_early_detection_of_invalid_request(data):
981+
c = Connection(SERVER)
982+
# Early detection should occur before even receiving a `\r\n`
983+
c.receive_data(data)
984+
with pytest.raises(RemoteProtocolError):
985+
c.next_event()
986+
987+
988+
@pytest.mark.parametrize(
989+
"data",
990+
[
991+
b"\x00",
992+
b"\x20",
993+
b"\x16\x03\x03\x00\x31", # Typical start of a TLS Server Hello
994+
],
995+
)
996+
def test_early_detection_of_invalid_response(data):
997+
c = Connection(CLIENT)
998+
# Early detection should occur before even receiving a `\r\n`
999+
c.receive_data(data)
1000+
with pytest.raises(RemoteProtocolError):
1001+
c.next_event()
1002+
1003+
9721004
# This used to give different headers for HEAD and GET.
9731005
# The correct way to handle HEAD is to put whatever headers we *would* have
9741006
# put if it were a GET -- even though we know that for HEAD, those headers

newsfragments/122.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add early detection of invalid http data when request line starts with binary

0 commit comments

Comments
 (0)