Skip to content

Commit 45c3387

Browse files
julieqiugopherbot
authored andcommitted
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 Fixes #51853 Change-Id: Iffe768be53c8ddc0036fea0671d290f8f797692c Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1391157 Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> (cherry picked from commit 794ea5e828010e8b68493b2fc6d2963263195a02) Reviewed-on: https://go-review.googlesource.com/c/go/+/399820 Reviewed-by: Dmitri Shuralyov <[email protected]> Run-TryBot: Dmitri Shuralyov <[email protected]> Auto-Submit: Dmitri Shuralyov <[email protected]> Reviewed-by: Cherry Mui <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 9ccf5b8 commit 45c3387

File tree

2 files changed

+100
-100
lines changed

2 files changed

+100
-100
lines changed

src/encoding/pem/pem.go

+73-99
Original file line numberDiff line numberDiff line change
@@ -90,122 +90,96 @@ func Decode(data []byte) (p *Block, rest []byte) {
9090
// pemStart begins with a newline. However, at the very beginning of
9191
// the byte array, we'll accept the start string without it.
9292
rest = data
93-
if bytes.HasPrefix(data, pemStart[1:]) {
94-
rest = rest[len(pemStart)-1 : len(data)]
95-
} else if _, after, ok := bytes.Cut(data, pemStart); ok {
96-
rest = after
97-
} else {
98-
return nil, data
99-
}
100-
101-
typeLine, rest := getLine(rest)
102-
if !bytes.HasSuffix(typeLine, pemEndOfLine) {
103-
return decodeError(data, rest)
104-
}
105-
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
106-
107-
p = &Block{
108-
Headers: make(map[string]string),
109-
Type: string(typeLine),
110-
}
111-
11293
for {
113-
// This loop terminates because getLine's second result is
114-
// always smaller than its argument.
115-
if len(rest) == 0 {
94+
if bytes.HasPrefix(rest, pemStart[1:]) {
95+
rest = rest[len(pemStart)-1:]
96+
} else if _, after, ok := bytes.Cut(rest, pemStart); ok {
97+
rest = after
98+
} else {
11699
return nil, data
117100
}
118-
line, next := getLine(rest)
119101

120-
key, val, ok := bytes.Cut(line, colon)
121-
if !ok {
122-
break
102+
var typeLine []byte
103+
typeLine, rest = getLine(rest)
104+
if !bytes.HasSuffix(typeLine, pemEndOfLine) {
105+
continue
123106
}
107+
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
124108

125-
// TODO(agl): need to cope with values that spread across lines.
126-
key = bytes.TrimSpace(key)
127-
val = bytes.TrimSpace(val)
128-
p.Headers[string(key)] = string(val)
129-
rest = next
130-
}
109+
p = &Block{
110+
Headers: make(map[string]string),
111+
Type: string(typeLine),
112+
}
131113

132-
var endIndex, endTrailerIndex int
114+
for {
115+
// This loop terminates because getLine's second result is
116+
// always smaller than its argument.
117+
if len(rest) == 0 {
118+
return nil, data
119+
}
120+
line, next := getLine(rest)
133121

134-
// If there were no headers, the END line might occur
135-
// immediately, without a leading newline.
136-
if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
137-
endIndex = 0
138-
endTrailerIndex = len(pemEnd) - 1
139-
} else {
140-
endIndex = bytes.Index(rest, pemEnd)
141-
endTrailerIndex = endIndex + len(pemEnd)
142-
}
122+
key, val, ok := bytes.Cut(line, colon)
123+
if !ok {
124+
break
125+
}
143126

144-
if endIndex < 0 {
145-
return decodeError(data, rest)
146-
}
127+
// TODO(agl): need to cope with values that spread across lines.
128+
key = bytes.TrimSpace(key)
129+
val = bytes.TrimSpace(val)
130+
p.Headers[string(key)] = string(val)
131+
rest = next
132+
}
147133

148-
// After the "-----" of the ending line, there should be the same type
149-
// and then a final five dashes.
150-
endTrailer := rest[endTrailerIndex:]
151-
endTrailerLen := len(typeLine) + len(pemEndOfLine)
152-
if len(endTrailer) < endTrailerLen {
153-
return decodeError(data, rest)
154-
}
134+
var endIndex, endTrailerIndex int
155135

156-
restOfEndLine := endTrailer[endTrailerLen:]
157-
endTrailer = endTrailer[:endTrailerLen]
158-
if !bytes.HasPrefix(endTrailer, typeLine) ||
159-
!bytes.HasSuffix(endTrailer, pemEndOfLine) {
160-
return decodeError(data, rest)
161-
}
136+
// If there were no headers, the END line might occur
137+
// immediately, without a leading newline.
138+
if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
139+
endIndex = 0
140+
endTrailerIndex = len(pemEnd) - 1
141+
} else {
142+
endIndex = bytes.Index(rest, pemEnd)
143+
endTrailerIndex = endIndex + len(pemEnd)
144+
}
162145

163-
// The line must end with only whitespace.
164-
if s, _ := getLine(restOfEndLine); len(s) != 0 {
165-
return decodeError(data, rest)
166-
}
146+
if endIndex < 0 {
147+
continue
148+
}
167149

168-
base64Data := removeSpacesAndTabs(rest[:endIndex])
169-
p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
170-
n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
171-
if err != nil {
172-
return decodeError(data, rest)
173-
}
174-
p.Bytes = p.Bytes[:n]
150+
// After the "-----" of the ending line, there should be the same type
151+
// and then a final five dashes.
152+
endTrailer := rest[endTrailerIndex:]
153+
endTrailerLen := len(typeLine) + len(pemEndOfLine)
154+
if len(endTrailer) < endTrailerLen {
155+
continue
156+
}
157+
158+
restOfEndLine := endTrailer[endTrailerLen:]
159+
endTrailer = endTrailer[:endTrailerLen]
160+
if !bytes.HasPrefix(endTrailer, typeLine) ||
161+
!bytes.HasSuffix(endTrailer, pemEndOfLine) {
162+
continue
163+
}
175164

176-
// the -1 is because we might have only matched pemEnd without the
177-
// leading newline if the PEM block was empty.
178-
_, rest = getLine(rest[endIndex+len(pemEnd)-1:])
165+
// The line must end with only whitespace.
166+
if s, _ := getLine(restOfEndLine); len(s) != 0 {
167+
continue
168+
}
179169

180-
return
181-
}
170+
base64Data := removeSpacesAndTabs(rest[:endIndex])
171+
p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
172+
n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
173+
if err != nil {
174+
continue
175+
}
176+
p.Bytes = p.Bytes[:n]
182177

