Skip to content

Commit fb6ad8f

Browse files
authored
4.3.9 (#2721)
* Fix HTTP request smuggling vulnerability See GHSA-48w2-rm65-62xx or CVE-2021-41136 for more info. * 4.3.9 release note * 4.3.9
1 parent b911c13 commit fb6ad8f

File tree

6 files changed

+90
-48
lines changed

6 files changed

+90
-48
lines changed

History.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 4.3.9 / 2021-10-12
2+
3+
* Security
4+
* Do not allow LF as a line ending in a header (CVE-2021-41136)
5+
16
## 4.3.8 / 2021-05-11
27

38
* Security

ext/puma_http11/http11_parser.c

+19-12
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,13 @@ case 17:
428428
case 18:
429429
#line 428 "ext/puma_http11/http11_parser.c"
430430
switch( (*p) ) {
431+
case 9: goto tr25;
431432
case 13: goto tr26;
432433
case 32: goto tr27;
433434
}
434-
goto tr25;
435+
if ( 33 <= (*p) && (*p) <= 126 )
436+
goto tr25;
437+
goto st0;
435438
tr25:
436439
#line 44 "ext/puma_http11/http11_parser.rl"
437440
{ MARK(mark, p); }
@@ -440,10 +443,14 @@ case 18:
440443
if ( ++p == pe )
441444
goto _test_eof19;
442445
case 19:
443-
#line 442 "ext/puma_http11/http11_parser.c"
444-
if ( (*p) == 13 )
445-
goto tr29;
446-
goto st19;
446+
#line 445 "ext/puma_http11/http11_parser.c"
447+
switch( (*p) ) {
448+
case 9: goto st19;
449+
case 13: goto tr29;
450+
}
451+
if ( 32 <= (*p) && (*p) <= 126 )
452+
goto st19;
453+
goto st0;
447454
tr9:
448455
#line 51 "ext/puma_http11/http11_parser.rl"
449456
{
@@ -486,7 +493,7 @@ case 19:
486493
if ( ++p == pe )
487494
goto _test_eof20;
488495
case 20:
489-
#line 488 "ext/puma_http11/http11_parser.c"
496+
#line 495 "ext/puma_http11/http11_parser.c"
490497
switch( (*p) ) {
491498
case 32: goto tr31;
492499
case 60: goto st0;
@@ -507,7 +514,7 @@ case 20:
507514
if ( ++p == pe )
508515
goto _test_eof21;
509516
case 21:
510-
#line 509 "ext/puma_http11/http11_parser.c"
517+
#line 516 "ext/puma_http11/http11_parser.c"
511518
switch( (*p) ) {
512519
case 32: goto tr33;
513520
case 60: goto st0;
@@ -528,7 +535,7 @@ case 21:
528535
if ( ++p == pe )
529536
goto _test_eof22;
530537
case 22:
531-
#line 530 "ext/puma_http11/http11_parser.c"
538+
#line 537 "ext/puma_http11/http11_parser.c"
532539
switch( (*p) ) {
533540
case 43: goto st22;
534541
case 58: goto st23;
@@ -553,7 +560,7 @@ case 22:
553560
if ( ++p == pe )
554561
goto _test_eof23;
555562
case 23:
556-
#line 555 "ext/puma_http11/http11_parser.c"
563+
#line 562 "ext/puma_http11/http11_parser.c"
557564
switch( (*p) ) {
558565
case 32: goto tr8;
559566
case 34: goto st0;
@@ -573,7 +580,7 @@ case 23:
573580
if ( ++p == pe )
574581
goto _test_eof24;
575582
case 24:
576-
#line 575 "ext/puma_http11/http11_parser.c"
583+
#line 582 "ext/puma_http11/http11_parser.c"
577584
switch( (*p) ) {
578585
case 32: goto tr37;
579586
case 34: goto st0;
@@ -596,7 +603,7 @@ case 24:
596603
if ( ++p == pe )
597604
goto _test_eof25;
598605
case 25:
599-
#line 598 "ext/puma_http11/http11_parser.c"
606+
#line 605 "ext/puma_http11/http11_parser.c"
600607
switch( (*p) ) {
601608
case 32: goto tr41;
602609
case 34: goto st0;
@@ -616,7 +623,7 @@ case 25:
616623
if ( ++p == pe )
617624
goto _test_eof26;
618625
case 26:
619-
#line 618 "ext/puma_http11/http11_parser.c"
626+
#line 625 "ext/puma_http11/http11_parser.c"
620627
switch( (*p) ) {
621628
case 32: goto tr44;
622629
case 34: goto st0;

ext/puma_http11/http11_parser_common.rl

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field;
4545

46-
field_value = any* >start_value %write_value;
46+
field_value = ( print | "\t" )* >start_value %write_value;
4747

4848
message_header = field_name ":" " "* field_value :> CRLF;
4949

ext/puma_http11/org/jruby/puma/Http11Parser.java

+34-34
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ private static short[] init__puma_parser_key_offsets_0()
3434
{
3535
return new short [] {
3636
0, 0, 8, 17, 27, 29, 30, 31, 32, 33, 34, 36,
37-
39, 41, 44, 45, 61, 62, 78, 80, 81, 89, 97, 107,
38-
115, 124, 132, 140, 149, 158, 167, 176, 185, 194, 203, 212,
39-
221, 230, 239, 248, 257, 266, 275, 284, 293, 302, 303
37+
39, 41, 44, 45, 61, 62, 78, 83, 87, 95, 103, 113,
38+
121, 130, 138, 146, 155, 164, 173, 182, 191, 200, 209, 218,
39+
227, 236, 245, 254, 263, 272, 281, 290, 299, 308, 309
4040
};
4141
}
4242

@@ -52,14 +52,13 @@ private static char[] init__puma_parser_trans_keys_0()
5252
46, 48, 57, 48, 57, 13, 48, 57, 10, 13, 33, 124,
5353
126, 35, 39, 42, 43, 45, 46, 48, 57, 65, 90, 94,
5454
122, 10, 33, 58, 124, 126, 35, 39, 42, 43, 45, 46,
55-
48, 57, 65, 90, 94, 122, 13, 32, 13, 32, 60, 62,
56-
127, 0, 31, 34, 35, 32, 60, 62, 127, 0, 31, 34,
57-
35, 43, 58, 45, 46, 48, 57, 65, 90, 97, 122, 32,
58-
34, 35, 60, 62, 127, 0, 31, 32, 34, 35, 60, 62,
59-
63, 127, 0, 31, 32, 34, 35, 60, 62, 127, 0, 31,
60-
32, 34, 35, 60, 62, 127, 0, 31, 32, 36, 95, 45,
61-
46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57,
62-
65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32,
55+
48, 57, 65, 90, 94, 122, 9, 13, 32, 33, 126, 9,
56+
13, 32, 126, 32, 60, 62, 127, 0, 31, 34, 35, 32,
57+
60, 62, 127, 0, 31, 34, 35, 43, 58, 45, 46, 48,
58+
57, 65, 90, 97, 122, 32, 34, 35, 60, 62, 127, 0,
59+
31, 32, 34, 35, 60, 62, 63, 127, 0, 31, 32, 34,
60+
35, 60, 62, 127, 0, 31, 32, 34, 35, 60, 62, 127,
61+
0, 31, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32,
6362
36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45,
6463
46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57,
6564
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()
7170
65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32,
7271
36, 95, 45, 46, 48, 57, 65, 90, 32, 36, 95, 45,
7372
46, 48, 57, 65, 90, 32, 36, 95, 45, 46, 48, 57,
74-
65, 90, 32, 0
73+
65, 90, 32, 36, 95, 45, 46, 48, 57, 65, 90, 32,
74+
36, 95, 45, 46, 48, 57, 65, 90, 32, 0
7575
};
7676
}
7777

@@ -82,7 +82,7 @@ private static byte[] init__puma_parser_single_lengths_0()
8282
{
8383
return new byte [] {
8484
0, 2, 3, 4, 2, 1, 1, 1, 1, 1, 0, 1,
85-
0, 1, 1, 4, 1, 4, 2, 1, 4, 4, 2, 6,
85+
0, 1, 1, 4, 1, 4, 3, 2, 4, 4, 2, 6,
8686
7, 6, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3,
8787
3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 0
8888
};
@@ -95,7 +95,7 @@ private static byte[] init__puma_parser_range_lengths_0()
9595
{
9696
return new byte [] {
9797
0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 1, 1,
98-
1, 1, 0, 6, 0, 6, 0, 0, 2, 2, 4, 1,
98+
1, 1, 0, 6, 0, 6, 1, 1, 2, 2, 4, 1,
9999
1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
100100
3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0
101101
};
@@ -108,9 +108,9 @@ private static short[] init__puma_parser_index_offsets_0()
108108
{
109109
return new short [] {
110110
0, 0, 6, 13, 21, 24, 26, 28, 30, 32, 34, 36,
111-
39, 41, 44, 46, 57, 59, 70, 73, 75, 82, 89, 96,
112-
104, 113, 121, 129, 136, 143, 150, 157, 164, 171, 178, 185,
113-
192, 199, 206, 213, 220, 227, 234, 241, 248, 255, 257
111+
39, 41, 44, 46, 57, 59, 70, 75, 79, 86, 93, 100,
112+
108, 117, 125, 133, 140, 147, 154, 161, 168, 175, 182, 189,
113+
196, 203, 210, 217, 224, 231, 238, 245, 252, 259, 261
114114
};
115115
}
116116

@@ -125,23 +125,23 @@ private static byte[] init__puma_parser_indicies_0()
125125
10, 1, 11, 1, 12, 1, 13, 1, 14, 1, 15, 1,
126126
16, 15, 1, 17, 1, 18, 17, 1, 19, 1, 20, 21,
127127
21, 21, 21, 21, 21, 21, 21, 21, 1, 22, 1, 23,
128-
24, 23, 23, 23, 23, 23, 23, 23, 23, 1, 26, 27,
129-
25, 29, 28, 30, 1, 1, 1, 1, 1, 31, 32, 1,
130-
1, 1, 1, 1, 33, 34, 35, 34, 34, 34, 34, 1,
131-
8, 1, 9, 1, 1, 1, 1, 35, 36, 1, 38, 1,
132-
1, 39, 1, 1, 37, 40, 1, 42, 1, 1, 1, 1,
133-
41, 43, 1, 45, 1, 1, 1, 1, 44, 2, 46, 46,
134-
46, 46, 46, 1, 2, 47, 47, 47, 47, 47, 1, 2,
135-
48, 48, 48, 48, 48, 1, 2, 49, 49, 49, 49, 49,
136-
1, 2, 50, 50, 50, 50, 50, 1, 2, 51, 51, 51,
137-
51, 51, 1, 2, 52, 52, 52, 52, 52, 1, 2, 53,
138-
53, 53, 53, 53, 1, 2, 54, 54, 54, 54, 54, 1,
139-
2, 55, 55, 55, 55, 55, 1, 2, 56, 56, 56, 56,
140-
56, 1, 2, 57, 57, 57, 57, 57, 1, 2, 58, 58,
141-
58, 58, 58, 1, 2, 59, 59, 59, 59, 59, 1, 2,
142-
60, 60, 60, 60, 60, 1, 2, 61, 61, 61, 61, 61,
143-
1, 2, 62, 62, 62, 62, 62, 1, 2, 63, 63, 63,
144-
63, 63, 1, 2, 1, 1, 0
128+
24, 23, 23, 23, 23, 23, 23, 23, 23, 1, 25, 26,
129+
27, 25, 1, 28, 29, 28, 1, 30, 1, 1, 1, 1,
130+
1, 31, 32, 1, 1, 1, 1, 1, 33, 34, 35, 34,
131+
34, 34, 34, 1, 8, 1, 9, 1, 1, 1, 1, 35,
132+
36, 1, 38, 1, 1, 39, 1, 1, 37, 40, 1, 42,
133+
1, 1, 1, 1, 41, 43, 1, 45, 1, 1, 1, 1,
134+
44, 2, 46, 46, 46, 46, 46, 1, 2, 47, 47, 47,
135+
47, 47, 1, 2, 48, 48, 48, 48, 48, 1, 2, 49,
136+
49, 49, 49, 49, 1, 2, 50, 50, 50, 50, 50, 1,
137+
2, 51, 51, 51, 51, 51, 1, 2, 52, 52, 52, 52,
138+
52, 1, 2, 53, 53, 53, 53, 53, 1, 2, 54, 54,
139+
54, 54, 54, 1, 2, 55, 55, 55, 55, 55, 1, 2,
140+
56, 56, 56, 56, 56, 1, 2, 57, 57, 57, 57, 57,
141+
1, 2, 58, 58, 58, 58, 58, 1, 2, 59, 59, 59,
142+
59, 59, 1, 2, 60, 60, 60, 60, 60, 1, 2, 61,
143+
61, 61, 61, 61, 1, 2, 62, 62, 62, 62, 62, 1,
144+
2, 63, 63, 63, 63, 63, 1, 2, 1, 1, 0
145145
};
146146
}
147147

lib/puma/const.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class UnsupportedOption < RuntimeError
100100
# too taxing on performance.
101101
module Const
102102

103-
PUMA_VERSION = VERSION = "4.3.8".freeze
103+
PUMA_VERSION = VERSION = "4.3.9".freeze
104104
CODE_NAME = "Mysterious Traveller".freeze
105105
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106106

test/test_http11.rb

+30
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,34 @@ def test_trims_whitespace_from_headers
208208

209209
assert_equal "Strip This", req["HTTP_X_STRIP_ME"]
210210
end
211+
212+
def test_newline_smuggler
213+
parser = Puma::HttpParser.new
214+
req = {}
215+
http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\nDummy2: y\r\n\r\n"
216+
217+
parser.execute(req, http, 0) rescue nil # We test the raise elsewhere.
218+
219+
assert parser.error?, "Parser SHOULD have error"
220+
end
221+
222+
def test_newline_smuggler_two
223+
parser = Puma::HttpParser.new
224+
req = {}
225+
http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: x\r\nDummy: y\nDummy2: z\r\n\r\n"
226+
227+
parser.execute(req, http, 0) rescue nil
228+
229+
assert parser.error?, "Parser SHOULD have error"
230+
end
231+
232+
def test_htab_in_header_val
233+
parser = Puma::HttpParser.new
234+
req = {}
235+
http = "GET / HTTP/1.1\r\nHost: localhost:8080\r\nDummy: Valid\tValue\r\n\r\n"
236+
237+
parser.execute(req, http, 0)
238+
239+
assert_equal "Valid\tValue", req['HTTP_DUMMY']
240+
end
211241
end

0 commit comments

Comments
 (0)