snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
563
credentials/alts/internal/conn/record_test.go
Normal file
563
credentials/alts/internal/conn/record_test.go
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
core "google.golang.org/grpc/credentials/alts/internal"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
const (
|
||||
rekeyRecordProtocol = "ALTSRP_GCM_AES128_REKEY"
|
||||
)
|
||||
|
||||
var (
|
||||
recordProtocols = []string{rekeyRecordProtocol}
|
||||
altsRecordFuncs = map[string]ALTSRecordFunc{
|
||||
// ALTS handshaker protocols.
|
||||
rekeyRecordProtocol: func(s core.Side, keyData []byte) (ALTSRecordCrypto, error) {
|
||||
return NewAES128GCM(s, keyData)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
for protocol, f := range altsRecordFuncs {
|
||||
if err := RegisterProtocol(protocol, f); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testConn mimics a net.Conn to the peer.
|
||||
type testConn struct {
|
||||
net.Conn
|
||||
in *bytes.Buffer
|
||||
out *bytes.Buffer
|
||||
}
|
||||
|
||||
func (c *testConn) Read(b []byte) (n int, err error) {
|
||||
return c.in.Read(b)
|
||||
}
|
||||
|
||||
func (c *testConn) Write(b []byte) (n int, err error) {
|
||||
return c.out.Write(b)
|
||||
}
|
||||
|
||||
func (c *testConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newTestALTSRecordConn(in, out *bytes.Buffer, side core.Side, rp string, protected []byte) *conn {
|
||||
key := []byte{
|
||||
// 16 arbitrary bytes.
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
|
||||
tc := testConn{
|
||||
in: in,
|
||||
out: out,
|
||||
}
|
||||
c, err := NewConn(&tc, side, rp, key, protected)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Unexpected error creating test ALTS record connection: %v", err))
|
||||
}
|
||||
return c.(*conn)
|
||||
}
|
||||
|
||||
func newConnPair(rp string, clientProtected []byte, serverProtected []byte) (client, server *conn) {
|
||||
clientBuf := new(bytes.Buffer)
|
||||
serverBuf := new(bytes.Buffer)
|
||||
clientConn := newTestALTSRecordConn(clientBuf, serverBuf, core.ClientSide, rp, clientProtected)
|
||||
serverConn := newTestALTSRecordConn(serverBuf, clientBuf, core.ServerSide, rp, serverProtected)
|
||||
return clientConn, serverConn
|
||||
}
|
||||
|
||||
func testPingPong(t *testing.T, rp string) {
|
||||
clientConn, serverConn := newConnPair(rp, nil, nil)
|
||||
clientMsg := []byte("Client Message")
|
||||
if n, err := clientConn.Write(clientMsg); n != len(clientMsg) || err != nil {
|
||||
t.Fatalf("Client Write() = %v, %v; want %v, <nil>", n, err, len(clientMsg))
|
||||
}
|
||||
rcvClientMsg := make([]byte, len(clientMsg))
|
||||
if n, err := serverConn.Read(rcvClientMsg); n != len(rcvClientMsg) || err != nil {
|
||||
t.Fatalf("Server Read() = %v, %v; want %v, <nil>", n, err, len(rcvClientMsg))
|
||||
}
|
||||
if !reflect.DeepEqual(clientMsg, rcvClientMsg) {
|
||||
t.Fatalf("Client Write()/Server Read() = %v, want %v", rcvClientMsg, clientMsg)
|
||||
}
|
||||
|
||||
serverMsg := []byte("Server Message")
|
||||
if n, err := serverConn.Write(serverMsg); n != len(serverMsg) || err != nil {
|
||||
t.Fatalf("Server Write() = %v, %v; want %v, <nil>", n, err, len(serverMsg))
|
||||
}
|
||||
rcvServerMsg := make([]byte, len(serverMsg))
|
||||
if n, err := clientConn.Read(rcvServerMsg); n != len(rcvServerMsg) || err != nil {
|
||||
t.Fatalf("Client Read() = %v, %v; want %v, <nil>", n, err, len(rcvServerMsg))
|
||||
}
|
||||
if !reflect.DeepEqual(serverMsg, rcvServerMsg) {
|
||||
t.Fatalf("Server Write()/Client Read() = %v, want %v", rcvServerMsg, serverMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestPingPong(t *testing.T) {
|
||||
for _, rp := range recordProtocols {
|
||||
testPingPong(t, rp)
|
||||
}
|
||||
}
|
||||
|
||||
func testSmallReadBuffer(t *testing.T, rp string) {
|
||||
clientConn, serverConn := newConnPair(rp, nil, nil)
|
||||
msg := []byte("Very Important Message")
|
||||
if n, err := clientConn.Write(msg); err != nil {
|
||||
t.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
|
||||
}
|
||||
rcvMsg := make([]byte, len(msg))
|
||||
n := 2 // Arbitrary index to break rcvMsg in two.
|
||||
rcvMsg1 := rcvMsg[:n]
|
||||
rcvMsg2 := rcvMsg[n:]
|
||||
if n, err := serverConn.Read(rcvMsg1); n != len(rcvMsg1) || err != nil {
|
||||
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg1))
|
||||
}
|
||||
if n, err := serverConn.Read(rcvMsg2); n != len(rcvMsg2) || err != nil {
|
||||
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg2))
|
||||
}
|
||||
if !reflect.DeepEqual(msg, rcvMsg) {
|
||||
t.Fatalf("Write()/Read() = %v, want %v", rcvMsg, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestSmallReadBuffer(t *testing.T) {
|
||||
for _, rp := range recordProtocols {
|
||||
testSmallReadBuffer(t, rp)
|
||||
}
|
||||
}
|
||||
|
||||
func testLargeMsg(t *testing.T, rp string) {
|
||||
clientConn, serverConn := newConnPair(rp, nil, nil)
|
||||
// msgLen is such that the length in the framing is larger than the
|
||||
// default size of one frame.
|
||||
msgLen := altsRecordDefaultLength - msgTypeFieldSize - clientConn.crypto.EncryptionOverhead() + 1
|
||||
msg := make([]byte, msgLen)
|
||||
if n, err := clientConn.Write(msg); n != len(msg) || err != nil {
|
||||
t.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
|
||||
}
|
||||
rcvMsg := make([]byte, len(msg))
|
||||
if n, err := io.ReadFull(serverConn, rcvMsg); n != len(rcvMsg) || err != nil {
|
||||
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg))
|
||||
}
|
||||
if !reflect.DeepEqual(msg, rcvMsg) {
|
||||
t.Fatalf("Write()/Server Read() = %v, want %v", rcvMsg, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestLargeMsg(t *testing.T) {
|
||||
for _, rp := range recordProtocols {
|
||||
testLargeMsg(t, rp)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLargeRecord writes a very large ALTS record and verifies that the server
|
||||
// receives it correctly. The large ALTS record should cause the reader to
|
||||
// expand it's read buffer to hold the entire record and store the decrypted
|
||||
// message until the receiver reads all of the bytes.
|
||||
func (s) TestLargeRecord(t *testing.T) {
|
||||
clientConn, serverConn := newConnPair(rekeyRecordProtocol, nil, nil)
|
||||
msg := []byte(strings.Repeat("a", 2*altsReadBufferInitialSize))
|
||||
// Increase the size of ALTS records written by the client.
|
||||
clientConn.payloadLengthLimit = math.MaxInt32
|
||||
if n, err := clientConn.Write(msg); n != len(msg) || err != nil {
|
||||
t.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
|
||||
}
|
||||
rcvMsg := make([]byte, len(msg))
|
||||
if n, err := io.ReadFull(serverConn, rcvMsg); n != len(rcvMsg) || err != nil {
|
||||
t.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg))
|
||||
}
|
||||
if !reflect.DeepEqual(msg, rcvMsg) {
|
||||
t.Fatalf("Write()/Server Read() = %v, want %v", rcvMsg, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLargeMessage measures the performance of ALTS conns for sending and
|
||||
// receiving a large message.
|
||||
func BenchmarkLargeMessage(b *testing.B) {
|
||||
msgLen := 20 * 1024 * 1024 // 20 MiB
|
||||
msg := make([]byte, msgLen)
|
||||
rcvMsg := make([]byte, len(msg))
|
||||
b.ResetTimer()
|
||||
clientConn, serverConn := newConnPair(rekeyRecordProtocol, nil, nil)
|
||||
for range b.N {
|
||||
// Write 20 MiB 5 times to transfer a total of 100 MiB.
|
||||
for range 5 {
|
||||
if n, err := clientConn.Write(msg); n != len(msg) || err != nil {
|
||||
b.Fatalf("Write() = %v, %v; want %v, <nil>", n, err, len(msg))
|
||||
}
|
||||
if n, err := io.ReadFull(serverConn, rcvMsg); n != len(rcvMsg) || err != nil {
|
||||
b.Fatalf("Read() = %v, %v; want %v, <nil>", n, err, len(rcvMsg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIncorrectMsgType(t *testing.T, rp string) {
|
||||
// framedMsg is an empty ciphertext with correct framing but wrong
|
||||
// message type.
|
||||
framedMsg := make([]byte, MsgLenFieldSize+msgTypeFieldSize)
|
||||
binary.LittleEndian.PutUint32(framedMsg[:MsgLenFieldSize], msgTypeFieldSize)
|
||||
wrongMsgType := uint32(0x22)
|
||||
binary.LittleEndian.PutUint32(framedMsg[MsgLenFieldSize:], wrongMsgType)
|
||||
|
||||
in := bytes.NewBuffer(framedMsg)
|
||||
c := newTestALTSRecordConn(in, nil, core.ClientSide, rp, nil)
|
||||
b := make([]byte, 1)
|
||||
if n, err := c.Read(b); n != 0 || err == nil {
|
||||
t.Fatalf("Read() = <nil>, want %v", fmt.Errorf("received frame with incorrect message type %v", wrongMsgType))
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestIncorrectMsgType(t *testing.T) {
|
||||
for _, rp := range recordProtocols {
|
||||
testIncorrectMsgType(t, rp)
|
||||
}
|
||||
}
|
||||
|
||||
func testFrameTooLarge(t *testing.T, rp string) {
|
||||
buf := new(bytes.Buffer)
|
||||
clientConn := newTestALTSRecordConn(nil, buf, core.ClientSide, rp, nil)
|
||||
serverConn := newTestALTSRecordConn(buf, nil, core.ServerSide, rp, nil)
|
||||
// payloadLen is such that the length in the framing is larger than
|
||||
// allowed in one frame.
|
||||
payloadLen := altsRecordLengthLimit - msgTypeFieldSize - clientConn.crypto.EncryptionOverhead() + 1
|
||||
payload := make([]byte, payloadLen)
|
||||
c, err := clientConn.crypto.Encrypt(nil, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Error encrypting message: %v", err)
|
||||
}
|
||||
msgLen := msgTypeFieldSize + len(c)
|
||||
framedMsg := make([]byte, MsgLenFieldSize+msgLen)
|
||||
binary.LittleEndian.PutUint32(framedMsg[:MsgLenFieldSize], uint32(msgTypeFieldSize+len(c)))
|
||||
msg := framedMsg[MsgLenFieldSize:]
|
||||
binary.LittleEndian.PutUint32(msg[:msgTypeFieldSize], altsRecordMsgType)
|
||||
copy(msg[msgTypeFieldSize:], c)
|
||||
if _, err = buf.Write(framedMsg); err != nil {
|
||||
t.Fatalf("Unexpected error writing to buffer: %v", err)
|
||||
}
|
||||
b := make([]byte, 1)
|
||||
if n, err := serverConn.Read(b); n != 0 || err == nil {
|
||||
t.Fatalf("Read() = <nil>, want %v", fmt.Errorf("received the frame length %d larger than the limit %d", altsRecordLengthLimit+1, altsRecordLengthLimit))
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestFrameTooLarge(t *testing.T) {
|
||||
for _, rp := range recordProtocols {
|
||||
testFrameTooLarge(t, rp)
|
||||
}
|
||||
}
|
||||
|
||||
func testWriteLargeData(t *testing.T, rp string) {
|
||||
// Test sending and receiving messages larger than the maximum write
|
||||
// buffer size.
|
||||
clientConn, serverConn := newConnPair(rp, nil, nil)
|
||||
// Message size is intentionally chosen to not be multiple of
|
||||
// payloadLengthLimit.
|
||||
msgSize := altsWriteBufferMaxSize + (100 * 1024)
|
||||
clientMsg := make([]byte, msgSize)
|
||||
for i := 0; i < msgSize; i++ {
|
||||
clientMsg[i] = 0xAA
|
||||
}
|
||||
if n, err := clientConn.Write(clientMsg); n != len(clientMsg) || err != nil {
|
||||
t.Fatalf("Client Write() = %v, %v; want %v, <nil>", n, err, len(clientMsg))
|
||||
}
|
||||
// We need to keep reading until the entire message is received. The
|
||||
// reason we set all bytes of the message to a value other than zero is
|
||||
// to avoid ambiguous zero-init value of rcvClientMsg buffer and the
|
||||
// actual received data.
|
||||
rcvClientMsg := make([]byte, 0, msgSize)
|
||||
numberOfExpectedFrames := int(math.Ceil(float64(msgSize) / float64(serverConn.payloadLengthLimit)))
|
||||
for i := 0; i < numberOfExpectedFrames; i++ {
|
||||
expectedRcvSize := serverConn.payloadLengthLimit
|
||||
if i == numberOfExpectedFrames-1 {
|
||||
// Last frame might be smaller.
|
||||
expectedRcvSize = msgSize % serverConn.payloadLengthLimit
|
||||
}
|
||||
tmpBuf := make([]byte, expectedRcvSize)
|
||||
if n, err := serverConn.Read(tmpBuf); n != len(tmpBuf) || err != nil {
|
||||
t.Fatalf("Server Read() = %v, %v; want %v, <nil>", n, err, len(tmpBuf))
|
||||
}
|
||||
rcvClientMsg = append(rcvClientMsg, tmpBuf...)
|
||||
}
|
||||
if !reflect.DeepEqual(clientMsg, rcvClientMsg) {
|
||||
t.Fatalf("Client Write()/Server Read() = %v, want %v", rcvClientMsg, clientMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestWriteLargeData(t *testing.T) {
|
||||
for _, rp := range recordProtocols {
|
||||
testWriteLargeData(t, rp)
|
||||
}
|
||||
}
|
||||
|
||||
func testProtectedBuffer(t *testing.T, rp string) {
|
||||
key := []byte{
|
||||
// 16 arbitrary bytes.
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
|
||||
|
||||
// Encrypt a message to be passed to NewConn as a client-side protected
|
||||
// buffer.
|
||||
newCrypto := protocols[rp]
|
||||
if newCrypto == nil {
|
||||
t.Fatalf("Unknown record protocol %q", rp)
|
||||
}
|
||||
crypto, err := newCrypto(core.ClientSide, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a crypter for protocol %q: %v", rp, err)
|
||||
}
|
||||
msg := []byte("Client Protected Message")
|
||||
encryptedMsg, err := crypto.Encrypt(nil, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to encrypt the client protected message: %v", err)
|
||||
}
|
||||
protectedMsg := make([]byte, 8) // 8 bytes = 4 length + 4 type
|
||||
binary.LittleEndian.PutUint32(protectedMsg, uint32(len(encryptedMsg))+4) // 4 bytes for the type
|
||||
binary.LittleEndian.PutUint32(protectedMsg[4:], altsRecordMsgType)
|
||||
protectedMsg = append(protectedMsg, encryptedMsg...)
|
||||
|
||||
_, serverConn := newConnPair(rp, nil, protectedMsg)
|
||||
rcvClientMsg := make([]byte, len(msg))
|
||||
if n, err := serverConn.Read(rcvClientMsg); n != len(rcvClientMsg) || err != nil {
|
||||
t.Fatalf("Server Read() = %v, %v; want %v, <nil>", n, err, len(rcvClientMsg))
|
||||
}
|
||||
if !reflect.DeepEqual(msg, rcvClientMsg) {
|
||||
t.Fatalf("Client protected/Server Read() = %v, want %v", rcvClientMsg, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestProtectedBuffer(t *testing.T) {
|
||||
for _, rp := range recordProtocols {
|
||||
testProtectedBuffer(t, rp)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteMemoryUsage measures the allocations per ALTS connection.
|
||||
// Run this with: go test -bench=BenchmarkWriteMemoryUsage -benchmem
|
||||
func BenchmarkWriteMemoryUsage(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(strings.Repeat("d", 256))
|
||||
key := []byte{
|
||||
// 16 arbitrary bytes.
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
|
||||
conn := &noopConn{}
|
||||
|
||||
for b.Loop() {
|
||||
c, err := NewConn(conn, core.ClientSide, rekeyRecordProtocol, key, nil)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := c.Write(data); err != nil {
|
||||
b.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadMemoryUsage measures the allocations per ALTS connection.
|
||||
// Run this with: go test -bench=BenchmarkReadMemoryUsage -benchmem
|
||||
func BenchmarkReadMemoryUsage(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
data := []byte(strings.Repeat("d", 257))
|
||||
readBuf := make([]byte, 1024)
|
||||
|
||||
for b.Loop() {
|
||||
clientConn, serverConn := newConnPair(rekeyRecordProtocol, nil, nil)
|
||||
b.StopTimer()
|
||||
clientConn.Write(data)
|
||||
b.StartTimer()
|
||||
if _, err := serverConn.Read(readBuf); err != nil {
|
||||
b.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type noopConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *noopConn) Write(b []byte) (n int, err error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *noopConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s) TestParseFramedMsgVulnerability(t *testing.T) {
|
||||
key := []byte{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4c, 0xce, 0x4f, 0x49}
|
||||
tc := testConn{
|
||||
in: new(bytes.Buffer),
|
||||
out: new(bytes.Buffer),
|
||||
}
|
||||
// We pass a protected buffer representing a framed message length of 0,
|
||||
// followed by bytes that will act as a dirty buffer capacity. If the capacity
|
||||
// bytes happen to start with `0x06` (altsRecordMsgType), the vulnerable
|
||||
// code will try to slice `msg[4:]` which panics because len(msg) is 0.
|
||||
malformedProtected := []byte{0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00}
|
||||
c, err := NewConn(&tc, core.ServerSide, rekeyRecordProtocol, key, malformedProtected)
|
||||
if err != nil {
|
||||
t.Fatalf("NewConn failed: %v", err)
|
||||
}
|
||||
buf := make([]byte, 1024)
|
||||
const wantErr = "shorter than message type field size"
|
||||
if _, err := c.Read(buf); err == nil || !strings.Contains(err.Error(), wantErr) {
|
||||
t.Fatalf("c.Read(buf) returned error: %v, want error containing %q", err, wantErr)
|
||||
}
|
||||
}
|
||||
|
||||
// countALTSFrames parses raw ALTS wire bytes and returns the number of complete frames.
|
||||
func countALTSFrames(data []byte) int {
|
||||
count := 0
|
||||
for len(data) >= MsgLenFieldSize {
|
||||
frameLen := int(binary.LittleEndian.Uint32(data[:MsgLenFieldSize]))
|
||||
total := MsgLenFieldSize + frameLen
|
||||
if total > len(data) {
|
||||
break
|
||||
}
|
||||
data = data[total:]
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (s) TestNewConnNegotiatedMaxFrameSize(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
negotiatedMaxFrameSize int
|
||||
wantMaxRecordLen int
|
||||
}{
|
||||
{"NotNegotiated", 0, altsRecordDefaultLength},
|
||||
{"SmallerThanDefault_10B", 10, altsRecordDefaultLength},
|
||||
{"SmallerThanDefault_1024B", 1024, altsRecordDefaultLength},
|
||||
{"Negotiated_4KB", 4 * 1024, 4 * 1024},
|
||||
{"Negotiated_16KB", 16 * 1024, 16 * 1024},
|
||||
{"Negotiated_64KB", 64 * 1024, 64 * 1024},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tcConn := testConn{}
|
||||
c, err := NewConnWithMaxFrameSize(&tcConn, core.ClientSide, rekeyRecordProtocol, key, nil, tc.negotiatedMaxFrameSize)
|
||||
if err != nil {
|
||||
t.Fatalf("NewConn() err = %v, want nil", err)
|
||||
}
|
||||
altsC := c.(*conn)
|
||||
wantPayloadLimit := tc.wantMaxRecordLen - altsC.overhead
|
||||
if altsC.payloadLengthLimit != wantPayloadLimit {
|
||||
t.Errorf("payloadLengthLimit = %v, want %v", altsC.payloadLengthLimit, wantPayloadLimit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWriteHonorsNegotiatedMaxFrameSize verifies that Write uses the negotiated
|
||||
// max_frame_size so we never send a frame larger than negotiated.
|
||||
func (s) TestWriteHonorsNegotiatedMaxFrameSize(t *testing.T) {
|
||||
key := make([]byte, 16)
|
||||
const negotiatedMaxFrameSize = 16 * 1024
|
||||
out := new(bytes.Buffer)
|
||||
c, err := NewConnWithMaxFrameSize(&testConn{in: new(bytes.Buffer), out: out}, core.ClientSide, rekeyRecordProtocol, key, nil, negotiatedMaxFrameSize)
|
||||
if err != nil {
|
||||
t.Fatalf("NewConn failed: %v", err)
|
||||
}
|
||||
altsC := c.(*conn)
|
||||
if got, want := altsC.payloadLengthLimit+altsC.overhead, negotiatedMaxFrameSize; got != want {
|
||||
t.Fatalf("frame size = %v, want %v", got, want)
|
||||
}
|
||||
// Two negotiatedMaxFrameSize-worth of plaintext should split into exactly two frames.
|
||||
payload := make([]byte, 2*altsC.payloadLengthLimit)
|
||||
if _, err := c.Write(payload); err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
if got, want := countALTSFrames(out.Bytes()), 2; got != want {
|
||||
t.Errorf("Write produced %d frames, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type partialWriteTestConn struct {
|
||||
net.Conn
|
||||
maxWriteBytes int
|
||||
err error
|
||||
out bytes.Buffer
|
||||
}
|
||||
|
||||
func (c *partialWriteTestConn) Write(b []byte) (int, error) {
|
||||
if len(b) > c.maxWriteBytes {
|
||||
c.out.Write(b[:c.maxWriteBytes])
|
||||
return c.maxWriteBytes, c.err
|
||||
}
|
||||
c.out.Write(b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// TestWritePartialWriteErrorWithNegotiatedMaxFrameSize verifies that when a partial Write error occurs,
|
||||
// the number of written bytes returned correctly accounts for the negotiated max_frame_size.
|
||||
func (s) TestWritePartialWriteErrorWithNegotiatedMaxFrameSize(t *testing.T) {
|
||||
key := make([]byte, 16)
|
||||
const negotiatedMaxFrameSize = 16 * 1024
|
||||
|
||||
// Allow writing 1.5 frames (16384 + 8192 = 24576 bytes) before returning io.ErrUnexpectedEOF.
|
||||
writeLimit := negotiatedMaxFrameSize + negotiatedMaxFrameSize/2
|
||||
errConn := &partialWriteTestConn{
|
||||
maxWriteBytes: writeLimit,
|
||||
err: io.ErrUnexpectedEOF,
|
||||
}
|
||||
|
||||
c, err := NewConnWithMaxFrameSize(errConn, core.ClientSide, rekeyRecordProtocol, key, nil, negotiatedMaxFrameSize)
|
||||
if err != nil {
|
||||
t.Fatalf("NewConn failed: %v", err)
|
||||
}
|
||||
altsC := c.(*conn)
|
||||
|
||||
// Send 3 frames worth of payload
|
||||
payload := make([]byte, 3*altsC.payloadLengthLimit)
|
||||
n, writeErr := c.Write(payload)
|
||||
if writeErr != io.ErrUnexpectedEOF {
|
||||
t.Fatalf("Write err = %v, want %v", writeErr, io.ErrUnexpectedEOF)
|
||||
}
|
||||
|
||||
// Exactly 1 complete frame was written before the partial write error.
|
||||
wantWrittenBytes := altsC.payloadLengthLimit
|
||||
if n != wantWrittenBytes {
|
||||
t.Errorf("Write() returned n = %d, want %d", n, wantWrittenBytes)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user