mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 00:01:36 +00:00
[sans-io] extend HTTP/2 fuzzing, fix found bugs
This commit is contained in:
parent
0549d50a47
commit
34d96da876
@ -1,4 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
|
from enum import Enum
|
||||||
from typing import ClassVar, Dict, Iterable, List, Optional, Set, Tuple, Type, Union
|
from typing import ClassVar, Dict, Iterable, List, Optional, Set, Tuple, Type, Union
|
||||||
|
|
||||||
import h2.connection
|
import h2.connection
|
||||||
@ -23,6 +24,11 @@ from ...events import ConnectionClosed, DataReceived, Event, Start
|
|||||||
from ...layer import CommandGenerator
|
from ...layer import CommandGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class StreamState(Enum):
|
||||||
|
EXPECTING_HEADERS = 1
|
||||||
|
HEADERS_RECEIVED = 2
|
||||||
|
|
||||||
|
|
||||||
class Http2Connection(HttpConnection):
|
class Http2Connection(HttpConnection):
|
||||||
h2_conf: ClassVar[h2.config.H2Configuration]
|
h2_conf: ClassVar[h2.config.H2Configuration]
|
||||||
h2_conf_defaults = dict(
|
h2_conf_defaults = dict(
|
||||||
@ -34,7 +40,7 @@ class Http2Connection(HttpConnection):
|
|||||||
# logger=H2ConnectionLogger("server")
|
# logger=H2ConnectionLogger("server")
|
||||||
)
|
)
|
||||||
h2_conn: BufferedH2Connection
|
h2_conn: BufferedH2Connection
|
||||||
active_stream_ids: Set[int]
|
streams: Dict[int, StreamState]
|
||||||
"""keep track of all active stream ids to send protocol errors on teardown"""
|
"""keep track of all active stream ids to send protocol errors on teardown"""
|
||||||
|
|
||||||
ReceiveProtocolError: Type[Union[RequestProtocolError, ResponseProtocolError]]
|
ReceiveProtocolError: Type[Union[RequestProtocolError, ResponseProtocolError]]
|
||||||
@ -47,7 +53,7 @@ class Http2Connection(HttpConnection):
|
|||||||
def __init__(self, context: Context, conn: Connection):
|
def __init__(self, context: Context, conn: Connection):
|
||||||
super().__init__(context, conn)
|
super().__init__(context, conn)
|
||||||
self.h2_conn = BufferedH2Connection(self.h2_conf)
|
self.h2_conn = BufferedH2Connection(self.h2_conf)
|
||||||
self.active_stream_ids = set()
|
self.streams = {}
|
||||||
|
|
||||||
def _handle_event(self, event: Event) -> CommandGenerator[None]:
|
def _handle_event(self, event: Event) -> CommandGenerator[None]:
|
||||||
if isinstance(event, Start):
|
if isinstance(event, Start):
|
||||||
@ -67,7 +73,13 @@ class Http2Connection(HttpConnection):
|
|||||||
|
|
||||||
elif isinstance(event, DataReceived):
|
elif isinstance(event, DataReceived):
|
||||||
try:
|
try:
|
||||||
events = self.h2_conn.receive_data(event.data)
|
try:
|
||||||
|
events = self.h2_conn.receive_data(event.data)
|
||||||
|
except ValueError as e: # pragma: no cover
|
||||||
|
# this should never raise a ValueError, but we triggered one while fuzzing:
|
||||||
|
# https://github.com/python-hyper/hyper-h2/issues/1231
|
||||||
|
# this stays here as defense-in-depth.
|
||||||
|
raise h2.exceptions.ProtocolError(f"uncaught hyper-h2 error: {e}") from e
|
||||||
except h2.exceptions.ProtocolError as e:
|
except h2.exceptions.ProtocolError as e:
|
||||||
events = [e]
|
events = [e]
|
||||||
|
|
||||||
@ -80,38 +92,47 @@ class Http2Connection(HttpConnection):
|
|||||||
yield SendData(self.conn, data_to_send)
|
yield SendData(self.conn, data_to_send)
|
||||||
|
|
||||||
elif isinstance(event, ConnectionClosed):
|
elif isinstance(event, ConnectionClosed):
|
||||||
yield from self._unexpected_close("peer closed connection")
|
yield from self.close_connection("peer closed connection")
|
||||||
else:
|
else:
|
||||||
raise AssertionError(f"Unexpected event: {event!r}")
|
raise AssertionError(f"Unexpected event: {event!r}")
|
||||||
|
|
||||||
|
# noinspection PyArgumentList
|
||||||
def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]:
|
def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]:
|
||||||
"""returns true if further processing should be stopped."""
|
"""returns true if further processing should be stopped."""
|
||||||
if isinstance(event, h2.events.DataReceived):
|
if isinstance(event, h2.events.DataReceived):
|
||||||
if event.stream_id in self.active_stream_ids:
|
state = self.streams.get(event.stream_id, None)
|
||||||
# noinspection PyArgumentList
|
if state is StreamState.HEADERS_RECEIVED:
|
||||||
yield ReceiveHttp(self.ReceiveData(event.stream_id, event.data))
|
yield ReceiveHttp(self.ReceiveData(event.stream_id, event.data))
|
||||||
|
elif state is StreamState.EXPECTING_HEADERS:
|
||||||
|
yield from self.protocol_error(f"Received HTTP/2 data frame, expected headers.")
|
||||||
|
return True
|
||||||
self.h2_conn.acknowledge_received_data(event.flow_controlled_length, event.stream_id)
|
self.h2_conn.acknowledge_received_data(event.flow_controlled_length, event.stream_id)
|
||||||
elif isinstance(event, h2.events.StreamEnded):
|
elif isinstance(event, h2.events.StreamEnded):
|
||||||
if event.stream_id in self.active_stream_ids:
|
state = self.streams.get(event.stream_id, None)
|
||||||
# noinspection PyArgumentList
|
if state is StreamState.HEADERS_RECEIVED:
|
||||||
yield ReceiveHttp(self.ReceiveEndOfMessage(event.stream_id))
|
yield ReceiveHttp(self.ReceiveEndOfMessage(event.stream_id))
|
||||||
self.active_stream_ids.remove(event.stream_id)
|
elif state is StreamState.EXPECTING_HEADERS:
|
||||||
|
raise AssertionError("unreachable")
|
||||||
|
self.streams.pop(event.stream_id, None)
|
||||||
|
elif isinstance(event, h2.events.StreamReset):
|
||||||
|
if event.stream_id in self.streams:
|
||||||
|
yield ReceiveHttp(self.ReceiveProtocolError(event.stream_id, "Stream reset"))
|
||||||
elif isinstance(event, h2.exceptions.ProtocolError):
|
elif isinstance(event, h2.exceptions.ProtocolError):
|
||||||
yield from self._unexpected_close(f"HTTP/2 protocol error: {event}")
|
yield from self.protocol_error(f"HTTP/2 protocol error: {event}")
|
||||||
return True
|
return True
|
||||||
elif isinstance(event, h2.events.ConnectionTerminated):
|
elif isinstance(event, h2.events.ConnectionTerminated):
|
||||||
yield from self._unexpected_close(f"HTTP/2 connection closed: {event!r}")
|
yield from self.close_connection(f"HTTP/2 connection closed: {event!r}")
|
||||||
return True
|
return True
|
||||||
elif isinstance(event, h2.events.StreamReset):
|
|
||||||
if event.stream_id in self.active_stream_ids:
|
|
||||||
# noinspection PyArgumentList
|
|
||||||
yield ReceiveHttp(self.ReceiveProtocolError(event.stream_id, "Stream reset"))
|
|
||||||
elif isinstance(event, h2.events.RemoteSettingsChanged):
|
elif isinstance(event, h2.events.RemoteSettingsChanged):
|
||||||
pass
|
pass
|
||||||
elif isinstance(event, h2.events.SettingsAcknowledged):
|
elif isinstance(event, h2.events.SettingsAcknowledged):
|
||||||
pass
|
pass
|
||||||
elif isinstance(event, h2.events.PriorityUpdated):
|
elif isinstance(event, h2.events.PriorityUpdated):
|
||||||
pass
|
pass
|
||||||
|
elif isinstance(event, h2.events.TrailersReceived):
|
||||||
|
yield Log("Received HTTP/2 trailers, which are currently unimplemented and silently discarded", "error")
|
||||||
|
elif isinstance(event, h2.events.PushedStreamReceived):
|
||||||
|
yield Log("Received HTTP/2 push promise, even though we signalled no support.", "error")
|
||||||
elif isinstance(event, h2.events.UnknownFrameReceived):
|
elif isinstance(event, h2.events.UnknownFrameReceived):
|
||||||
# https://http2.github.io/http2-spec/#rfc.section.4.1
|
# https://http2.github.io/http2-spec/#rfc.section.4.1
|
||||||
# Implementations MUST ignore and discard any frame that has a type that is unknown.
|
# Implementations MUST ignore and discard any frame that has a type that is unknown.
|
||||||
@ -119,11 +140,21 @@ class Http2Connection(HttpConnection):
|
|||||||
else:
|
else:
|
||||||
raise AssertionError(f"Unexpected event: {event!r}")
|
raise AssertionError(f"Unexpected event: {event!r}")
|
||||||
|
|
||||||
def _unexpected_close(self, err: str) -> CommandGenerator[None]:
|
def protocol_error(
|
||||||
|
self,
|
||||||
|
message: str,
|
||||||
|
error_code: int = h2.errors.ErrorCodes.PROTOCOL_ERROR,
|
||||||
|
) -> CommandGenerator[None]:
|
||||||
|
yield Log(f"{human.format_address(self.conn.peername)}: {message}")
|
||||||
|
self.h2_conn.close_connection(error_code, message.encode())
|
||||||
|
yield SendData(self.conn, self.h2_conn.data_to_send())
|
||||||
|
yield from self.close_connection(message)
|
||||||
|
|
||||||
|
def close_connection(self, msg: str) -> CommandGenerator[None]:
|
||||||
yield CloseConnection(self.conn)
|
yield CloseConnection(self.conn)
|
||||||
for stream_id in self.active_stream_ids:
|
for stream_id in self.streams:
|
||||||
# noinspection PyArgumentList
|
# noinspection PyArgumentList
|
||||||
yield ReceiveHttp(self.ReceiveProtocolError(stream_id, err))
|
yield ReceiveHttp(self.ReceiveProtocolError(stream_id, msg))
|
||||||
|
|
||||||
|
|
||||||
def normalize_h1_headers(headers: List[Tuple[bytes, bytes]], is_client: bool) -> List[Tuple[bytes, bytes]]:
|
def normalize_h1_headers(headers: List[Tuple[bytes, bytes]], is_client: bool) -> List[Tuple[bytes, bytes]]:
|
||||||
@ -177,10 +208,8 @@ class Http2Server(Http2Connection):
|
|||||||
try:
|
try:
|
||||||
host, port, method, scheme, authority, path, headers = parse_h2_request_headers(event.headers)
|
host, port, method, scheme, authority, path, headers = parse_h2_request_headers(event.headers)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
yield Log(f"{human.format_address(self.conn.peername)}: {e}")
|
yield from self.protocol_error(f"Invalid HTTP/2 request headers: {e}")
|
||||||
self.h2_conn.reset_stream(event.stream_id, h2.errors.ErrorCodes.PROTOCOL_ERROR)
|
return True
|
||||||
yield SendData(self.conn, self.h2_conn.data_to_send())
|
|
||||||
return
|
|
||||||
request = http.HTTPRequest(
|
request = http.HTTPRequest(
|
||||||
host=host,
|
host=host,
|
||||||
port=port,
|
port=port,
|
||||||
@ -195,7 +224,7 @@ class Http2Server(Http2Connection):
|
|||||||
timestamp_start=time.time(),
|
timestamp_start=time.time(),
|
||||||
timestamp_end=None,
|
timestamp_end=None,
|
||||||
)
|
)
|
||||||
self.active_stream_ids.add(event.stream_id)
|
self.streams[event.stream_id] = StreamState.HEADERS_RECEIVED
|
||||||
yield ReceiveHttp(RequestHeaders(event.stream_id, request))
|
yield ReceiveHttp(RequestHeaders(event.stream_id, request))
|
||||||
else:
|
else:
|
||||||
return (yield from super().handle_h2_event(event))
|
return (yield from super().handle_h2_event(event))
|
||||||
@ -204,7 +233,7 @@ class Http2Server(Http2Connection):
|
|||||||
class Http2Client(Http2Connection):
|
class Http2Client(Http2Connection):
|
||||||
h2_conf = h2.config.H2Configuration(
|
h2_conf = h2.config.H2Configuration(
|
||||||
**Http2Connection.h2_conf_defaults,
|
**Http2Connection.h2_conf_defaults,
|
||||||
client_side = True,
|
client_side=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
ReceiveProtocolError = ResponseProtocolError
|
ReceiveProtocolError = ResponseProtocolError
|
||||||
@ -237,14 +266,23 @@ class Http2Client(Http2Connection):
|
|||||||
event.stream_id,
|
event.stream_id,
|
||||||
headers,
|
headers,
|
||||||
)
|
)
|
||||||
self.active_stream_ids.add(event.stream_id)
|
self.streams[event.stream_id] = StreamState.EXPECTING_HEADERS
|
||||||
yield SendData(self.conn, self.h2_conn.data_to_send())
|
yield SendData(self.conn, self.h2_conn.data_to_send())
|
||||||
else:
|
else:
|
||||||
yield from super()._handle_event(event)
|
yield from super()._handle_event(event)
|
||||||
|
|
||||||
def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]:
|
def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]:
|
||||||
if isinstance(event, h2.events.ResponseReceived):
|
if isinstance(event, h2.events.ResponseReceived):
|
||||||
status_code, headers = parse_h2_response_headers(event.headers)
|
if self.streams.get(event.stream_id, None) is not StreamState.EXPECTING_HEADERS:
|
||||||
|
yield from self.protocol_error(f"Received unexpected HTTP/2 response.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
status_code, headers = parse_h2_response_headers(event.headers)
|
||||||
|
except ValueError as e:
|
||||||
|
yield from self.protocol_error(f"Invalid HTTP/2 response headers: {e}")
|
||||||
|
return True
|
||||||
|
|
||||||
response = http.HTTPResponse(
|
response = http.HTTPResponse(
|
||||||
http_version=b"HTTP/2.0",
|
http_version=b"HTTP/2.0",
|
||||||
status_code=status_code,
|
status_code=status_code,
|
||||||
@ -255,7 +293,11 @@ class Http2Client(Http2Connection):
|
|||||||
timestamp_start=time.time(),
|
timestamp_start=time.time(),
|
||||||
timestamp_end=None,
|
timestamp_end=None,
|
||||||
)
|
)
|
||||||
|
self.streams[event.stream_id] = StreamState.HEADERS_RECEIVED
|
||||||
yield ReceiveHttp(ResponseHeaders(event.stream_id, response))
|
yield ReceiveHttp(ResponseHeaders(event.stream_id, response))
|
||||||
|
elif isinstance(event, h2.events.RequestReceived):
|
||||||
|
yield from self.protocol_error(f"HTTP/2 protocol error: received request from server")
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
return (yield from super().handle_h2_event(event))
|
return (yield from super().handle_h2_event(event))
|
||||||
|
|
||||||
@ -277,6 +319,7 @@ def split_pseudo_headers(h2_headers: Iterable[Tuple[bytes, bytes]]) -> Tuple[Dic
|
|||||||
|
|
||||||
return pseudo_headers, headers
|
return pseudo_headers, headers
|
||||||
|
|
||||||
|
|
||||||
def parse_h2_request_headers(
|
def parse_h2_request_headers(
|
||||||
h2_headers: Iterable[Tuple[bytes, bytes]]
|
h2_headers: Iterable[Tuple[bytes, bytes]]
|
||||||
) -> Tuple[str, int, bytes, bytes, bytes, bytes, net_http.Headers]:
|
) -> Tuple[str, int, bytes, bytes, bytes, bytes, net_http.Headers]:
|
||||||
|
@ -14,6 +14,7 @@ from mitmproxy.proxy2.commands import OpenConnection, SendData
|
|||||||
from mitmproxy.proxy2.events import DataReceived, Start
|
from mitmproxy.proxy2.events import DataReceived, Start
|
||||||
from mitmproxy.proxy2.layers import http
|
from mitmproxy.proxy2.layers import http
|
||||||
from test.mitmproxy.proxy2.layers.http.hyper_h2_test_helpers import FrameFactory
|
from test.mitmproxy.proxy2.layers.http.hyper_h2_test_helpers import FrameFactory
|
||||||
|
from test.mitmproxy.proxy2.layers.http.test_http2 import make_h2
|
||||||
from test.mitmproxy.proxy2.tutils import Placeholder, Playbook, reply
|
from test.mitmproxy.proxy2.tutils import Placeholder, Playbook, reply
|
||||||
|
|
||||||
settings.register_profile("fast", max_examples=10)
|
settings.register_profile("fast", max_examples=10)
|
||||||
@ -50,7 +51,10 @@ bodies = sampled_from([
|
|||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
def mutate(draw, data: bytes) -> bytes:
|
@composite
|
||||||
|
def mutations(draw, elements):
|
||||||
|
data = draw(elements)
|
||||||
|
|
||||||
cut_start = draw(integers(0, len(data)))
|
cut_start = draw(integers(0, len(data)))
|
||||||
cut_end = draw(integers(cut_start, len(data)))
|
cut_end = draw(integers(cut_start, len(data)))
|
||||||
data = data[:cut_start] + data[cut_end:]
|
data = data[:cut_start] + data[cut_end:]
|
||||||
@ -60,40 +64,42 @@ def mutate(draw, data: bytes) -> bytes:
|
|||||||
return data[:replace_start] + draw(binary()) + data[replace_end:]
|
return data[:replace_start] + draw(binary()) + data[replace_end:]
|
||||||
|
|
||||||
|
|
||||||
def split(draw, data: bytes) -> Iterable[bytes]:
|
@composite
|
||||||
|
def chunks(draw, elements):
|
||||||
|
data = draw(elements)
|
||||||
|
|
||||||
|
chunks = []
|
||||||
a, b = sorted([
|
a, b = sorted([
|
||||||
draw(integers(0, len(data))),
|
draw(integers(0, len(data))),
|
||||||
draw(integers(0, len(data)))
|
draw(integers(0, len(data)))
|
||||||
])
|
])
|
||||||
if a > 0:
|
if a > 0:
|
||||||
yield data[:a]
|
chunks.append(data[:a])
|
||||||
if a != b:
|
if a != b:
|
||||||
yield data[a:b]
|
chunks.append(data[a:b])
|
||||||
if b < len(data):
|
if b < len(data):
|
||||||
yield data[b:]
|
chunks.append(data[b:])
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
@composite
|
@composite
|
||||||
def fuzz_h1_request(draw):
|
def h1_requests(draw):
|
||||||
request = draw(request_lines) + b"\r\n"
|
request = draw(request_lines) + b"\r\n"
|
||||||
request += b"\r\n".join(draw(headers))
|
request += b"\r\n".join(draw(headers))
|
||||||
request += b"\r\n\r\n" + draw(bodies)
|
request += b"\r\n\r\n" + draw(bodies)
|
||||||
request = mutate(draw, request)
|
|
||||||
request = list(split(draw, request))
|
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
|
||||||
@composite
|
@composite
|
||||||
def fuzz_h1_response(draw):
|
def h2_responses(draw):
|
||||||
response = draw(response_lines) + b"\r\n"
|
response = draw(response_lines) + b"\r\n"
|
||||||
response += b"\r\n".join(draw(headers))
|
response += b"\r\n".join(draw(headers))
|
||||||
response += b"\r\n\r\n" + draw(bodies)
|
response += b"\r\n\r\n" + draw(bodies)
|
||||||
response = mutate(draw, response)
|
|
||||||
response = list(split(draw, response))
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@given(fuzz_h1_request())
|
@given(chunks(mutations(h1_requests())))
|
||||||
def test_fuzz_h1_request(data):
|
def test_fuzz_h1_request(data):
|
||||||
tctx = context.Context(context.Client(("client", 1234), ("127.0.0.1", 8080)), opts)
|
tctx = context.Context(context.Client(("client", 1234), ("127.0.0.1", 8080)), opts)
|
||||||
|
|
||||||
@ -105,7 +111,7 @@ def test_fuzz_h1_request(data):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@given(fuzz_h1_response())
|
@given(chunks(mutations(h2_responses())))
|
||||||
@example([b'0 OK\r\n\r\n', b'\r\n', b'5\r\n12345\r\n0\r\n\r\n'])
|
@example([b'0 OK\r\n\r\n', b'\r\n', b'5\r\n12345\r\n0\r\n\r\n'])
|
||||||
def test_fuzz_h1_response(data):
|
def test_fuzz_h1_response(data):
|
||||||
tctx = context.Context(context.Client(("client", 1234), ("127.0.0.1", 8080)), opts)
|
tctx = context.Context(context.Client(("client", 1234), ("127.0.0.1", 8080)), opts)
|
||||||
@ -127,10 +133,45 @@ h2_flags = sets(sampled_from([
|
|||||||
"END_STREAM",
|
"END_STREAM",
|
||||||
"END_HEADERS",
|
"END_HEADERS",
|
||||||
]))
|
]))
|
||||||
|
h2_stream_ids = integers(0, 3)
|
||||||
|
h2_stream_ids_nonzero = integers(1, 3)
|
||||||
|
|
||||||
|
|
||||||
def _h2_request_parts(draw):
|
@composite
|
||||||
|
def h2_headers(draw):
|
||||||
|
required_headers = [
|
||||||
|
[":path", '/'],
|
||||||
|
[":scheme", draw(sampled_from(['http', 'https']))],
|
||||||
|
[":method", draw(sampled_from(['GET', 'POST', 'CONNECT']))],
|
||||||
|
]
|
||||||
|
optional_headers = [
|
||||||
|
[":authority", draw(sampled_from(['example.com:443', 'example.com']))],
|
||||||
|
["cookie", "foobaz"],
|
||||||
|
["host", "example.com"],
|
||||||
|
["content-length", "42"],
|
||||||
|
]
|
||||||
|
headers = required_headers + draw(lists(sampled_from(optional_headers), max_size=3))
|
||||||
|
|
||||||
|
i = draw(integers(0, len(headers)))
|
||||||
|
p = int(draw(booleans()))
|
||||||
|
r = draw(text())
|
||||||
|
if i > 0:
|
||||||
|
headers[i - 1][p - 1] = r
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
@composite
|
||||||
|
def h2_frames(draw):
|
||||||
ff = FrameFactory()
|
ff = FrameFactory()
|
||||||
|
headers1 = ff.build_headers_frame(headers=draw(h2_headers()))
|
||||||
|
headers1.flags.clear()
|
||||||
|
headers1.flags |= draw(h2_flags)
|
||||||
|
headers2 = ff.build_headers_frame(headers=draw(h2_headers()),
|
||||||
|
depends_on=draw(h2_stream_ids),
|
||||||
|
stream_weight=draw(integers(0, 255)),
|
||||||
|
exclusive=draw(booleans()))
|
||||||
|
headers2.flags.clear()
|
||||||
|
headers2.flags |= draw(h2_flags)
|
||||||
settings = ff.build_settings_frame(
|
settings = ff.build_settings_frame(
|
||||||
settings=draw(dictionaries(
|
settings=draw(dictionaries(
|
||||||
keys=sampled_from(SettingCodes),
|
keys=sampled_from(SettingCodes),
|
||||||
@ -138,39 +179,32 @@ def _h2_request_parts(draw):
|
|||||||
max_size=5,
|
max_size=5,
|
||||||
)), ack=draw(booleans())
|
)), ack=draw(booleans())
|
||||||
)
|
)
|
||||||
headers = ff.build_headers_frame(
|
continuation = ff.build_continuation_frame(header_block=ff.encoder.encode(draw(h2_headers())), flags=draw(h2_flags))
|
||||||
headers=draw(permutations([
|
goaway = ff.build_goaway_frame(draw(h2_stream_ids))
|
||||||
(':authority', draw(sampled_from(['example.com', 'example.com:443', draw(text())]))),
|
push_promise = ff.build_push_promise_frame(
|
||||||
(':path', draw(sampled_from(['/', draw(text())]))),
|
stream_id=draw(h2_stream_ids_nonzero),
|
||||||
(':scheme', draw(sampled_from(['http', 'https', draw(text())]))),
|
promised_stream_id=draw(h2_stream_ids),
|
||||||
(':method', draw(sampled_from(['GET', 'POST', 'CONNECT', draw(text())]))),
|
headers=draw(h2_headers())
|
||||||
('cookie', draw(text())),
|
|
||||||
('host', draw(text())),
|
|
||||||
('content-length', draw(text()))
|
|
||||||
]))
|
|
||||||
)
|
)
|
||||||
headers.flags.clear()
|
rst = ff.build_rst_stream_frame(draw(h2_stream_ids_nonzero))
|
||||||
headers.flags |= draw(h2_flags)
|
prio = ff.build_priority_frame(
|
||||||
data = ff.build_data_frame(
|
stream_id=draw(h2_stream_ids_nonzero),
|
||||||
|
weight=draw(integers(0, 255)),
|
||||||
|
depends_on=draw(h2_stream_ids),
|
||||||
|
exclusive=draw(booleans()),
|
||||||
|
)
|
||||||
|
data1 = ff.build_data_frame(
|
||||||
draw(binary()), draw(h2_flags)
|
draw(binary()), draw(h2_flags)
|
||||||
)
|
)
|
||||||
window_update = ff.build_window_update_frame(draw(sampled_from([1, 0, 2])), draw(integers(0, 2 ** 32 - 1)))
|
data2 = ff.build_data_frame(
|
||||||
|
draw(binary()), draw(h2_flags), stream_id=draw(h2_stream_ids_nonzero)
|
||||||
|
)
|
||||||
|
window_update = ff.build_window_update_frame(draw(h2_stream_ids), draw(integers(0, 2 ** 32 - 1)))
|
||||||
|
|
||||||
return draw(lists(sampled_from([headers, settings, data, window_update]), min_size=1, max_size=4))
|
frames = draw(lists(sampled_from([
|
||||||
|
headers1, headers2, settings, continuation, goaway, push_promise, rst, prio, data1, data2, window_update
|
||||||
|
]), min_size=1, max_size=11))
|
||||||
@composite
|
return b"".join(x.serialize() for x in frames)
|
||||||
def h2_request_parts(draw):
|
|
||||||
return _h2_request_parts(draw)
|
|
||||||
|
|
||||||
|
|
||||||
@composite
|
|
||||||
def h2_request_chunks(draw):
|
|
||||||
parts = _h2_request_parts(draw)
|
|
||||||
request = b"".join(x.serialize() for x in parts)
|
|
||||||
request = mutate(draw, request)
|
|
||||||
request = list(split(draw, request))
|
|
||||||
return request
|
|
||||||
|
|
||||||
|
|
||||||
def h2_layer(opts):
|
def h2_layer(opts):
|
||||||
@ -185,23 +219,59 @@ def h2_layer(opts):
|
|||||||
return tctx, layer
|
return tctx, layer
|
||||||
|
|
||||||
|
|
||||||
@given(h2_request_parts())
|
def _h2_request(chunks):
|
||||||
def test_fuzz_h2_request(parts):
|
|
||||||
tctx, layer = h2_layer(opts)
|
tctx, layer = h2_layer(opts)
|
||||||
for part in parts:
|
for chunk in chunks:
|
||||||
for _ in layer.handle_event(DataReceived(tctx.client, part.serialize())):
|
for _ in layer.handle_event(DataReceived(tctx.client, chunk)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@given(h2_request_chunks())
|
@given(chunks(h2_frames()))
|
||||||
|
@example([b'\x00\x00\x00\x01\x05\x00\x00\x00\x01\x00\x00\x00\x01\x05\x00\x00\x00\x01'])
|
||||||
@example([b'\x00\x00\x00\x01\x07\x00\x00\x00\x01A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x86\x82`\x80f\x80\\\x80'])
|
@example([b'\x00\x00\x00\x01\x07\x00\x00\x00\x01A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x86\x82`\x80f\x80\\\x80'])
|
||||||
@example([b'\x00\x00\x05\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00'])
|
@example([b'\x00\x00\x05\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00'])
|
||||||
@example([b'\x00\x00\x13\x01\x04\x00\x00\x00\x01A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x86\x82`\x80f\x80\\\x80'])
|
@example([b'\x00\x00\x13\x01\x04\x00\x00\x00\x01A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x86\x82`\x80f\x80\\\x80'])
|
||||||
@example([b'\x00\x00\x12\x01\x04\x00\x00\x00\x01\x84\x86\x82`\x80A\x88/\x91\xd3]\x05\\\x87\xa7\\\x81\x07'])
|
@example([b'\x00\x00\x12\x01\x04\x00\x00\x00\x01\x84\x86\x82`\x80A\x88/\x91\xd3]\x05\\\x87\xa7\\\x81\x07'])
|
||||||
@example([b'\x00\x00\x12\x01\x04\x00\x00\x00\x01\x84\x86\x82`\x80A\x88/\x91\xd3]\x05\\\x87\xa7\\\x81\x07'])
|
@example([b'\x00\x00\x12\x01\x04\x00\x00\x00\x01\x84\x86\x82`\x80A\x88/\x91\xd3]\x05\\\x87\xa7\\\x81\x07'])
|
||||||
@example([b'\x00\x00\x14\x01\x04\x00\x00\x00\x01A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x86`\x80\x82f\x80\\\x81\x07'])
|
@example([b'\x00\x00\x14\x01\x04\x00\x00\x00\x01A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x86`\x80\x82f\x80'])
|
||||||
def test_fuzz_h2_request2(chunks):
|
@example([
|
||||||
tctx, layer = h2_layer(opts)
|
b'\x00\x00%\x01\x04\x00\x00\x00\x01A\x8b/\x91\xd3]\x05\\\x87\xa6\xe3M3\x84\x86\x82`\x85\x94\xe7\x8c~\xfff\x88/\x91\xd3]\x05\\\x87\xa7\\\x82h_\x00\x00\x07\x01\x05\x00\x00\x00\x01\xc1\x84\x86\x82\xc0\xbf\xbe'])
|
||||||
|
def test_fuzz_h2_request_chunks(chunks):
|
||||||
|
_h2_request(chunks)
|
||||||
|
|
||||||
|
|
||||||
|
@given(chunks(mutations(h2_frames())))
|
||||||
|
def test_fuzz_h2_request_mutations(chunks):
|
||||||
|
_h2_request(chunks)
|
||||||
|
|
||||||
|
|
||||||
|
def _h2_response(chunks):
|
||||||
|
tctx = context.Context(context.Client(("client", 1234), ("127.0.0.1", 8080)), opts)
|
||||||
|
playbook = Playbook(http.HttpLayer(tctx, HTTPMode.regular), hooks=False)
|
||||||
|
server = Placeholder(context.Server)
|
||||||
|
assert (
|
||||||
|
playbook
|
||||||
|
>> DataReceived(tctx.client, b"GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
||||||
|
<< OpenConnection(server)
|
||||||
|
>> reply(None, side_effect=make_h2)
|
||||||
|
<< SendData(server, Placeholder())
|
||||||
|
)
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
for _ in layer.handle_event(DataReceived(tctx.client, chunk)):
|
for _ in playbook.layer.handle_event(events.DataReceived(server(), chunk)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@given(chunks(h2_frames()))
|
||||||
|
@example([b'\x00\x00\x03\x01\x04\x00\x00\x00\x01\x84\x86\x82',
|
||||||
|
b'\x00\x00\x07\x05\x04\x00\x00\x00\x01\x00\x00\x00\x00\x84\x86\x82'])
|
||||||
|
@example([b'\x00\x00\x00\x00\x00\x00\x00\x00\x01'])
|
||||||
|
@example([b'\x00\x00\x00\x01\x04\x00\x00\x00\x01'])
|
||||||
|
@example([b'\x00\x00\x07\x05\x04\x00\x00\x00\x01\x00\x00\x00\x02\x84\x86\x82'])
|
||||||
|
@example([b'\x00\x00\x06\x014\x00\x01\x00\x00\x00\x00\x01@\x80\x81c\x86\x82'])
|
||||||
|
def test_fuzz_h2_response_chunks(chunks):
|
||||||
|
_h2_response(chunks)
|
||||||
|
|
||||||
|
|
||||||
|
@given(chunks(mutations(h2_frames())))
|
||||||
|
def test_fuzz_h2_response_mutations(chunks):
|
||||||
|
_h2_response(chunks)
|
||||||
|
Loading…
Reference in New Issue
Block a user