Commit 57efc9c3 authored by Olivier Poitrey's avatar Olivier Poitrey Committed by Tom Bergan

http2: reject DATA frame before HEADERS frame

If the server returns a DATA frame before a HEADERS frame, the client
must close the connection with a PROTOCOL_ERROR as describe in the
section 8.1.2.6.

The current implementation does not reject such sequence and panics
trying to write on the non-initialized bufPipe.

Fixes golang/go#21466

Change-Id: I55755dba259d026529a031e9ff82c915f5e4afae
Reviewed-on: https://go-review.googlesource.com/56770Reviewed-by: 's avatarTom Bergan <tombergan@google.com>
Run-TryBot: Tom Bergan <tombergan@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 1c05540f
...@@ -1789,6 +1789,14 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error { ...@@ -1789,6 +1789,14 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
} }
return nil return nil
} }
if !cs.firstByte {
cc.logf("protocol error: received DATA before a HEADERS frame")
rl.endStreamError(cs, StreamError{
StreamID: f.StreamID,
Code: ErrCodeProtocol,
})
return nil
}
if f.Length > 0 { if f.Length > 0 {
// Check connection-level flow control. // Check connection-level flow control.
cc.mu.Lock() cc.mu.Lock()
......
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
"net/http/httptest"
"net/url" "net/url"
"os" "os"
"reflect" "reflect"
...@@ -3036,6 +3037,60 @@ func TestTransportRetryHasLimit(t *testing.T) { ...@@ -3036,6 +3037,60 @@ func TestTransportRetryHasLimit(t *testing.T) {
ct.run() ct.run()
} }
func TestTransportResponseDataBeforeHeaders(t *testing.T) {
ct := newClientTester(t)
ct.client = func() error {
defer ct.cc.(*net.TCPConn).CloseWrite()
req := httptest.NewRequest("GET", "https://dummy.tld/", nil)
// First request is normal to ensure the check is per stream and not per connection.
_, err := ct.tr.RoundTrip(req)
if err != nil {
return fmt.Errorf("RoundTrip expected no error, got: %v", err)
}
// Second request returns a DATA frame with no HEADERS.
resp, err := ct.tr.RoundTrip(req)
if err == nil {
return fmt.Errorf("RoundTrip expected error, got response: %+v", resp)
}
if err, ok := err.(StreamError); !ok || err.Code != ErrCodeProtocol {
return fmt.Errorf("expected stream PROTOCOL_ERROR, got: %v", err)
}
return nil
}
ct.server = func() error {
ct.greet()
for {
f, err := ct.fr.ReadFrame()
if err == io.EOF {
return nil
} else if err != nil {
return err
}
switch f := f.(type) {
case *WindowUpdateFrame, *SettingsFrame:
case *HeadersFrame:
switch f.StreamID {
case 1:
// Send a valid response to first request.
var buf bytes.Buffer
enc := hpack.NewEncoder(&buf)
enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
ct.fr.WriteHeaders(HeadersFrameParam{
StreamID: f.StreamID,
EndHeaders: true,
EndStream: true,
BlockFragment: buf.Bytes(),
})
case 3:
ct.fr.WriteData(f.StreamID, true, []byte("payload"))
}
default:
return fmt.Errorf("Unexpected client frame %v", f)
}
}
}
ct.run()
}
func TestTransportRequestsStallAtServerLimit(t *testing.T) { func TestTransportRequestsStallAtServerLimit(t *testing.T) {
const maxConcurrent = 2 const maxConcurrent = 2
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment