language: count '_' alongside '-' in ParseAcceptLanguage guard The BCP 47 scanner aliases '_' to '-' in scanner.init at internal/language/parse.go lines 104-111, so a payload built with '_' as the separator bypasses the 1000-dash guard at language/parse.go:169 (the CVE-2022-32149 fix landed in CL 442235) and re-triggers the O(N^2) gobble path on attacker-controlled Accept-Language input. Count both separators in the guard. Reproduction summary: 100k tokens with '_' separator: 1.35 s parse time Same 100k tokens with '-': 49.9 us (guard fires) Amplification: ~27,000x See golang/go#79684 for the full reproducer with go.mod, main.go, and measurement output. Test: TestParseAcceptLanguageUnderscoreGuard in language/parse_test.go asserts errTagListTooLarge is returned when 1001 '_' separators are present. Filed per the upstream invitation from the Go Security Officers: the original security@golang.org report was determined to be not a security issue, with an invitation to file as an upstream change. Fixes golang/go#79684 Change-Id: I4e9a582c54a3e7268072870c0b22a3a392cfa1f2 GitHub-Last-Rev: 701c4da23913b82c62e71ade7c80d178f22d7b21 GitHub-Pull-Request: golang/text#66 Reviewed-on: https://go-review.googlesource.com/c/text/+/783500 Reviewed-by: Nicholas Husin <husin@google.com> Reviewed-by: Nicholas Husin <nsh@golang.org> Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Damien Neil <dneil@google.com> Auto-Submit: Roland Shoemaker <roland@golang.org>
diff --git a/language/parse.go b/language/parse.go index 053336e..65b53b9 100644 --- a/language/parse.go +++ b/language/parse.go
@@ -166,7 +166,9 @@ } }() - if strings.Count(s, "-") > 1000 { + // The BCP 47 scanner aliases '_' to '-' in scanner.init + // (internal/language/parse.go); the guard must count both. + if strings.Count(s, "-")+strings.Count(s, "_") > 1000 { return nil, nil, errTagListTooLarge }
diff --git a/language/parse_test.go b/language/parse_test.go index 0eee033..189b24f 100644 --- a/language/parse_test.go +++ b/language/parse_test.go
@@ -407,3 +407,18 @@ t.Errorf("ParseAcceptLanguage() unexpected error: got %v, want %v", err, errTagListTooLarge) } } + +func TestParseAcceptLanguageUnderscoreGuard(t *testing.T) { + // Build a payload that would trigger the quadratic path with + // '_' but is blocked by the dash-count guard. Verify the guard + // now also counts '_'. + parts := make([]string, 0, 1002) + parts = append(parts, "en") + for i := 0; i < 1001; i++ { + parts = append(parts, "abcdefghi") + } + s := strings.Join(parts, "_") + if _, _, err := ParseAcceptLanguage(s); err != errTagListTooLarge { + t.Errorf("ParseAcceptLanguage with 1001 '_' separators: got err=%v, want errTagListTooLarge", err) + } +}