diff options
author | Brad Fitzpatrick <bradfitz@golang.org> | 2011-08-09 10:55:14 -0700 |
---|---|---|
committer | Brad Fitzpatrick <bradfitz@golang.org> | 2011-08-09 10:55:14 -0700 |
commit | d24a9785eb871a7addb022bca6aedf7842600032 (patch) | |
tree | 25960f3e664f1a392b1edd6c056dfefea1c7e057 | |
parent | 29df7bb7ecf311f3c863754fb8215396923a0261 (diff) | |
download | go-d24a9785eb871a7addb022bca6aedf7842600032.tar.gz go-d24a9785eb871a7addb022bca6aedf7842600032.zip |
http: configurable and default request header size limit
This addresses the biggest DoS in issue 2093
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4841050
-rw-r--r-- | src/pkg/http/serve_test.go | 22 | ||||
-rw-r--r-- | src/pkg/http/server.go | 64 |
2 files changed, 71 insertions, 15 deletions
diff --git a/src/pkg/http/serve_test.go b/src/pkg/http/serve_test.go index 9c8a122ff0..2725c3b428 100644 --- a/src/pkg/http/serve_test.go +++ b/src/pkg/http/serve_test.go @@ -873,6 +873,28 @@ func TestStripPrefix(t *testing.T) { } } +func TestRequestLimit(t *testing.T) { + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + t.Fatalf("didn't expect to get request in Handler") + })) + defer ts.Close() + req, _ := NewRequest("GET", ts.URL, nil) + var bytesPerHeader = len("header12345: val12345\r\n") + for i := 0; i < ((DefaultMaxHeaderBytes+4096)/bytesPerHeader)+1; i++ { + req.Header.Set(fmt.Sprintf("header%05d", i), fmt.Sprintf("val%05d", i)) + } + res, err := DefaultClient.Do(req) + if err != nil { + // Some HTTP clients may fail on this undefined behavior (server replying and + // closing the connection while the request is still being written), but + // we do support it (at least currently), so we expect a response below. + t.Fatalf("Do: %v", err) + } + if res.StatusCode != 400 { + t.Fatalf("expected 400 response status; got: %d %s", res.StatusCode, res.Status) + } +} + type errorListener struct { errs []os.Error } diff --git a/src/pkg/http/server.go b/src/pkg/http/server.go index 96547c4eff..1955b67e65 100644 --- a/src/pkg/http/server.go +++ b/src/pkg/http/server.go @@ -94,9 +94,10 @@ type Hijacker interface { // A conn represents the server side of an HTTP connection. type conn struct { remoteAddr string // network address of remote side - handler Handler // request handler + server *Server // the Server on which the connection arrived rwc net.Conn // i/o connection - buf *bufio.ReadWriter // buffered rwc + lr *io.LimitedReader // io.LimitReader(rwc) + buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio->limitReader->rwc hijacked bool // connection has been hijacked by handler tlsState *tls.ConnectionState // or nil when not using TLS body []byte @@ -143,14 +144,18 @@ func (r *response) ReadFrom(src io.Reader) (n int64, err os.Error) { return io.Copy(writerOnly{r}, src) } +// noLimit is an effective infinite upper bound for io.LimitedReader +const noLimit int64 = (1 << 63) - 1 + // Create new connection from rwc. -func newConn(rwc net.Conn, handler Handler) (c *conn, err os.Error) { +func (srv *Server) newConn(rwc net.Conn) (c *conn, err os.Error) { c = new(conn) c.remoteAddr = rwc.RemoteAddr().String() - c.handler = handler + c.server = srv c.rwc = rwc c.body = make([]byte, sniffLen) - br := bufio.NewReader(rwc) + c.lr = io.LimitReader(rwc, noLimit).(*io.LimitedReader) + br := bufio.NewReader(c.lr) bw := bufio.NewWriter(rwc) c.buf = bufio.NewReadWriter(br, bw) @@ -163,6 +168,18 @@ func newConn(rwc net.Conn, handler Handler) (c *conn, err os.Error) { return c, nil } +// DefaultMaxHeaderBytes is the maximum permitted size of the headers +// in an HTTP request. +// This can be overridden by setting Server.MaxHeaderBytes. +const DefaultMaxHeaderBytes = 1 << 20 // 1 MB + +func (srv *Server) maxHeaderBytes() int { + if srv.MaxHeaderBytes > 0 { + return srv.MaxHeaderBytes + } + return DefaultMaxHeaderBytes +} + // wrapper around io.ReaderCloser which on first read, sends an // HTTP/1.1 100 Continue header type expectContinueReader struct { @@ -194,15 +211,22 @@ func (ecr *expectContinueReader) Close() os.Error { // It is like time.RFC1123 but hard codes GMT as the time zone. const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" +var errTooLarge = os.NewError("http: request too large") + // Read next request from connection. func (c *conn) readRequest() (w *response, err os.Error) { if c.hijacked { return nil, ErrHijacked } + c.lr.N = int64(c.server.maxHeaderBytes()) + 4096 /* bufio slop */ var req *Request if req, err = ReadRequest(c.buf.Reader); err != nil { + if c.lr.N == 0 { + return nil, errTooLarge + } return nil, err } + c.lr.N = noLimit req.RemoteAddr = c.remoteAddr req.TLS = c.tlsState @@ -567,6 +591,14 @@ func (c *conn) serve() { for { w, err := c.readRequest() if err != nil { + if err == errTooLarge { + // Their HTTP client may or may not be + // able to read this if we're + // responding to them and hanging up + // while they're still writing their + // request. Undefined behavior. + fmt.Fprintf(c.rwc, "HTTP/1.1 400 Request Too Large\r\n\r\n") + } break } @@ -603,12 +635,17 @@ func (c *conn) serve() { break } + handler := c.server.Handler + if handler == nil { + handler = DefaultServeMux + } + // HTTP cannot have multiple simultaneous active requests.[*] // Until the server replies to this request, it can't read another, // so we might as well run the handler in this goroutine. // [*] Not strictly true: HTTP pipelining. We could let them all process // in parallel even if their responses need to be serialized. - c.handler.ServeHTTP(w, w.req) + handler.ServeHTTP(w, w.req) if c.hijacked { return } @@ -906,10 +943,11 @@ func Serve(l net.Listener, handler Handler) os.Error { // A Server defines parameters for running an HTTP server. type Server struct { - Addr string // TCP address to listen on, ":http" if empty - Handler Handler // handler to invoke, http.DefaultServeMux if nil - ReadTimeout int64 // the net.Conn.SetReadTimeout value for new connections - WriteTimeout int64 // the net.Conn.SetWriteTimeout value for new connections + Addr string // TCP address to listen on, ":http" if empty + Handler Handler // handler to invoke, http.DefaultServeMux if nil + ReadTimeout int64 // the net.Conn.SetReadTimeout value for new connections + WriteTimeout int64 // the net.Conn.SetWriteTimeout value for new connections + MaxHeaderBytes int // maximum size of request headers, DefaultMaxHeaderBytes if 0 } // ListenAndServe listens on the TCP network address srv.Addr and then @@ -932,10 +970,6 @@ func (srv *Server) ListenAndServe() os.Error { // then call srv.Handler to reply to them. func (srv *Server) Serve(l net.Listener) os.Error { defer l.Close() - handler := srv.Handler - if handler == nil { - handler = DefaultServeMux - } for { rw, e := l.Accept() if e != nil { @@ -951,7 +985,7 @@ func (srv *Server) Serve(l net.Listener) os.Error { if srv.WriteTimeout != 0 { rw.SetWriteTimeout(srv.WriteTimeout) } - c, err := newConn(rw, handler) + c, err := srv.newConn(rw) if err != nil { continue } |