http3: reject duplicate QPACK decoder and encoder streams (#4388)

This commit is contained in:
Marten Seemann
2024-03-25 07:17:11 +10:00
committed by GitHub
parent 268208fbef
commit d540f545b0
2 changed files with 51 additions and 4 deletions

View File

@@ -40,7 +40,11 @@ func newConnection(
}
func (c *connection) HandleUnidirectionalStreams() {
var rcvdControlStream atomic.Bool
var (
rcvdControlStr atomic.Bool
rcvdQPACKEncoderStr atomic.Bool
rcvdQPACKDecoderStr atomic.Bool
)
for {
str, err := c.quicConn.AcceptUniStream(context.Background())
@@ -61,9 +65,17 @@ func (c *connection) HandleUnidirectionalStreams() {
// We're only interested in the control stream here.
switch streamType {
case streamTypeControlStream:
case streamTypeQPACKEncoderStream, streamTypeQPACKDecoderStream:
case streamTypeQPACKEncoderStream:
if isFirst := rcvdQPACKEncoderStr.CompareAndSwap(false, true); !isFirst {
c.quicConn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK encoder stream")
}
// Our QPACK implementation doesn't use the dynamic table yet.
return
case streamTypeQPACKDecoderStream:
if isFirst := rcvdQPACKDecoderStr.CompareAndSwap(false, true); !isFirst {
c.quicConn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate QPACK decoder stream")
}
// Our QPACK implementation doesn't use the dynamic table yet.
// TODO: check that only one stream of each type is opened.
return
case streamTypePushStream:
switch c.perspective {
@@ -83,7 +95,7 @@ func (c *connection) HandleUnidirectionalStreams() {
return
}
// Only a single control stream is allowed.
if isFirstControlStr := rcvdControlStream.CompareAndSwap(false, true); !isFirstControlStr {
if isFirstControlStr := rcvdControlStr.CompareAndSwap(false, true); !isFirstControlStr {
c.quicConn.CloseWithError(quic.ApplicationErrorCode(ErrCodeStreamCreationError), "duplicate control stream")
return
}

View File

@@ -124,6 +124,41 @@ var _ = Describe("Connection", func() {
}()
Eventually(done).Should(BeClosed())
})
It(fmt.Sprintf("rejects duplicate QPACK %s streams", name), func() {
qconn := mockquic.NewMockEarlyConnection(mockCtrl)
conn := newConnection(
qconn,
false,
nil,
protocol.PerspectiveClient,
utils.DefaultLogger,
)
buf := bytes.NewBuffer(quicvarint.Append(nil, streamType))
str1 := mockquic.NewMockStream(mockCtrl)
str1.EXPECT().Read(gomock.Any()).DoAndReturn(buf.Read).AnyTimes()
buf2 := bytes.NewBuffer(quicvarint.Append(nil, streamType))
str2 := mockquic.NewMockStream(mockCtrl)
str2.EXPECT().Read(gomock.Any()).DoAndReturn(buf2.Read).AnyTimes()
qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(str1, nil)
qconn.EXPECT().AcceptUniStream(gomock.Any()).Return(str2, nil)
testDone := make(chan struct{})
qconn.EXPECT().AcceptUniStream(gomock.Any()).DoAndReturn(func(context.Context) (quic.ReceiveStream, error) {
<-testDone
return nil, errors.New("test done")
})
qconn.EXPECT().CloseWithError(qerr.ApplicationErrorCode(ErrCodeStreamCreationError), gomock.Any()).Do(func(qerr.ApplicationErrorCode, string) error {
close(testDone)
return nil
})
done := make(chan struct{})
go func() {
defer GinkgoRecover()
defer close(done)
conn.HandleUnidirectionalStreams()
}()
Eventually(done).Should(BeClosed())
})
}
It("resets streams other than the control stream and the QPACK streams", func() {