CVE-2023-37475 (published 2023-07-17T17:15Z)

Hamba avro is a go lang encoder/decoder implementation of the avro codec specification. In affected versions a well-crafted string passed to avro's `github.com/hamba/avro/v2.Unmarshal()` can throw a `fatal error: runtime: out of memory` which is unrecoverable and can cause denial of service of the consumer of avro. The root cause of the issue is that avro uses part of the input to `Unmarshal()` to determine the size when creating a new slice and hence an attacker may consume arbitrary amounts of memory which in turn may cause the application to crash. This issue has been addressed in commit `b4a402f4` which has been included in release version `2.13.0`. Users are advised to upgrade. There are no known workarounds for this vulnerability.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-400
cwe_name: Uncontrolled Resource Consumption
description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
extended_description: 
            Limited resources include memory, file system storage, database connection pool entries, and CPU. If an attacker can trigger the allocation of these limited resources, but the number or size of the resources is not controlled, then the attacker could cause a denial of service that consumes all available resources. This would prevent valid users from accessing the product, and it could potentially have an impact on the surrounding environment. For example, a memory exhaustion attack against an application could slow down the application as well as its host operating system.
            There are at least three distinct scenarios which can commonly lead to resource exhaustion:
               
                  Lack of throttling for the number of allocated resources
                  Losing all references to a resource before reaching the shutdown stage
                  Not closing/returning a resource after processing
               
            Resource exhaustion problems are often result due to an incorrect implementation of the following situations:
               
                  Error conditions and other exceptional circumstances.
                  Confusion over which part of the program is responsible for releasing the resource.
               
         
         

Fix information:
repo_url: https://github.com/hamba/avro
commit_details: repo_url: https://github.com/hamba/avro
author: Nicholas Wiersma
author_date: 2023-07-16 19:22:09+02:00
msg: feat: add max byte slice size config (#273)
repository: repo_url: https://github.com/hamba/avro
repo_name: hamba/avro
description: A fast Go Avro codec
repo_language: Go
owner: hamba
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -125,11 +125,18 @@ encoding and decoding.
 Enums may also implement `TextMarshaler` and `TextUnmarshaler`, and must resolve to valid symbols in the given enum schema.
 
 ##### Identical Underlying Types
+
 One type can be [ConvertibleTo](https://go.dev/ref/spec#Conversions) another type if they have identical underlying types. 
 A non-native type is allowed be used if it can be convertible to *time.Time*, *big.Rat* or *avro.LogicalDuration* for the particular of *LogicalTypes*.
 
 Ex.: `type Timestamp time.Time`
 
+##### Untrusted Input With Bytes and Strings
+
+For security reasons, the configuration `Config.MaxByteSliceSize` restricts the maximum size of `bytes` and `string` types created
+by the `Reader`. The default maximum size is `1MiB` and is configurable. This is required to stop untrusted input from consuming all memory and
+crashing the application. Should this not be need, setting a negative number will disable the behaviour.
+
 ### Recursive Structs
 
 At this moment recursive structs are not supported. It is planned for the future.

programming_language: Markdown
filename: config.go
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,8 @@ import (
 	"github.com/modern-go/reflect2"
 )
 
+const maxByteSliceSize = 1024 * 1024
+
 // DefaultConfig is the default API.
 var DefaultConfig = Config{}.Freeze()
 
@@ -43,6 +45,10 @@ type Config struct {
 	// Disable caching layer for encoders and decoders, forcing them to get rebuilt on every
 	// call to Marshal() and Unmarshal()
 	DisableCaching bool
+
+	// MaxByteSliceSize is the maximum size of `bytes` or `string` types the Reader will create, defaulting to 1MiB.
+	// If this size is exceeded, the Reader returns an error. This can be disabled by setting a negative number.
+	MaxByteSliceSize int
 }
 
 // Freeze makes the configuration immutable.
@@ -252,3 +258,11 @@ func (c *frozenConfig) getBlockLength() int {
 	}
 	return blockSize
 }
+
+func (c *frozenConfig) getMaxByteSliceSize() int {
+	size := c.config.MaxByteSliceSize
+	if size == 0 {
+		return maxByteSliceSize
+	}
+	return size
+}

programming_language: Go
filename: reader.go
change_type: ModificationType.MODIFY
diff: @@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"strings"
 	"unsafe"
 )
 
@@ -216,12 +217,18 @@ func (r *Reader) ReadString() string {
 func (r *Reader) readBytes(op string) []byte {
 	size := int(r.ReadLong())
 	if size < 0 {
-		r.ReportError("ReadString", "invalid "+op+" length")
+		fnName := "Read" + strings.ToTitle(op)
+		r.ReportError(fnName, "invalid "+op+" length")
 		return nil
 	}
 	if size == 0 {
 		return []byte{}
 	}
+	if max := r.cfg.getMaxByteSliceSize(); max > 0 && size > max {
+		fnName := "Read" + strings.ToTitle(op)
+		r.ReportError(fnName, "size is greater than `Config.MaxByteSliceSize`")
+		return nil
+	}
 
 	// The bytes are entirely in the buffer and of a reasonable size.
 	// Use the byte slab.

programming_language: Go
filename: reader_test.go
change_type: ModificationType.MODIFY
diff: @@ -507,6 +507,19 @@ func TestReader_ReadBytes(t *testing.T) {
 	}
 }
 
+func TestReader_ReadBytesLargerThanMaxByteSliceSize(t *testing.T) {
+	data := []byte{
+		246, 255, 255, 255, 255, 10, 255, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+		32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+		32, 32, 32, 32, 32, 32, 32,
+	}
+	r := avro.NewReader(bytes.NewReader(data), 4)
+
+	_ = r.ReadBytes()
+
+	assert.Error(t, r.Error)
+}
+
 func TestReader_ReadString(t *testing.T) {
 	tests := []struct {
 		data    []byte
@@ -583,6 +596,19 @@ func TestReader_ReadString(t *testing.T) {
 	}
 }
 
+func TestReader_ReadStringLargerThanMaxByteSliceSize(t *testing.T) {
+	data := []byte{
+		246, 255, 255, 255, 255, 10, 255, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+		32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
+		32, 32, 32, 32, 32, 32, 32,
+	}
+	r := avro.NewReader(bytes.NewReader(data), 4)
+
+	_ = r.ReadString()
+
+	assert.Error(t, r.Error)
+}
+
 func TestReader_ReadStringFastPathIsntBoundToBuffer(t *testing.T) {
 	data := []byte{0x06, 0x66, 0x6F, 0x6F, 0x08, 0x61, 0x76, 0x72, 0x6F}
 	r := avro.NewReader(bytes.NewReader(data), 4)

programming_language: Go