p2p_invalid_messages.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2015-2021 The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test node responses to invalid network messages.""" 6 7 import random 8 import struct 9 import time 10 11 from test_framework.messages import ( 12 CBlockHeader, 13 CInv, 14 MAX_HEADERS_RESULTS, 15 MAX_INV_SIZE, 16 MAX_PROTOCOL_MESSAGE_LENGTH, 17 MSG_TX, 18 from_hex, 19 msg_getdata, 20 msg_headers, 21 msg_inv, 22 msg_ping, 23 msg_version, 24 ser_string, 25 ) 26 from test_framework.p2p import ( 27 P2PDataStore, 28 P2PInterface, 29 ) 30 from test_framework.test_framework import BitcoinTestFramework 31 from test_framework.util import ( 32 assert_equal, 33 ) 34 35 VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte length prefix 36 37 38 class msg_unrecognized: 39 """Nonsensical message. Modeled after similar types in test_framework.messages.""" 40 41 msgtype = b'badmsg\x01' 42 43 def __init__(self, *, str_data): 44 self.str_data = str_data.encode() if not isinstance(str_data, bytes) else str_data 45 46 def serialize(self): 47 return ser_string(self.str_data) 48 49 def __repr__(self): 50 return "{}(data={})".format(self.msgtype, self.str_data) 51 52 53 class SenderOfAddrV2(P2PInterface): 54 def wait_for_sendaddrv2(self): 55 self.wait_until(lambda: 'sendaddrv2' in self.last_message) 56 57 58 class InvalidMessagesTest(BitcoinTestFramework): 59 def set_test_params(self): 60 self.num_nodes = 1 61 self.setup_clean_chain = True 62 self.extra_args = [["-whitelist=addr@127.0.0.1"]] 63 64 def run_test(self): 65 self.test_buffer() 66 self.test_duplicate_version_msg() 67 self.test_magic_bytes() 68 self.test_checksum() 69 self.test_size() 70 self.test_msgtype() 71 self.test_addrv2_empty() 72 self.test_addrv2_no_addresses() 73 self.test_addrv2_too_long_address() 74 self.test_addrv2_unrecognized_network() 75 self.test_oversized_inv_msg() 76 self.test_oversized_getdata_msg() 77 self.test_oversized_headers_msg() 78 self.test_invalid_pow_headers_msg() 79 self.test_noncontinuous_headers_msg() 80 self.test_resource_exhaustion() 81 82 def test_buffer(self): 83 self.log.info("Test message with header split across two buffers is received") 84 conn = self.nodes[0].add_p2p_connection(P2PDataStore()) 85 # After add_p2p_connection both sides have the verack processed. 86 # However the pong from conn in reply to the ping from the node has not 87 # been processed and recorded in totalbytesrecv. 88 # Flush the pong from conn by sending a ping from conn. 89 conn.sync_with_ping(timeout=1) 90 # Create valid message 91 msg = conn.build_message(msg_ping(nonce=12345)) 92 cut_pos = 12 # Chosen at an arbitrary position within the header 93 # Send message in two pieces 94 before = self.nodes[0].getnettotals()['totalbytesrecv'] 95 conn.send_raw_message(msg[:cut_pos]) 96 # Wait until node has processed the first half of the message 97 self.wait_until(lambda: self.nodes[0].getnettotals()['totalbytesrecv'] != before) 98 middle = self.nodes[0].getnettotals()['totalbytesrecv'] 99 assert_equal(middle, before + cut_pos) 100 conn.send_raw_message(msg[cut_pos:]) 101 conn.sync_with_ping(timeout=1) 102 self.nodes[0].disconnect_p2ps() 103 104 def test_duplicate_version_msg(self): 105 self.log.info("Test duplicate version message is ignored") 106 conn = self.nodes[0].add_p2p_connection(P2PDataStore()) 107 with self.nodes[0].assert_debug_log(['redundant version message from peer']): 108 conn.send_and_ping(msg_version()) 109 self.nodes[0].disconnect_p2ps() 110 111 def test_magic_bytes(self): 112 # Skip with v2, magic bytes are v1-specific 113 if self.options.v2transport: 114 return 115 self.log.info("Test message with invalid magic bytes disconnects peer") 116 conn = self.nodes[0].add_p2p_connection(P2PDataStore()) 117 with self.nodes[0].assert_debug_log(['Header error: Wrong MessageStart ffffffff received']): 118 msg = conn.build_message(msg_unrecognized(str_data="d")) 119 # modify magic bytes 120 msg = b'\xff' * 4 + msg[4:] 121 conn.send_raw_message(msg) 122 conn.wait_for_disconnect(timeout=1) 123 self.nodes[0].disconnect_p2ps() 124 125 def test_checksum(self): 126 # Skip with v2, the checksum is v1-specific 127 if self.options.v2transport: 128 return 129 self.log.info("Test message with invalid checksum logs an error") 130 conn = self.nodes[0].add_p2p_connection(P2PDataStore()) 131 with self.nodes[0].assert_debug_log(['Header error: Wrong checksum (badmsg, 2 bytes), expected 78df0a04 was ffffffff']): 132 msg = conn.build_message(msg_unrecognized(str_data="d")) 133 # Checksum is after start bytes (4B), message type (12B), len (4B) 134 cut_len = 4 + 12 + 4 135 # modify checksum 136 msg = msg[:cut_len] + b'\xff' * 4 + msg[cut_len + 4:] 137 conn.send_raw_message(msg) 138 conn.sync_with_ping(timeout=1) 139 # Check that traffic is accounted for (24 bytes header + 2 bytes payload) 140 assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26) 141 self.nodes[0].disconnect_p2ps() 142 143 def test_size(self): 144 self.log.info("Test message with oversized payload disconnects peer") 145 conn = self.nodes[0].add_p2p_connection(P2PDataStore()) 146 error_msg = ( 147 ['V2 transport error: packet too large (4000014 bytes)'] if self.options.v2transport 148 else ['Header error: Size too large (badmsg, 4000001 bytes)'] 149 ) 150 with self.nodes[0].assert_debug_log(error_msg): 151 msg = msg_unrecognized(str_data="d" * (VALID_DATA_LIMIT + 1)) 152 msg = conn.build_message(msg) 153 conn.send_raw_message(msg) 154 conn.wait_for_disconnect(timeout=1) 155 self.nodes[0].disconnect_p2ps() 156 157 def test_msgtype(self): 158 self.log.info("Test message with invalid message type logs an error") 159 conn = self.nodes[0].add_p2p_connection(P2PDataStore()) 160 if self.options.v2transport: 161 msgtype = 99 # not defined 162 msg = msg_unrecognized(str_data="d") 163 contents = msgtype.to_bytes(1, 'big') + msg.serialize() 164 tmsg = conn.v2_state.v2_enc_packet(contents, ignore=False) 165 with self.nodes[0].assert_debug_log(['V2 transport error: invalid message type']): 166 conn.send_raw_message(tmsg) 167 conn.sync_with_ping(timeout=1) 168 # Check that traffic is accounted for (20 bytes plus 3 bytes contents) 169 assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 23) 170 else: 171 with self.nodes[0].assert_debug_log(['Header error: Invalid message type']): 172 msg = msg_unrecognized(str_data="d") 173 msg = conn.build_message(msg) 174 # Modify msgtype 175 msg = msg[:7] + b'\x00' + msg[7 + 1:] 176 conn.send_raw_message(msg) 177 conn.sync_with_ping(timeout=1) 178 # Check that traffic is accounted for (24 bytes header + 2 bytes payload) 179 assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26) 180 self.nodes[0].disconnect_p2ps() 181 182 def test_addrv2(self, label, required_log_messages, raw_addrv2): 183 node = self.nodes[0] 184 conn = node.add_p2p_connection(SenderOfAddrV2()) 185 186 # Make sure bitcoind signals support for ADDRv2, otherwise this test 187 # will bombard an old node with messages it does not recognize which 188 # will produce unexpected results. 189 conn.wait_for_sendaddrv2() 190 191 self.log.info('Test addrv2: ' + label) 192 193 msg = msg_unrecognized(str_data=b'') 194 msg.msgtype = b'addrv2' 195 with node.assert_debug_log(required_log_messages): 196 # override serialize() which would include the length of the data 197 msg.serialize = lambda: raw_addrv2 198 conn.send_raw_message(conn.build_message(msg)) 199 conn.sync_with_ping() 200 201 node.disconnect_p2ps() 202 203 def test_addrv2_empty(self): 204 self.test_addrv2('empty', 205 [ 206 'received: addrv2 (0 bytes)', 207 'ProcessMessages(addrv2, 0 bytes): Exception', 208 'end of data', 209 ], 210 b'') 211 212 def test_addrv2_no_addresses(self): 213 self.test_addrv2('no addresses', 214 [ 215 'received: addrv2 (1 bytes)', 216 ], 217 bytes.fromhex('00')) 218 219 def test_addrv2_too_long_address(self): 220 self.test_addrv2('too long address', 221 [ 222 'received: addrv2 (525 bytes)', 223 'ProcessMessages(addrv2, 525 bytes): Exception', 224 'Address too long: 513 > 512', 225 ], 226 bytes.fromhex( 227 '01' + # number of entries 228 '61bc6649' + # time, Fri Jan 9 02:54:25 UTC 2009 229 '00' + # service flags, COMPACTSIZE(NODE_NONE) 230 '01' + # network type (IPv4) 231 'fd0102' + # address length (COMPACTSIZE(513)) 232 'ab' * 513 + # address 233 '208d')) # port 234 235 def test_addrv2_unrecognized_network(self): 236 now_hex = struct.pack('<I', int(time.time())).hex() 237 self.test_addrv2('unrecognized network', 238 [ 239 'received: addrv2 (25 bytes)', 240 '9.9.9.9:8333', 241 'Added 1 addresses', 242 ], 243 bytes.fromhex( 244 '02' + # number of entries 245 # this should be ignored without impeding acceptance of subsequent ones 246 now_hex + # time 247 '01' + # service flags, COMPACTSIZE(NODE_NETWORK) 248 '99' + # network type (unrecognized) 249 '02' + # address length (COMPACTSIZE(2)) 250 'ab' * 2 + # address 251 '208d' + # port 252 # this should be added: 253 now_hex + # time 254 '01' + # service flags, COMPACTSIZE(NODE_NETWORK) 255 '01' + # network type (IPv4) 256 '04' + # address length (COMPACTSIZE(4)) 257 '09' * 4 + # address 258 '208d')) # port 259 260 def test_oversized_msg(self, msg, size): 261 msg_type = msg.msgtype.decode('ascii') 262 self.log.info("Test {} message of size {} is logged as misbehaving".format(msg_type, size)) 263 with self.nodes[0].assert_debug_log(['Misbehaving', '{} message size = {}'.format(msg_type, size)]): 264 self.nodes[0].add_p2p_connection(P2PInterface()).send_and_ping(msg) 265 self.nodes[0].disconnect_p2ps() 266 267 def test_oversized_inv_msg(self): 268 size = MAX_INV_SIZE + 1 269 self.test_oversized_msg(msg_inv([CInv(MSG_TX, 1)] * size), size) 270 271 def test_oversized_getdata_msg(self): 272 size = MAX_INV_SIZE + 1 273 self.test_oversized_msg(msg_getdata([CInv(MSG_TX, 1)] * size), size) 274 275 def test_oversized_headers_msg(self): 276 size = MAX_HEADERS_RESULTS + 1 277 self.test_oversized_msg(msg_headers([CBlockHeader()] * size), size) 278 279 def test_invalid_pow_headers_msg(self): 280 self.log.info("Test headers message with invalid proof-of-work is logged as misbehaving and disconnects peer") 281 blockheader_tip_hash = self.nodes[0].getbestblockhash() 282 blockheader_tip = from_hex(CBlockHeader(), self.nodes[0].getblockheader(blockheader_tip_hash, False)) 283 284 # send valid headers message first 285 assert_equal(self.nodes[0].getblockchaininfo()['headers'], 0) 286 blockheader = CBlockHeader() 287 blockheader.hashPrevBlock = int(blockheader_tip_hash, 16) 288 blockheader.nTime = int(time.time()) 289 blockheader.nBits = blockheader_tip.nBits 290 blockheader.rehash() 291 while not blockheader.hash.startswith('0'): 292 blockheader.nNonce += 1 293 blockheader.rehash() 294 peer = self.nodes[0].add_p2p_connection(P2PInterface()) 295 peer.send_and_ping(msg_headers([blockheader])) 296 assert_equal(self.nodes[0].getblockchaininfo()['headers'], 1) 297 chaintips = self.nodes[0].getchaintips() 298 assert_equal(chaintips[0]['status'], 'headers-only') 299 assert_equal(chaintips[0]['hash'], blockheader.hash) 300 301 # invalidate PoW 302 while not blockheader.hash.startswith('f'): 303 blockheader.nNonce += 1 304 blockheader.rehash() 305 with self.nodes[0].assert_debug_log(['Misbehaving', 'header with invalid proof of work']): 306 peer.send_message(msg_headers([blockheader])) 307 peer.wait_for_disconnect() 308 309 def test_noncontinuous_headers_msg(self): 310 self.log.info("Test headers message with non-continuous headers sequence is logged as misbehaving") 311 block_hashes = self.generate(self.nodes[0], 10) 312 block_headers = [] 313 for block_hash in block_hashes: 314 block_headers.append(from_hex(CBlockHeader(), self.nodes[0].getblockheader(block_hash, False))) 315 316 # continuous headers sequence should be fine 317 MISBEHAVING_NONCONTINUOUS_HEADERS_MSGS = ['Misbehaving', 'non-continuous headers sequence'] 318 peer = self.nodes[0].add_p2p_connection(P2PInterface()) 319 with self.nodes[0].assert_debug_log([], unexpected_msgs=MISBEHAVING_NONCONTINUOUS_HEADERS_MSGS): 320 peer.send_and_ping(msg_headers(block_headers)) 321 322 # delete arbitrary block header somewhere in the middle to break link 323 del block_headers[random.randrange(1, len(block_headers)-1)] 324 with self.nodes[0].assert_debug_log(expected_msgs=MISBEHAVING_NONCONTINUOUS_HEADERS_MSGS): 325 peer.send_and_ping(msg_headers(block_headers)) 326 self.nodes[0].disconnect_p2ps() 327 328 def test_resource_exhaustion(self): 329 self.log.info("Test node stays up despite many large junk messages") 330 # Don't use v2 here - the non-optimised encryption would take too long to encrypt 331 # the large messages 332 conn = self.nodes[0].add_p2p_connection(P2PDataStore(), supports_v2_p2p=False) 333 conn2 = self.nodes[0].add_p2p_connection(P2PDataStore(), supports_v2_p2p=False) 334 msg_at_size = msg_unrecognized(str_data="b" * VALID_DATA_LIMIT) 335 assert len(msg_at_size.serialize()) == MAX_PROTOCOL_MESSAGE_LENGTH 336 337 self.log.info("(a) Send 80 messages, each of maximum valid data size (4MB)") 338 for _ in range(80): 339 conn.send_message(msg_at_size) 340 341 # Check that, even though the node is being hammered by nonsense from one 342 # connection, it can still service other peers in a timely way. 343 self.log.info("(b) Check node still services peers in a timely way") 344 for _ in range(20): 345 conn2.sync_with_ping(timeout=2) 346 347 self.log.info("(c) Wait for node to drop junk messages, while remaining connected") 348 conn.sync_with_ping(timeout=400) 349 350 # Despite being served up a bunch of nonsense, the peers should still be connected. 351 assert conn.is_connected 352 assert conn2.is_connected 353 self.nodes[0].disconnect_p2ps() 354 355 356 if __name__ == '__main__': 357 InvalidMessagesTest().main()