From 1a7e601d07b67aec8d795c8182ee7257ba7d1960 Mon Sep 17 00:00:00 2001 From: "Nicholas S. Husin" Date: Mon, 11 May 2026 18:04:07 -0400 Subject: [PATCH] net/textproto: escape arbitrary input when including them in errors When returning errors, functions in the net/textproto package would include its input as part of the error, without any escaping. Note that said input is often controlled by external parties when using this package naturally. For example, a net/http client uses ReadMIMEHeader when parsing the headers it receive from a server. As a result, an attacker could inject arbitrary content into the error. Practically, this can result in an attacker injecting misleading content, terminal control bytes, etc. into a victim's output or logs. Fix this issue by making sure that ProtocolError usages within the package are properly escaped, and that Error.String will escape its Msg. Fixes #79346 Fixes CVE-2026-42507 Change-Id: Ide4c1005d8254f90d95d7a389b8ca3a26a6a6964 Reviewed-on: https://go-review.googlesource.com/c/go/+/777060 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Roland Shoemaker Reviewed-by: Nicholas Husin Reviewed-by: Damien Neil --- src/net/smtp/smtp_test.go | 6 +++--- src/net/textproto/reader.go | 14 +++++++------- src/net/textproto/reader_test.go | 6 ++++-- src/net/textproto/textproto.go | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/net/smtp/smtp_test.go b/src/net/smtp/smtp_test.go index b272b29dc5..7995fb7d80 100644 --- a/src/net/smtp/smtp_test.go +++ b/src/net/smtp/smtp_test.go @@ -695,7 +695,7 @@ func TestHello(t *testing.T) { err = c.Hello("customhost") case 1: err = c.StartTLS(nil) - if err.Error() == "502 Not implemented" { + if err.Error() == `502 "Not implemented"` { err = nil } case 2: @@ -953,8 +953,8 @@ func TestAuthFailed(t *testing.T) { if err == nil { t.Error("Auth: expected error; got none") - } else if err.Error() != "535 Invalid credentials\nplease see www.example.com" { - t.Errorf("Auth: got error: %v, want: %s", err, "535 Invalid credentials\nplease see www.example.com") + } else if err.Error() != `535 "Invalid credentials\nplease see www.example.com"` { + t.Errorf("Auth: got error: %v, want: %s", err, `535 "Invalid credentials\nplease see www.example.com"`) } bcmdbuf.Flush() diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go index 1f497c55ae..997e42aff1 100644 --- a/src/net/textproto/reader.go +++ b/src/net/textproto/reader.go @@ -215,13 +215,13 @@ func (r *Reader) readCodeLine(expectCode int) (code int, continued bool, message func parseCodeLine(line string, expectCode int) (code int, continued bool, message string, err error) { if len(line) < 4 || line[3] != ' ' && line[3] != '-' { - err = ProtocolError("short response: " + line) + err = ProtocolError(fmt.Sprintf("short response: %q", line)) return } continued = line[3] == '-' code, err = strconv.Atoi(line[0:3]) if err != nil || code < 100 { - err = ProtocolError("invalid response code: " + line) + err = ProtocolError(fmt.Sprintf("invalid response code: %q", line)) return } message = line[4:] @@ -253,7 +253,7 @@ func parseCodeLine(line string, expectCode int) (code int, continued bool, messa func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err error) { code, continued, message, err := r.readCodeLine(expectCode) if err == nil && continued { - err = ProtocolError("unexpected multi-line response: " + message) + err = ProtocolError(fmt.Sprintf("unexpected multi-line response: %q", message)) } return } @@ -541,7 +541,7 @@ func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error) if err != nil { return m, err } - return m, ProtocolError("malformed MIME header initial line: " + string(line)) + return m, ProtocolError(fmt.Sprintf("malformed MIME header initial line: %q", line)) } for { @@ -553,15 +553,15 @@ func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error) // Key ends at first colon. k, v, ok := bytes.Cut(kv, colon) if !ok { - return m, ProtocolError("malformed MIME header line: " + string(kv)) + return m, ProtocolError(fmt.Sprintf("malformed MIME header line: %q", kv)) } key, ok := canonicalMIMEHeaderKey(k) if !ok { - return m, ProtocolError("malformed MIME header line: " + string(kv)) + return m, ProtocolError(fmt.Sprintf("malformed MIME header line: %q", kv)) } for _, c := range v { if !validHeaderValueByte(c) { - return m, ProtocolError("malformed MIME header line: " + string(kv)) + return m, ProtocolError(fmt.Sprintf("malformed MIME header line: %q", kv)) } } diff --git a/src/net/textproto/reader_test.go b/src/net/textproto/reader_test.go index d510f9b338..3b2d003bcf 100644 --- a/src/net/textproto/reader_test.go +++ b/src/net/textproto/reader_test.go @@ -411,6 +411,8 @@ func TestReadMultiLineError(t *testing.T) { "Unexpected but legal text!\n" + "5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp" + wantError := `550 "5.1.1 The email account that you tried to reach does not exist. Please try\n5.1.1 double-checking the recipient's email address for typos or\n5.1.1 unnecessary spaces. Learn more at\nUnexpected but legal text!\n5.1.1 https://support.google.com/mail/answer/6596 h20si25154304pfd.166 - gsmtp"` + code, msg, err := r.ReadResponse(250) if err == nil { t.Errorf("ReadResponse: no error, want error") @@ -421,8 +423,8 @@ func TestReadMultiLineError(t *testing.T) { if msg != wantMsg { t.Errorf("ReadResponse: msg=%q, want %q", msg, wantMsg) } - if err != nil && err.Error() != "550 "+wantMsg { - t.Errorf("ReadResponse: error=%q, want %q", err.Error(), "550 "+wantMsg) + if err != nil && err.Error() != wantError { + t.Errorf("ReadResponse: error=%q, want %q", err.Error(), wantError) } } diff --git a/src/net/textproto/textproto.go b/src/net/textproto/textproto.go index 00dc8cbee5..8d323e1f16 100644 --- a/src/net/textproto/textproto.go +++ b/src/net/textproto/textproto.go @@ -41,7 +41,7 @@ type Error struct { } func (e *Error) Error() string { - return fmt.Sprintf("%03d %s", e.Code, e.Msg) + return fmt.Sprintf("%03d %q", e.Code, e.Msg) } // A ProtocolError describes a protocol violation such