1# -*- coding: utf-8 -*- 2""" 3test_invalid_headers.py 4~~~~~~~~~~~~~~~~~~~~~~~ 5 6This module contains tests that use invalid header blocks, and validates that 7they fail appropriately. 8""" 9import itertools 10 11import pytest 12 13import h2.config 14import h2.connection 15import h2.errors 16import h2.events 17import h2.exceptions 18import h2.settings 19import h2.utilities 20 21import hyperframe.frame 22 23from hypothesis import given 24from hypothesis.strategies import binary, lists, tuples 25 26HEADERS_STRATEGY = lists(tuples(binary(min_size=1), binary())) 27 28 29class TestInvalidFrameSequences(object): 30 """ 31 Invalid header sequences cause ProtocolErrors to be thrown when received. 32 """ 33 base_request_headers = [ 34 (':authority', 'example.com'), 35 (':path', '/'), 36 (':scheme', 'https'), 37 (':method', 'GET'), 38 ('user-agent', 'someua/0.0.1'), 39 ] 40 invalid_header_blocks = [ 41 base_request_headers + [('Uppercase', 'name')], 42 base_request_headers + [(':late', 'pseudo-header')], 43 [(':path', 'duplicate-pseudo-header')] + base_request_headers, 44 base_request_headers + [('connection', 'close')], 45 base_request_headers + [('proxy-connection', 'close')], 46 base_request_headers + [('keep-alive', 'close')], 47 base_request_headers + [('transfer-encoding', 'gzip')], 48 base_request_headers + [('upgrade', 'super-protocol/1.1')], 49 base_request_headers + [('te', 'chunked')], 50 base_request_headers + [('host', 'notexample.com')], 51 base_request_headers + [(' name', 'name with leading space')], 52 base_request_headers + [('name ', 'name with trailing space')], 53 base_request_headers + [('name', ' value with leading space')], 54 base_request_headers + [('name', 'value with trailing space ')], 55 [header for header in base_request_headers 56 if header[0] != ':authority'], 57 [(':protocol', 'websocket')] + base_request_headers, 58 ] 59 server_config = h2.config.H2Configuration( 60 client_side=False, header_encoding='utf-8' 61 ) 62 63 @pytest.mark.parametrize('headers', invalid_header_blocks) 64 def test_headers_event(self, frame_factory, headers): 65 """ 66 Test invalid headers are rejected with PROTOCOL_ERROR. 67 """ 68 c = h2.connection.H2Connection(config=self.server_config) 69 c.receive_data(frame_factory.preamble()) 70 c.clear_outbound_data_buffer() 71 72 f = frame_factory.build_headers_frame(headers) 73 data = f.serialize() 74 75 with pytest.raises(h2.exceptions.ProtocolError): 76 c.receive_data(data) 77 78 expected_frame = frame_factory.build_goaway_frame( 79 last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR 80 ) 81 assert c.data_to_send() == expected_frame.serialize() 82 83 @pytest.mark.parametrize('headers', invalid_header_blocks) 84 def test_push_promise_event(self, frame_factory, headers): 85 """ 86 If a PUSH_PROMISE header frame is received with an invalid header block 87 it is rejected with a PROTOCOL_ERROR. 88 """ 89 c = h2.connection.H2Connection() 90 c.initiate_connection() 91 c.send_headers( 92 stream_id=1, headers=self.base_request_headers, end_stream=True 93 ) 94 c.clear_outbound_data_buffer() 95 96 f = frame_factory.build_push_promise_frame( 97 stream_id=1, 98 promised_stream_id=2, 99 headers=headers 100 ) 101 data = f.serialize() 102 103 with pytest.raises(h2.exceptions.ProtocolError): 104 c.receive_data(data) 105 106 expected_frame = frame_factory.build_goaway_frame( 107 last_stream_id=0, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR 108 ) 109 assert c.data_to_send() == expected_frame.serialize() 110 111 @pytest.mark.parametrize('headers', invalid_header_blocks) 112 def test_push_promise_skipping_validation(self, frame_factory, headers): 113 """ 114 If we have ``validate_inbound_headers`` disabled, then invalid header 115 blocks in push promise frames are allowed to pass. 116 """ 117 config = h2.config.H2Configuration( 118 client_side=True, 119 validate_inbound_headers=False, 120 header_encoding='utf-8' 121 ) 122 123 c = h2.connection.H2Connection(config=config) 124 c.initiate_connection() 125 c.send_headers( 126 stream_id=1, headers=self.base_request_headers, end_stream=True 127 ) 128 c.clear_outbound_data_buffer() 129 130 f = frame_factory.build_push_promise_frame( 131 stream_id=1, 132 promised_stream_id=2, 133 headers=headers 134 ) 135 data = f.serialize() 136 137 events = c.receive_data(data) 138 assert len(events) == 1 139 pp_event = events[0] 140 assert pp_event.headers == headers 141 142 @pytest.mark.parametrize('headers', invalid_header_blocks) 143 def test_headers_event_skipping_validation(self, frame_factory, headers): 144 """ 145 If we have ``validate_inbound_headers`` disabled, then all of these 146 invalid header blocks are allowed to pass. 147 """ 148 config = h2.config.H2Configuration( 149 client_side=False, 150 validate_inbound_headers=False, 151 header_encoding='utf-8' 152 ) 153 154 c = h2.connection.H2Connection(config=config) 155 c.receive_data(frame_factory.preamble()) 156 157 f = frame_factory.build_headers_frame(headers) 158 data = f.serialize() 159 160 events = c.receive_data(data) 161 assert len(events) == 1 162 request_event = events[0] 163 assert request_event.headers == headers 164 165 def test_transfer_encoding_trailers_is_valid(self, frame_factory): 166 """ 167 Transfer-Encoding trailers is allowed by the filter. 168 """ 169 headers = ( 170 self.base_request_headers + [('te', 'trailers')] 171 ) 172 173 c = h2.connection.H2Connection(config=self.server_config) 174 c.receive_data(frame_factory.preamble()) 175 176 f = frame_factory.build_headers_frame(headers) 177 data = f.serialize() 178 179 events = c.receive_data(data) 180 assert len(events) == 1 181 request_event = events[0] 182 assert request_event.headers == headers 183 184 def test_pseudo_headers_rejected_in_trailer(self, frame_factory): 185 """ 186 Ensure we reject pseudo headers included in trailers 187 """ 188 trailers = [(':path', '/'), ('extra', 'value')] 189 190 c = h2.connection.H2Connection(config=self.server_config) 191 c.receive_data(frame_factory.preamble()) 192 c.clear_outbound_data_buffer() 193 194 header_frame = frame_factory.build_headers_frame( 195 self.base_request_headers 196 ) 197 trailer_frame = frame_factory.build_headers_frame( 198 trailers, flags=["END_STREAM"] 199 ) 200 head = header_frame.serialize() 201 trailer = trailer_frame.serialize() 202 203 c.receive_data(head) 204 # Raise exception if pseudo header in trailer 205 with pytest.raises(h2.exceptions.ProtocolError) as e: 206 c.receive_data(trailer) 207 assert "pseudo-header in trailer" in str(e.value) 208 209 # Test appropriate response frame is generated 210 expected_frame = frame_factory.build_goaway_frame( 211 last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR 212 ) 213 assert c.data_to_send() == expected_frame.serialize() 214 215 216class TestSendingInvalidFrameSequences(object): 217 """ 218 Trying to send invalid header sequences cause ProtocolErrors to 219 be thrown. 220 """ 221 base_request_headers = [ 222 (':authority', 'example.com'), 223 (':path', '/'), 224 (':scheme', 'https'), 225 (':method', 'GET'), 226 ('user-agent', 'someua/0.0.1'), 227 ] 228 invalid_header_blocks = [ 229 base_request_headers + [(':late', 'pseudo-header')], 230 [(':path', 'duplicate-pseudo-header')] + base_request_headers, 231 base_request_headers + [('te', 'chunked')], 232 base_request_headers + [('host', 'notexample.com')], 233 [header for header in base_request_headers 234 if header[0] != ':authority'], 235 ] 236 strippable_header_blocks = [ 237 base_request_headers + [('connection', 'close')], 238 base_request_headers + [('proxy-connection', 'close')], 239 base_request_headers + [('keep-alive', 'close')], 240 base_request_headers + [('transfer-encoding', 'gzip')], 241 base_request_headers + [('upgrade', 'super-protocol/1.1')] 242 ] 243 all_header_blocks = invalid_header_blocks + strippable_header_blocks 244 245 server_config = h2.config.H2Configuration(client_side=False) 246 247 @pytest.mark.parametrize('headers', invalid_header_blocks) 248 def test_headers_event(self, frame_factory, headers): 249 """ 250 Test sending invalid headers raise a ProtocolError. 251 """ 252 c = h2.connection.H2Connection() 253 c.initiate_connection() 254 255 # Clear the data, then try to send headers. 256 c.clear_outbound_data_buffer() 257 with pytest.raises(h2.exceptions.ProtocolError): 258 c.send_headers(1, headers) 259 260 @pytest.mark.parametrize('headers', invalid_header_blocks) 261 def test_send_push_promise(self, frame_factory, headers): 262 """ 263 Sending invalid headers in a push promise raises a ProtocolError. 264 """ 265 c = h2.connection.H2Connection(config=self.server_config) 266 c.initiate_connection() 267 c.receive_data(frame_factory.preamble()) 268 269 header_frame = frame_factory.build_headers_frame( 270 self.base_request_headers 271 ) 272 c.receive_data(header_frame.serialize()) 273 274 # Clear the data, then try to send a push promise. 275 c.clear_outbound_data_buffer() 276 with pytest.raises(h2.exceptions.ProtocolError): 277 c.push_stream( 278 stream_id=1, promised_stream_id=2, request_headers=headers 279 ) 280 281 @pytest.mark.parametrize('headers', all_header_blocks) 282 def test_headers_event_skipping_validation(self, frame_factory, headers): 283 """ 284 If we have ``validate_outbound_headers`` disabled, then all of these 285 invalid header blocks are allowed to pass. 286 """ 287 config = h2.config.H2Configuration( 288 validate_outbound_headers=False 289 ) 290 291 c = h2.connection.H2Connection(config=config) 292 c.initiate_connection() 293 294 # Clear the data, then send headers. 295 c.clear_outbound_data_buffer() 296 c.send_headers(1, headers) 297 298 # Ensure headers are still normalized. 299 norm_headers = h2.utilities.normalize_outbound_headers(headers, None) 300 f = frame_factory.build_headers_frame(norm_headers) 301 assert c.data_to_send() == f.serialize() 302 303 @pytest.mark.parametrize('headers', all_header_blocks) 304 def test_push_promise_skipping_validation(self, frame_factory, headers): 305 """ 306 If we have ``validate_outbound_headers`` disabled, then all of these 307 invalid header blocks are allowed to pass. 308 """ 309 config = h2.config.H2Configuration( 310 client_side=False, 311 validate_outbound_headers=False, 312 ) 313 314 c = h2.connection.H2Connection(config=config) 315 c.initiate_connection() 316 c.receive_data(frame_factory.preamble()) 317 318 header_frame = frame_factory.build_headers_frame( 319 self.base_request_headers 320 ) 321 c.receive_data(header_frame.serialize()) 322 323 # Create push promise frame with normalized headers. 324 frame_factory.refresh_encoder() 325 norm_headers = h2.utilities.normalize_outbound_headers(headers, None) 326 pp_frame = frame_factory.build_push_promise_frame( 327 stream_id=1, promised_stream_id=2, headers=norm_headers 328 ) 329 330 # Clear the data, then send a push promise. 331 c.clear_outbound_data_buffer() 332 c.push_stream( 333 stream_id=1, promised_stream_id=2, request_headers=headers 334 ) 335 assert c.data_to_send() == pp_frame.serialize() 336 337 @pytest.mark.parametrize('headers', all_header_blocks) 338 def test_headers_event_skip_normalization(self, frame_factory, headers): 339 """ 340 If we have ``normalize_outbound_headers`` disabled, then all of these 341 invalid header blocks are sent through unmodified. 342 """ 343 config = h2.config.H2Configuration( 344 validate_outbound_headers=False, 345 normalize_outbound_headers=False 346 ) 347 348 c = h2.connection.H2Connection(config=config) 349 c.initiate_connection() 350 351 f = frame_factory.build_headers_frame( 352 headers, 353 stream_id=1, 354 ) 355 356 # Clear the data, then send headers. 357 c.clear_outbound_data_buffer() 358 c.send_headers(1, headers) 359 assert c.data_to_send() == f.serialize() 360 361 @pytest.mark.parametrize('headers', all_header_blocks) 362 def test_push_promise_skip_normalization(self, frame_factory, headers): 363 """ 364 If we have ``normalize_outbound_headers`` disabled, then all of these 365 invalid header blocks are allowed to pass unmodified. 366 """ 367 config = h2.config.H2Configuration( 368 client_side=False, 369 validate_outbound_headers=False, 370 normalize_outbound_headers=False, 371 ) 372 373 c = h2.connection.H2Connection(config=config) 374 c.initiate_connection() 375 c.receive_data(frame_factory.preamble()) 376 377 header_frame = frame_factory.build_headers_frame( 378 self.base_request_headers 379 ) 380 c.receive_data(header_frame.serialize()) 381 382 frame_factory.refresh_encoder() 383 pp_frame = frame_factory.build_push_promise_frame( 384 stream_id=1, promised_stream_id=2, headers=headers 385 ) 386 387 # Clear the data, then send a push promise. 388 c.clear_outbound_data_buffer() 389 c.push_stream( 390 stream_id=1, promised_stream_id=2, request_headers=headers 391 ) 392 assert c.data_to_send() == pp_frame.serialize() 393 394 @pytest.mark.parametrize('headers', strippable_header_blocks) 395 def test_strippable_headers(self, frame_factory, headers): 396 """ 397 Test connection related headers are removed before sending. 398 """ 399 c = h2.connection.H2Connection() 400 c.initiate_connection() 401 402 # Clear the data, then try to send headers. 403 c.clear_outbound_data_buffer() 404 c.send_headers(1, headers) 405 406 f = frame_factory.build_headers_frame(self.base_request_headers) 407 assert c.data_to_send() == f.serialize() 408 409 410class TestFilter(object): 411 """ 412 Test the filter function directly. 413 414 These tests exists to confirm the behaviour of the filter function in a 415 wide range of scenarios. Many of these scenarios may not be legal for 416 HTTP/2 and so may never hit the function, but it's worth validating that it 417 behaves as expected anyway. 418 """ 419 validation_functions = [ 420 h2.utilities.validate_headers, 421 h2.utilities.validate_outbound_headers 422 ] 423 424 hdr_validation_combos = [ 425 h2.utilities.HeaderValidationFlags( 426 is_client, is_trailer, is_response_header, is_push_promise 427 ) 428 for is_client, is_trailer, is_response_header, is_push_promise in ( 429 itertools.product([True, False], repeat=4) 430 ) 431 ] 432 433 hdr_validation_response_headers = [ 434 flags for flags in hdr_validation_combos 435 if flags.is_response_header 436 ] 437 438 hdr_validation_request_headers_no_trailer = [ 439 flags for flags in hdr_validation_combos 440 if not (flags.is_trailer or flags.is_response_header) 441 ] 442 443 invalid_request_header_blocks_bytes = ( 444 # First, missing :method 445 ( 446 (b':authority', b'google.com'), 447 (b':path', b'/'), 448 (b':scheme', b'https'), 449 ), 450 # Next, missing :path 451 ( 452 (b':authority', b'google.com'), 453 (b':method', b'GET'), 454 (b':scheme', b'https'), 455 ), 456 # Next, missing :scheme 457 ( 458 (b':authority', b'google.com'), 459 (b':method', b'GET'), 460 (b':path', b'/'), 461 ), 462 # Finally, path present but empty. 463 ( 464 (b':authority', b'google.com'), 465 (b':method', b'GET'), 466 (b':scheme', b'https'), 467 (b':path', b''), 468 ), 469 ) 470 invalid_request_header_blocks_unicode = ( 471 # First, missing :method 472 ( 473 (u':authority', u'google.com'), 474 (u':path', u'/'), 475 (u':scheme', u'https'), 476 ), 477 # Next, missing :path 478 ( 479 (u':authority', u'google.com'), 480 (u':method', u'GET'), 481 (u':scheme', u'https'), 482 ), 483 # Next, missing :scheme 484 ( 485 (u':authority', u'google.com'), 486 (u':method', u'GET'), 487 (u':path', u'/'), 488 ), 489 # Finally, path present but empty. 490 ( 491 (u':authority', u'google.com'), 492 (u':method', u'GET'), 493 (u':scheme', u'https'), 494 (u':path', u''), 495 ), 496 ) 497 498 # All headers that are forbidden from either request or response blocks. 499 forbidden_request_headers_bytes = (b':status',) 500 forbidden_request_headers_unicode = (u':status',) 501 forbidden_response_headers_bytes = ( 502 b':path', b':scheme', b':authority', b':method' 503 ) 504 forbidden_response_headers_unicode = ( 505 u':path', u':scheme', u':authority', u':method' 506 ) 507 508 @pytest.mark.parametrize('validation_function', validation_functions) 509 @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos) 510 @given(headers=HEADERS_STRATEGY) 511 def test_range_of_acceptable_outputs(self, 512 headers, 513 validation_function, 514 hdr_validation_flags): 515 """ 516 The header validation functions either return the data unchanged 517 or throw a ProtocolError. 518 """ 519 try: 520 assert headers == list(validation_function( 521 headers, hdr_validation_flags)) 522 except h2.exceptions.ProtocolError: 523 assert True 524 525 @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos) 526 def test_invalid_pseudo_headers(self, hdr_validation_flags): 527 headers = [(b':custom', b'value')] 528 with pytest.raises(h2.exceptions.ProtocolError): 529 list(h2.utilities.validate_headers(headers, hdr_validation_flags)) 530 531 @pytest.mark.parametrize('validation_function', validation_functions) 532 @pytest.mark.parametrize( 533 'hdr_validation_flags', hdr_validation_request_headers_no_trailer 534 ) 535 def test_matching_authority_host_headers(self, 536 validation_function, 537 hdr_validation_flags): 538 """ 539 If a header block has :authority and Host headers and they match, 540 the headers should pass through unchanged. 541 """ 542 headers = [ 543 (b':authority', b'example.com'), 544 (b':path', b'/'), 545 (b':scheme', b'https'), 546 (b':method', b'GET'), 547 (b'host', b'example.com'), 548 ] 549 assert headers == list(h2.utilities.validate_headers( 550 headers, hdr_validation_flags 551 )) 552 553 @pytest.mark.parametrize( 554 'hdr_validation_flags', hdr_validation_response_headers 555 ) 556 def test_response_header_without_status(self, hdr_validation_flags): 557 headers = [(b'content-length', b'42')] 558 with pytest.raises(h2.exceptions.ProtocolError): 559 list(h2.utilities.validate_headers(headers, hdr_validation_flags)) 560 561 @pytest.mark.parametrize( 562 'hdr_validation_flags', hdr_validation_request_headers_no_trailer 563 ) 564 @pytest.mark.parametrize( 565 'header_block', 566 ( 567 invalid_request_header_blocks_bytes + 568 invalid_request_header_blocks_unicode 569 ) 570 ) 571 def test_outbound_req_header_missing_pseudo_headers(self, 572 hdr_validation_flags, 573 header_block): 574 with pytest.raises(h2.exceptions.ProtocolError): 575 list( 576 h2.utilities.validate_outbound_headers( 577 header_block, hdr_validation_flags 578 ) 579 ) 580 581 @pytest.mark.parametrize( 582 'hdr_validation_flags', hdr_validation_request_headers_no_trailer 583 ) 584 @pytest.mark.parametrize( 585 'header_block', invalid_request_header_blocks_bytes 586 ) 587 def test_inbound_req_header_missing_pseudo_headers(self, 588 hdr_validation_flags, 589 header_block): 590 with pytest.raises(h2.exceptions.ProtocolError): 591 list( 592 h2.utilities.validate_headers( 593 header_block, hdr_validation_flags 594 ) 595 ) 596 597 @pytest.mark.parametrize( 598 'hdr_validation_flags', hdr_validation_request_headers_no_trailer 599 ) 600 @pytest.mark.parametrize( 601 'invalid_header', 602 forbidden_request_headers_bytes + forbidden_request_headers_unicode 603 ) 604 def test_outbound_req_header_extra_pseudo_headers(self, 605 hdr_validation_flags, 606 invalid_header): 607 """ 608 Outbound request header blocks containing the forbidden request headers 609 fail validation. 610 """ 611 headers = [ 612 (b':path', b'/'), 613 (b':scheme', b'https'), 614 (b':authority', b'google.com'), 615 (b':method', b'GET'), 616 ] 617 headers.append((invalid_header, b'some value')) 618 with pytest.raises(h2.exceptions.ProtocolError): 619 list( 620 h2.utilities.validate_outbound_headers( 621 headers, hdr_validation_flags 622 ) 623 ) 624 625 @pytest.mark.parametrize( 626 'hdr_validation_flags', hdr_validation_request_headers_no_trailer 627 ) 628 @pytest.mark.parametrize( 629 'invalid_header', 630 forbidden_request_headers_bytes 631 ) 632 def test_inbound_req_header_extra_pseudo_headers(self, 633 hdr_validation_flags, 634 invalid_header): 635 """ 636 Inbound request header blocks containing the forbidden request headers 637 fail validation. 638 """ 639 headers = [ 640 (b':path', b'/'), 641 (b':scheme', b'https'), 642 (b':authority', b'google.com'), 643 (b':method', b'GET'), 644 ] 645 headers.append((invalid_header, b'some value')) 646 with pytest.raises(h2.exceptions.ProtocolError): 647 list(h2.utilities.validate_headers(headers, hdr_validation_flags)) 648 649 @pytest.mark.parametrize( 650 'hdr_validation_flags', hdr_validation_response_headers 651 ) 652 @pytest.mark.parametrize( 653 'invalid_header', 654 forbidden_response_headers_bytes + forbidden_response_headers_unicode 655 ) 656 def test_outbound_resp_header_extra_pseudo_headers(self, 657 hdr_validation_flags, 658 invalid_header): 659 """ 660 Outbound response header blocks containing the forbidden response 661 headers fail validation. 662 """ 663 headers = [(b':status', b'200')] 664 headers.append((invalid_header, b'some value')) 665 with pytest.raises(h2.exceptions.ProtocolError): 666 list( 667 h2.utilities.validate_outbound_headers( 668 headers, hdr_validation_flags 669 ) 670 ) 671 672 @pytest.mark.parametrize( 673 'hdr_validation_flags', hdr_validation_response_headers 674 ) 675 @pytest.mark.parametrize( 676 'invalid_header', 677 forbidden_response_headers_bytes 678 ) 679 def test_inbound_resp_header_extra_pseudo_headers(self, 680 hdr_validation_flags, 681 invalid_header): 682 """ 683 Inbound response header blocks containing the forbidden response 684 headers fail validation. 685 """ 686 headers = [(b':status', b'200')] 687 headers.append((invalid_header, b'some value')) 688 with pytest.raises(h2.exceptions.ProtocolError): 689 list(h2.utilities.validate_headers(headers, hdr_validation_flags)) 690 691 692class TestOversizedHeaders(object): 693 """ 694 Tests that oversized header blocks are correctly rejected. This replicates 695 the "HPACK Bomb" attack, and confirms that we're resistant against it. 696 """ 697 request_header_block = [ 698 (b':method', b'GET'), 699 (b':authority', b'example.com'), 700 (b':scheme', b'https'), 701 (b':path', b'/'), 702 ] 703 704 response_header_block = [ 705 (b':status', b'200'), 706 ] 707 708 # The first header block contains a single header that fills the header 709 # table. To do that, we'll give it a single-character header name and a 710 # 4063 byte header value. This will make it exactly the size of the header 711 # table. It must come last, so that it evicts all other headers. 712 # This block must be appended to either a request or response block. 713 first_header_block = [ 714 (b'a', b'a' * 4063), 715 ] 716 717 # The second header "block" is actually a custom HEADERS frame body that 718 # simply repeatedly refers to the first entry for 16kB. Each byte has the 719 # high bit set (0x80), and then uses the remaining 7 bits to encode the 720 # number 62 (0x3e), leading to a repeat of the byte 0xbe. 721 second_header_block = b'\xbe' * 2**14 722 723 server_config = h2.config.H2Configuration(client_side=False) 724 725 def test_hpack_bomb_request(self, frame_factory): 726 """ 727 A HPACK bomb request causes the connection to be torn down with the 728 error code ENHANCE_YOUR_CALM. 729 """ 730 c = h2.connection.H2Connection(config=self.server_config) 731 c.receive_data(frame_factory.preamble()) 732 c.clear_outbound_data_buffer() 733 734 f = frame_factory.build_headers_frame( 735 self.request_header_block + self.first_header_block 736 ) 737 data = f.serialize() 738 c.receive_data(data) 739 740 # Build the attack payload. 741 attack_frame = hyperframe.frame.HeadersFrame(stream_id=3) 742 attack_frame.data = self.second_header_block 743 attack_frame.flags.add('END_HEADERS') 744 data = attack_frame.serialize() 745 746 with pytest.raises(h2.exceptions.DenialOfServiceError): 747 c.receive_data(data) 748 749 expected_frame = frame_factory.build_goaway_frame( 750 last_stream_id=1, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM 751 ) 752 assert c.data_to_send() == expected_frame.serialize() 753 754 def test_hpack_bomb_response(self, frame_factory): 755 """ 756 A HPACK bomb response causes the connection to be torn down with the 757 error code ENHANCE_YOUR_CALM. 758 """ 759 c = h2.connection.H2Connection() 760 c.initiate_connection() 761 c.send_headers( 762 stream_id=1, headers=self.request_header_block 763 ) 764 c.send_headers( 765 stream_id=3, headers=self.request_header_block 766 ) 767 c.clear_outbound_data_buffer() 768 769 f = frame_factory.build_headers_frame( 770 self.response_header_block + self.first_header_block 771 ) 772 data = f.serialize() 773 c.receive_data(data) 774 775 # Build the attack payload. 776 attack_frame = hyperframe.frame.HeadersFrame(stream_id=3) 777 attack_frame.data = self.second_header_block 778 attack_frame.flags.add('END_HEADERS') 779 data = attack_frame.serialize() 780 781 with pytest.raises(h2.exceptions.DenialOfServiceError): 782 c.receive_data(data) 783 784 expected_frame = frame_factory.build_goaway_frame( 785 last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM 786 ) 787 assert c.data_to_send() == expected_frame.serialize() 788 789 def test_hpack_bomb_push(self, frame_factory): 790 """ 791 A HPACK bomb push causes the connection to be torn down with the 792 error code ENHANCE_YOUR_CALM. 793 """ 794 c = h2.connection.H2Connection() 795 c.initiate_connection() 796 c.send_headers( 797 stream_id=1, headers=self.request_header_block 798 ) 799 c.clear_outbound_data_buffer() 800 801 f = frame_factory.build_headers_frame( 802 self.response_header_block + self.first_header_block 803 ) 804 data = f.serialize() 805 c.receive_data(data) 806 807 # Build the attack payload. We need to shrink it by four bytes because 808 # the promised_stream_id consumes four bytes of body. 809 attack_frame = hyperframe.frame.PushPromiseFrame(stream_id=3) 810 attack_frame.promised_stream_id = 2 811 attack_frame.data = self.second_header_block[:-4] 812 attack_frame.flags.add('END_HEADERS') 813 data = attack_frame.serialize() 814 815 with pytest.raises(h2.exceptions.DenialOfServiceError): 816 c.receive_data(data) 817 818 expected_frame = frame_factory.build_goaway_frame( 819 last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM 820 ) 821 assert c.data_to_send() == expected_frame.serialize() 822 823 def test_reject_headers_when_list_size_shrunk(self, frame_factory): 824 """ 825 When we've shrunk the header list size, we reject new header blocks 826 that violate the new size. 827 """ 828 c = h2.connection.H2Connection(config=self.server_config) 829 c.receive_data(frame_factory.preamble()) 830 c.clear_outbound_data_buffer() 831 832 # Receive the first request, which causes no problem. 833 f = frame_factory.build_headers_frame( 834 stream_id=1, 835 headers=self.request_header_block 836 ) 837 data = f.serialize() 838 c.receive_data(data) 839 840 # Now, send a settings change. It's un-ACKed at this time. A new 841 # request arrives, also without incident. 842 c.update_settings({h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 50}) 843 c.clear_outbound_data_buffer() 844 f = frame_factory.build_headers_frame( 845 stream_id=3, 846 headers=self.request_header_block 847 ) 848 data = f.serialize() 849 c.receive_data(data) 850 851 # We get a SETTINGS ACK. 852 f = frame_factory.build_settings_frame({}, ack=True) 853 data = f.serialize() 854 c.receive_data(data) 855 856 # Now a third request comes in. This explodes. 857 f = frame_factory.build_headers_frame( 858 stream_id=5, 859 headers=self.request_header_block 860 ) 861 data = f.serialize() 862 863 with pytest.raises(h2.exceptions.DenialOfServiceError): 864 c.receive_data(data) 865 866 expected_frame = frame_factory.build_goaway_frame( 867 last_stream_id=3, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM 868 ) 869 assert c.data_to_send() == expected_frame.serialize() 870 871 def test_reject_headers_when_table_size_shrunk(self, frame_factory): 872 """ 873 When we've shrunk the header table size, we reject header blocks that 874 do not respect the change. 875 """ 876 c = h2.connection.H2Connection(config=self.server_config) 877 c.receive_data(frame_factory.preamble()) 878 c.clear_outbound_data_buffer() 879 880 # Receive the first request, which causes no problem. 881 f = frame_factory.build_headers_frame( 882 stream_id=1, 883 headers=self.request_header_block 884 ) 885 data = f.serialize() 886 c.receive_data(data) 887 888 # Now, send a settings change. It's un-ACKed at this time. A new 889 # request arrives, also without incident. 890 c.update_settings({h2.settings.SettingCodes.HEADER_TABLE_SIZE: 128}) 891 c.clear_outbound_data_buffer() 892 f = frame_factory.build_headers_frame( 893 stream_id=3, 894 headers=self.request_header_block 895 ) 896 data = f.serialize() 897 c.receive_data(data) 898 899 # We get a SETTINGS ACK. 900 f = frame_factory.build_settings_frame({}, ack=True) 901 data = f.serialize() 902 c.receive_data(data) 903 904 # Now a third request comes in. This explodes, as it does not contain 905 # a dynamic table size update. 906 f = frame_factory.build_headers_frame( 907 stream_id=5, 908 headers=self.request_header_block 909 ) 910 data = f.serialize() 911 912 with pytest.raises(h2.exceptions.ProtocolError): 913 c.receive_data(data) 914 915 expected_frame = frame_factory.build_goaway_frame( 916 last_stream_id=3, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR 917 ) 918 assert c.data_to_send() == expected_frame.serialize() 919 920 def test_reject_headers_exceeding_table_size(self, frame_factory): 921 """ 922 When the remote peer sends a dynamic table size update that exceeds our 923 setting, we reject it. 924 """ 925 c = h2.connection.H2Connection(config=self.server_config) 926 c.receive_data(frame_factory.preamble()) 927 c.clear_outbound_data_buffer() 928 929 # Receive the first request, which causes no problem. 930 f = frame_factory.build_headers_frame( 931 stream_id=1, 932 headers=self.request_header_block 933 ) 934 data = f.serialize() 935 c.receive_data(data) 936 937 # Now a second request comes in that sets the table size too high. 938 # This explodes. 939 frame_factory.change_table_size(c.local_settings.header_table_size + 1) 940 f = frame_factory.build_headers_frame( 941 stream_id=5, 942 headers=self.request_header_block 943 ) 944 data = f.serialize() 945 946 with pytest.raises(h2.exceptions.ProtocolError): 947 c.receive_data(data) 948 949 expected_frame = frame_factory.build_goaway_frame( 950 last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR 951 ) 952 assert c.data_to_send() == expected_frame.serialize() 953