183-
func decodeError(data, rest []byte) (*Block, []byte) {
184-
// If we get here then we have rejected a likely looking, but
185-
// ultimately invalid PEM block. We need to start over from a new
186-
// position. We have consumed the preamble line and will have consumed
187-
// any lines which could be header lines. However, a valid preamble
188-
// line is not a valid header line, therefore we cannot have consumed
189-
// the preamble line for the any subsequent block. Thus, we will always
190-
// find any valid block, no matter what bytes precede it.
191-
//
192-
// For example, if the input is
193-
//
194-
// -----BEGIN MALFORMED BLOCK-----
195-
// junk that may look like header lines
196-
// or data lines, but no END line
197-
//
198-
// -----BEGIN ACTUAL BLOCK-----
199-
// realdata
200-
// -----END ACTUAL BLOCK-----
201-
//
202-
// we've failed to parse using the first BEGIN line
203-
// and now will try again, using the second BEGIN line.
204-
p, rest := Decode(rest)
205-
if p == nil {
206-
rest = data
178+
// the -1 is because we might have only matched pemEnd without the
179+
// leading newline if the PEM block was empty.
180+
_, rest = getLine(rest[endIndex+len(pemEnd)-1:])
181+
return p, rest
207182
}
208-
return p, rest
209183
}
210184

211185
const pemLineLength = 64

src/encoding/pem/pem_test.go

+27-1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ const pemMissingEndingSpace = `
107107
dGVzdA==
108108
-----ENDBAR-----`
109109

110+
const pemMissingEndLine = `
111+
-----BEGIN FOO-----
112+
Header: 1`
113+
114+
var pemRepeatingBegin = strings.Repeat("-----BEGIN \n", 10)
115+
110116
var badPEMTests = []struct {
111117
name string
112118
input string
@@ -131,14 +137,34 @@ var badPEMTests = []struct {
131137
"missing ending space",
132138
pemMissingEndingSpace,
133139
},
140+
{
141+
"repeating begin",
142+
pemRepeatingBegin,
143+
},
144+
{
145+
"missing end line",
146+
pemMissingEndLine,
147+
},
134148
}
135149

136150
func TestBadDecode(t *testing.T) {
137151
for _, test := range badPEMTests {
138-
result, _ := Decode([]byte(test.input))
152+
result, rest := Decode([]byte(test.input))
139153
if result != nil {
140154
t.Errorf("unexpected success while parsing %q", test.name)
141155
}
156+
if string(rest) != test.input {
157+
t.Errorf("unexpected rest: %q; want = %q", rest, test.input)
158+
}
159+
}
160+
}
161+
162+
func TestCVE202224675(t *testing.T) {
163+
// Prior to CVE-2022-24675, this input would cause a stack overflow.
164+
input := []byte(strings.Repeat("-----BEGIN \n", 10000000))
165+
result, rest := Decode(input)
166+
if result != nil || !reflect.DeepEqual(rest, input) {
167+
t.Errorf("Encode of %#v decoded as %#v", input, rest)
142168
}
143169
}
144170

0 commit comments

Comments
 (0)