mirror of https://github.com/golang/go.git
[release-branch.go1.17] encoding/pem: fix stack overflow in Decode
Previously, Decode called decodeError, a recursive function that was prone to stack overflows when given a large PEM file containing errors. Credit to Juho Nurminen of Mattermost who reported the error. Fixes CVE-2022-24675 Updates #51853 Fixes #52036 Change-Id: Iffe768be53c8ddc0036fea0671d290f8f797692c Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1391157 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Filippo Valsorda <valsorda@google.com> (cherry picked from commit 794ea5e828010e8b68493b2fc6d2963263195a02) Reviewed-on: https://go-review.googlesource.com/c/go/+/399816 Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
7139e8b024
commit
2116d60993
|
|
@ -87,123 +87,97 @@ func Decode(data []byte) (p *Block, rest []byte) {
|
||||||
// pemStart begins with a newline. However, at the very beginning of
|
// pemStart begins with a newline. However, at the very beginning of
|
||||||
// the byte array, we'll accept the start string without it.
|
// the byte array, we'll accept the start string without it.
|
||||||
rest = data
|
rest = data
|
||||||
if bytes.HasPrefix(data, pemStart[1:]) {
|
|
||||||
rest = rest[len(pemStart)-1 : len(data)]
|
|
||||||
} else if i := bytes.Index(data, pemStart); i >= 0 {
|
|
||||||
rest = rest[i+len(pemStart) : len(data)]
|
|
||||||
} else {
|
|
||||||
return nil, data
|
|
||||||
}
|
|
||||||
|
|
||||||
typeLine, rest := getLine(rest)
|
|
||||||
if !bytes.HasSuffix(typeLine, pemEndOfLine) {
|
|
||||||
return decodeError(data, rest)
|
|
||||||
}
|
|
||||||
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
|
|
||||||
|
|
||||||
p = &Block{
|
|
||||||
Headers: make(map[string]string),
|
|
||||||
Type: string(typeLine),
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// This loop terminates because getLine's second result is
|
if bytes.HasPrefix(rest, pemStart[1:]) {
|
||||||
// always smaller than its argument.
|
rest = rest[len(pemStart)-1:]
|
||||||
if len(rest) == 0 {
|
} else if i := bytes.Index(rest, pemStart); i >= 0 {
|
||||||
|
rest = rest[i+len(pemStart) : len(rest)]
|
||||||
|
} else {
|
||||||
return nil, data
|
return nil, data
|
||||||
}
|
}
|
||||||
line, next := getLine(rest)
|
|
||||||
|
|
||||||
i := bytes.IndexByte(line, ':')
|
var typeLine []byte
|
||||||
if i == -1 {
|
typeLine, rest = getLine(rest)
|
||||||
break
|
if !bytes.HasSuffix(typeLine, pemEndOfLine) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
|
||||||
|
|
||||||
|
p = &Block{
|
||||||
|
Headers: make(map[string]string),
|
||||||
|
Type: string(typeLine),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(agl): need to cope with values that spread across lines.
|
for {
|
||||||
key, val := line[:i], line[i+1:]
|
// This loop terminates because getLine's second result is
|
||||||
key = bytes.TrimSpace(key)
|
// always smaller than its argument.
|
||||||
val = bytes.TrimSpace(val)
|
if len(rest) == 0 {
|
||||||
p.Headers[string(key)] = string(val)
|
return nil, data
|
||||||
rest = next
|
}
|
||||||
|
line, next := getLine(rest)
|
||||||
|
|
||||||
|
i := bytes.IndexByte(line, ':')
|
||||||
|
if i == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(agl): need to cope with values that spread across lines.
|
||||||
|
key, val := line[:i], line[i+1:]
|
||||||
|
key = bytes.TrimSpace(key)
|
||||||
|
val = bytes.TrimSpace(val)
|
||||||
|
p.Headers[string(key)] = string(val)
|
||||||
|
rest = next
|
||||||
|
}
|
||||||
|
|
||||||
|
var endIndex, endTrailerIndex int
|
||||||
|
|
||||||
|
// If there were no headers, the END line might occur
|
||||||
|
// immediately, without a leading newline.
|
||||||
|
if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
|
||||||
|
endIndex = 0
|
||||||
|
endTrailerIndex = len(pemEnd) - 1
|
||||||
|
} else {
|
||||||
|
endIndex = bytes.Index(rest, pemEnd)
|
||||||
|
endTrailerIndex = endIndex + len(pemEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if endIndex < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// After the "-----" of the ending line, there should be the same type
|
||||||
|
// and then a final five dashes.
|
||||||
|
endTrailer := rest[endTrailerIndex:]
|
||||||
|
endTrailerLen := len(typeLine) + len(pemEndOfLine)
|
||||||
|
if len(endTrailer) < endTrailerLen {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
restOfEndLine := endTrailer[endTrailerLen:]
|
||||||
|
endTrailer = endTrailer[:endTrailerLen]
|
||||||
|
if !bytes.HasPrefix(endTrailer, typeLine) ||
|
||||||
|
!bytes.HasSuffix(endTrailer, pemEndOfLine) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// The line must end with only whitespace.
|
||||||
|
if s, _ := getLine(restOfEndLine); len(s) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
base64Data := removeSpacesAndTabs(rest[:endIndex])
|
||||||
|
p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
|
||||||
|
n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.Bytes = p.Bytes[:n]
|
||||||
|
|
||||||
|
// the -1 is because we might have only matched pemEnd without the
|
||||||
|
// leading newline if the PEM block was empty.
|
||||||
|
_, rest = getLine(rest[endIndex+len(pemEnd)-1:])
|
||||||
|
return p, rest
|
||||||
}
|
}
|
||||||
|
|
||||||
var endIndex, endTrailerIndex int
|
|
||||||
|
|
||||||
// If there were no headers, the END line might occur
|
|
||||||
// immediately, without a leading newline.
|
|
||||||
if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
|
|
||||||
endIndex = 0
|
|
||||||
endTrailerIndex = len(pemEnd) - 1
|
|
||||||
} else {
|
|
||||||
endIndex = bytes.Index(rest, pemEnd)
|
|
||||||
endTrailerIndex = endIndex + len(pemEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if endIndex < 0 {
|
|
||||||
return decodeError(data, rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// After the "-----" of the ending line, there should be the same type
|
|
||||||
// and then a final five dashes.
|
|
||||||
endTrailer := rest[endTrailerIndex:]
|
|
||||||
endTrailerLen := len(typeLine) + len(pemEndOfLine)
|
|
||||||
if len(endTrailer) < endTrailerLen {
|
|
||||||
return decodeError(data, rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
restOfEndLine := endTrailer[endTrailerLen:]
|
|
||||||
endTrailer = endTrailer[:endTrailerLen]
|
|
||||||
if !bytes.HasPrefix(endTrailer, typeLine) ||
|
|
||||||
!bytes.HasSuffix(endTrailer, pemEndOfLine) {
|
|
||||||
return decodeError(data, rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The line must end with only whitespace.
|
|
||||||
if s, _ := getLine(restOfEndLine); len(s) != 0 {
|
|
||||||
return decodeError(data, rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
base64Data := removeSpacesAndTabs(rest[:endIndex])
|
|
||||||
p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
|
|
||||||
n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
|
|
||||||
if err != nil {
|
|
||||||
return decodeError(data, rest)
|
|
||||||
}
|
|
||||||
p.Bytes = p.Bytes[:n]
|
|
||||||
|
|
||||||
// the -1 is because we might have only matched pemEnd without the
|
|
||||||
// leading newline if the PEM block was empty.
|
|
||||||
_, rest = getLine(rest[endIndex+len(pemEnd)-1:])
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeError(data, rest []byte) (*Block, []byte) {
|
|
||||||
// If we get here then we have rejected a likely looking, but
|
|
||||||
// ultimately invalid PEM block. We need to start over from a new
|
|
||||||
// position. We have consumed the preamble line and will have consumed
|
|
||||||
// any lines which could be header lines. However, a valid preamble
|
|
||||||
// line is not a valid header line, therefore we cannot have consumed
|
|
||||||
// the preamble line for the any subsequent block. Thus, we will always
|
|
||||||
// find any valid block, no matter what bytes precede it.
|
|
||||||
//
|
|
||||||
// For example, if the input is
|
|
||||||
//
|
|
||||||
// -----BEGIN MALFORMED BLOCK-----
|
|
||||||
// junk that may look like header lines
|
|
||||||
// or data lines, but no END line
|
|
||||||
//
|
|
||||||
// -----BEGIN ACTUAL BLOCK-----
|
|
||||||
// realdata
|
|
||||||
// -----END ACTUAL BLOCK-----
|
|
||||||
//
|
|
||||||
// we've failed to parse using the first BEGIN line
|
|
||||||
// and now will try again, using the second BEGIN line.
|
|
||||||
p, rest := Decode(rest)
|
|
||||||
if p == nil {
|
|
||||||
rest = data
|
|
||||||
}
|
|
||||||
return p, rest
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pemLineLength = 64
|
const pemLineLength = 64
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,12 @@ const pemMissingEndingSpace = `
|
||||||
dGVzdA==
|
dGVzdA==
|
||||||
-----ENDBAR-----`
|
-----ENDBAR-----`
|
||||||
|
|
||||||
|
const pemMissingEndLine = `
|
||||||
|
-----BEGIN FOO-----
|
||||||
|
Header: 1`
|
||||||
|
|
||||||
|
var pemRepeatingBegin = strings.Repeat("-----BEGIN \n", 10)
|
||||||
|
|
||||||
var badPEMTests = []struct {
|
var badPEMTests = []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
|
@ -131,14 +137,34 @@ var badPEMTests = []struct {
|
||||||
"missing ending space",
|
"missing ending space",
|
||||||
pemMissingEndingSpace,
|
pemMissingEndingSpace,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"repeating begin",
|
||||||
|
pemRepeatingBegin,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"missing end line",
|
||||||
|
pemMissingEndLine,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBadDecode(t *testing.T) {
|
func TestBadDecode(t *testing.T) {
|
||||||
for _, test := range badPEMTests {
|
for _, test := range badPEMTests {
|
||||||
result, _ := Decode([]byte(test.input))
|
result, rest := Decode([]byte(test.input))
|
||||||
if result != nil {
|
if result != nil {
|
||||||
t.Errorf("unexpected success while parsing %q", test.name)
|
t.Errorf("unexpected success while parsing %q", test.name)
|
||||||
}
|
}
|
||||||
|
if string(rest) != test.input {
|
||||||
|
t.Errorf("unexpected rest: %q; want = %q", rest, test.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCVE202224675(t *testing.T) {
|
||||||
|
// Prior to CVE-2022-24675, this input would cause a stack overflow.
|
||||||
|
input := []byte(strings.Repeat("-----BEGIN \n", 10000000))
|
||||||
|
result, rest := Decode(input)
|
||||||
|
if result != nil || !reflect.DeepEqual(rest, input) {
|
||||||
|
t.Errorf("Encode of %#v decoded as %#v", input, rest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue