From 0d0799f055dcc9b3b41df74bee3fbe398ae2f0e7 Mon Sep 17 00:00:00 2001 From: mohammadmseet-hue Date: Sat, 4 Apr 2026 05:17:25 +0000 Subject: net/mail: fix quadratic complexity in consumeComment consumeComment builds the comment string by repeated string concatenation inside a loop. Each concatenation copies the entire string built so far, making the function O(n^2) in the depth of nested comments. Replace the concatenation with a strings.Builder, which amortizes allocation by doubling its internal buffer. This reduces consumeComment from O(n^2) to O(n). This is the same bug class as the consumeDomainLiteral fix in CVE-2025-61725. Benchmark results (benchstat, 8 runs): name old time/op new time/op delta ConsumeComment/depth10 2.481us 1.838us -25.92% ConsumeComment/depth100 86.58us 6.498us -92.50% ConsumeComment/depth1000 7.963ms 52.82us -99.34% ConsumeComment/depth10000 897.8ms 521.3us -99.94% The quadratic cost becomes visible at depth 100 and dominant by depth 1000. At depth 10000, the fix is roughly 1700x faster. Change-Id: I3c927f02646fcab7bab167cb82fd46d3327d6d34 GitHub-Last-Rev: 7742dad716ee371766543f88e82bd163bd9d7ac2 GitHub-Pull-Request: golang/go#78393 Reviewed-on: https://go-review.googlesource.com/c/go/+/759940 Reviewed-by: Sean Liao LUCI-TryBot-Result: Go LUCI Auto-Submit: Sean Liao Reviewed-by: David Chase Reviewed-by: Junyang Shao --- src/net/mail/message.go | 6 +++--- src/net/mail/message_test.go | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) (limited to 'src/net') diff --git a/src/net/mail/message.go b/src/net/mail/message.go index 1502b35962..fbf1fca68f 100644 --- a/src/net/mail/message.go +++ b/src/net/mail/message.go @@ -832,7 +832,7 @@ func (p *addrParser) consumeComment() (string, bool) { // '(' already consumed. depth := 1 - var comment string + var comment strings.Builder for { if p.empty() || depth == 0 { break @@ -846,12 +846,12 @@ func (p *addrParser) consumeComment() (string, bool) { depth-- } if depth > 0 { - comment += p.s[:1] + comment.WriteByte(p.s[0]) } p.s = p.s[1:] } - return comment, depth == 0 + return comment.String(), depth == 0 } func (p *addrParser) decodeRFC2047Word(s string) (word string, isEncoded bool, err error) { diff --git a/src/net/mail/message_test.go b/src/net/mail/message_test.go index dad9c367f3..3393b03af5 100644 --- a/src/net/mail/message_test.go +++ b/src/net/mail/message_test.go @@ -6,6 +6,7 @@ package mail import ( "bytes" + "fmt" "io" "mime" "reflect" @@ -1260,3 +1261,21 @@ func TestEmptyAddress(t *testing.T) { t.Errorf(`ParseAddressList("") = %v, %v, want nil, error`, list, err) } } + +func BenchmarkConsumeComment(b *testing.B) { + for _, n := range []int{10, 100, 1000, 10000} { + b.Run(fmt.Sprintf("depth-%d", n), func(b *testing.B) { + // Build a deeply nested comment: (((...a...))) + open := strings.Repeat("(", n) + close := strings.Repeat(")", n) + // consumeComment expects the leading '(' already consumed, + // so we start with one fewer opening paren and the parser + // will handle nesting from there. + input := open[:n-1] + "a" + close + for b.Loop() { + p := addrParser{s: input} + p.consumeComment() + } + }) + } +} -- cgit v1.3-6-g1900