From 436c71807f00e07070902a03f79fd3e130eb6b18 Mon Sep 17 00:00:00 2001 From: Nate Berkopec Date: Sun, 10 Oct 2021 16:02:37 -0600 Subject: [PATCH 1/3] Fix HTTP request smuggling vulnerability See GHSA-48w2-rm65-62xx or CVE-2021-41136 for more info. --- ext/puma_http11/http11_parser.c | 31 +++++---- ext/puma_http11/http11_parser_common.rl | 2 +- .../org/jruby/puma/Http11Parser.java | 68 +++++++++---------- test/test_http11.rb | 30 ++++++++ 4 files changed, 84 insertions(+), 47 deletions(-) diff --git a/ext/puma_http11/http11_parser.c b/ext/puma_http11/http11_parser.c index bf1dd89ab9..932724c12f 100644 --- a/ext/puma_http11/http11_parser.c +++ b/ext/puma_http11/http11_parser.c @@ -428,10 +428,13 @@ case 17: case 18: #line 428 "ext/puma_http11/http11_parser.c" switch( (*p) ) { + case 9: goto tr25; case 13: goto tr26; case 32: goto tr27; } - goto tr25; + if ( 33 <= (*p) && (*p) <= 126 ) + goto tr25; + goto st0; tr25: #line 44 "ext/puma_http11/http11_parser.rl" { MARK(mark, p); } @@ -440,10 +443,14 @@ case 18: if ( ++p == pe ) goto _test_eof19; case 19: -#line 442 "ext/puma_http11/http11_parser.c" - if ( (*p) == 13 ) - goto tr29; - goto st19; +#line 445 "ext/puma_http11/http11_parser.c" + switch( (*p) ) { + case 9: goto st19; + case 13: goto tr29; + } + if ( 32 <= (*p) && (*p) <= 126 ) + goto st19; + goto st0; tr9: #line 51 "ext/puma_http11/http11_parser.rl" { @@ -486,7 +493,7 @@ case 19: if ( ++p == pe ) goto _test_eof20; case 20: -#line 488 "ext/puma_http11/http11_parser.c" +#line 495 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr31; case 60: goto st0; @@ -507,7 +514,7 @@ case 20: if ( ++p == pe ) goto _test_eof21; case 21: -#line 509 "ext/puma_http11/http11_parser.c" +#line 516 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr33; case 60: goto st0; @@ -528,7 +535,7 @@ case 21: if ( ++p == pe ) goto _test_eof22; case 22: -#line 530 "ext/puma_http11/http11_parser.c" +#line 537 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 43: goto st22; case 58: goto st23; @@ -553,7 +560,7 @@ case 22: if ( ++p == pe ) goto _test_eof23; case 23: -#line 555 "ext/puma_http11/http11_parser.c" +#line 562 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr8; case 34: goto st0; @@ -573,7 +580,7 @@ case 23: if ( ++p == pe ) goto _test_eof24; case 24: -#line 575 "ext/puma_http11/http11_parser.c" +#line 582 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr37; case 34: goto st0; @@ -596,7 +603,7 @@ case 24: if ( ++p == pe ) goto _test_eof25; case 25: -#line 598 "ext/puma_http11/http11_parser.c" +#line 605 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr41; case 34: goto st0; @@ -616,7 +623,7 @@ case 25: if ( ++p == pe ) goto _test_eof26; case 26: -#line 618 "ext/puma_http11/http11_parser.c" +#line 625 "ext/puma_http11/http11_parser.c" switch( (*p) ) { case 32: goto tr44; case 34: goto st0; diff --git a/ext/puma_http11/http11_parser_common.rl b/ext/puma_http11/http11_parser_common.rl index ba5f8a56d6..5eba09f2c0 100644 --- a/ext/puma_http11/http11_parser_common.rl +++ b/ext/puma_http11/http11_parser_common.rl @@ -43,7 +43,7 @@ field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field; - field_value = any* >start_value %write_value; + field_value = ( print | "\t" )* >start_value %write_value; message_header = field_name ":" " "* field_value :> CRLF; diff --git a/ext/puma_http11/org/jruby/puma/Http11Parser.java b/ext/puma_http11/org/jruby/puma/Http11Parser.java index a11583b1fa..ecb81ad9d1 100644 --- a/ext/puma_http11/org/jruby/puma/Http11Parser.java +++ b/ext/puma_http11/org/jruby/puma/Http11Parser.java @@ -34,9 +34,9 @@ private static short[] init__puma_parser_key_offsets_0() { return new short [] { 0, 0, 8, 17, 27, 29, 30, 31, 32, 33, 34, 36, - 39, 41, 44, 45, 61, 62, 78, 80, 81, 89, 97, 107, - 115, 124, 132, 140, 149, 158, 167, 176, 185, 194, 203, 212, - 221, 230, 239, 248, 257, 266, 275, 284, 293, 302, 303 + 39, 41, 44, 45, 61, 62, 78, 83, 87, 95, 103, 113, + 121, 130, 138, 146, 155, 164, 173, 182, 191, 200, 209, 218, + 227, 236, 245, 254, 263, 272, 281, 290, 299, 308, 309 }; } @@ -52,14 +52,13 @@ private static char[] init__puma_parser_trans_keys_0() 46, 48, 57, 48, 57, 13, 48, 57, 10, 13, 33, 124, 126, 35, 39, 42, 43, 45, 46, 48, 57, 65, 90, 94, 122, 10, 33, 58, 124, 126, 35, 39, 42, 43, 45, 46, - 48, 57, 65, 90, 94, 122, 13, 32, 13, 32, 60, 62, - 127, 0, 31, 34, 35, 32, 60, 62, 127, 0, 31, 34, - 35, 43, 58, 45, 46, 48, 57, 65, 90, 97, 122, 32, - 34, 35, 60, 62, 127, 0, 31, 32, 34, 35, 60, 62, - 63, 127, 0, 31, 32, 34, 35, 60, 62, 127, 0, 31, - 32, 34, 35, 60, 62, 127, 0, 31, 32, 36, 95, 45, - 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, - 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, + 48, 57, 65, 90, 94, 122, 9, 13, 32, 33, 126, 9, + 13, 32, 126, 32, 60, 62, 127, 0, 31, 34, 35, 32, + 60, 62, 127, 0, 31, 34, 35, 43, 58, 45, 46, 48, + 57, 65, 90, 97, 122, 32, 34, 35, 60, 62, 127, 0, + 31, 32, 34, 35, 60, 62, 63, 127, 0, 31, 32, 34, + 35, 60, 62, 127, 0, 31, 32, 34, 35, 60, 62, 127, + 0, 31, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, @@ -71,7 +70,8 @@ private static char[] init__puma_parser_trans_keys_0() 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57, - 65, 90, 32, 0 + 65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32, + 36, 95, 45, 46, 48, 57, 65, 90, 32, 0 }; } @@ -82,7 +82,7 @@ private static byte[] init__puma_parser_single_lengths_0() { return new byte [] { 0, 2, 3, 4, 2, 1, 1, 1, 1, 1, 0, 1, - 0, 1, 1, 4, 1, 4, 2, 1, 4, 4, 2, 6, + 0, 1, 1, 4, 1, 4, 3, 2, 4, 4, 2, 6, 7, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 0 }; @@ -95,7 +95,7 @@ private static byte[] init__puma_parser_range_lengths_0() { return new byte [] { 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 6, 0, 6, 0, 0, 2, 2, 4, 1, + 1, 1, 0, 6, 0, 6, 1, 1, 2, 2, 4, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0 }; @@ -108,9 +108,9 @@ private static short[] init__puma_parser_index_offsets_0() { return new short [] { 0, 0, 6, 13, 21, 24, 26, 28, 30, 32, 34, 36, - 39, 41, 44, 46, 57, 59, 70, 73, 75, 82, 89, 96, - 104, 113, 121, 129, 136, 143, 150, 157, 164, 171, 178, 185, - 192, 199, 206, 213, 220, 227, 234, 241, 248, 255, 257 + 39, 41, 44, 46, 57, 59, 70, 75, 79, 86, 93, 100, + 108, 117, 125, 133, 140, 147, 154, 161, 168, 175, 182, 189, + 196, 203, 210, 217, 224, 231, 238, 245, 252, 259, 261 }; } @@ -125,23 +125,23 @@ private static byte[] init__puma_parser_indicies_0() 10, 1, 11, 1, 12, 1, 13, 1, 14, 1, 15, 1, 16, 15, 1, 17, 1, 18, 17, 1, 19, 1, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 22, 1, 23, - 24, 23, 23, 23, 23, 23, 23, 23, 23, 1, 26, 27, - 25, 29, 28, 30, 1, 1, 1, 1, 1, 31, 32, 1, - 1, 1, 1, 1, 33, 34, 35, 34, 34, 34, 34, 1, - 8, 1, 9, 1, 1, 1, 1, 35, 36, 1, 38, 1, - 1, 39, 1, 1, 37, 40, 1, 42, 1, 1, 1, 1, - 41, 43, 1, 45, 1, 1, 1, 1, 44, 2, 46, 46, - 46, 46, 46, 1, 2, 47, 47, 47, 47, 47, 1, 2, - 48, 48, 48, 48, 48, 1, 2, 49, 49, 49, 49, 49, - 1, 2, 50, 50, 50, 50, 50, 1, 2, 51, 51, 51, - 51, 51, 1, 2, 52, 52, 52, 52, 52, 1, 2, 53, - 53, 53, 53, 53, 1, 2, 54, 54, 54, 54, 54, 1, - 2, 55, 55, 55, 55, 55, 1, 2, 56, 56, 56, 56, - 56, 1, 2, 57, 57, 57, 57, 57, 1, 2, 58, 58, - 58, 58, 58, 1, 2, 59, 59, 59, 59, 59, 1, 2, - 60, 60, 60, 60, 60, 1, 2, 61, 61, 61, 61, 61, - 1, 2, 62, 62, 62, 62, 62, 1, 2, 63, 63, 63, - 63, 63, 1, 2, 1, 1, 0 + 24, 23, 23, 23, 23, 23, 23, 23, 23, 1, 25, 26, + 27, 25, 1, 28, 29, 28, 1, 30, 1, 1, 1, 1, + 1, 31, 32, 1, 1, 1, 1, 1, 33, 34, 35, 34, + 34, 34, 34, 1, 8, 1, 9, 1, 1, 1, 1, 35, + 36, 1, 38, 1, 1, 39, 1, 1, 37, 40, 1, 42, + 1, 1, 1, 1, 41, 43, 1, 45, 1, 1, 1, 1, + 44, 2, 46, 46, 46, 46, 46, 1, 2, 47, 47, 47, + 47, 47, 1, 2, 48, 48, 48, 48, 48, 1, 2, 49, + 49, 49, 49, 49, 1, 2, 50, 50, 50, 50, 50, 1, + 2, 51, 51, 51, 51, 51, 1, 2, 52, 52, 52, 52, + 52, 1, 2, 53, 53, 53, 53, 53, 1, 2, 54, 54, + 54, 54, 54, 1, 2, 55, 55, 55, 55, 55, 1, 2, + 56, 56, 56, 56, 56, 1, 2, 57, 57, 57, 57, 57, + 1, 2, 58, 58, 58, 58, 58, 1, 2, 59, 59, 59, + 59, 59, 1, 2, 60, 60, 60, 60, 60, 1, 2, 61, + 61, 61, 61, 61, 1, 2, 62, 62, 62, 62, 62, 1, + 2, 63, 63, 63, 63, 63, 1, 2, 1, 1, 0 }; } diff --git a/test/test_http11.rb b/test/test_http11.rb index be01404c75..abdd1d64e9 100644 --- a/test/test_http11.rb +++ b/test/test_http11.rb @@ -208,4 +208,34 @@ def test_trims_whitespace_from_headers assert_equal "Strip This", req["HTTP_X_STRIP_ME"] end + + def test_newline_smuggler + parser = Puma::HttpParser.new + req = {} + http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\nDummy2: y\r\n\r\n" + + parser.execute(req, http, 0) rescue nil # We test the raise elsewhere. + + assert parser.error?, "Parser SHOULD have error" + end + + def test_newline_smuggler_two + parser = Puma::HttpParser.new + req = {} + http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\r\nDummy: y\nDummy2: z\r\n\r\n" + + parser.execute(req, http, 0) rescue nil + + assert parser.error?, "Parser SHOULD have error" + end + + def test_htab_in_header_val + parser = Puma::HttpParser.new + req = {} + http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: Valid\tValue\r\n\r\n" + + parser.execute(req, http, 0) + + assert_equal "Valid\tValue", req['HTTP_DUMMY'] + end end From 3167548596648502db18f1cfbb6c8fec626e3edf Mon Sep 17 00:00:00 2001 From: Nate Berkopec Date: Tue, 12 Oct 2021 08:01:14 -0600 Subject: [PATCH 2/3] 4.3.9 release note --- History.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/History.md b/History.md index 9b75c9ff6f..54c2373fcd 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +## 4.3.9 / 2021-10-12 + +* Security + * Do not allow LF as a line ending in a header (CVE-2021-41136) + ## 4.3.8 / 2021-05-11 * Security From 011d0aa1a8db9e980226c8cd90fa0bb4c6ccfde4 Mon Sep 17 00:00:00 2001 From: Nate Berkopec Date: Tue, 12 Oct 2021 08:38:22 -0600 Subject: [PATCH 3/3] 4.3.9 --- lib/puma/const.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puma/const.rb b/lib/puma/const.rb index f583429f07..af84919ea5 100644 --- a/lib/puma/const.rb +++ b/lib/puma/const.rb @@ -100,7 +100,7 @@ class UnsupportedOption < RuntimeError # too taxing on performance. module Const - PUMA_VERSION = VERSION = "4.3.8".freeze + PUMA_VERSION = VERSION = "4.3.9".freeze CODE_NAME = "Mysterious Traveller".freeze PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze