mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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 #75671 Fixes CVE-2025-58185 Change-Id: Id50e76187eda43f594be75e516b9ca1d2ae6f428 Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2700 Reviewed-by: Roland Shoemaker <bracewell@google.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/709856 Reviewed-by: Carlos Amedee <carlos@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
9b9d02c5a0
commit
8709a41d5e
3 changed files with 48 additions and 2 deletions
|
|
@ -22,6 +22,7 @@ package asn1
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"internal/saferio"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -666,10 +667,17 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type
|
||||||
offset += t.length
|
offset += t.length
|
||||||
numElements++
|
numElements++
|
||||||
}
|
}
|
||||||
ret = reflect.MakeSlice(sliceType, numElements, numElements)
|
elemSize := uint64(elemType.Size())
|
||||||
|
safeCap := saferio.SliceCapWithSize(elemSize, uint64(numElements))
|
||||||
|
if safeCap < 0 {
|
||||||
|
err = SyntaxError{fmt.Sprintf("%s slice too big: %d elements of %d bytes", elemType.Kind(), numElements, elemSize)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret = reflect.MakeSlice(sliceType, 0, safeCap)
|
||||||
params := fieldParameters{}
|
params := fieldParameters{}
|
||||||
offset := 0
|
offset := 0
|
||||||
for i := 0; i < numElements; i++ {
|
for i := 0; i < numElements; i++ {
|
||||||
|
ret = reflect.Append(ret, reflect.Zero(elemType))
|
||||||
offset, err = parseField(ret.Index(i), bytes, offset, params)
|
offset, err = parseField(ret.Index(i), bytes, offset, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ package asn1
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -1216,3 +1218,39 @@ func TestImplicitTypeRoundtrip(t *testing.T) {
|
||||||
t.Fatalf("Unexpected diff after roundtripping struct\na: %#v\nb: %#v", a, b)
|
t.Fatalf("Unexpected diff after roundtripping struct\na: %#v\nb: %#v", a, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParsingMemoryConsumption(t *testing.T) {
|
||||||
|
// Craft a syntatically valid, but empty, ~10 MB DER bomb. A successful
|
||||||
|
// unmarshal of this bomb should yield ~280 MB. However, the parsing should
|
||||||
|
// fail due to the empty content; and, in such cases, we want to make sure
|
||||||
|
// that we do not unnecessarily allocate memories.
|
||||||
|
derBomb := make([]byte, 10_000_000)
|
||||||
|
for i := range derBomb {
|
||||||
|
derBomb[i] = 0x30
|
||||||
|
}
|
||||||
|
derBomb = append([]byte{0x30, 0x83, 0x98, 0x96, 0x80}, derBomb...)
|
||||||
|
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.GC()
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
memBefore := m.TotalAlloc
|
||||||
|
|
||||||
|
var out []struct {
|
||||||
|
Id []int
|
||||||
|
Critical bool `asn1:"optional"`
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
_, err := Unmarshal(derBomb, &out)
|
||||||
|
if !errors.As(err, &SyntaxError{}) {
|
||||||
|
t.Fatalf("Incorrect error result: want (%v), but got (%v) instead", &SyntaxError{}, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
memDiff := m.TotalAlloc - memBefore
|
||||||
|
|
||||||
|
// Ensure that the memory allocated does not exceed 10<<21 (~20 MB) when
|
||||||
|
// the parsing fails.
|
||||||
|
if memDiff > 10<<21 {
|
||||||
|
t.Errorf("Too much memory allocated while parsing DER: %v MiB", memDiff/1024/1024)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -564,7 +564,7 @@ var depsRules = `
|
||||||
|
|
||||||
# CRYPTO-MATH is crypto that exposes math/big APIs - no cgo, net; fmt now ok.
|
# CRYPTO-MATH is crypto that exposes math/big APIs - no cgo, net; fmt now ok.
|
||||||
|
|
||||||
CRYPTO, FMT, math/big
|
CRYPTO, FMT, math/big, internal/saferio
|
||||||
< crypto/internal/boring/bbig
|
< crypto/internal/boring/bbig
|
||||||
< crypto/internal/fips140cache
|
< crypto/internal/fips140cache
|
||||||
< crypto/rand
|
< crypto/rand
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue