CVE-2021-41136 (Ruby, published 2021-10-12T16:15Z)
Description: value: Puma is a HTTP 1.1 server for Ruby/Rack applications. Prior to versions 5.5.1 and 4.3.9, using `puma` with a proxy which forwards HTTP header values which contain the LF character could allow HTTP request smugggling. A client could smuggle a request through a proxy, causing the proxy to send a response back to another unknown client. The only proxy which has this behavior, as far as the Puma team is aware of, is Apache Traffic Server. If the proxy uses persistent connections and the client adds another request in via HTTP pipelining, the proxy may mistake it as the first request's body. Puma, however, would see it as two requests, and when processing the second request, send back a response that the proxy does not expect. If the proxy has reused the persistent connection to Puma to send another request for a different client, the second response from the first client will be sent to the second client. This vulnerability was patched in Puma 5.5.1 and 4.3.9. As a workaround, do not use Apache Traffic Server with `puma`.
Severity: LOW (CVSS v3 base score: 3.7)
Weakness: CWE-444 \u2014 Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')
The product acts as an intermediary HTTP agent
         (such as a proxy or firewall) in the data flow between two
         entities such as a client and server, but it does not
         interpret malformed HTTP requests or responses in ways that
         are consistent with how the messages will be processed by
         those entities that are at the ultimate destination.
Repository: https://github.com/puma/puma
Files changed:
History.md
ext/puma_http11/http11_parser.c
ext/puma_http11/http11_parser_common.rl
ext/puma_http11/org/jruby/puma/Http11Parser.java
lib/puma/const.rb
test/test_http11.rb
Commit message: Merge pull request from GHSA-48w2-rm65-62xx

* Fix HTTP request smuggling vulnerability

See GHSA-48w2-rm65-62xx or CVE-2021-41136 for more info.

* 4.3.9 release note

* 5.5.1 release note

* 5.5.1

Vulnerable code:
	goto tr25;
#line 442 "ext/puma_http11/http11_parser.c"
	if ( (*p) == 13 )
		goto tr29;
	goto st19;
#line 488 "ext/puma_http11/http11_parser.c"
#line 509 "ext/puma_http11/http11_parser.c"
#line 530 "ext/puma_http11/http11_parser.c"
#line 555 "ext/puma_http11/http11_parser.c"
#line 575 "ext/puma_http11/http11_parser.c"
#line 598 "ext/puma_http11/http11_parser.c"
#line 618 "ext/puma_http11/http11_parser.c"
  field_value = any* >start_value %write_value;
	   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
	   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,
	   65,   90,   32,    0
	    0,    1,    1,    4,    1,    4,    2,    1,    4,    4,    2,    6,
	    1,    1,    0,    6,    0,    6,    0,    0,    2,    2,    4,    1,
	   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
	   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
    PUMA_VERSION = VERSION = "5.5.0".freeze

Fixed code:
## 5.5.1 / 2021-10-12

* Security
  * Do not allow LF as a line ending in a header (CVE-2021-41136)

## 4.3.9 / 2021-10-12

* Security
  * Do not allow LF as a line ending in a header (CVE-2021-41136)

		case 9: goto tr25;
	if ( 33 <= (*p) && (*p) <= 126 )
		goto tr25;
	goto st0;
#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;
#line 495 "ext/puma_http11/http11_parser.c"
#line 516 "ext/puma_http11/http11_parser.c"
#line 537 "ext/puma_http11/http11_parser.c"
#line 562 "ext/puma_http11/http11_parser.c"
#line 582 "ext/puma_http11/http11_parser.c"
#line 605 "ext/puma_http11/http11_parser.c"
#line 625 "ext/puma_http11/http11_parser.c"
  field_value = ( print | "\t" )* >start_value %write_value;
	   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
	   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,
	   65,   90,   32,   36,   95,   45,   46,   48,   57,   65,   90,   32,
	   36,   95,   45,   46,   48,   57,   65,   90,   32,    0
	    0,    1,    1,    4,    1,    4,    3,    2,    4,    4,    2,    6,
	    1,    1,    0,    6,    0,    6,    1,    1,    2,    2,    4,    1,
	   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
	   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
    PUMA_VERSION = VERSION = "5.5.1".freeze

  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