Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GODRIVER-1923 Error if BSON cstrings contain null bytes #622

Merged
merged 5 commits into from Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 0 additions & 8 deletions bson/extjson_prose_test.go
Expand Up @@ -45,11 +45,3 @@ func TestExtJSON(t *testing.T) {
})
}
}

func TestExtJSONNullBytes(t *testing.T) {
t.Run("element keys", func(t *testing.T) {
doc := D{{"a\x00", "foo"}}
res, err := MarshalExtJSON(doc, false, false)
assert.NotNil(t, err, "expected MarshalExtJSON error but got nil with result %v", string(res))
})
}
26 changes: 21 additions & 5 deletions x/bsonx/bsoncore/bsoncore.go
Expand Up @@ -30,17 +30,21 @@ import (
"fmt"
"math"
"strconv"
"strings"
"time"

"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)

// EmptyDocumentLength is the length of a document that has been started/ended but has no elements.
const EmptyDocumentLength = 5

// nullTerminator is a string version of the 0 byte that is appended at the end of cstrings.
const nullTerminator = string(byte(0))
const (
// EmptyDocumentLength is the length of a document that has been started/ended but has no elements.
EmptyDocumentLength = 5
// nullTerminator is a string version of the 0 byte that is appended at the end of cstrings.
nullTerminator = string(byte(0))
invalidKeyPanicMsg = "BSON element keys cannot contain null bytes"
invalidRegexPanicMsg = "BSON regex values cannot contain null bytes"
)

// AppendType will append t to dst and return the extended buffer.
func AppendType(dst []byte, t bsontype.Type) []byte { return append(dst, byte(t)) }
Expand All @@ -51,6 +55,10 @@ func AppendKey(dst []byte, key string) []byte { return append(dst, key+nullTermi
// AppendHeader will append Type t and key to dst and return the extended
// buffer.
func AppendHeader(dst []byte, t bsontype.Type, key string) []byte {
if !isValidCString(key) {
panic(invalidKeyPanicMsg)
}

dst = AppendType(dst, t)
dst = append(dst, key...)
return append(dst, 0x00)
Expand Down Expand Up @@ -430,6 +438,10 @@ func AppendNullElement(dst []byte, key string) []byte { return AppendHeader(dst,

// AppendRegex will append pattern and options to dst and return the extended buffer.
func AppendRegex(dst []byte, pattern, options string) []byte {
if !isValidCString(pattern) || !isValidCString(options) {
panic(invalidRegexPanicMsg)
}

return append(dst, pattern+nullTerminator+options+nullTerminator...)
}

Expand Down Expand Up @@ -844,3 +856,7 @@ func appendBinarySubtype2(dst []byte, subtype byte, b []byte) []byte {
dst = appendLength(dst, int32(len(b)))
return append(dst, b...)
}

func isValidCString(cs string) bool {
return !strings.ContainsRune(cs, '\x00')
}
55 changes: 55 additions & 0 deletions x/bsonx/bsoncore/bsoncore_test.go
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/google/go-cmp/cmp"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal/testutil/assert"
)

func noerr(t *testing.T, err error) {
Expand Down Expand Up @@ -899,6 +900,60 @@ func TestBuild(t *testing.T) {
}
}

func TestNullBytes(t *testing.T) {
// Helper function to execute the provided callback and assert that it panics with the expected message. The
// createBSONFn callback should create a BSON document/array/value and return the stringified version.
assertBSONCreationPanics := func(t *testing.T, createBSONFn func() string, expected string) {
t.Helper()

var res string
defer func() {
got := recover()
divjotarora marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, expected, got, "expected panic with error %v, got error %v with result %q", expected, got, res)
}()
res = createBSONFn()
}

t.Run("element keys", func(t *testing.T) {
createDocFn := func() string {
return NewDocumentBuilder().AppendString("a\x00", "foo").Build().String()
kevinAlbs marked this conversation as resolved.
Show resolved Hide resolved
}
assertBSONCreationPanics(t, createDocFn, invalidKeyPanicMsg)
})
t.Run("regex values", func(t *testing.T) {
testCases := []struct {
name string
pattern string
options string
}{
{"null bytes in pattern", "a\x00", "i"},
{"null bytes in options", "pattern", "i\x00"},
}
for _, tc := range testCases {
t.Run(tc.name+"-AppendRegexElement", func(t *testing.T) {
createDocFn := func() string {
docBytes := BuildDocumentFromElements(
nil,
AppendRegexElement(nil, "foo", tc.pattern, tc.options),
)
return Document(docBytes).String()
}
assertBSONCreationPanics(t, createDocFn, invalidRegexPanicMsg)
})
t.Run(tc.name+"-AppendRegex", func(t *testing.T) {
createValFn := func() string {
valBytes := Value{
Type: bsontype.Regex,
Data: AppendRegex(nil, tc.pattern, tc.options),
}
return Value(valBytes).String()
}
assertBSONCreationPanics(t, createValFn, invalidRegexPanicMsg)
})
}
})
}

func compareDecimal128(d1, d2 primitive.Decimal128) bool {
d1H, d1L := d1.GetBytes()
d2H, d2L := d2.GetBytes()
Expand Down