Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_invalid_messages.py
Show All 9 Lines | |||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
class msg_unrecognized: | class msg_unrecognized: | ||||
"""Nonsensical message. Modeled after similar types in test_framework.messages.""" | """Nonsensical message. Modeled after similar types in test_framework.messages.""" | ||||
command = b'badmsg' | command = b'badmsg' | ||||
def __init__(self, str_data): | def __init__(self, *, str_data): | ||||
self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data | self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data | ||||
def serialize(self): | def serialize(self): | ||||
return messages.ser_string(self.str_data) | return messages.ser_string(self.str_data) | ||||
def __repr__(self): | def __repr__(self): | ||||
return "{}(data={})".format(self.command, self.str_data) | return "{}(data={})".format(self.command, self.str_data) | ||||
class msg_nametoolong(msg_unrecognized): | |||||
command = b'thisnameiswayyyyyyyyytoolong' | |||||
class InvalidMessagesTest(BitcoinTestFramework): | class InvalidMessagesTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
def run_test(self): | def run_test(self): | ||||
""" | """ | ||||
. Test msg header | |||||
0. Send a bunch of large (2MB) messages of an unrecognized type. Check to see | 0. Send a bunch of large (2MB) messages of an unrecognized type. Check to see | ||||
that it isn't an effective DoS against the node. | that it isn't an effective DoS against the node. | ||||
1. Send an oversized (2MB+) message and check that we're disconnected. | 1. Send an oversized (2MB+) message and check that we're disconnected. | ||||
2. Send a few messages with an incorrect data size in the header, ensure the | 2. Send a few messages with an incorrect data size in the header, ensure the | ||||
messages are ignored. | messages are ignored. | ||||
3. Send an unrecognized message with a command name longer than 12 characters. | |||||
""" | """ | ||||
self.test_magic_bytes() | |||||
self.test_checksum() | |||||
self.test_size() | |||||
self.test_command() | |||||
node = self.nodes[0] | node = self.nodes[0] | ||||
self.node = node | self.node = node | ||||
node.add_p2p_connection(P2PDataStore()) | node.add_p2p_connection(P2PDataStore()) | ||||
conn2 = node.add_p2p_connection(P2PDataStore()) | conn2 = node.add_p2p_connection(P2PDataStore()) | ||||
# 2MB, per MAX_PROTOCOL_MESSAGE_LENGTH | # 2MB, per MAX_PROTOCOL_MESSAGE_LENGTH | ||||
msg_limit = 2 * 1024 * 1024 | msg_limit = 2 * 1024 * 1024 | ||||
# Account for the 4-byte length prefix | # Account for the 4-byte length prefix | ||||
valid_data_limit = msg_limit - 5 | valid_data_limit = msg_limit - 5 | ||||
# | # | ||||
# 0. | # 0. | ||||
# | # | ||||
# Send as large a message as is valid, ensure we aren't disconnected but | # Send as large a message as is valid, ensure we aren't disconnected but | ||||
# also can't exhaust resources. | # also can't exhaust resources. | ||||
# | # | ||||
msg_at_size = msg_unrecognized("b" * valid_data_limit) | msg_at_size = msg_unrecognized(str_data="b" * valid_data_limit) | ||||
assert len(msg_at_size.serialize()) == msg_limit | assert len(msg_at_size.serialize()) == msg_limit | ||||
self.log.info( | self.log.info( | ||||
"Sending a bunch of large, junk messages to test memory exhaustion. May take a bit...") | "Sending a bunch of large, junk messages to test memory exhaustion. May take a bit...") | ||||
# Run a bunch of times to test for memory exhaustion. | # Run a bunch of times to test for memory exhaustion. | ||||
for _ in range(80): | for _ in range(80): | ||||
node.p2p.send_message(msg_at_size) | node.p2p.send_message(msg_at_size) | ||||
Show All 9 Lines | def run_test(self): | ||||
node.p2p.sync_with_ping(timeout=320) | node.p2p.sync_with_ping(timeout=320) | ||||
assert node.p2p.is_connected | assert node.p2p.is_connected | ||||
# | # | ||||
# 1. | # 1. | ||||
# | # | ||||
# Send an oversized message, ensure we're disconnected. | # Send an oversized message, ensure we're disconnected. | ||||
# | # | ||||
msg_over_size = msg_unrecognized("b" * (valid_data_limit + 1)) | msg_over_size = msg_unrecognized(str_data="b" * (valid_data_limit + 1)) | ||||
assert len(msg_over_size.serialize()) == (msg_limit + 1) | assert len(msg_over_size.serialize()) == (msg_limit + 1) | ||||
with node.assert_debug_log(["Oversized header detected"]): | with node.assert_debug_log(["Oversized header detected"]): | ||||
# An unknown message type (or *any* message type) over | # An unknown message type (or *any* message type) over | ||||
# MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. | # MAX_PROTOCOL_MESSAGE_LENGTH should result in a disconnect. | ||||
node.p2p.send_message(msg_over_size) | node.p2p.send_message(msg_over_size) | ||||
node.p2p.wait_for_disconnect(timeout=4) | node.p2p.wait_for_disconnect(timeout=4) | ||||
node.disconnect_p2ps() | node.disconnect_p2ps() | ||||
conn = node.add_p2p_connection(P2PDataStore()) | conn = node.add_p2p_connection(P2PDataStore()) | ||||
conn.wait_for_verack() | conn.wait_for_verack() | ||||
# | # | ||||
# 2. | # 2. | ||||
# | # | ||||
# Send messages with an incorrect data size in the header. | # Send messages with an incorrect data size in the header. | ||||
# | # | ||||
actual_size = 100 | actual_size = 100 | ||||
msg = msg_unrecognized("b" * actual_size) | msg = msg_unrecognized(str_data="b" * actual_size) | ||||
# TODO: handle larger-than cases. I haven't been able to pin down what | # TODO: handle larger-than cases. I haven't been able to pin down what | ||||
# behavior to expect. | # behavior to expect. | ||||
for wrong_size in (2, 77, 78, 79): | for wrong_size in (2, 77, 78, 79): | ||||
self.log.info( | self.log.info( | ||||
"Sending a message with incorrect size of {}".format(wrong_size)) | "Sending a message with incorrect size of {}".format(wrong_size)) | ||||
# Unmodified message should submit okay. | # Unmodified message should submit okay. | ||||
Show All 14 Lines | def run_test(self): | ||||
node.p2p.send_message(messages.msg_ping(nonce=123123)) | node.p2p.send_message(messages.msg_ping(nonce=123123)) | ||||
except IOError: | except IOError: | ||||
pass | pass | ||||
node.p2p.wait_for_disconnect(timeout=10) | node.p2p.wait_for_disconnect(timeout=10) | ||||
node.disconnect_p2ps() | node.disconnect_p2ps() | ||||
node.add_p2p_connection(P2PDataStore()) | node.add_p2p_connection(P2PDataStore()) | ||||
# | |||||
# 3. | |||||
# | |||||
# Send a message with a too-long command name. | |||||
# | |||||
node.p2p.send_message(msg_nametoolong("foobar")) | |||||
node.p2p.wait_for_disconnect(timeout=4) | |||||
# Node is still up. | # Node is still up. | ||||
conn = node.add_p2p_connection(P2PDataStore()) | conn = node.add_p2p_connection(P2PDataStore()) | ||||
conn.sync_with_ping() | conn.sync_with_ping() | ||||
def test_magic_bytes(self): | |||||
conn = self.nodes[0].add_p2p_connection(P2PDataStore()) | |||||
conn.magic_bytes = b'\x00\x11\x22\x32' | |||||
with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART ping']): | |||||
conn.send_message(messages.msg_ping(nonce=0xff)) | |||||
conn.wait_for_disconnect(timeout=1) | |||||
self.nodes[0].disconnect_p2ps() | |||||
def test_checksum(self): | |||||
conn = self.nodes[0].add_p2p_connection(P2PDataStore()) | |||||
with self.nodes[0].assert_debug_log(['ProcessMessages(badmsg, 2 bytes): CHECKSUM ERROR expected 78df0a04 was ffffffff']): | |||||
msg = conn.build_message(msg_unrecognized(str_data="d")) | |||||
cut_len = ( | |||||
# magic | |||||
4 + | |||||
# command | |||||
12 + | |||||
# len | |||||
4 | |||||
) | |||||
# modify checksum | |||||
msg = msg[:cut_len] + b'\xff' * 4 + msg[cut_len + 4:] | |||||
self.nodes[0].p2p.send_raw_message(msg) | |||||
conn.wait_for_disconnect() | |||||
self.nodes[0].disconnect_p2ps() | |||||
def test_size(self): | |||||
conn = self.nodes[0].add_p2p_connection(P2PDataStore()) | |||||
with self.nodes[0].assert_debug_log(['']): | |||||
msg = conn.build_message(msg_unrecognized(str_data="d")) | |||||
cut_len = ( | |||||
# magic | |||||
4 + | |||||
# command | |||||
12 | |||||
) | |||||
# modify len to MAX_SIZE + 1 | |||||
msg = msg[:cut_len] + \ | |||||
struct.pack("<I", 0x02000000 + 1) + msg[cut_len + 4:] | |||||
self.nodes[0].p2p.send_raw_message(msg) | |||||
conn.wait_for_disconnect(timeout=1) | |||||
self.nodes[0].disconnect_p2ps() | |||||
def test_command(self): | |||||
conn = self.nodes[0].add_p2p_connection(P2PDataStore()) | |||||
with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: ERRORS IN HEADER']): | |||||
msg = msg_unrecognized(str_data="d") | |||||
msg.command = b'\xff' * 12 | |||||
msg = conn.build_message(msg) | |||||
# Modify command | |||||
msg = msg[:7] + b'\x00' + msg[7 + 1:] | |||||
self.nodes[0].p2p.send_raw_message(msg) | |||||
conn.sync_with_ping(timeout=1) | |||||
self.nodes[0].disconnect_p2ps() | |||||
def _tweak_msg_data_size(self, message, wrong_size): | def _tweak_msg_data_size(self, message, wrong_size): | ||||
""" | """ | ||||
Return a raw message based on another message but with an incorrect data size in | Return a raw message based on another message but with an incorrect data size in | ||||
the message header. | the message header. | ||||
""" | """ | ||||
raw_msg = self.node.p2p.build_message(message) | raw_msg = self.node.p2p.build_message(message) | ||||
bad_size_bytes = struct.pack("<I", wrong_size) | bad_size_bytes = struct.pack("<I", wrong_size) | ||||
Show All 15 Lines |