Skip to content

Commit

Permalink
http3: reject reserved frame types
Browse files Browse the repository at this point in the history
  • Loading branch information
marten-seemann committed Apr 26, 2024
1 parent 083ceb4 commit 501bcef
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 15 deletions.
12 changes: 8 additions & 4 deletions http3/client.go
Expand Up @@ -121,12 +121,16 @@ func (c *SingleDestinationRoundTripper) handleBidirectionalStreams() {
}
return
}
go func(str quic.Stream) {
_, err := parseNextFrame(str, func(ft FrameType, e error) (processed bool, err error) {
fp := &frameParser{
r: str,
conn: c.hconn,
unknownFrameHandler: func(ft FrameType, e error) (processed bool, err error) {
id := c.hconn.Context().Value(quic.ConnectionTracingKey).(quic.ConnectionTracingID)
return c.StreamHijacker(ft, id, str, e)
})
if err == errHijacked {
},
}
go func(str quic.Stream) {
if _, err := fp.ParseNext(); err == errHijacked {
return
}
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion http3/conn.go
Expand Up @@ -201,7 +201,8 @@ func (c *connection) HandleUnidirectionalStreams(hijack func(StreamType, quic.Co
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
return
}
f, err := parseNextFrame(str, nil)
fp := &frameParser{conn: c.Connection, r: str}
f, err := fp.ParseNext()
if err != nil {
c.Connection.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameError), "")
return
Expand Down
24 changes: 17 additions & 7 deletions http3/frames.go
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"

"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/quicvarint"
)

Expand All @@ -18,13 +19,19 @@ type frame interface{}

var errHijacked = errors.New("hijacked")

func parseNextFrame(r io.Reader, unknownFrameHandler unknownFrameHandlerFunc) (frame, error) {
qr := quicvarint.NewReader(r)
type frameParser struct {
r io.Reader
conn quic.Connection
unknownFrameHandler unknownFrameHandlerFunc
}

func (p *frameParser) ParseNext() (frame, error) {
qr := quicvarint.NewReader(p.r)
for {
t, err := quicvarint.Read(qr)
if err != nil {
if unknownFrameHandler != nil {
hijacked, err := unknownFrameHandler(0, err)
if p.unknownFrameHandler != nil {
hijacked, err := p.unknownFrameHandler(0, err)
if err != nil {
return nil, err
}
Expand All @@ -35,8 +42,8 @@ func parseNextFrame(r io.Reader, unknownFrameHandler unknownFrameHandlerFunc) (f
return nil, err
}
// Call the unknownFrameHandler for frames not defined in the HTTP/3 spec
if t > 0xd && unknownFrameHandler != nil {
hijacked, err := unknownFrameHandler(FrameType(t), nil)
if t > 0xd && p.unknownFrameHandler != nil {
hijacked, err := p.unknownFrameHandler(FrameType(t), nil)
if err != nil {
return nil, err
}
Expand All @@ -56,11 +63,14 @@ func parseNextFrame(r io.Reader, unknownFrameHandler unknownFrameHandlerFunc) (f
case 0x1:
return &headersFrame{Length: l}, nil
case 0x4:
return parseSettingsFrame(r, l)
return parseSettingsFrame(p.r, l)
case 0x3: // CANCEL_PUSH
case 0x5: // PUSH_PROMISE
case 0x7: // GOAWAY
case 0xd: // MAX_PUSH_ID
case 0x2, 0x6, 0x8, 0x9:
p.conn.CloseWithError(quic.ApplicationErrorCode(ErrCodeFrameUnexpected), "")
return nil, fmt.Errorf("reserved frame type: %d", t)
}
// skip over unknown frames
if _, err := io.CopyN(io.Discard, qr, int64(l)); err != nil {
Expand Down
12 changes: 10 additions & 2 deletions http3/http_stream.go
Expand Up @@ -63,10 +63,14 @@ func newStream(str quic.Stream, conn *connection, datagrams *datagrammer) *strea
}

func (s *stream) Read(b []byte) (int, error) {
fp := &frameParser{
r: s.Stream,
conn: s.conn,
}
if s.bytesRemainingInFrame == 0 {
parseLoop:
for {
frame, err := parseNextFrame(s.Stream, nil)
frame, err := fp.ParseNext()
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -177,7 +181,11 @@ func (s *requestStream) SendRequestHeader(req *http.Request) error {
}

func (s *requestStream) ReadResponse() (*http.Response, error) {
frame, err := parseNextFrame(s.Stream, nil)
fp := &frameParser{
r: s.Stream,
conn: s.conn,
}
frame, err := fp.ParseNext()
if err != nil {
s.Stream.CancelRead(quic.StreamErrorCode(ErrCodeFrameError))
s.Stream.CancelWrite(quic.StreamErrorCode(ErrCodeFrameError))
Expand Down
3 changes: 2 additions & 1 deletion http3/server.go
Expand Up @@ -477,7 +477,8 @@ func (s *Server) handleRequest(conn *connection, str quic.Stream, datagrams *dat
)
}
}
frame, err := parseNextFrame(str, ufh)
fp := &frameParser{conn: conn, r: str, unknownFrameHandler: ufh}
frame, err := fp.ParseNext()
if err != nil {
if !errors.Is(err, errHijacked) {
str.CancelRead(quic.StreamErrorCode(ErrCodeRequestIncomplete))
Expand Down

0 comments on commit 501bcef

Please sign in to comment.