Skip to content

Commit 8709a41

Browse files
nicholashusingopherbot
authored andcommitted
encoding/asn1: prevent memory exhaustion when parsing using internal/saferio
Within parseSequenceOf, reflect.MakeSlice is being used to pre-allocate a slice that is needed in order to fully validate the given DER payload. The size of the slice allocated are also multiple times larger than the input DER: - When using asn1.Unmarshal directly, the allocated slice is ~28x larger. - When passing in DER using x509.ParseCertificateRequest, the allocated slice is ~48x larger. - When passing in DER using ocsp.ParseResponse, the allocated slice is ~137x larger. As a result, a malicious actor can craft a big empty DER payload, resulting in an unnecessary large allocation of memories. This can be a way to cause memory exhaustion. To prevent this, we now use SliceCapWithSize within internal/saferio to enforce a memory allocation cap. Thanks to Jakub Ciolek for reporting this issue. For golang#75671 Fixes CVE-2025-58185 Change-Id: Id50e76187eda43f594be75e516b9ca1d2ae6f428 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2700 Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/go/+/709856 Reviewed-by: Carlos Amedee <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Michael Pratt <[email protected]>
1 parent 9b9d02c commit 8709a41

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

src/encoding/asn1/asn1.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package asn1
2222
import (
2323
"errors"
2424
"fmt"
25+
"internal/saferio"
2526
"math"
2627
"math/big"
2728
"reflect"
@@ -666,10 +667,17 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type
666667
offset += t.length
667668
numElements++
668669
}
669-
ret = reflect.MakeSlice(sliceType, numElements, numElements)
670+
elemSize := uint64(elemType.Size())
671+
safeCap := saferio.SliceCapWithSize(elemSize, uint64(numElements))
672+
if safeCap < 0 {
673+
err = SyntaxError{fmt.Sprintf("%s slice too big: %d elements of %d bytes", elemType.Kind(), numElements, elemSize)}
674+
return
675+
}
676+
ret = reflect.MakeSlice(sliceType, 0, safeCap)
670677
params := fieldParameters{}
671678
offset := 0
672679
for i := 0; i < numElements; i++ {
680+
ret = reflect.Append(ret, reflect.Zero(elemType))
673681
offset, err = parseField(ret.Index(i), bytes, offset, params)
674682
if err != nil {
675683
return

src/encoding/asn1/asn1_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ package asn1
77
import (
88
"bytes"
99
"encoding/hex"
10+
"errors"
1011
"fmt"
1112
"math"
1213
"math/big"
1314
"reflect"
15+
"runtime"
1416
"strings"
1517
"testing"
1618
"time"
@@ -1216,3 +1218,39 @@ func TestImplicitTypeRoundtrip(t *testing.T) {
12161218
t.Fatalf("Unexpected diff after roundtripping struct\na: %#v\nb: %#v", a, b)
12171219
}
12181220
}
1221+
1222+
func TestParsingMemoryConsumption(t *testing.T) {
1223+
// Craft a syntatically valid, but empty, ~10 MB DER bomb. A successful
1224+
// unmarshal of this bomb should yield ~280 MB. However, the parsing should
1225+
// fail due to the empty content; and, in such cases, we want to make sure
1226+
// that we do not unnecessarily allocate memories.
1227+
derBomb := make([]byte, 10_000_000)
1228+
for i := range derBomb {
1229+
derBomb[i] = 0x30
1230+
}
1231+
derBomb = append([]byte{0x30, 0x83, 0x98, 0x96, 0x80}, derBomb...)
1232+
1233+
var m runtime.MemStats
1234+
runtime.GC()
1235+
runtime.ReadMemStats(&m)
1236+
memBefore := m.TotalAlloc
1237+
1238+
var out []struct {
1239+
Id []int
1240+
Critical bool `asn1:"optional"`
1241+
Value []byte
1242+
}
1243+
_, err := Unmarshal(derBomb, &out)
1244+
if !errors.As(err, &SyntaxError{}) {
1245+
t.Fatalf("Incorrect error result: want (%v), but got (%v) instead", &SyntaxError{}, err)
1246+
}
1247+
1248+
runtime.ReadMemStats(&m)
1249+
memDiff := m.TotalAlloc - memBefore
1250+
1251+
// Ensure that the memory allocated does not exceed 10<<21 (~20 MB) when
1252+
// the parsing fails.
1253+
if memDiff > 10<<21 {
1254+
t.Errorf("Too much memory allocated while parsing DER: %v MiB", memDiff/1024/1024)
1255+
}
1256+
}

src/go/build/deps_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ var depsRules = `
564564
565565
# CRYPTO-MATH is crypto that exposes math/big APIs - no cgo, net; fmt now ok.
566566
567-
CRYPTO, FMT, math/big
567+
CRYPTO, FMT, math/big, internal/saferio
568568
< crypto/internal/boring/bbig
569569
< crypto/internal/fips140cache
570570
< crypto/rand

0 commit comments

Comments
 (0)