diff --git a/base/error.go b/base/error.go index bc7453ba6a..5b2d5ca5ab 100644 --- a/base/error.go +++ b/base/error.go @@ -48,6 +48,9 @@ var ( ErrXattrNotFound = &sgError{"Xattr Not Found"} ErrTimeout = &sgError{"Operation timed out"} + // ErrXattrInvalidLen is returned if the xattr is corrupt. + ErrXattrInvalidLen = &sgError{"Xattr stream length"} + // ErrPartialViewErrors is returned if the view call contains any partial errors. // This is more of a warning, and inspecting ViewResult.Errors is required for detail. ErrPartialViewErrors = &sgError{"Partial errors in view"} diff --git a/db/attachment_compaction.go b/db/attachment_compaction.go index b6e11a2888..09a23b938d 100644 --- a/db/attachment_compaction.go +++ b/db/attachment_compaction.go @@ -172,7 +172,7 @@ func getAttachmentSyncData(dataType uint8, data []byte) (*AttachmentCompactionDa if dataType&base.MemcachedDataTypeXattr != 0 { body, xattr, _, err := parseXattrStreamData(base.SyncXattrName, "", data) if err != nil { - if errors.Is(err, base.ErrXattrNotFound) { + if errors.Is(err, base.ErrXattrNotFound) || errors.Is(err, base.ErrXattrInvalidLen) { return nil, nil } return nil, err diff --git a/db/change_cache_test.go b/db/change_cache_test.go index 44cd59e5e0..50c9e4c560 100644 --- a/db/change_cache_test.go +++ b/db/change_cache_test.go @@ -2243,3 +2243,12 @@ func BenchmarkDocChanged(b *testing.B) { }) } } + +func TestInvalidXattrStream(t *testing.T) { + + body, xattr, userXattr, err := parseXattrStreamData(base.SyncXattrName, "", []byte("abcde")) + require.Error(t, err) + require.Nil(t, body) + require.Nil(t, xattr) + require.Nil(t, userXattr) +} diff --git a/db/document.go b/db/document.go index aba226d895..8ab75ff593 100644 --- a/db/document.go +++ b/db/document.go @@ -504,6 +504,9 @@ func parseXattrStreamData(xattrName string, userXattrName string, data []byte) ( } xattrsLen := binary.BigEndian.Uint32(data[0:4]) + if int(xattrsLen+4) > len(data) { + return nil, nil, nil, fmt.Errorf("%w (%d) from bytes %+v", base.ErrXattrInvalidLen, xattrsLen, data[0:4]) + } body = data[xattrsLen+4:] if xattrsLen == 0 { return body, nil, nil, nil diff --git a/db/document_test.go b/db/document_test.go index 5ad3d2ee1a..b285aeb77e 100644 --- a/db/document_test.go +++ b/db/document_test.go @@ -19,6 +19,7 @@ import ( "github.com/couchbase/sync_gateway/base" goassert "github.com/couchbaselabs/go.assert" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TODO: Could consider checking this in as a file and include it into the compiled test binary using something like https://github.com/jteeuwen/go-bindata @@ -290,3 +291,61 @@ func TestGetDeepMutableBody(t *testing.T) { }) } } + +func TestInvalidXattrStreamDataLen(t *testing.T) { + testCases := []struct { + name string + body []byte + expectedErr error + }{ + { + name: "bad value", + body: []byte("abcde"), + expectedErr: base.ErrXattrInvalidLen, + }, + { + name: "xattr length 4, overflow", + body: []byte{0x00, 0x00, 0x00, 0x04, 0x01}, + expectedErr: base.ErrXattrInvalidLen, + }, + } + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + // parseXattrStreamData is the underlying function + body, xattr, userXattr, err := parseXattrStreamData(base.SyncXattrName, "", test.body) + require.Error(t, err) + require.ErrorIs(t, err, test.expectedErr) + require.Nil(t, body) + require.Nil(t, xattr) + require.Nil(t, userXattr) + // UnmarshalDocumentSyncData wraps parseXattrStreamData + result, rawBody, rawXattr, rawUserXattr, err := UnmarshalDocumentSyncDataFromFeed(test.body, base.MemcachedDataTypeXattr, "", false) + require.ErrorIs(t, err, base.ErrXattrInvalidLen) + require.Nil(t, result) + require.Nil(t, rawBody) + require.Nil(t, rawXattr) + require.Nil(t, rawUserXattr) + + }) + } +} + +func TestInvalidXattrStreamEmptyBody(t *testing.T) { + inputStream := []byte{0x00, 0x00, 0x00, 0x01, 0x01} + emptyBody := []byte{} + // parseXattrStreamData is the underlying function + body, xattr, userXattr, err := parseXattrStreamData(base.SyncXattrName, "", inputStream) + require.NoError(t, err) + require.Equal(t, emptyBody, body) + require.Nil(t, xattr) + require.Nil(t, userXattr) + + // UnmarshalDocumentSyncData wraps parseXattrStreamData + result, rawBody, rawXattr, rawUserXattr, err := UnmarshalDocumentSyncDataFromFeed(inputStream, base.MemcachedDataTypeXattr, "", false) + require.Error(t, err) // unexpected end of JSON input + require.Nil(t, result) + require.Equal(t, emptyBody, rawBody) + require.Nil(t, rawXattr) + require.Nil(t, rawUserXattr) + +}