The recent DoS attack on Apache is caused by sending in a malformed Range header. I decided to send the same header into Go’s range header parser and see what happened. It passed with flying colors, giving the “invalid range” error, which would result in the Go webserver sending back HTTP response code 416 to the HTTP client.

I didn’t test this under load (which is part of what the DoS was doing), but my quick reading of parseRange leads me to believe that the only effect of sending Range headers in like this is that garbage is created under control of the attacker (for example, due to strings.Split(s[len(b):], “,”)). Of course, that’s bad and should be limited. However, this risk is no greater than other places in the server. For example an attacker could send an unlimited number of headers, or headers of unlimited length. This is already mentioned in the net/textproto package’s reader.go.

I did notice something strange while looking into this, which is that there’s a constant named maxHeaderLines (1024) that is never referenced. This is probably because the (incorrectly non-plurally-named) textproto.ReadMIMEHeader is now in charge of reading all the headers, but it does not accept a max headers parameter.

The fact that this particular constructed header causes no problem is no great surprise, because this attack was highly targeted at Apache. But it is nice to see that Go’s webserver is already (somewhat) hardened against this type of attack.

PS: Here’s a diff showing how I tested this. It might be interesting for beginner Go folks to see how to generate test data at test time.

diff --git a/src/pkg/http/range_test.go b/src/pkg/http/range_test.go --- a/src/pkg/http/range_test.go +++ b/src/pkg/http/range_test.go @@ -6,13 +6,16 @@ import ( "testing" + "fmt" ) -var ParseRangeTests = []struct { +type testDef struct { s string length int64 r []httpRange -}{ +} + +var ParseRangeTests = []testDef{ {"", 0, nil}, {"foo", 0, nil}, {"bytes=", 0, nil}, @@ -34,6 +37,16 @@ {"bytes=500-700,601-999", 10000, []httpRange{{500, 201}, {601, 399}}}, } +func init() { + var p string + for k:=0 ; k<1300 ; k++ { + p += ",5-" + p += fmt.Sprintf("%d", k) + } + x := "bytes=0-" + p + ParseRangeTests = append(ParseRangeTests, testDef{ x, 10, []httpRange{} }) +} + func TestParseRange(t *testing.T) { for _, test := range ParseRangeTests { r := test.r

As set up right now, it won't pass. This is because I wanted to see the exact error it was returning. If you change the []httpRange{} to nil, it will pass.

PPS: Looks like someone else has been thinking about this as well.