net/textproto: avoid quadratic complexity in Reader.ReadResponse

Reader.ReadResponse constructed a response string from repeated
string concatenation, permitting a malicious sender to cause excessive
memory allocation and CPU consumption by sending a response consisting
of many short lines.

Use a strings.Builder to construct the string instead.

Thanks to Jakub Ciolek for reporting this issue.

Fixes CVE-2025-61724
Fixes #75716

Change-Id: I1a98ce85a21b830cb25799f9ac9333a67400d736
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2940
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709859
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
Damien Neil 2025-09-30 15:11:16 -07:00 committed by Gopher Robot
parent 5ce8cd16f3
commit 5ede095649

View file

@ -285,8 +285,10 @@ func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err err
// //
// An expectCode <= 0 disables the check of the status code. // An expectCode <= 0 disables the check of the status code.
func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error) { func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error) {
code, continued, message, err := r.readCodeLine(expectCode) code, continued, first, err := r.readCodeLine(expectCode)
multi := continued multi := continued
var messageBuilder strings.Builder
messageBuilder.WriteString(first)
for continued { for continued {
line, err := r.ReadLine() line, err := r.ReadLine()
if err != nil { if err != nil {
@ -297,12 +299,15 @@ func (r *Reader) ReadResponse(expectCode int) (code int, message string, err err
var moreMessage string var moreMessage string
code2, continued, moreMessage, err = parseCodeLine(line, 0) code2, continued, moreMessage, err = parseCodeLine(line, 0)
if err != nil || code2 != code { if err != nil || code2 != code {
message += "\n" + strings.TrimRight(line, "\r\n") messageBuilder.WriteByte('\n')
messageBuilder.WriteString(strings.TrimRight(line, "\r\n"))
continued = true continued = true
continue continue
} }
message += "\n" + moreMessage messageBuilder.WriteByte('\n')
messageBuilder.WriteString(moreMessage)
} }
message = messageBuilder.String()
if err != nil && multi && message != "" { if err != nil && multi && message != "" {
// replace one line error message with all lines (full message) // replace one line error message with all lines (full message)
err = &Error{code, message} err = &Error{code, message}