Compare commits

...

53 Commits

Author SHA1 Message Date
Gopher Robot
28622c1959 [release-branch.go1.25] go1.25.3
Change-Id: Ibb61bf455e8ec92bb10038b1de0ce79ee771c53a
Reviewed-on: https://go-review.googlesource.com/c/go/+/711481
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
TryBot-Bypass: Gopher Robot <gobot@golang.org>
2025-10-13 14:14:41 -07:00
Roland Shoemaker
e05b2c92d9 [release-branch.go1.25] crypto/x509: rework fix for CVE-2025-58187
In CL 709854 we enabled strict validation for a number of properties of
domain names (and their constraints). This caused significant breakage,
since we didn't previously disallow the creation of certificates which
contained these malformed domains.

Rollback a number of the properties we enforced, making domainNameValid
only enforce the same properties that domainToReverseLabels does. Since
this also undoes some of the DoS protections our initial fix enabled,
this change also adds caching of constraints in isValid (which perhaps
is the fix we should've initially chosen).

Updates #75835
Updates #75828
Fixes #75861

Change-Id: Ie6ca6b4f30e9b8a143692b64757f7bbf4671ed0e
Reviewed-on: https://go-review.googlesource.com/c/go/+/710735
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
(cherry picked from commit 1cd71689f2)
Reviewed-on: https://go-review.googlesource.com/c/go/+/710677
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
2025-10-13 08:57:00 -07:00
Robert Griesemer
79ec0c94f3 [release-branch.go1.25] spec: update spec date to match release date
Ths spec date in the 1.25 release branch dates back to Feb 2025
which is rather confusing. Moving it forward to 1.25 release date.

This is a roll forward of CL 709515 which was rolled back by CL 709535.

For #75743.
Fixes #75777.

Change-Id: I18d7ccfc343aa1f8fba78a896fb69ad6eeb182e7
Reviewed-on: https://go-review.googlesource.com/c/go/+/710215
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-10-08 10:09:43 -07:00
Gopher Robot
bed6c81c2d [release-branch.go1.25] go1.25.2
Change-Id: I0a685789be057167e6d40fbdaee29ebdbc6a2164
Reviewed-on: https://go-review.googlesource.com/c/go/+/709916
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
TryBot-Bypass: Gopher Robot <gobot@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
2025-10-07 11:16:13 -07:00
Damien Neil
2612dcfd3c [release-branch.go1.25] archive/tar: set a limit on the size of GNU sparse file 1.0 regions
Sparse files in tar archives contain only the non-zero components
of the file. There are several different encodings for sparse
files. When reading GNU tar pax 1.0 sparse files, archive/tar did
not set a limit on the size of the sparse region data. A malicious
archive containing a large number of sparse blocks could cause
archive/tar to read an unbounded amount of data from the archive
into memory.

Since a malicious input can be highly compressable, a small
compressed input could cause very large allocations.

Cap the size of the sparse block data to the same limit used
for PAX headers (1 MiB).

Thanks to Harshit Gupta (Mr HAX) (https://www.linkedin.com/in/iam-harshit-gupta/)
for reporting this issue.

Fixes CVE-2025-58183
For #75677
Fixes #75711

Change-Id: I70b907b584a7b8676df8a149a1db728ae681a770
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2800
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2987
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709852
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
2025-10-07 11:04:24 -07:00
Roland Shoemaker
90f72bd500 [release-branch.go1.25] encoding/pem: make Decode complexity linear
Because Decode scanned the input first for the first BEGIN line, and
then the first END line, the complexity of Decode is quadratic. If the
input contained a large number of BEGINs and then a single END right at
the end of the input, we would find the first BEGIN, and then scan the
entire input for the END, and fail to parse the block, so move onto the
next BEGIN, scan the entire input for the END, etc.

Instead, look for the first END in the input, and then the first BEGIN
that precedes the found END. We then process the bytes between the BEGIN
and END, and move onto the bytes after the END for further processing.
This gives us linear complexity.

Fixes CVE-2025-61723
For #75676
Fixes #75709

Change-Id: I813c4f63e78bca4054226c53e13865c781564ccf
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2921
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2985
Reviewed-on: https://go-review.googlesource.com/c/go/+/709851
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Bypass: Michael Pratt <mpratt@google.com>
2025-10-07 11:04:16 -07:00
Nicholas Husin
e0f655bf3f [release-branch.go1.25] encoding/asn1: prevent memory exhaustion when parsing using internal/saferio
Within parseSequenceOf, reflect.MakeSlice is being used to pre-allocate
a slice that is needed in order to fully validate the given DER payload.
The size of the slice allocated are also multiple times larger than the
input DER:

- When using asn1.Unmarshal directly, the allocated slice is ~28x
  larger.
- When passing in DER using x509.ParseCertificateRequest, the allocated
  slice is ~48x larger.
- When passing in DER using ocsp.ParseResponse, the allocated slice is
  ~137x larger.

As a result, a malicious actor can craft a big empty DER payload,
resulting in an unnecessary large allocation of memories. This can be a
way to cause memory exhaustion.

To prevent this, we now use SliceCapWithSize within internal/saferio to
enforce a memory allocation cap.

Thanks to Jakub Ciolek for reporting this issue.

For #75671
Fixes #75705
Fixes CVE-2025-58185

Change-Id: Id50e76187eda43f594be75e516b9ca1d2ae6f428
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2700
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2966
Reviewed-by: Nicholas Husin <husin@google.com>
Commit-Queue: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709850
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
2025-10-07 11:02:22 -07:00
Nicholas Husin
100c5a6680 [release-branch.go1.25] net/http: add httpcookiemaxnum GODEBUG option to limit number of cookies parsed
When handling HTTP headers, net/http does not currently limit the number
of cookies that can be parsed. The only limitation that exists is for
the size of the entire HTTP header, which is controlled by
MaxHeaderBytes (defaults to 1 MB).

Unfortunately, this allows a malicious actor to send HTTP headers which
contain a massive amount of small cookies, such that as much cookies as
possible can be fitted within the MaxHeaderBytes limitation. Internally,
this causes us to allocate a massive number of Cookie struct.

For example, a 1 MB HTTP header with cookies that repeats "a=;" will
cause an allocation of ~66 MB in the heap. This can serve as a way for
malicious actors to induce memory exhaustion.

To fix this, we will now limit the number of cookies we are willing to
parse to 3000 by default. This behavior can be changed by setting a new
GODEBUG option: GODEBUG=httpcookiemaxnum. httpcookiemaxnum can be set to
allow a higher or lower cookie limit. Setting it to 0 will also allow an
infinite number of cookies to be parsed.

Thanks to jub0bs for reporting this issue.

For #75672
Fixes #75707
Fixes CVE-2025-58186

Change-Id: Ied58b3bc8acf5d11c880f881f36ecbf1d5d52622
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2720
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2965
Reviewed-by: Nicholas Husin <husin@google.com>
Commit-Queue: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709849
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
2025-10-07 11:02:18 -07:00
Neal Patel
f0c69db15a [release-branch.go1.25] crypto/x509: improve domain name verification
Don't use domainToReverseLabels to check if domain names are valid,
since it is not particularly performant, and can contribute to DoS
vectors. Instead just iterate over the name and enforce the properties
we care about.

This also enforces that DNS names, both in SANs and name constraints,
are valid. We previously allowed invalid SANs, because some
intermediates had these weird names (see #23995), but there are
currently no trusted intermediates that have this property, and since we
target the web PKI, supporting this particular case is not a high
priority.

Thank you to Jakub Ciolek for reporting this issue.

Fixes CVE-2025-58187
For #75681
Fixes #75715

Change-Id: I6ebce847dcbe5fc63ef2f9a74f53f11c4c56d3d1
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2820
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2981
Commit-Queue: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709848
Auto-Submit: Michael Pratt <mpratt@google.com>
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-10-07 11:02:15 -07:00
Ethan Lee
9fd3ac8a10 [release-branch.go1.25] net/url: enforce stricter parsing of bracketed IPv6 hostnames
- Previously, url.Parse did not enforce validation of hostnames within
  square brackets.
- RFC 3986 stipulates that only IPv6 hostnames can be embedded within
  square brackets in a URL.
- Now, the parsing logic should strictly enforce that only IPv6
  hostnames can be resolved when in square brackets. IPv4, IPv4-mapped
  addresses and other input will be rejected.
- Update url_test to add test cases that cover the above scenarios.

Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua
University for reporting this issue.

Fixes CVE-2025-47912
For #75678
Fixes #75713

Change-Id: Iaa41432bf0ee86de95a39a03adae5729e4deb46c
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2680
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2988
Commit-Queue: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709847
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-10-07 11:02:12 -07:00
Damien Neil
5d7a787aa2 [release-branch.go1.25] 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
For #75716
Fixes #75718

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-internal-review.googlesource.com/c/go/+/2960
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709846
Reviewed-by: Carlos Amedee <carlos@golang.org>
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
2025-10-07 11:02:09 -07:00
Neal Patel
930ce220d0 [release-branch.go1.25] crypto/x509: mitigate DoS vector when intermediate certificate contains DSA public key
An attacker could craft an intermediate X.509 certificate
containing a DSA public key and can crash a remote host
with an unauthenticated call to any endpoint that
verifies the certificate chain.

Thank you to Jakub Ciolek for reporting this issue.

Fixes CVE-2025-58188
For #75675
Fixes #75703

Change-Id: I2ecbb87b9b8268dbc55c8795891e596ab60f0088
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2780
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2963
Commit-Queue: Roland Shoemaker <bracewell@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709845
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Pratt <mpratt@google.com>
2025-10-07 11:02:06 -07:00
Damien Neil
6a057327cf [release-branch.go1.25] net/mail: avoid quadratic behavior in mail address parsing
RFC 5322 domain-literal parsing built the dtext value one character
at a time with string concatenation, resulting in excessive
resource consumption when parsing very large domain-literal values.

Replace with a subslice.

Benchmark not included in this CL because it's too narrow to be
of general ongoing use, but for:

    ParseAddress("alice@[" + strings.Repeat("a", 0x40000) + "]")

goos: darwin
goarch: arm64
pkg: net/mail
cpu: Apple M4 Pro
                │  /tmp/bench.0  │            /tmp/bench.1             │
                │     sec/op     │   sec/op     vs base                │
ParseAddress-14   1987.732m ± 9%   1.524m ± 5%  -99.92% (p=0.000 n=10)

                │   /tmp/bench.0   │             /tmp/bench.1              │
                │       B/op       │     B/op      vs base                 │
ParseAddress-14   33692.767Mi ± 0%   1.282Mi ± 0%  -100.00% (p=0.000 n=10)

                │  /tmp/bench.0  │            /tmp/bench.1            │
                │   allocs/op    │ allocs/op   vs base                │
ParseAddress-14   263711.00 ± 0%   17.00 ± 0%  -99.99% (p=0.000 n=10)

Thanks to Philippe Antoine (Catena cyber) for reporting this issue.

Fixes CVE-2025-61725
For #75680
Fixes #75701

Change-Id: Id971c2d5b59882bb476e22fceb7e01ec08234bb7
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2840
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2961
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/709844
TryBot-Bypass: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-10-07 11:01:04 -07:00
Robert Griesemer
66f6feaa53 [release-branch.go1.25] spec: revert "update spec date to match release date"
This reverts CL 709515 (commit d6f2741248).

Reason for revert: Minor release is in process. Let's hold off with this until the minor release is out.

Change-Id: Ie6ee91cb61836f8b3494fb895ef4b9976f54dd1d
Reviewed-on: https://go-review.googlesource.com/c/go/+/709535
Reviewed-by: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
2025-10-06 14:12:00 -07:00
Robert Griesemer
d6f2741248 [release-branch.go1.25] spec: update spec date to match release date
Ths spec date in the 1.25 release branch dates back to Feb 2025
which is rather confusing. Moving it forward to 1.25 release date.

Fixes #75743.

Change-Id: Ibb2da5dc238a3b876862eef802e90bee6326d6b5
Reviewed-on: https://go-review.googlesource.com/c/go/+/709515
Auto-Submit: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
2025-10-06 11:11:50 -07:00
Damien Neil
28ac8d2104 [release-branch.go1.25] net/http: avoid connCount underflow race
Remove a race condition in counting the number of connections per host,
which can cause a connCount underflow and a panic.

The race occurs when:

  - A RoundTrip call attempts to use a HTTP/2 roundtripper (pconn.alt != nil)
    and receives an isNoCachedConn error. The call removes the pconn from
    the idle conn pool and decrements the connCount for its host.
  - A second RoundTrip call on the same pconn succeeds,
    and delivers the pconn to a third RoundTrip waiting for a conn.
  - The third RoundTrip receives the pconn at the same moment its request
    context is canceled. It places the pconn back into the idle conn pool.

At this time, the connCount is incorrect, because the conn returned to
the idle pool is not matched by an increment in the connCount.

Fix this by not adding HTTP/2 pconns back to the idle pool in
wantConn.cancel.

For #61474
Fixes #75539

Change-Id: I104d6cf85a54d0382eebf3fcf5dda99c69a7c3f6
Reviewed-on: https://go-review.googlesource.com/c/go/+/703936
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit 3203a5da29)
Reviewed-on: https://go-review.googlesource.com/c/go/+/705376
Reviewed-by: Cherry Mui <cherryyz@google.com>
2025-10-01 12:10:04 -07:00
Damien Neil
06993c7721 [release-branch.go1.25] context: don't return a non-nil from Err before Done is closed
The Context.Err documentation states that it returns nil if the
context's done channel is not closed. Fix a race condition introduced
by CL 653795 where Err could return a non-nil error slightly before
the Done channel is closed.

No impact on Err performance when returning nil.

Slows down Err when returning non-nil by about 3x,
but that's still almost 2x faster than before CL 653795
and the performance of this path is less important.
(A tight loop checking Err for doneness will be terminated
by the first Err call to return a non-nil result.)

    goos: darwin
    goarch: arm64
    pkg: context
    cpu: Apple M4 Pro
                   │ /tmp/bench.0 │            /tmp/bench.1             │
                   │    sec/op    │   sec/op     vs base                │
    ErrOK-14          1.806n ± 1%   1.774n ± 0%    -1.77% (p=0.000 n=8)
    ErrCanceled-14    1.821n ± 1%   7.525n ± 3%  +313.23% (p=0.000 n=8)
    geomean           1.813n        3.654n       +101.47%

Fixes #75533
Fixes #75537

Change-Id: Iea22781a199ace7e7f70cf65168c36e090cd2e2a
Reviewed-on: https://go-review.googlesource.com/c/go/+/705235
TryBot-Bypass: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Auto-Submit: Damien Neil <dneil@google.com>
(cherry picked from commit 8ca209ec39)
Reviewed-on: https://go-review.googlesource.com/c/go/+/705375
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2025-10-01 12:06:47 -07:00
Ian Lance Taylor
0b53e410f8 [release-branch.go1.25] debug/pe: permit symbols with no name
They are reportedly generated by llvm-mingw clang21.

For #75219
Fixes #75221

Change-Id: I7fa7e13039bc7eee826cc19826985ca0e357a9ff
Reviewed-on: https://go-review.googlesource.com/c/go/+/700137
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
Auto-Submit: Ian Lance Taylor <iant@golang.org>
(cherry picked from commit ea00650784)
Reviewed-on: https://go-review.googlesource.com/c/go/+/708356
2025-10-01 11:58:18 -07:00
Keith Randall
7735dc90ed [release-branch.go1.25] cmd/compile: don't rely on loop info when there are irreducible loops
Loop information is sketchy when there are irreducible loops.
Sometimes blocks inside 2 loops can be recorded as only being part of
the outer loop. That causes tighten to move values that want to move
into such a block to move out of the loop altogether, breaking the
invariant that operations have to be scheduled after their args.

Fixes #75595

Change-Id: Idd80e6d2268094b8ae6387563081fdc1e211856a
Reviewed-on: https://go-review.googlesource.com/c/go/+/706355
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
(cherry picked from commit f15cd63ec4)
Reviewed-on: https://go-review.googlesource.com/c/go/+/706576
2025-10-01 11:40:47 -07:00
Roland Shoemaker
205d086595 [release-branch.go1.25] crypto/tls: quote protocols in ALPN error message
Quote the protocols sent by the client when returning the ALPN
negotiation error message.

Fixes CVE-2025-58189
Updates #75652
Fixes #75661

Change-Id: Ie7b3a1ed0b6efcc1705b71f0f1e8417126661330
Reviewed-on: https://go-review.googlesource.com/c/go/+/707776
Auto-Submit: Roland Shoemaker <roland@golang.org>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
Auto-Submit: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
TryBot-Bypass: Roland Shoemaker <roland@golang.org>
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
(cherry picked from commit 4e9006a716)
Reviewed-on: https://go-review.googlesource.com/c/go/+/708095
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-10-01 11:37:43 -07:00
Michael Pratt
16fdaac4b1 [release-branch.go1.25] sync/atomic: correct Uintptr.Or return doc
Uintptr.Or returns the old value, just like all of the other Or
functions. This was a typo in the original CL 544455.

For #75607.
Fixes #75610.

Change-Id: I260959e7e32e51f1152b5271df6cc51adfa02a4d
Reviewed-on: https://go-review.googlesource.com/c/go/+/706816
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Mauri de Souza Meneguzzo <mauri870@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
(cherry picked from commit d70ad4e740)
Reviewed-on: https://go-review.googlesource.com/c/go/+/706856
2025-10-01 09:40:26 -07:00
Steve Muir
f3dc4aac0b [release-branch.go1.25] runtime: initialise debug settings much earlier in startup process
This is necessary specifically to set the value of `debug.decoratemappings`
sufficiently early in the startup sequence that all memory ranges allocated
can be named appropriately using the new Linux-specific naming API
introduced in #71546.

Example output (on ARM64):
https://gist.github.com/9muir/3667654b9c3f52e8be92756219371672

For: #75324
Fixes #75669

Change-Id: Ic0b16233f54a45adef1660c4d0df59af2f5af86a
Reviewed-on: https://go-review.googlesource.com/c/go/+/703476
Auto-Submit: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
(cherry picked from commit 300d9d2714)
Reviewed-on: https://go-review.googlesource.com/c/go/+/708359
Reviewed-by: Cherry Mui <cherryyz@google.com>
2025-10-01 09:39:42 -07:00
qmuntal
79c3081b4b [release-branch.go1.25] internal/poll: don't call Seek for overlapped Windows handles
Overlapped handles don't have the file pointer updated when performing
I/O operations, so there is no need to call FD.Seek to reset the file
pointer.

Also, some overlapped file handles don't support seeking. See #74951.

For #74951.
Fixes #75111.

Change-Id: I0edd53beed7d3862730f3b2ed5fe9ba490e66c06
Reviewed-on: https://go-review.googlesource.com/c/go/+/697295
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
(cherry picked from commit 509d5f647f)
Reviewed-on: https://go-review.googlesource.com/c/go/+/704315
2025-10-01 08:06:41 -07:00
Filippo Valsorda
b816c79658 [release-branch.go1.25] lib/fips140: re-seal v1.0.0
Exceptionally, we decided to make a compliance-related change following
CMVP's updated Implementation Guidance on September 2nd.

The Security Policy will be updated to reflect the new zip hash.

mkzip.go has been modified to accept versions of the form vX.Y.Z-hash,
where the -hash suffix is ignored for fips140.Version() but used to
name the zip file and the unpacked cache directory.

The new zip is generated with

	go run ../../src/cmd/go/internal/fips140/mkzip.go -b c2097c7c v1.0.0-c2097c7c

from c2097c7c which is the current release-branch.go1.24 head.

The full diff between the zip file contents is included below.

Fixes #75524
For #74947
Updates #69536


$ diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/cast.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/cast.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/cast.go	1980-01-10 00:00:00.000000000 +0100
@@ -56,9 +56,10 @@
 }

 // PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and
-// returns any errors. If an error is returned, the key must not be used.
+// aborts the program (stopping the module input/output and entering the "error
+// state") if the test fails.
 //
-// PCTs are mandatory for every key pair that is generated/imported, including
+// PCTs are mandatory for every generated (but not imported) key pair, including
 // ephemeral keys (which effectively doubles the cost of key establishment). See
 // Implementation Guidance 10.3.A Additional Comment 1.
 //
@@ -66,17 +67,23 @@
 //
 // If a package p calls PCT during key generation, an invocation of that
 // function should be added to fipstest.TestConditionals.
-func PCT(name string, f func() error) error {
+func PCT(name string, f func() error) {
 	if strings.ContainsAny(name, ",#=:") {
 		panic("fips: invalid self-test name: " + name)
 	}
 	if !Enabled {
-		return nil
+		return
 	}

 	err := f()
 	if name == failfipscast {
 		err = errors.New("simulated PCT failure")
 	}
-	return err
+	if err != nil {
+		fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
+		panic("unreachable")
+	}
+	if debug {
+		println("FIPS 140-3 PCT passed:", name)
+	}
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdh/ecdh.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdh/ecdh.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdh/ecdh.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdh/ecdh.go	1980-01-10 00:00:00.000000000 +0100
@@ -161,6 +161,27 @@
 		if err != nil {
 			continue
 		}
+
+		// A "Pairwise Consistency Test" makes no sense if we just generated the
+		// public key from an ephemeral private key. Moreover, there is no way to
+		// check it aside from redoing the exact same computation again. SP 800-56A
+		// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
+		// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
+		// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
+		// Comment 1 goes out of its way to say that "the PCT shall be performed
+		// consistent [...], even if the underlying standard does not require a
+		// PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode.
+		fips140.PCT("ECDH PCT", func() error {
+			p1, err := c.newPoint().ScalarBaseMult(privateKey.d)
+			if err != nil {
+				return err
+			}
+			if !bytes.Equal(p1.Bytes(), privateKey.pub.q) {
+				return errors.New("crypto/ecdh: public key does not match private key")
+			}
+			return nil
+		})
+
 		return privateKey, nil
 	}
 }
@@ -188,28 +209,6 @@
 		panic("crypto/ecdh: internal error: public key is the identity element")
 	}

-	// A "Pairwise Consistency Test" makes no sense if we just generated the
-	// public key from an ephemeral private key. Moreover, there is no way to
-	// check it aside from redoing the exact same computation again. SP 800-56A
-	// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
-	// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
-	// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
-	// Comment 1 goes out of its way to say that "the PCT shall be performed
-	// consistent [...], even if the underlying standard does not require a
-	// PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode.
-	if err := fips140.PCT("ECDH PCT", func() error {
-		p1, err := c.newPoint().ScalarBaseMult(key)
-		if err != nil {
-			return err
-		}
-		if !bytes.Equal(p1.Bytes(), publicKey) {
-			return errors.New("crypto/ecdh: public key does not match private key")
-		}
-		return nil
-	}); err != nil {
-		panic(err)
-	}
-
 	k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}}
 	return k, nil
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/cast.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/cast.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/cast.go	1980-01-10 00:00:00.000000000 +0100
@@ -51,8 +51,8 @@
 	}
 }

-func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
-	return fips140.PCT("ECDSA PCT", func() error {
+func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) {
+	fips140.PCT("ECDSA PCT", func() error {
 		hash := testHash()
 		drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil)
 		sig, err := sign(c, k, drbg, hash)
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/ecdsa.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/ecdsa.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/ecdsa.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/ecdsa.go	1980-01-10 00:00:00.000000000 +0100
@@ -166,11 +166,6 @@
 		return nil, err
 	}
 	priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)}
-	if err := fipsPCT(c, priv); err != nil {
-		// This can happen if the application went out of its way to make an
-		// ecdsa.PrivateKey with a mismatching PublicKey.
-		return nil, err
-	}
 	return priv, nil
 }

@@ -203,10 +198,7 @@
 		},
 		d: k.Bytes(c.N),
 	}
-	if err := fipsPCT(c, priv); err != nil {
-		// This clearly can't happen, but FIPS 140-3 mandates that we check it.
-		panic(err)
-	}
+	fipsPCT(c, priv)
 	return priv, nil
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/hmacdrbg.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/hmacdrbg.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ecdsa/hmacdrbg.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ecdsa/hmacdrbg.go	1980-01-10 00:00:00.000000000 +0100
@@ -121,7 +121,7 @@
 //
 // This should only be used for ACVP testing. hmacDRBG is not intended to be
 // used directly.
-func TestingOnlyNewDRBG(hash func() fips140.Hash, entropy, nonce []byte, s []byte) *hmacDRBG {
+func TestingOnlyNewDRBG[H fips140.Hash](hash func() H, entropy, nonce []byte, s []byte) *hmacDRBG {
 	return newDRBG(hash, entropy, nonce, plainPersonalizationString(s))
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/cast.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/cast.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/cast.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/cast.go	1980-01-10 00:00:00.000000000 +0100
@@ -12,8 +12,8 @@
 	"sync"
 )

-func fipsPCT(k *PrivateKey) error {
-	return fips140.PCT("Ed25519 sign and verify PCT", func() error {
+func fipsPCT(k *PrivateKey) {
+	fips140.PCT("Ed25519 sign and verify PCT", func() error {
 		return pairwiseTest(k)
 	})
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/ed25519.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/ed25519.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/ed25519/ed25519.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/ed25519/ed25519.go	1980-01-10 00:00:00.000000000 +0100
@@ -69,10 +69,7 @@
 	fips140.RecordApproved()
 	drbg.Read(priv.seed[:])
 	precomputePrivateKey(priv)
-	if err := fipsPCT(priv); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires that we check.
-		panic(err)
-	}
+	fipsPCT(priv)
 	return priv, nil
 }

@@ -88,10 +85,6 @@
 	}
 	copy(priv.seed[:], seed)
 	precomputePrivateKey(priv)
-	if err := fipsPCT(priv); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires that we check.
-		panic(err)
-	}
 	return priv, nil
 }

@@ -137,12 +130,6 @@

 	copy(priv.prefix[:], h[32:])

-	if err := fipsPCT(priv); err != nil {
-		// This can happen if the application messed with the private key
-		// encoding, and the public key doesn't match the seed anymore.
-		return nil, err
-	}
-
 	return priv, nil
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/fips140.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/fips140.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/fips140.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/fips140.go	1980-01-10 00:00:00.000000000 +0100
@@ -62,6 +62,10 @@
 	return "Go Cryptographic Module"
 }

+// Version returns the formal version (such as "v1.0.0") if building against a
+// frozen module with GOFIPS140. Otherwise, it returns "latest".
 func Version() string {
-	return "v1.0"
+	// This return value is replaced by mkzip.go, it must not be changed or
+	// moved to a different file.
+	return "v1.0.0"
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem1024.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem1024.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem1024.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem1024.go	1980-01-10 00:00:00.000000000 +0100
@@ -118,10 +118,7 @@
 	var z [32]byte
 	drbg.Read(z[:])
 	kemKeyGen1024(dk, &d, &z)
-	if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires us to check.
-		panic(err)
-	}
+	fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) })
 	fips140.RecordApproved()
 	return dk, nil
 }
@@ -149,10 +146,6 @@
 	d := (*[32]byte)(seed[:32])
 	z := (*[32]byte)(seed[32:])
 	kemKeyGen1024(dk, d, z)
-	if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires us to check.
-		panic(err)
-	}
 	fips140.RecordApproved()
 	return dk, nil
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem768.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem768.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/mlkem/mlkem768.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/mlkem/mlkem768.go	1980-01-10 00:00:00.000000000 +0100
@@ -177,10 +177,7 @@
 	var z [32]byte
 	drbg.Read(z[:])
 	kemKeyGen(dk, &d, &z)
-	if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires us to check.
-		panic(err)
-	}
+	fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) })
 	fips140.RecordApproved()
 	return dk, nil
 }
@@ -208,10 +205,6 @@
 	d := (*[32]byte)(seed[:32])
 	z := (*[32]byte)(seed[32:])
 	kemKeyGen(dk, d, z)
-	if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
-		// This clearly can't happen, but FIPS 140-3 requires us to check.
-		panic(err)
-	}
 	fips140.RecordApproved()
 	return dk, nil
 }
diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/keygen.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/keygen.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/keygen.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/keygen.go	1980-01-10 00:00:00.000000000 +0100
@@ -105,7 +105,28 @@
 		// negligible chance of failure we can defer the check to the end of key
 		// generation and return an error if it fails. See [checkPrivateKey].

-		return newPrivateKey(N, 65537, d, P, Q)
+		k, err := newPrivateKey(N, 65537, d, P, Q)
+		if err != nil {
+			return nil, err
+		}
+
+		if k.fipsApproved {
+			fips140.PCT("RSA sign and verify PCT", func() error {
+				hash := []byte{
+					0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+					0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
+					0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+					0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+				}
+				sig, err := signPKCS1v15(k, "SHA-256", hash)
+				if err != nil {
+					return err
+				}
+				return verifyPKCS1v15(k.PublicKey(), "SHA-256", hash, sig)
+			})
+		}
+
+		return k, nil
 	}
 }

diff -ru golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/rsa.go golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/rsa.go
--- golang.org/fips140@v1.0.0/fips140/v1.0.0/rsa/rsa.go	1980-01-10 00:00:00.000000000 +0100
+++ golang.org/fips140@v1.0.0-c2097c7c/fips140/v1.0.0-c2097c7c/rsa/rsa.go	1980-01-10 00:00:00.000000000 +0100
@@ -310,26 +310,6 @@
 		return errors.New("crypto/rsa: d too small")
 	}

-	// If the key is still in scope for FIPS mode, perform a Pairwise
-	// Consistency Test.
-	if priv.fipsApproved {
-		if err := fips140.PCT("RSA sign and verify PCT", func() error {
-			hash := []byte{
-				0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
-				0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
-				0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
-				0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
-			}
-			sig, err := signPKCS1v15(priv, "SHA-256", hash)
-			if err != nil {
-				return err
-			}
-			return verifyPKCS1v15(priv.PublicKey(), "SHA-256", hash, sig)
-		}); err != nil {
-			return err
-		}
-	}
-
 	return nil
 }


Change-Id: I6a6a6964b1780f19ec2b5202052de58b47d9342c
Reviewed-on: https://go-review.googlesource.com/c/go/+/701520
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Commit-Queue: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-on: https://go-review.googlesource.com/c/go/+/706719
2025-09-26 10:44:50 -07:00
Filippo Valsorda
90de3b3399 [release-branch.go1.25] crypto/internal/fips140: remove key import PCTs, make keygen PCTs fatal
CMVP clarified with the September 2nd changes to IG 10.3.A that PCTs
don't need to run on imported keys.

However, PCT failure must enter the error state (which for us is fatal).

Thankfully, now that PCTs only run on key generation, we can be assured
they will never fail.

This change should only affect FIPS 140-3 mode.

While at it, make the CAST/PCT testing more robust, checking
TestConditional is terminated by a fatal error (and not by t.Fatal).

Updates #75524
Updates #74947
Updates #69536

Change-Id: I6a6a696439e1560c10f3cce2cb208fd40c5bc641
Reviewed-on: https://go-review.googlesource.com/c/go/+/706718
TryBot-Bypass: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-09-26 10:44:47 -07:00
Filippo Valsorda
bec452a3a2 [release-branch.go1.25] crypto/internal/fips140: update frozen module version to "v1.0.0"
We are re-sealing the .zip file anyway for another reason, might as well
take the opportunity to fix the "v1.0" mistake.

Note that the actual returned version change will happen when re-sealing
the .zip, as the latest mkzip.go will inject "v1.0.0" instead of "v1.0".

This reapplies CL 701518, reverted in CL 702255.

Updates #75524

Change-Id: Ib5b3721bda35c32dd48293b3d1193c12661662dd
Reviewed-on: https://go-review.googlesource.com/c/go/+/706717
Reviewed-by: Roland Shoemaker <roland@golang.org>
TryBot-Bypass: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-09-26 10:44:44 -07:00
Filippo Valsorda
57bd28ab7f [release-branch.go1.25] crypto/internal/fips140/ecdsa: make TestingOnlyNewDRBG generic
We are re-sealing the .zip file anyway for another reason, might as well
take the opportunity to remove the fips140.Hash type indirection.

Updates #75524

Change-Id: I6a6a6964fdb312cc2c64e327f845c398c0f6279b
Reviewed-on: https://go-review.googlesource.com/c/go/+/706716
TryBot-Bypass: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
2025-09-26 10:44:40 -07:00
Damien Neil
f75bcffa4a [release-branch.go1.25] os: set full name for Roots created with Root.OpenRoot
Set the Name for a Root created within a Root to be the
concatenation of the parent's path and the name used to open the child.

This matches the behavior for files opened within a Root
with Root.Open.

For #73868
Fixes #75139

Change-Id: Idf4021602ac25556721b7ef6924dec652c7bf4db
Reviewed-on: https://go-review.googlesource.com/c/go/+/698376
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit ed7f804775)
Reviewed-on: https://go-review.googlesource.com/c/go/+/704277
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
2025-09-22 09:47:39 -07:00
database64128
7d570090a9 [release-branch.go1.25] os: fix Root.MkdirAll to handle race of directory creation
No tests were added, because in order to reproduce, the directory would
have to be created precisely between the rootOpenDir and mkdirat calls,
which is impossible to do in a test.

Fixes #75116

Change-Id: I6f86a5b33c87452c35728318eaf2169a7534ef37
Reviewed-on: https://go-review.googlesource.com/c/go/+/698215
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Sean Liao <sean@liao.dev>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Sean Liao <sean@liao.dev>
(cherry picked from commit a076f49757)
Reviewed-on: https://go-review.googlesource.com/c/go/+/700655
Reviewed-by: Mark Freeman <markfreeman@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2025-09-22 08:07:45 -07:00
Alessandro Arzilli
be61132165 [release-branch.go1.25] cmd/compile: export to DWARF types only referenced through interfaces
Delve and viewcore use DWARF type DIEs to display and explore the
runtime value of interface variables.
This has always been slightly problematic since the runtime type of an
interface variable might only be reachable through interfaces and thus
be missing from debug_info (see issue #46670).
Prior to commit f4de2ecf this was not a severe problem since a struct
literal caused the allocation of a struct into an autotemp variable,
which was then used by dwarfgen to make sure that the DIE for that type
would be generated.
After f4de2ecf such autotemps are no longer being generated and
go1.25.0 ends up having many more instances of interfaces with
unreadable runtime type  (https://github.com/go-delve/delve/issues/4080).
This commit fixes this problem by scanning the relocation of the
function symbol and adding to the function's DIE symbol references to
all types used by the function to create interfaces.

Fixes go-delve/delve#4080
Updates #46670
Fixes #75255

Change-Id: I3e9db1c0d1662905373239816a72604ac533b09e
Reviewed-on: https://go-review.googlesource.com/c/go/+/696955
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Than McIntosh <thanm@golang.org>
Reviewed-by: Florian Lehner <lehner.florian86@gmail.com>
(cherry picked from commit 80038586ed)
Reviewed-on: https://go-review.googlesource.com/c/go/+/704335
Reviewed-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
2025-09-16 10:04:05 -07:00
Richard Miller
a86792b169 [release-branch.go1.25] net: skip TestIPv4WriteMsgUDPAddrPort on plan9
This test uses method (*UDPConn).WriteMsgUDPAddrPort, which is
not supported on Plan 9. The test needs to be skipped, like
for example TestAllocs which is already skipped for the
same reason.

For #75017
Fixes #75357

Change-Id: Iaa0e6ecdba0938736d8f675fcac43c46db34cb5d
Reviewed-on: https://go-review.googlesource.com/c/go/+/696095
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit cb814bd5bc)
Reviewed-on: https://go-review.googlesource.com/c/go/+/704280
Reviewed-by: Mark Freeman <markfreeman@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
2025-09-16 10:04:01 -07:00
Damien Neil
879e3cb5f7 [release-branch.go1.25] runtime: lock mheap_.speciallock when allocating synctest specials
Avoid racing use of mheap_.specialBubbleAlloc.

For #75134
Fixes #75347

Change-Id: I0c9140c18d2bca1e1c3387cd81230f0e8c9ac23e
Reviewed-on: https://go-review.googlesource.com/c/go/+/699255
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit 5dcedd6550)
Reviewed-on: https://go-review.googlesource.com/c/go/+/701797
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
2025-09-11 10:49:57 -07:00
Gopher Robot
56ebf80e57 [release-branch.go1.25] go1.25.1
Change-Id: I93a703d161b821cf7a78934f8711416ac6b00485
Reviewed-on: https://go-review.googlesource.com/c/go/+/700736
Auto-Submit: Gopher Robot <gobot@golang.org>
TryBot-Bypass: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2025-09-03 10:46:11 -07:00
Filippo Valsorda
b1959cf6f7 [release-branch.go1.25] net/http: require exact match for CrossSiteProtection bypass patterns
Fixes #75160
Updates #75054
Fixes CVE-2025-47910

Change-Id: I6a6a696440c45c450d2cd681f418b01aa0422a60
Reviewed-on: https://go-review.googlesource.com/c/go/+/699276
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-08-27 08:45:05 -07:00
database64128
cdd8cf4988 [release-branch.go1.25] net: fix WriteMsgUDPAddrPort addr handling on IPv4 sockets
Accept IPv4-mapped IPv6 destination addresses on IPv4 UDP sockets.

Fixes #74999.

Change-Id: I4624b9b8f861aedcae29e51d5298d23ce1c0f2c7
Reviewed-on: https://go-review.googlesource.com/c/go/+/689976
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
(cherry picked from commit bdb2d50fdf)
Reviewed-on: https://go-review.googlesource.com/c/go/+/695875
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-08-25 19:49:59 -07:00
qmuntal
8995e84ac6 [release-branch.go1.25] internal/poll: set the correct file offset in FD.Seek for Windows overlapped handles
Windows doesn't keep the file pointer for overlapped file handles. To
work around this, we keep track of the current offset ourselves and use
it on every Read/Write operation.

When the user calls File.Seek with whence == io.SeekCurrent, it expects
that the offset we keep track of is also accounted for, else the the
seek'ed value won't match the file pointer seen by the user.

Fixes #75083.

Change-Id: Ieca7c3779e5349292883ffc293a8474088a4dec7
Reviewed-on: https://go-review.googlesource.com/c/go/+/697275
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
(cherry picked from CL 697275)
Reviewed-on: https://go-review.googlesource.com/c/go/+/697995
2025-08-25 11:19:12 -07:00
Damien Neil
749dff880a [release-branch.go1.25] runtime: make all synctest bubble violations fatal panics
Unblocking a bubbled goroutine from outside the bubble is an error
and panics. Currently, some of those panics are regular panics
and some are fatal. We use fatal panics in cases where its difficult
to panic without leaving something in an inconsistent state.

Change the regular panics (channel and timer operations) to be fatal.

This makes our behavior more consistent: All bubble violations are
always fatal.

More importantly, it avoids introducing new, recoverable panics.
A motivating example for this change is the context package,
which performs channel operations with a mutex held in the
expectation that those operations can never panic. These operations
can now panic as a result of a bubble violation, potentially
leaving a context.Context in an inconsistent state.

For #74837
Fixes #75021

Change-Id: Ie6efd916b7f505c0f13dde42de1572992401f15c
Reviewed-on: https://go-review.googlesource.com/c/go/+/696195
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
(cherry picked from commit a8564bd412)
Reviewed-on: https://go-review.googlesource.com/c/go/+/696196
2025-08-25 11:17:11 -07:00
Richard Miller
21ac81c1e1 [release-branch.go1.25] os/exec: fix incorrect expansion of ".." in LookPath on plan9
The correction in CL 685755 is incomplete for plan9, where path
search is performed even on file strings containing "/". By
applying filepath.Clean to the argument of validateLookPath,
we can check for bogus file strings containing ".." where the
later call to filepath.Join would transform a path like
"badfile/dir/.." to "badfile" even where "dir" isn't a directory
or doesn't exist.

For #74466
Fixes #75008

Change-Id: I3f8b73a1de6bc7d8001b1ca8e74b78722408548e
Reviewed-on: https://go-review.googlesource.com/c/go/+/693935
Reviewed-by: David du Colombier <0intro@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
(cherry picked from commit 674c5f0edd)
Reviewed-on: https://go-review.googlesource.com/c/go/+/698416
Reviewed-by: Cherry Mui <cherryyz@google.com>
2025-08-25 11:15:17 -07:00
Michael Matloob
c72fcab6d6 [release-branch.go1.25] cmd/go/internal/gover: fix ModIsPrerelease for toolchain versions
We forgot to call the IsPrerelease function on FromToolchain(vers)
rather than on vers itself. IsPrerelase expects a version without the
"go" prefix. See the corresponding code in ModIsValid and ModIsPrefix
that call FromToolchain before passing the versions to IsValid and
IsLang respectively.

Fixes #74822

Change-Id: I3cf055e1348e6a9dc0334e414f06fe85eaf78024
Reviewed-on: https://go-review.googlesource.com/c/go/+/691655
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Matloob <matloob@golang.org>
Reviewed-by: Michael Matloob <matloob@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
(cherry picked from commit 69338a335a)
Reviewed-on: https://go-review.googlesource.com/c/go/+/691958
2025-08-20 12:47:59 -07:00
Gopher Robot
6e676ab2b8 [release-branch.go1.25] go1.25.0
Change-Id: I46dcb2de47fd752d61863cc351ad792b64995a93
Reviewed-on: https://go-review.googlesource.com/c/go/+/695416
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Bypass: Gopher Robot <gobot@golang.org>
Commit-Queue: David Chase <drchase@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: David Chase <drchase@google.com>
2025-08-12 13:50:10 -07:00
Gopher Robot
ac94297758 [release-branch.go1.25] go1.25rc3
Change-Id: I7801c8fe17b0712b479d45fda0d81c060a904097
Reviewed-on: https://go-review.googlesource.com/c/go/+/693716
TryBot-Bypass: Gopher Robot <gobot@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
2025-08-06 11:09:03 -07:00
Damien Neil
6961c3775f [release-branch.go1.25] database/sql: avoid closing Rows while scan is in progress
A database/sql/driver.Rows can return database-owned data
from Rows.Next. The driver.Rows documentation doesn't explicitly
document the lifetime guarantees for this data, but a reasonable
expectation is that the caller of Next should only access it
until the next call to Rows.Close or Rows.Next.

Avoid violating that constraint when a query is cancelled while
a call to database/sql.Rows.Scan (note the difference between
the two different Rows types!) is in progress. We previously
took care to avoid closing a driver.Rows while the user has
access to driver-owned memory via a RawData, but we could still
close a driver.Rows while a Scan call was in the process of
reading previously-returned driver-owned data.

Update the fake DB used in database/sql tests to invalidate
returned data to help catch other places we might be
incorrectly retaining it.

Updates #74831
Fixes #74834

Change-Id: Ice45b5fad51b679c38e3e1d21ef39156b56d6037
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2540
Reviewed-by: Roland Shoemaker <bracewell@google.com>
Reviewed-by: Neal Patel <nealpatel@google.com>
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2600
Reviewed-on: https://go-review.googlesource.com/c/go/+/693559
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Mark Freeman <markfreeman@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2025-08-06 10:52:26 -07:00
Olivier Mengué
ebee011a54 [release-branch.go1.25] os/exec: fix incorrect expansion of "", "." and ".." in LookPath
Fix incorrect expansion of "" and "." when $PATH contains an executable
file or, on Windows, a parent directory of a %PATH% element contains an
file with the same name as the %PATH% element but with one of the
%PATHEXT% extension (ex: C:\utils\bin is in PATH, and C:\utils\bin.exe
exists).

Fix incorrect expansion of ".." when $PATH contains an element which is
an the concatenation of the path to an executable file (or on Windows
a path that can be expanded to an executable by appending a %PATHEXT%
extension), a path separator and a name.

"", "." and ".." are now rejected early with ErrNotFound.

Fixes CVE-2025-47906
Fixes #74466

Change-Id: Ie50cc0a660fce8fbdc952a7f2e05c36062dcb50e
Reviewed-on: https://go-review.googlesource.com/c/go/+/685755
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
(cherry picked from commit e0b07dc22e)
Reviewed-on: https://go-review.googlesource.com/c/go/+/691775
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
2025-07-30 08:43:19 -07:00
qmuntal
84fb1b8253 [release-branch.go1.25] os/user: user random name for the test user account
TestImpersonated and TestGroupIdsTestUser are flaky due to sporadic
failures when creating the test user account when running the tests
from different processes at the same time.

This flakiness can be fixed by using a random name for the test user
account.

Fixes #73523
Fixes #74727
Fixes #74728
Fixes #74729
Fixes #74745
Fixes #74751

Cq-Include-Trybots: luci.golang.try:go1.25-windows-amd64-longtest
Change-Id: Ib2283a888437420502b1c11d876c975f5af4bc03
Reviewed-on: https://go-review.googlesource.com/c/go/+/690175
Auto-Submit: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
(cherry picked from commit 374e3be2eb)
Reviewed-on: https://go-review.googlesource.com/c/go/+/690555
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Mark Freeman <mark@golang.org>
2025-07-28 11:34:45 -07:00
Michael Matloob
c95d3093ca [release-branch.go1.25] cmd/go: always return the cached path from go tool -n
If we're running go tool -n always return the cached path of the tool.
We can't always use the cached path when running the tool because if we
copied the tool to the cached location in the same process and then try
to run it we'll run into #22315, producing spurious ETXTBSYs.

Fixes #72824

Change-Id: I81f23773b9028f955ccc97453627ae4f2573814b
Reviewed-on: https://go-review.googlesource.com/c/go/+/688895
Auto-Submit: Michael Matloob <matloob@golang.org>
Reviewed-by: Michael Matloob <matloob@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
(cherry picked from commit efc37e97c0)
Reviewed-on: https://go-review.googlesource.com/c/go/+/690895
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2025-07-28 10:58:29 -07:00
Michael Anthony Knyszek
561964c9a8 [release-branch.go1.25] all: merge master (489868f) into release-branch.go1.25
Merge List:

+ 2025-07-23 489868f776 cmd/link: scope test to linux & net.sendFile
+ 2025-07-22 71c2bf5513 cmd/compile: fix loclist for heap return vars without optimizations
+ 2025-07-22 c74399e7f5 net: correct comment for ListenConfig.ListenPacket
+ 2025-07-22 4ed9943b26 all: go fmt
+ 2025-07-22 1aaf7422f1 cmd/internal/objabi: remove redundant word in comment
+ 2025-07-21 d5ec0815e6 runtime: relax TestMemoryLimitNoGCPercent a bit
+ 2025-07-21 f7cc61e7d7 cmd/compile: for arm64 epilog, do SP increment with a single instruction
+ 2025-07-21 5dac42363b runtime: fix asan wrapper for riscv64
+ 2025-07-21 e5502e0959 cmd/go: check subcommand properties
+ 2025-07-19 2363897932 cmd/internal/obj: enable got pcrel itype in fips140 for riscv64
+ 2025-07-19 e32255fcc0 cmd/compile/internal/ssa: restrict architectures for TestDebugLines_74576
+ 2025-07-18 0451816430 os: revert the use of AddCleanup to close files and roots
+ 2025-07-18 34b70684ba go/types: infer correct type for y in append(bytes, y...)
+ 2025-07-17 66536242fc cmd/compile/internal/escape: improve DWARF .debug_line numbering for literal rewriting optimizations
+ 2025-07-16 385000b004 runtime: fix idle time double-counting bug
+ 2025-07-16 f506ad2644 cmd/compile/internal/escape: speed up analyzing some functions with many closures
+ 2025-07-16 9c507e7942 cmd/link, runtime: on Wasm, put only function index in method table and func table
+ 2025-07-16 9782dcfd16 runtime: use 32-bit function index on Wasm
+ 2025-07-16 c876bf9346 cmd/internal/obj/wasm: use 64-bit instructions for indirect calls
+ 2025-07-15 b4309ece66 cmd/internal/doc: upgrade godoc pkgsite to 01b046e
+ 2025-07-15 75a19dbcd7 runtime: use memclrNoHeapPointers to clear inline mark bits
+ 2025-07-15 6d4a91c7a5 runtime: only clear inline mark bits on span alloc if necessary
+ 2025-07-15 0c6296ab12 runtime: have mergeInlineMarkBits also clear the inline mark bits
+ 2025-07-15 397d2117ec runtime: merge inline mark bits with gcmarkBits 8 bytes at a time
+ 2025-07-15 7dceabd3be runtime/maps: fix typo in group.go comment (instrinsified -> intrinsified)
+ 2025-07-15 d826bf4d74 os: remove useless error check
+ 2025-07-14 bb07e55aff runtime: expand GOMAXPROCS documentation
+ 2025-07-14 9159cd4ec6 encoding/json: decompose legacy options
+ 2025-07-14 c6556b8eb3 encoding/json/v2: add security section to doc
+ 2025-07-11 6ebb5f56d9 runtime: gofmt after CL 643897 and CL 662455
+ 2025-07-11 1e48ca7020 encoding/json: remove legacy option to EscapeInvalidUTF8
+ 2025-07-11 a0a99cb22b encoding/json/v2: report wrapped io.ErrUnexpectedEOF
+ 2025-07-11 9d04122d24 crypto/rsa: drop contradictory promise to keep PublicKey modulus secret
+ 2025-07-11 1ca23682dd crypto/rsa: fix documentation formatting
+ 2025-07-11 4bc3373c8e runtime: turn off large memmove tests under asan/msan
+ 2025-07-11 88cf0c5d55 cmd/link: do size fixups after symbol references are loaded
+ 2025-07-10 7a38975a48 os: trivial comment fix
+ 2025-07-10 aa5de9ebb5 synctest: fix comments for time.Now() in synctests
+ 2025-07-10 63ec70d4e1 crypto/cipher: Fix comment punctuation
+ 2025-07-09 8131635e5a runtime: run TestSignalDuringExec in its own process group
+ 2025-07-09 67c1704444 crypto/tls: empty server_name conf. ext. from server
+ 2025-07-08 54c9d77630 cmd/go: disable support for multiple vcs in one module
+ 2025-07-08 fca43a8436 internal: make struct comment match struct name
+ 2025-07-08 bb917bb030 cmd/compile: document that nosplit directive is unsafe
+ 2025-07-08 a5bda585d5 cmd/compile: run fmt on ssa
+ 2025-07-07 86b5ba7310 internal/trace: only test for sync preemption if async preemption is off
+ 2025-07-07 ef46e1b164 cmd/internal/doc: fix GOROOT skew and path joining bugs
+ 2025-07-07 75b43f9a97 runtime: make traceStack testable and add a benchmark
+ 2025-07-07 20978f46fd crypto/rsa: remove another forgotten note to future self
+ 2025-07-07 33fb4819f5 cmd/compile/internal/ssa: skip EndSequence entries in TestStmtLines
+ 2025-07-07 a995269a93 sort: clarify Less doc
+ 2025-07-03 6c3b5a2798 runtime: correct vdsoSP on S390X
+ 2025-07-03 dd687c3860 hash: document that Clone may only return ErrUnsupported or a nil error
+ 2025-07-02 b325151453 cmd/cgo/internal/testsanitizers: skip asan tests when FIPS140 mode is on
+ 2025-07-02 15d9fe43d6 testing/synctest: explicitly state Run will be removed in Go 1.26
+ 2025-07-01 de646d94f7 cmd/go/internal/modindex: apply changes in CL 502615 to modindex package

Change-Id: I0420eec24c176a76a0ae51ddf6e34ee3fe4ae8ba
2025-07-23 18:53:29 +00:00
Gopher Robot
e73dadc758 [release-branch.go1.25] go1.25rc2
Change-Id: Iaf3a30e4c794c3f58abf429000d41f1c4f2fede1
Reviewed-on: https://go-review.googlesource.com/c/go/+/686456
TryBot-Bypass: Gopher Robot <gobot@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
2025-07-08 10:29:35 -07:00
Roland Shoemaker
2899144b8d [release-branch.go1.25] cmd/go: disable support for multiple vcs in one module
Removes the somewhat redundant vcs.FromDir, "allowNesting" argument,
which was always enabled, and disallow multiple VCS metadata folders
being present in a single directory. This makes VCS injection attacks
much more difficult.

Also adds a GODEBUG, allowmultiplevcs, which re-enables this behavior.

Thanks to RyotaK (https://ryotak.net) of GMO Flatt Security Inc for
reporting this issue.

Fixes #74380
Fixes CVE-2025-4674

Change-Id: I95b619588ecb6661770aa4e1d6023d6cb22e2263
Reviewed-on: https://go-review.googlesource.com/c/go/+/686338
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Carlos Amedee <carlos@golang.org>
TryBot-Bypass: Carlos Amedee <carlos@golang.org>
2025-07-08 09:29:36 -07:00
David Chase
b062eb46e8 [release-branch.go1.25] all: merge master (2f653a5) into release-branch.go1.25
Merge List:

+ 2025-07-01 2f653a5a9e crypto/tls: ensure the ECDSA curve matches the signature algorithm
+ 2025-07-01 6e95fd96cc crypto/ecdsa: fix crypto/x509 godoc links
+ 2025-07-01 7755a05209 Revert "crypto/internal/fips140/subtle: add assembly implementation of xorBytes for arm"
+ 2025-07-01 d168ad18e1 slices: update TestIssue68488 to avoid false positives
+ 2025-07-01 27ad1f5013 internal/abi: fix comment on NonEmptyInterface
+ 2025-06-30 86fca3dcb6 encoding/json/jsontext: use bytes.Buffer.AvailableBuffer
+ 2025-06-30 6bd9944c9a encoding/json/v2: avoid escaping jsonopts.Struct
+ 2025-06-30 e46d586edd cmd/compile/internal/escape: add debug hash for literal allocation optimizations
+ 2025-06-30 479b51ee1f cmd/compile/internal/escape: stop disabling literal allocation optimizations when coverage is enabled
+ 2025-06-30 8002d283e8 crypto/tls: update bogo version
+ 2025-06-30 fdd7713fe5 internal/goexperiment: fix godoc formatting
+ 2025-06-30 740857f529 runtime: stash allpSnapshot on the M
+ 2025-06-30 9ae38be302 sync: disassociate WaitGroups from bubbles on Wait
+ 2025-06-30 4731832342 crypto/hmac: wrap ErrUnsupported returned by Clone
+ 2025-06-30 03ad694dcb runtime: update skips for TestGdbBacktrace
+ 2025-06-30 9d1cd0b881 iter: add missing type parameter in doc
+ 2025-06-29 acb914f2c2 cmd/doc: fix -http on Windows
+ 2025-06-27 b51f1cdb87 runtime: remove arbitrary 5-second timeout in TestNeedmDeadlock
+ 2025-06-27 f1e6ae2f6f reflect: fix TypeAssert on nil interface values
+ 2025-06-27 e81c624656 os: use minimal file permissions when opening parent directory in RemoveAll
+ 2025-06-27 2a22aefa1f encoding/json: add security section to doc
+ 2025-06-27 742fda9524 runtime: account for missing frame pointer in preamble
+ 2025-06-27 fdc076ce76 net/http: fix RoundTrip context cancellation for js/wasm
+ 2025-06-27 d9d2cadd63 encoding/json: fix typo in hotlink for jsontext.PreserveRawStrings
+ 2025-06-26 0f8ab2db17 cmd/link: permit a larger size BSS reference to a smaller DATA symbol
+ 2025-06-26 988a20c8c5 cmd/compile/internal/escape: evaluate any side effects when rewriting with literals
+ 2025-06-25 b5d555991a encoding/json/jsontext: remove Encoder.UnusedBuffer
+ 2025-06-25 0b4d2eab2f encoding/json/jsontext: rename Encoder.UnusedBuffer as Encoder.AvailableBuffer
+ 2025-06-25 f8ccda2e05 runtime: make explicit nil check in (*spanInlineMarkBits).init
+ 2025-06-25 f069a82998 runtime: note custom GOMAXPROCS even if value doesn't change
+ 2025-06-24 e515ef8bc2 context: fix typo in context_test.go
+ 2025-06-24 47b941f445 cmd/link: add one more linkname to the blocklist
+ 2025-06-24 34cf5f6205 go/types: add test for interface method field type
+ 2025-06-24 6e618cd42a encoding/json: use zstd compressed testdata
+ 2025-06-24 fcb9850859 net/http: reduce allocs in CrossOriginProtection.Check
+ 2025-06-24 11f11f2a00 encoding/json/v2: support ISO 8601 durations
+ 2025-06-24 62deaf4fb8 doc: fix links to runtime Environment Variables
+ 2025-06-24 2e9bb62bfe encoding/json/v2: reject unquoted dash as a JSON field name
+ 2025-06-23 ed7815726d encoding/json/v2: report error on time.Duration without explicit format
+ 2025-06-23 f866958246 cmd/dist: test encoding/json/... with GOEXPERIMENT=jsonv2
+ 2025-06-23 f77a0aa6b6 internal/trace: improve gc-stress test
+ 2025-06-23 4506796a6e encoding/json/jsontext: consistently use JSON terminology
+ 2025-06-23 456a90aa16 runtime: add missing unlock in sysReserveAlignedSbrk
+ 2025-06-23 1cf6386b5e Revert "go/types, types2: don't register interface methods in Info.Types map"
+ 2025-06-20 49cdf0c42e testing, testing/synctest: handle T.Helper in synctest bubbles
+ 2025-06-20 3bf1eecbd3 runtime: fix struct comment
+ 2025-06-20 8ed23a2936 crypto/cipher: fix link to crypto/aes
+ 2025-06-20 ef60769b46 go/doc: add a golden test that reproduces #62640
+ 2025-06-18 8552bcf7c2 cmd/go/internal/fips140: ignore GOEXPERIMENT on error
+ 2025-06-18 4c7567290c runtime: set mspan limit field early and eagerly
+ 2025-06-18 c6ac736288 runtime: prevent mutual deadlock between GC stopTheWorld and suspendG
+ 2025-06-17 53af292aed encoding/json/jsontext: fix spelling error
+ 2025-06-16 d058254689 cmd/dist: always include variant in package names
+ 2025-06-16 3254c2bb83 internal/reflectlite: fix comment about meaning of flag field
+ 2025-06-16 816199e421 runtime: don't let readTrace spin on trace.shutdown
+ 2025-06-16 ea00461b17 internal/trace: make Value follow reflect conventions
+ 2025-06-13 96a6e147b2 runtime: comment that some linknames are used by runtime/trace
+ 2025-06-13 644905891f runtime: remove unused unique.runtime_blockUntilEmptyFinalizerQueue
+ 2025-06-13 683810a368 cmd/link: block new standard library linknames
+ 2025-06-12 9149876112 all: replace a few user-visible mentions of golang.org and godoc.org
+ 2025-06-12 934d5f2cf7 internal/trace: end test programs with SIGQUIT
+ 2025-06-12 5a08865de3 net: remove some BUG entries
+ 2025-06-11 d166a0b03e encoding/json/jsontext, encoding/json/v2: document experimental nature
+ 2025-06-11 d4c6effaa7 cmd/compile: add up-to-date test for generated files

Change-Id: I555d5d1bf8c8607fa0660146019657f4c04084e3
2025-07-01 14:01:40 -04:00
Gopher Robot
8ac5714ef2 [release-branch.go1.25] go1.25rc1
Change-Id: I2611db09afd71b4b4811d118ec8c2446de4f8d40
Reviewed-on: https://go-review.googlesource.com/c/go/+/681056
Auto-Submit: Gopher Robot <gobot@golang.org>
TryBot-Bypass: Gopher Robot <gobot@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
2025-06-11 11:56:35 -07:00
Cherry Mui
9546293d22 [release-branch.go1.25] all: merge master (7fa2c73) into release-branch.go1.25
Merge List:

+ 2025-06-10 7fa2c736b3 os: disallow Root.Remove(".") on Plan 9, js, and Windows
+ 2025-06-10 281cfcfc1b runtime: handle system goroutines later in goroutine profiling
+ 2025-06-10 4f86f22671 testing/synctest, runtime: avoid panic when using linker-alloc WG from bubble
+ 2025-06-10 773701a853 internal/trace: pass GOTRACEBACK=crash to testprogs
+ 2025-06-10 fb0c27c514 os: do not follow dangling symlinks in Root when O_CREATE|O_EXCL on AIX
+ 2025-06-10 1cafdfb63b net/http: make the zero value of CrossOriginProtection work
+ 2025-06-10 a35701b352 cmd/dist: only install necessary tools when doing local test
+ 2025-06-10 a189516d3a runtime: don't do a direct G handoff in semrelease on systemstack
+ 2025-06-10 f18d046568 all.{bash,rc}: use "../bin/go tool dist" instead of "%GOTOOLDIR%/dist" print build info
+ 2025-06-09 ee7bfbdbcc cmd/compile/internal/ssa: fix PPC64 merging of (AND (S[RL]Dconst ...)
+ 2025-06-09 985d600f3a runtime: use small struct TestSynctest to ensure cleanups run
+ 2025-06-09 848a768ba7 runtime: clarify stack traces for bubbled goroutines
+ 2025-06-09 049a5e6036 runtime: return a different bubble deadlock error when main goroutine is done
+ 2025-06-09 ac1686752b cmd/internal/doc: increase version of pkgsite doc command that's run

Change-Id: Iba7b2c2f06e91a39fa039c08170e6054e50de3c6
2025-06-11 09:16:16 -04:00
Cherry Mui
4b3a0b9785 [release-branch.go1.25] all: merge master (da0e8c4) into release-branch.go1.25
Merge List:

+ 2025-06-09 da0e8c4517 cmd/compile: relax reshaping condition
+ 2025-06-09 7800f4f0ad log/slog: fix level doc on handlers
+ 2025-06-07 d184f8dc02 runtime: check for gsignal in racecall on loong64
+ 2025-06-06 0ccfbc834a os/signal: doc link to syscall.EPIPE

Change-Id: I4e3cfdb4769207ba87788da1650ed2a1f731ed86
2025-06-09 12:41:15 -04:00
Carlos Amedee
5abb1d84f8 [release-branch.go1.25] update codereview.cfg for release-branch.go1.25
Change-Id: Id2aa864e4549623cc6d98d95028858d41459fa63
Reviewed-on: https://go-review.googlesource.com/c/go/+/679176
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2025-06-06 12:45:11 -07:00
95 changed files with 1770 additions and 542 deletions

2
VERSION Normal file
View File

@@ -0,0 +1,2 @@
go1.25.3
time 2025-10-13T16:08:43Z

View File

@@ -1 +1,2 @@
branch: master
branch: release-branch.go1.25
parent-branch: master

View File

@@ -1,6 +1,6 @@
<!--{
"Title": "The Go Programming Language Specification",
"Subtitle": "Language version go1.25 (Feb 25, 2025)",
"Subtitle": "Language version go1.25 (Aug 12, 2025)",
"Path": "/ref/spec"
}-->

View File

@@ -153,6 +153,16 @@ for example,
see the [runtime documentation](/pkg/runtime#hdr-Environment_Variables)
and the [go command documentation](/cmd/go#hdr-Build_and_test_caching).
### Go 1.26
Go 1.26 added a new `httpcookiemaxnum` setting that controls the maximum number
of cookies that net/http will accept when parsing HTTP headers. If the number of
cookie in a header exceeds the number set in `httpcookiemaxnum`, cookie parsing
will fail early. The default value is `httpcookiemaxnum=3000`. Setting
`httpcookiemaxnum=0` will allow the cookie parsing to accept an indefinite
number of cookies. To avoid denial of service attacks, this setting and default
was backported to Go 1.25.2 and Go 1.24.8.
### Go 1.25
Go 1.25 added a new `decoratemappings` setting that controls whether the Go

View File

@@ -9,4 +9,4 @@
#
# go test cmd/go/internal/fips140 -update
#
v1.0.0.zip b50508feaeff05d22516b21e1fd210bbf5d6a1e422eaf2cfa23fe379342713b8
v1.0.0-c2097c7c.zip daf3614e0406f67ae6323c902db3f953a1effb199142362a039e7526dfb9368b

View File

@@ -1 +1 @@
v1.0.0
v1.0.0-c2097c7c

1
lib/fips140/v1.0.0.txt Normal file
View File

@@ -0,0 +1 @@
v1.0.0-c2097c7c

View File

@@ -39,6 +39,7 @@ var (
errMissData = errors.New("archive/tar: sparse file references non-existent data")
errUnrefData = errors.New("archive/tar: sparse file contains unreferenced data")
errWriteHole = errors.New("archive/tar: write non-NUL byte in sparse hole")
errSparseTooLong = errors.New("archive/tar: sparse map too long")
)
type headerError []string

View File

@@ -531,12 +531,17 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) {
cntNewline int64
buf bytes.Buffer
blk block
totalSize int
)
// feedTokens copies data in blocks from r into buf until there are
// at least cnt newlines in buf. It will not read more blocks than needed.
feedTokens := func(n int64) error {
for cntNewline < n {
totalSize += len(blk)
if totalSize > maxSpecialFileSize {
return errSparseTooLong
}
if _, err := mustReadFull(r, blk[:]); err != nil {
return err
}
@@ -569,8 +574,8 @@ func readGNUSparseMap1x0(r io.Reader) (sparseDatas, error) {
}
// Parse for all member entries.
// numEntries is trusted after this since a potential attacker must have
// committed resources proportional to what this library used.
// numEntries is trusted after this since feedTokens limits the number of
// tokens based on maxSpecialFileSize.
if err := feedTokens(2 * numEntries); err != nil {
return nil, err
}

View File

@@ -621,6 +621,11 @@ func TestReader(t *testing.T) {
},
Format: FormatPAX,
}},
}, {
// Small compressed file that uncompresses to
// a file with a very large GNU 1.0 sparse map.
file: "testdata/gnu-sparse-many-zeros.tar.bz2",
err: errSparseTooLong,
}}
for _, v := range vectors {

Binary file not shown.

View File

@@ -128,14 +128,29 @@ func Info(ctxt *obj.Link, fnsym *obj.LSym, infosym *obj.LSym, curfn obj.Func) (s
// already referenced by a dwarf var, attach an R_USETYPE relocation to
// the function symbol to insure that the type included in DWARF
// processing during linking.
// Do the same with R_USEIFACE relocations from the function symbol for the
// same reason.
// All these R_USETYPE relocations are only looked at if the function
// survives deadcode elimination in the linker.
typesyms := []*obj.LSym{}
for t := range fnsym.Func().Autot {
typesyms = append(typesyms, t)
}
for i := range fnsym.R {
if fnsym.R[i].Type == objabi.R_USEIFACE && !strings.HasPrefix(fnsym.R[i].Sym.Name, "go:itab.") {
// Types referenced through itab will be referenced from somewhere else
typesyms = append(typesyms, fnsym.R[i].Sym)
}
}
slices.SortFunc(typesyms, func(a, b *obj.LSym) int {
return strings.Compare(a.Name, b.Name)
})
var lastsym *obj.LSym
for _, sym := range typesyms {
if sym == lastsym {
continue
}
lastsym = sym
infosym.AddRel(ctxt, obj.Reloc{Type: objabi.R_USETYPE, Sym: sym})
}
fnsym.Func().Autot = nil

View File

@@ -124,18 +124,21 @@ func tighten(f *Func) {
// If the target location is inside a loop,
// move the target location up to just before the loop head.
for _, b := range f.Blocks {
origloop := loops.b2l[b.ID]
for _, v := range b.Values {
t := target[v.ID]
if t == nil {
continue
}
targetloop := loops.b2l[t.ID]
for targetloop != nil && (origloop == nil || targetloop.depth > origloop.depth) {
t = idom[targetloop.header.ID]
target[v.ID] = t
targetloop = loops.b2l[t.ID]
if !loops.hasIrreducible {
// Loop info might not be correct for irreducible loops. See issue 75569.
for _, b := range f.Blocks {
origloop := loops.b2l[b.ID]
for _, v := range b.Values {
t := target[v.ID]
if t == nil {
continue
}
targetloop := loops.b2l[t.ID]
for targetloop != nil && (origloop == nil || targetloop.depth > origloop.depth) {
t = idom[targetloop.header.ID]
target[v.ID] = t
targetloop = loops.b2l[t.ID]
}
}
}
}

View File

@@ -27,10 +27,10 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"strings"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
modzip "golang.org/x/mod/zip"
)
@@ -61,7 +61,7 @@ func main() {
// Must have valid version, and must not overwrite existing file.
version := flag.Arg(0)
if !regexp.MustCompile(`^v\d+\.\d+\.\d+$`).MatchString(version) {
if semver.Canonical(version) != version {
log.Fatalf("invalid version %q; must be vX.Y.Z", version)
}
if _, err := os.Stat(version + ".zip"); err == nil {
@@ -117,7 +117,9 @@ func main() {
if !bytes.Contains(contents, []byte(returnLine)) {
log.Fatalf("did not find %q in fips140.go", returnLine)
}
newLine := `return "` + version + `"`
// Use only the vX.Y.Z part of a possible vX.Y.Z-hash version.
v, _, _ := strings.Cut(version, "-")
newLine := `return "` + v + `"`
contents = bytes.ReplaceAll(contents, []byte(returnLine), []byte(newLine))
wf, err := zw.Create(f.Name)
if err != nil {

View File

@@ -109,6 +109,9 @@ func ModIsPrefix(path, vers string) bool {
// The caller is assumed to have checked that ModIsValid(path, vers) is true.
func ModIsPrerelease(path, vers string) bool {
if IsToolchain(path) {
if path == "toolchain" {
return IsPrerelease(FromToolchain(vers))
}
return IsPrerelease(vers)
}
return semver.Prerelease(vers) != ""

View File

@@ -277,6 +277,29 @@ func loadModTool(ctx context.Context, name string) string {
return ""
}
func builtTool(runAction *work.Action) string {
linkAction := runAction.Deps[0]
if toolN {
// #72824: If -n is set, use the cached path if we can.
// This is only necessary if the binary wasn't cached
// before this invocation of the go command: if the binary
// was cached, BuiltTarget() will be the cached executable.
// It's only in the "first run", where we actually do the build
// and save the result to the cache that BuiltTarget is not
// the cached binary. Ideally, we would set BuiltTarget
// to the cached path even in the first run, but if we
// copy the binary to the cached path, and try to run it
// in the same process, we'll run into the dreaded #22315
// resulting in occasional ETXTBSYs. Instead of getting the
// ETXTBSY and then retrying just don't use the cached path
// on the first run if we're going to actually run the binary.
if cached := linkAction.CachedExecutable(); cached != "" {
return cached
}
}
return linkAction.BuiltTarget()
}
func buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []string) {
// Override GOOS and GOARCH for the build to build the tool using
// the same GOOS and GOARCH as this go command.
@@ -288,7 +311,7 @@ func buildAndRunBuiltinTool(ctx context.Context, toolName, tool string, args []s
modload.RootMode = modload.NoRoot
runFunc := func(b *work.Builder, ctx context.Context, a *work.Action) error {
cmdline := str.StringList(a.Deps[0].BuiltTarget(), a.Args)
cmdline := str.StringList(builtTool(a), a.Args)
return runBuiltTool(toolName, nil, cmdline)
}
@@ -300,7 +323,7 @@ func buildAndRunModtool(ctx context.Context, toolName, tool string, args []strin
// Use the ExecCmd to run the binary, as go run does. ExecCmd allows users
// to provide a runner to run the binary, for example a simulator for binaries
// that are cross-compiled to a different platform.
cmdline := str.StringList(work.FindExecCmd(), a.Deps[0].BuiltTarget(), a.Args)
cmdline := str.StringList(work.FindExecCmd(), builtTool(a), a.Args)
// Use same environment go run uses to start the executable:
// the original environment with cfg.GOROOTbin added to the path.
env := slices.Clip(cfg.OrigEnv)

View File

@@ -97,11 +97,12 @@ type Action struct {
CacheExecutable bool // Whether to cache executables produced by link steps
// Generated files, directories.
Objdir string // directory for intermediate objects
Target string // goal of the action: the created package or executable
built string // the actual created package or executable
actionID cache.ActionID // cache ID of action input
buildID string // build ID of action output
Objdir string // directory for intermediate objects
Target string // goal of the action: the created package or executable
built string // the actual created package or executable
cachedExecutable string // the cached executable, if CacheExecutable was set
actionID cache.ActionID // cache ID of action input
buildID string // build ID of action output
VetxOnly bool // Mode=="vet": only being called to supply info about dependencies
needVet bool // Mode=="build": need to fill in vet config
@@ -133,6 +134,10 @@ func (a *Action) BuildID() string { return a.buildID }
// from Target when the result was cached.
func (a *Action) BuiltTarget() string { return a.built }
// CachedExecutable returns the cached executable, if CacheExecutable
// was set and the executable could be cached, and "" otherwise.
func (a *Action) CachedExecutable() string { return a.cachedExecutable }
// An actionQueue is a priority queue of actions.
type actionQueue []*Action

View File

@@ -745,8 +745,9 @@ func (b *Builder) updateBuildID(a *Action, target string) error {
}
outputID, _, err := c.PutExecutable(a.actionID, name+cfg.ExeSuffix, r)
r.Close()
a.cachedExecutable = c.OutputFile(outputID)
if err == nil && cfg.BuildX {
sh.ShowCmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, c.OutputFile(outputID))))
sh.ShowCmd("", "%s # internal", joinUnambiguously(str.StringList("cp", target, a.cachedExecutable)))
}
}
}

View File

@@ -1,4 +1,4 @@
env snap=v1.0.0
env snap=v1.0.0-c2097c7c
env alias=inprocess
env GOFIPS140=$snap
@@ -23,8 +23,7 @@ stdout crypto/internal/fips140/$snap/sha256
! stdout crypto/internal/fips140/check
# again with GOFIPS140=$alias
# TODO: enable when we add inprocess.txt
# env GOFIPS140=$alias
env GOFIPS140=$alias
# default GODEBUG includes fips140=on
go list -f '{{.DefaultGODEBUG}}'

View File

@@ -94,12 +94,14 @@ stderr '^go: added toolchain go1.24rc1$'
grep 'go 1.22.9' go.mod # no longer implied
grep 'toolchain go1.24rc1' go.mod
# go get toolchain@latest finds go1.999testmod.
# go get toolchain@latest finds go1.23.9.
cp go.mod.orig go.mod
go get toolchain@latest
stderr '^go: added toolchain go1.999testmod$'
stderr '^go: added toolchain go1.23.9$'
grep 'go 1.21' go.mod
grep 'toolchain go1.999testmod' go.mod
grep 'toolchain go1.23.9' go.mod
# Bug fixes.
@@ -115,7 +117,7 @@ stderr '^go: upgraded go 1.19 => 1.21.0'
# go get toolchain@1.24rc1 is OK too.
go get toolchain@1.24rc1
stderr '^go: downgraded toolchain go1.999testmod => go1.24rc1$'
stderr '^go: upgraded toolchain go1.23.9 => go1.24rc1$'
# go get go@1.21 should work if we are the Go 1.21 language version,
# even though there's no toolchain for it.

View File

@@ -0,0 +1,27 @@
[short] skip 'does a build in using an empty cache'
# Start with a fresh cache because we want to verify the behavior
# when the tool hasn't been cached previously.
env GOCACHE=$WORK${/}cache
# Even when the tool hasn't been previously cached but was built and
# saved to the cache in the invocation of 'go tool -n' we should return
# its cached location.
go tool -n foo
stdout $GOCACHE
# And of course we should also return the cached location on subsequent
# runs.
go tool -n foo
stdout $GOCACHE
-- go.mod --
module example.com/foo
go 1.25
tool example.com/foo
-- main.go --
package main
func main() {}

View File

@@ -463,6 +463,8 @@ func (c *cancelCtx) Done() <-chan struct{} {
func (c *cancelCtx) Err() error {
// An atomic load is ~5x faster than a mutex, which can matter in tight loops.
if err := c.err.Load(); err != nil {
// Ensure the done channel has been closed before returning a non-nil error.
<-c.Done()
return err.(error)
}
return nil

View File

@@ -1177,3 +1177,23 @@ func (c *customContext) Err() error {
func (c *customContext) Value(key any) any {
return c.parent.Value(key)
}
// Issue #75533.
func TestContextErrDoneRace(t *testing.T) {
// 4 iterations reliably reproduced #75533.
for range 10 {
ctx, cancel := WithCancel(Background())
donec := ctx.Done()
go cancel()
for ctx.Err() == nil {
if runtime.GOARCH == "wasm" {
runtime.Gosched() // need to explicitly yield
}
}
select {
case <-donec:
default:
t.Fatalf("ctx.Err is non-nil, but ctx.Done is not closed")
}
}
}

View File

@@ -20,7 +20,7 @@ type MakeHash func() hash.Hash
// TestHash performs a set of tests on hash.Hash implementations, checking the
// documented requirements of Write, Sum, Reset, Size, and BlockSize.
func TestHash(t *testing.T, mh MakeHash) {
if boring.Enabled || fips140.Version() == "v1.0" {
if boring.Enabled || fips140.Version() == "v1.0.0" {
testhash.TestHashWithoutClone(t, testhash.MakeHash(mh))
return
}

View File

@@ -56,9 +56,10 @@ func CAST(name string, f func() error) {
}
// PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and
// returns any errors. If an error is returned, the key must not be used.
// aborts the program (stopping the module input/output and entering the "error
// state") if the test fails.
//
// PCTs are mandatory for every key pair that is generated/imported, including
// PCTs are mandatory for every generated (but not imported) key pair, including
// ephemeral keys (which effectively doubles the cost of key establishment). See
// Implementation Guidance 10.3.A Additional Comment 1.
//
@@ -66,17 +67,23 @@ func CAST(name string, f func() error) {
//
// If a package p calls PCT during key generation, an invocation of that
// function should be added to fipstest.TestConditionals.
func PCT(name string, f func() error) error {
func PCT(name string, f func() error) {
if strings.ContainsAny(name, ",#=:") {
panic("fips: invalid self-test name: " + name)
}
if !Enabled {
return nil
return
}
err := f()
if name == failfipscast {
err = errors.New("simulated PCT failure")
}
return err
if err != nil {
fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error())
panic("unreachable")
}
if debug {
println("FIPS 140-3 PCT passed:", name)
}
}

View File

@@ -161,6 +161,27 @@ func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
if err != nil {
continue
}
// A "Pairwise Consistency Test" makes no sense if we just generated the
// public key from an ephemeral private key. Moreover, there is no way to
// check it aside from redoing the exact same computation again. SP 800-56A
// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
// Comment 1 goes out of its way to say that "the PCT shall be performed
// consistent [...], even if the underlying standard does not require a
// PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode.
fips140.PCT("ECDH PCT", func() error {
p1, err := c.newPoint().ScalarBaseMult(privateKey.d)
if err != nil {
return err
}
if !bytes.Equal(p1.Bytes(), privateKey.pub.q) {
return errors.New("crypto/ecdh: public key does not match private key")
}
return nil
})
return privateKey, nil
}
}
@@ -188,28 +209,6 @@ func NewPrivateKey[P Point[P]](c *Curve[P], key []byte) (*PrivateKey, error) {
panic("crypto/ecdh: internal error: public key is the identity element")
}
// A "Pairwise Consistency Test" makes no sense if we just generated the
// public key from an ephemeral private key. Moreover, there is no way to
// check it aside from redoing the exact same computation again. SP 800-56A
// Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it.
// However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a
// PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional
// Comment 1 goes out of its way to say that "the PCT shall be performed
// consistent [...], even if the underlying standard does not require a
// PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode.
if err := fips140.PCT("ECDH PCT", func() error {
p1, err := c.newPoint().ScalarBaseMult(key)
if err != nil {
return err
}
if !bytes.Equal(p1.Bytes(), publicKey) {
return errors.New("crypto/ecdh: public key does not match private key")
}
return nil
}); err != nil {
panic(err)
}
k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}}
return k, nil
}

View File

@@ -51,8 +51,8 @@ func testHash() []byte {
}
}
func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
return fips140.PCT("ECDSA PCT", func() error {
func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) {
fips140.PCT("ECDSA PCT", func() error {
hash := testHash()
drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil)
sig, err := sign(c, k, drbg, hash)

View File

@@ -167,11 +167,6 @@ func NewPrivateKey[P Point[P]](c *Curve[P], D, Q []byte) (*PrivateKey, error) {
return nil, err
}
priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)}
if err := fipsPCT(c, priv); err != nil {
// This can happen if the application went out of its way to make an
// ecdsa.PrivateKey with a mismatching PublicKey.
return nil, err
}
return priv, nil
}
@@ -204,10 +199,7 @@ func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
},
d: k.Bytes(c.N),
}
if err := fipsPCT(c, priv); err != nil {
// This clearly can't happen, but FIPS 140-3 mandates that we check it.
panic(err)
}
fipsPCT(c, priv)
return priv, nil
}

View File

@@ -122,7 +122,7 @@ func newDRBG[H hash.Hash](hash func() H, entropy, nonce []byte, s personalizatio
//
// This should only be used for ACVP testing. hmacDRBG is not intended to be
// used directly.
func TestingOnlyNewDRBG(hash func() hash.Hash, entropy, nonce []byte, s []byte) *hmacDRBG {
func TestingOnlyNewDRBG[H hash.Hash](hash func() H, entropy, nonce []byte, s []byte) *hmacDRBG {
return newDRBG(hash, entropy, nonce, plainPersonalizationString(s))
}

View File

@@ -12,8 +12,8 @@ import (
"sync"
)
func fipsPCT(k *PrivateKey) error {
return fips140.PCT("Ed25519 sign and verify PCT", func() error {
func fipsPCT(k *PrivateKey) {
fips140.PCT("Ed25519 sign and verify PCT", func() error {
return pairwiseTest(k)
})
}

View File

@@ -69,10 +69,7 @@ func generateKey(priv *PrivateKey) (*PrivateKey, error) {
fips140.RecordApproved()
drbg.Read(priv.seed[:])
precomputePrivateKey(priv)
if err := fipsPCT(priv); err != nil {
// This clearly can't happen, but FIPS 140-3 requires that we check.
panic(err)
}
fipsPCT(priv)
return priv, nil
}
@@ -88,10 +85,6 @@ func newPrivateKeyFromSeed(priv *PrivateKey, seed []byte) (*PrivateKey, error) {
}
copy(priv.seed[:], seed)
precomputePrivateKey(priv)
if err := fipsPCT(priv); err != nil {
// This clearly can't happen, but FIPS 140-3 requires that we check.
panic(err)
}
return priv, nil
}
@@ -137,12 +130,6 @@ func newPrivateKey(priv *PrivateKey, privBytes []byte) (*PrivateKey, error) {
copy(priv.prefix[:], h[32:])
if err := fipsPCT(priv); err != nil {
// This can happen if the application messed with the private key
// encoding, and the public key doesn't match the seed anymore.
return nil, err
}
return priv, nil
}

View File

@@ -7,7 +7,6 @@ package fips140
import (
"crypto/internal/fips140deps/godebug"
"errors"
"hash"
"runtime"
)
@@ -63,16 +62,10 @@ func Name() string {
return "Go Cryptographic Module"
}
// Version returns the formal version (such as "v1.0") if building against a
// Version returns the formal version (such as "v1.0.0") if building against a
// frozen module with GOFIPS140. Otherwise, it returns "latest".
func Version() string {
// This return value is replaced by mkzip.go, it must not be changed or
// moved to a different file.
return "latest" //mkzip:version
}
// Hash is a legacy compatibility alias for hash.Hash.
//
// It's only here because [crypto/internal/fips140/ecdsa.TestingOnlyNewDRBG]
// takes a "func() fips140.Hash" in v1.0.0, instead of being generic.
type Hash = hash.Hash

View File

@@ -118,10 +118,7 @@ func generateKey1024(dk *DecapsulationKey1024) (*DecapsulationKey1024, error) {
var z [32]byte
drbg.Read(z[:])
kemKeyGen1024(dk, &d, &z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) })
fips140.RecordApproved()
return dk, nil
}
@@ -149,10 +146,6 @@ func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKe
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
kemKeyGen1024(dk, d, z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.RecordApproved()
return dk, nil
}

View File

@@ -177,10 +177,7 @@ func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) {
var z [32]byte
drbg.Read(z[:])
kemKeyGen(dk, &d, &z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) })
fips140.RecordApproved()
return dk, nil
}
@@ -208,10 +205,6 @@ func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768,
d := (*[32]byte)(seed[:32])
z := (*[32]byte)(seed[32:])
kemKeyGen(dk, d, z)
if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil {
// This clearly can't happen, but FIPS 140-3 requires us to check.
panic(err)
}
fips140.RecordApproved()
return dk, nil
}

View File

@@ -105,7 +105,28 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) {
// negligible chance of failure we can defer the check to the end of key
// generation and return an error if it fails. See [checkPrivateKey].
return newPrivateKey(N, 65537, d, P, Q)
k, err := newPrivateKey(N, 65537, d, P, Q)
if err != nil {
return nil, err
}
if k.fipsApproved {
fips140.PCT("RSA sign and verify PCT", func() error {
hash := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
}
sig, err := signPKCS1v15(k, "SHA-256", hash)
if err != nil {
return err
}
return verifyPKCS1v15(k.PublicKey(), "SHA-256", hash, sig)
})
}
return k, nil
}
}

View File

@@ -310,26 +310,6 @@ func checkPrivateKey(priv *PrivateKey) error {
return errors.New("crypto/rsa: d too small")
}
// If the key is still in scope for FIPS mode, perform a Pairwise
// Consistency Test.
if priv.fipsApproved {
if err := fips140.PCT("RSA sign and verify PCT", func() error {
hash := []byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
}
sig, err := signPKCS1v15(priv, "SHA-256", hash)
if err != nil {
return err
}
return verifyPKCS1v15(priv.PublicKey(), "SHA-256", hash, sig)
}); err != nil {
return err
}
}
return nil
}

View File

@@ -1624,7 +1624,7 @@ func cmdHmacDrbgAft(h func() hash.Hash) command {
// * Uninstantiate
// See Table 7 in draft-vassilev-acvp-drbg
out := make([]byte, outLen)
drbg := ecdsa.TestingOnlyNewDRBG(func() fips140.Hash { return h() }, entropy, nonce, personalization)
drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization)
drbg.Generate(out)
drbg.Generate(out)

View File

@@ -5,9 +5,9 @@
package fipstest
import (
"crypto"
"crypto/internal/fips140"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"internal/testenv"
"io/fs"
@@ -50,8 +50,6 @@ var allCASTs = []string{
"KAS-ECC-SSC P-256",
"ML-KEM PCT",
"ML-KEM PCT",
"ML-KEM PCT",
"ML-KEM PCT",
"ML-KEM-768",
"PBKDF2",
"RSA sign and verify PCT",
@@ -107,60 +105,65 @@ func TestAllCASTs(t *testing.T) {
// TestConditionals causes the conditional CASTs and PCTs to be invoked.
func TestConditionals(t *testing.T) {
mlkem.GenerateKey768()
k, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader)
kDH, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
t.Error(err)
} else {
ecdh.ECDH(ecdh.P256(), kDH, kDH.PublicKey())
}
ecdh.ECDH(ecdh.P256(), k, k.PublicKey())
kDSA, err := ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
t.Error(err)
} else {
ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
}
ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32))
k25519, err := ed25519.GenerateKey()
if err != nil {
t.Fatal(err)
t.Error(err)
} else {
ed25519.Sign(k25519, make([]byte, 32))
}
ed25519.Sign(k25519, make([]byte, 32))
rsa.VerifyPKCS1v15(&rsa.PublicKey{}, "", nil, nil)
// Parse an RSA key to hit the PCT rather than generating one (which is slow).
block, _ := pem.Decode([]byte(strings.ReplaceAll(
`-----BEGIN RSA TESTING KEY-----
MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso
tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE
89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU
l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s
B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59
3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+
dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI
FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J
aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2
BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx
IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/
fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u
pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT
Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl
u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD
fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X
Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE
k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo
qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS
CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ
XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw
AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r
UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0
2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5
7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3
-----END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY")))
if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
t.Fatal(err)
kRSA, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Error(err)
} else {
rsa.SignPKCS1v15(kRSA, crypto.SHA256.String(), make([]byte, 32))
}
t.Log("completed successfully")
}
func TestCASTPasses(t *testing.T) {
moduleStatus(t)
testenv.MustHaveExec(t)
if err := fips140.Supported(); err != nil {
t.Skipf("test requires FIPS 140 mode: %v", err)
}
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
cmd.Env = append(cmd.Env, "GODEBUG=fips140=debug")
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
if err != nil || !strings.Contains(string(out), "completed successfully") {
t.Errorf("TestConditionals did not complete successfully")
}
for _, name := range allCASTs {
t.Run(name, func(t *testing.T) {
if !strings.Contains(string(out), fmt.Sprintf("passed: %s\n", name)) {
t.Errorf("CAST/PCT %s success was not logged", name)
} else {
t.Logf("CAST/PCT succeeded: %s", name)
}
})
}
}
func TestCASTFailures(t *testing.T) {
moduleStatus(t)
testenv.MustHaveExec(t)
if err := fips140.Supported(); err != nil {
t.Skipf("test requires FIPS 140 mode: %v", err)
}
for _, name := range allCASTs {
t.Run(name, func(t *testing.T) {
@@ -169,7 +172,6 @@ func TestCASTFailures(t *testing.T) {
if !testing.Verbose() {
t.Parallel()
}
t.Logf("CAST/PCT succeeded: %s", name)
t.Logf("Testing CAST/PCT failure...")
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestConditionals$", "-test.v")
cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=failfipscast=%s,fips140=on", name))
@@ -180,6 +182,8 @@ func TestCASTFailures(t *testing.T) {
}
if strings.Contains(string(out), "completed successfully") {
t.Errorf("CAST/PCT %s failure did not stop the program", name)
} else if !strings.Contains(string(out), "self-test failed: "+name) {
t.Errorf("CAST/PCT %s failure did not log the expected message", name)
} else {
t.Logf("CAST/PCT %s failed as expected and caused the program to exit", name)
}

View File

@@ -74,11 +74,9 @@ func TestVersion(t *testing.T) {
continue
}
exp := setting.Value
if exp == "v1.0.0" {
// Unfortunately we enshrined the version of the first module as
// v1.0 before deciding to go for full versions.
exp = "v1.0"
}
// Remove the -hash suffix, if any.
// The version from fips140.Version omits it.
exp, _, _ = strings.Cut(exp, "-")
if v := fips140.Version(); v != exp {
t.Errorf("Version is %q, expected %q", v, exp)
}

View File

@@ -357,7 +357,7 @@ func negotiateALPN(serverProtos, clientProtos []string, quic bool) (string, erro
if http11fallback {
return "", nil
}
return "", fmt.Errorf("tls: client requested unsupported application protocols (%s)", clientProtos)
return "", fmt.Errorf("tls: client requested unsupported application protocols (%q)", clientProtos)
}
// supportsECDHE returns whether ECDHE key exchanges can be used with this

View File

@@ -429,10 +429,8 @@ func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string
if err != nil {
return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err)
}
if len(uri.Host) > 0 {
if _, ok := domainToReverseLabels(uri.Host); !ok {
return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr)
}
if len(uri.Host) > 0 && !domainNameValid(uri.Host, false) {
return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr)
}
uris = append(uris, uri)
case nameTypeIP:
@@ -598,15 +596,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error())
}
trimmedDomain := domain
if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' {
// constraints can have a leading
// period to exclude the domain
// itself, but that's not valid in a
// normal domain name.
trimmedDomain = trimmedDomain[1:]
}
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
if !domainNameValid(domain, true) {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse dnsName constraint %q", domain)
}
dnsNames = append(dnsNames, domain)
@@ -647,12 +637,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
}
} else {
// Otherwise it's a domain name.
domain := constraint
if len(domain) > 0 && domain[0] == '.' {
domain = domain[1:]
}
if _, ok := domainToReverseLabels(domain); !ok {
if !domainNameValid(constraint, true) {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse rfc822Name constraint %q", constraint)
}
}
@@ -668,15 +653,7 @@ func parseNameConstraintsExtension(out *Certificate, e pkix.Extension) (unhandle
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q: cannot be IP address", domain)
}
trimmedDomain := domain
if len(trimmedDomain) > 0 && trimmedDomain[0] == '.' {
// constraints can have a leading
// period to exclude the domain itself,
// but that's not valid in a normal
// domain name.
trimmedDomain = trimmedDomain[1:]
}
if _, ok := domainToReverseLabels(trimmedDomain); !ok {
if !domainNameValid(domain, true) {
return nil, nil, nil, nil, fmt.Errorf("x509: failed to parse URI constraint %q", domain)
}
uriDomains = append(uriDomains, domain)
@@ -1317,3 +1294,62 @@ func ParseRevocationList(der []byte) (*RevocationList, error) {
return rl, nil
}
// domainNameValid is an alloc-less version of the checks that
// domainToReverseLabels does.
func domainNameValid(s string, constraint bool) bool {
// TODO(#75835): This function omits a number of checks which we
// really should be doing to enforce that domain names are valid names per
// RFC 1034. We previously enabled these checks, but this broke a
// significant number of certificates we previously considered valid, and we
// happily create via CreateCertificate (et al). We should enable these
// checks, but will need to gate them behind a GODEBUG.
//
// I have left the checks we previously enabled, noted with "TODO(#75835)" so
// that we can easily re-enable them once we unbreak everyone.
// TODO(#75835): this should only be true for constraints.
if len(s) == 0 {
return true
}
// Do not allow trailing period (FQDN format is not allowed in SANs or
// constraints).
if s[len(s)-1] == '.' {
return false
}
// TODO(#75835): domains must have at least one label, cannot have
// a leading empty label, and cannot be longer than 253 characters.
// if len(s) == 0 || (!constraint && s[0] == '.') || len(s) > 253 {
// return false
// }
lastDot := -1
if constraint && s[0] == '.' {
s = s[1:]
}
for i := 0; i <= len(s); i++ {
if i < len(s) && (s[i] < 33 || s[i] > 126) {
// Invalid character.
return false
}
if i == len(s) || s[i] == '.' {
labelLen := i
if lastDot >= 0 {
labelLen -= lastDot + 1
}
if labelLen == 0 {
return false
}
// TODO(#75835): labels cannot be longer than 63 characters.
// if labelLen > 63 {
// return false
// }
lastDot = i
}
}
return true
}

View File

@@ -5,9 +5,13 @@
package x509
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/asn1"
"encoding/pem"
"os"
"strings"
"testing"
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
@@ -251,3 +255,106 @@ d5l1tRhScKu2NBgm74nYmJxJYgvuTA38wGhRrGU=
}
}
}
func TestDomainNameValid(t *testing.T) {
for _, tc := range []struct {
name string
dnsName string
constraint bool
valid bool
}{
// TODO(#75835): these tests are for stricter name validation, which we
// had to disable. Once we reenable these strict checks, behind a
// GODEBUG, we should add them back in.
// {"empty name, name", "", false, false},
// {"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, false},
// {"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, false},
// {"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, false},
// {"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, false},
// {"64 char single label, name", strings.Repeat("a", 64), false, false},
// {"64 char single label, constraint", strings.Repeat("a", 64), true, false},
// {"64 char label, name", "a." + strings.Repeat("a", 64), false, false},
// {"64 char label, constraint", "a." + strings.Repeat("a", 64), true, false},
// TODO(#75835): these are the inverse of the tests above, they should be removed
// once the strict checking is enabled.
{"254 char label, name", strings.Repeat("a.a", 84) + "aaa", false, true},
{"254 char label, constraint", strings.Repeat("a.a", 84) + "aaa", true, true},
{"253 char label, name", strings.Repeat("a.a", 84) + "aa", false, true},
{"253 char label, constraint", strings.Repeat("a.a", 84) + "aa", true, true},
{"64 char single label, name", strings.Repeat("a", 64), false, true},
{"64 char single label, constraint", strings.Repeat("a", 64), true, true},
{"64 char label, name", "a." + strings.Repeat("a", 64), false, true},
{"64 char label, constraint", "a." + strings.Repeat("a", 64), true, true},
// Check we properly enforce properties of domain names.
{"empty name, constraint", "", true, true},
{"empty label, name", "a..a", false, false},
{"empty label, constraint", "a..a", true, false},
{"period, name", ".", false, false},
{"period, constraint", ".", true, false}, // TODO(roland): not entirely clear if this is a valid constraint (require at least one label?)
{"valid, name", "a.b.c", false, true},
{"valid, constraint", "a.b.c", true, true},
{"leading period, name", ".a.b.c", false, false},
{"leading period, constraint", ".a.b.c", true, true},
{"trailing period, name", "a.", false, false},
{"trailing period, constraint", "a.", true, false},
{"bare label, name", "a", false, true},
{"bare label, constraint", "a", true, true},
{"63 char single label, name", strings.Repeat("a", 63), false, true},
{"63 char single label, constraint", strings.Repeat("a", 63), true, true},
{"63 char label, name", "a." + strings.Repeat("a", 63), false, true},
{"63 char label, constraint", "a." + strings.Repeat("a", 63), true, true},
} {
t.Run(tc.name, func(t *testing.T) {
valid := domainNameValid(tc.dnsName, tc.constraint)
if tc.valid != valid {
t.Errorf("domainNameValid(%q, %t) = %v; want %v", tc.dnsName, tc.constraint, !tc.valid, tc.valid)
}
// Also check that we enforce the same properties as domainToReverseLabels
trimmedName := tc.dnsName
if tc.constraint && len(trimmedName) > 1 && trimmedName[0] == '.' {
trimmedName = trimmedName[1:]
}
_, revValid := domainToReverseLabels(trimmedName)
if valid != revValid {
t.Errorf("domainNameValid(%q, %t) = %t != domainToReverseLabels(%q) = %t", tc.dnsName, tc.constraint, valid, trimmedName, revValid)
}
})
}
}
func TestRoundtripWeirdSANs(t *testing.T) {
// TODO(#75835): check that certificates we create with CreateCertificate that have malformed SAN values
// can be parsed by ParseCertificate. We should eventually restrict this, but for now we have to maintain
// this property as people have been relying on it.
k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
badNames := []string{
"baredomain",
"baredomain.",
strings.Repeat("a", 255),
strings.Repeat("a", 65) + ".com",
}
tmpl := &Certificate{
EmailAddresses: badNames,
DNSNames: badNames,
}
b, err := CreateCertificate(rand.Reader, tmpl, tmpl, &k.PublicKey, k)
if err != nil {
t.Fatal(err)
}
_, err = ParseCertificate(b)
if err != nil {
t.Fatalf("Couldn't roundtrip certificate: %v", err)
}
}
func FuzzDomainNameValid(f *testing.F) {
f.Fuzz(func(t *testing.T, data string) {
domainNameValid(data, false)
domainNameValid(data, true)
})
}

View File

@@ -391,6 +391,7 @@ func parseRFC2821Mailbox(in string) (mailbox rfc2821Mailbox, ok bool) {
// domainToReverseLabels converts a textual domain name like foo.example.com to
// the list of labels in reverse order, e.g. ["com", "example", "foo"].
func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
reverseLabels = make([]string, 0, strings.Count(domain, ".")+1)
for len(domain) > 0 {
if i := strings.LastIndexByte(domain, '.'); i == -1 {
reverseLabels = append(reverseLabels, domain)
@@ -428,7 +429,7 @@ func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) {
return reverseLabels, true
}
func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, error) {
func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) {
// If the constraint contains an @, then it specifies an exact mailbox
// name.
if strings.Contains(constraint, "@") {
@@ -441,10 +442,10 @@ func matchEmailConstraint(mailbox rfc2821Mailbox, constraint string) (bool, erro
// Otherwise the constraint is like a DNS constraint of the domain part
// of the mailbox.
return matchDomainConstraint(mailbox.domain, constraint)
return matchDomainConstraint(mailbox.domain, constraint, reversedDomainsCache, reversedConstraintsCache)
}
func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
func matchURIConstraint(uri *url.URL, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) {
// From RFC 5280, Section 4.2.1.10:
// “a uniformResourceIdentifier that does not include an authority
// component with a host name specified as a fully qualified domain
@@ -473,7 +474,7 @@ func matchURIConstraint(uri *url.URL, constraint string) (bool, error) {
return false, fmt.Errorf("URI with IP (%q) cannot be matched against constraints", uri.String())
}
return matchDomainConstraint(host, constraint)
return matchDomainConstraint(host, constraint, reversedDomainsCache, reversedConstraintsCache)
}
func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) {
@@ -490,16 +491,21 @@ func matchIPConstraint(ip net.IP, constraint *net.IPNet) (bool, error) {
return true, nil
}
func matchDomainConstraint(domain, constraint string) (bool, error) {
func matchDomainConstraint(domain, constraint string, reversedDomainsCache map[string][]string, reversedConstraintsCache map[string][]string) (bool, error) {
// The meaning of zero length constraints is not specified, but this
// code follows NSS and accepts them as matching everything.
if len(constraint) == 0 {
return true, nil
}
domainLabels, ok := domainToReverseLabels(domain)
if !ok {
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain)
domainLabels, found := reversedDomainsCache[domain]
if !found {
var ok bool
domainLabels, ok = domainToReverseLabels(domain)
if !ok {
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", domain)
}
reversedDomainsCache[domain] = domainLabels
}
// RFC 5280 says that a leading period in a domain name means that at
@@ -513,9 +519,14 @@ func matchDomainConstraint(domain, constraint string) (bool, error) {
constraint = constraint[1:]
}
constraintLabels, ok := domainToReverseLabels(constraint)
if !ok {
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint)
constraintLabels, found := reversedConstraintsCache[constraint]
if !found {
var ok bool
constraintLabels, ok = domainToReverseLabels(constraint)
if !ok {
return false, fmt.Errorf("x509: internal error: cannot parse domain %q", constraint)
}
reversedConstraintsCache[constraint] = constraintLabels
}
if len(domainLabels) < len(constraintLabels) ||
@@ -636,6 +647,19 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
}
}
// Each time we do constraint checking, we need to check the constraints in
// the current certificate against all of the names that preceded it. We
// reverse these names using domainToReverseLabels, which is a relatively
// expensive operation. Since we check each name against each constraint,
// this requires us to do N*C calls to domainToReverseLabels (where N is the
// total number of names that preceed the certificate, and C is the total
// number of constraints in the certificate). By caching the results of
// calling domainToReverseLabels, we can reduce that to N+C calls at the
// cost of keeping all of the parsed names and constraints in memory until
// we return from isValid.
reversedDomainsCache := map[string][]string{}
reversedConstraintsCache := map[string][]string{}
if (certType == intermediateCertificate || certType == rootCertificate) &&
c.hasNameConstraints() {
toCheck := []*Certificate{}
@@ -656,20 +680,20 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox,
func(parsedName, constraint any) (bool, error) {
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string))
return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
}, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil {
return err
}
case nameTypeDNS:
name := string(data)
if _, ok := domainToReverseLabels(name); !ok {
if !domainNameValid(name, false) {
return fmt.Errorf("x509: cannot parse dnsName %q", name)
}
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name,
func(parsedName, constraint any) (bool, error) {
return matchDomainConstraint(parsedName.(string), constraint.(string))
return matchDomainConstraint(parsedName.(string), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
}, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil {
return err
}
@@ -683,7 +707,7 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V
if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri,
func(parsedName, constraint any) (bool, error) {
return matchURIConstraint(parsedName.(*url.URL), constraint.(string))
return matchURIConstraint(parsedName.(*url.URL), constraint.(string), reversedDomainsCache, reversedConstraintsCache)
}, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil {
return err
}
@@ -927,7 +951,10 @@ func alreadyInChain(candidate *Certificate, chain []*Certificate) bool {
if !bytes.Equal(candidate.RawSubject, cert.RawSubject) {
continue
}
if !candidate.PublicKey.(pubKeyEqual).Equal(cert.PublicKey) {
// We enforce the canonical encoding of SPKI (by only allowing the
// correct AI paremeter encodings in parseCertificate), so it's safe to
// directly compare the raw bytes.
if !bytes.Equal(candidate.RawSubjectPublicKeyInfo, cert.RawSubjectPublicKeyInfo) {
continue
}
var certSAN *pkix.Extension

View File

@@ -6,6 +6,7 @@ package x509
import (
"crypto"
"crypto/dsa"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@@ -1351,7 +1352,7 @@ var nameConstraintTests = []struct {
func TestNameConstraints(t *testing.T) {
for i, test := range nameConstraintTests {
result, err := matchDomainConstraint(test.domain, test.constraint)
result, err := matchDomainConstraint(test.domain, test.constraint, map[string][]string{}, map[string][]string{})
if err != nil && !test.expectError {
t.Errorf("unexpected error for test #%d: domain=%s, constraint=%s, err=%s", i, test.domain, test.constraint, err)
@@ -3048,3 +3049,129 @@ func TestInvalidPolicyWithAnyKeyUsage(t *testing.T) {
t.Fatalf("unexpected error, got %q, want %q", err, expectedErr)
}
}
func TestCertificateChainSignedByECDSA(t *testing.T) {
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
root := &Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "X"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
IsCA: true,
KeyUsage: KeyUsageCertSign | KeyUsageCRLSign,
BasicConstraintsValid: true,
}
caDER, err := CreateCertificate(rand.Reader, root, root, &caKey.PublicKey, caKey)
if err != nil {
t.Fatal(err)
}
root, err = ParseCertificate(caDER)
if err != nil {
t.Fatal(err)
}
leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
leaf := &Certificate{
SerialNumber: big.NewInt(42),
Subject: pkix.Name{CommonName: "leaf"},
NotBefore: time.Now().Add(-10 * time.Minute),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: KeyUsageDigitalSignature,
ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
leafDER, err := CreateCertificate(rand.Reader, leaf, root, &leafKey.PublicKey, caKey)
if err != nil {
t.Fatal(err)
}
leaf, err = ParseCertificate(leafDER)
if err != nil {
t.Fatal(err)
}
inter, err := ParseCertificate(dsaSelfSignedCNX(t))
if err != nil {
t.Fatal(err)
}
inters := NewCertPool()
inters.AddCert(root)
inters.AddCert(inter)
wantErr := "certificate signed by unknown authority"
_, err = leaf.Verify(VerifyOptions{Intermediates: inters, Roots: NewCertPool()})
if !strings.Contains(err.Error(), wantErr) {
t.Errorf("got %v, want %q", err, wantErr)
}
}
// dsaSelfSignedCNX produces DER-encoded
// certificate with the properties:
//
// Subject=Issuer=CN=X
// DSA SPKI
// Matching inner/outer signature OIDs
// Dummy ECDSA signature
func dsaSelfSignedCNX(t *testing.T) []byte {
t.Helper()
var params dsa.Parameters
if err := dsa.GenerateParameters(&params, rand.Reader, dsa.L1024N160); err != nil {
t.Fatal(err)
}
var dsaPriv dsa.PrivateKey
dsaPriv.Parameters = params
if err := dsa.GenerateKey(&dsaPriv, rand.Reader); err != nil {
t.Fatal(err)
}
dsaPub := &dsaPriv.PublicKey
type dsaParams struct{ P, Q, G *big.Int }
paramDER, err := asn1.Marshal(dsaParams{dsaPub.P, dsaPub.Q, dsaPub.G})
if err != nil {
t.Fatal(err)
}
yDER, err := asn1.Marshal(dsaPub.Y)
if err != nil {
t.Fatal(err)
}
spki := publicKeyInfo{
Algorithm: pkix.AlgorithmIdentifier{
Algorithm: oidPublicKeyDSA,
Parameters: asn1.RawValue{FullBytes: paramDER},
},
PublicKey: asn1.BitString{Bytes: yDER, BitLength: 8 * len(yDER)},
}
rdn := pkix.Name{CommonName: "X"}.ToRDNSequence()
b, err := asn1.Marshal(rdn)
if err != nil {
t.Fatal(err)
}
rawName := asn1.RawValue{FullBytes: b}
algoIdent := pkix.AlgorithmIdentifier{Algorithm: oidSignatureDSAWithSHA256}
tbs := tbsCertificate{
Version: 0,
SerialNumber: big.NewInt(1002),
SignatureAlgorithm: algoIdent,
Issuer: rawName,
Validity: validity{NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(24 * time.Hour)},
Subject: rawName,
PublicKey: spki,
}
c := certificate{
TBSCertificate: tbs,
SignatureAlgorithm: algoIdent,
SignatureValue: asn1.BitString{Bytes: []byte{0}, BitLength: 8},
}
dsaDER, err := asn1.Marshal(c)
if err != nil {
t.Fatal(err)
}
return dsaDER
}

View File

@@ -335,7 +335,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
if rows == nil {
return errors.New("invalid context to convert cursor rows, missing parent *Rows")
}
rows.closemu.Lock()
*d = Rows{
dc: rows.dc,
releaseConn: func(error) {},
@@ -351,7 +350,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
parentCancel()
}
}
rows.closemu.Unlock()
return nil
}
}

View File

@@ -5,6 +5,7 @@
package sql
import (
"bytes"
"context"
"database/sql/driver"
"errors"
@@ -15,7 +16,6 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
)
@@ -91,8 +91,6 @@ func (cc *fakeDriverCtx) OpenConnector(name string) (driver.Connector, error) {
type fakeDB struct {
name string
useRawBytes atomic.Bool
mu sync.Mutex
tables map[string]*table
badConn bool
@@ -684,8 +682,6 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm
switch cmd {
case "WIPE":
// Nothing
case "USE_RAWBYTES":
c.db.useRawBytes.Store(true)
case "SELECT":
stmt, err = c.prepareSelect(stmt, parts)
case "CREATE":
@@ -789,9 +785,6 @@ func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (d
case "WIPE":
db.wipe()
return driver.ResultNoRows, nil
case "USE_RAWBYTES":
s.c.db.useRawBytes.Store(true)
return driver.ResultNoRows, nil
case "CREATE":
if err := db.createTable(s.table, s.colName, s.colType); err != nil {
return nil, err
@@ -1076,10 +1069,9 @@ type rowsCursor struct {
errPos int
err error
// a clone of slices to give out to clients, indexed by the
// original slice's first byte address. we clone them
// just so we're able to corrupt them on close.
bytesClone map[*byte][]byte
// Data returned to clients.
// We clone and stash it here so it can be invalidated by Close and Next.
driverOwnedMemory [][]byte
// Every operation writes to line to enable the race detector
// check for data races.
@@ -1096,9 +1088,19 @@ func (rc *rowsCursor) touchMem() {
rc.line++
}
func (rc *rowsCursor) invalidateDriverOwnedMemory() {
for _, buf := range rc.driverOwnedMemory {
for i := range buf {
buf[i] = 'x'
}
}
rc.driverOwnedMemory = nil
}
func (rc *rowsCursor) Close() error {
rc.touchMem()
rc.parentMem.touchMem()
rc.invalidateDriverOwnedMemory()
rc.closed = true
return rc.closeErr
}
@@ -1129,6 +1131,8 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
if rc.posRow >= len(rc.rows[rc.posSet]) {
return io.EOF // per interface spec
}
// Corrupt any previously returned bytes.
rc.invalidateDriverOwnedMemory()
for i, v := range rc.rows[rc.posSet][rc.posRow].cols {
// TODO(bradfitz): convert to subset types? naah, I
// think the subset types should only be input to
@@ -1136,20 +1140,13 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
// a wider range of types coming out of drivers. all
// for ease of drivers, and to prevent drivers from
// messing up conversions or doing them differently.
dest[i] = v
if bs, ok := v.([]byte); ok && !rc.db.useRawBytes.Load() {
if rc.bytesClone == nil {
rc.bytesClone = make(map[*byte][]byte)
}
clone, ok := rc.bytesClone[&bs[0]]
if !ok {
clone = make([]byte, len(bs))
copy(clone, bs)
rc.bytesClone[&bs[0]] = clone
}
dest[i] = clone
if bs, ok := v.([]byte); ok {
// Clone []bytes and stash for later invalidation.
bs = bytes.Clone(bs)
rc.driverOwnedMemory = append(rc.driverOwnedMemory, bs)
v = bs
}
dest[i] = v
}
return nil
}

View File

@@ -3368,38 +3368,36 @@ func (rs *Rows) Scan(dest ...any) error {
// without calling Next.
return fmt.Errorf("sql: Scan called without calling Next (closemuScanHold)")
}
rs.closemu.RLock()
if rs.lasterr != nil && rs.lasterr != io.EOF {
rs.closemu.RUnlock()
return rs.lasterr
}
if rs.closed {
err := rs.lasterrOrErrLocked(errRowsClosed)
rs.closemu.RUnlock()
return err
}
if scanArgsContainRawBytes(dest) {
rs.raw = rs.raw[:0]
err := rs.scanLocked(dest...)
if err == nil && scanArgsContainRawBytes(dest) {
rs.closemuScanHold = true
rs.raw = rs.raw[:0]
} else {
rs.closemu.RUnlock()
}
return err
}
func (rs *Rows) scanLocked(dest ...any) error {
if rs.lasterr != nil && rs.lasterr != io.EOF {
return rs.lasterr
}
if rs.closed {
return rs.lasterrOrErrLocked(errRowsClosed)
}
if rs.lastcols == nil {
rs.closemuRUnlockIfHeldByScan()
return errors.New("sql: Scan called without calling Next")
}
if len(dest) != len(rs.lastcols) {
rs.closemuRUnlockIfHeldByScan()
return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
}
for i, sv := range rs.lastcols {
err := convertAssignRows(dest[i], sv, rs)
if err != nil {
rs.closemuRUnlockIfHeldByScan()
return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err)
}
}

View File

@@ -5,6 +5,7 @@
package sql
import (
"bytes"
"context"
"database/sql/driver"
"errors"
@@ -4434,10 +4435,6 @@ func testContextCancelDuringRawBytesScan(t *testing.T, mode string) {
db := newTestDB(t, "people")
defer closeDB(t, db)
if _, err := db.Exec("USE_RAWBYTES"); err != nil {
t.Fatal(err)
}
// cancel used to call close asynchronously.
// This test checks that it waits so as not to interfere with RawBytes.
ctx, cancel := context.WithCancel(context.Background())
@@ -4529,6 +4526,61 @@ func TestContextCancelBetweenNextAndErr(t *testing.T) {
}
}
type testScanner struct {
scanf func(src any) error
}
func (ts testScanner) Scan(src any) error { return ts.scanf(src) }
func TestContextCancelDuringScan(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
scanStart := make(chan any)
scanEnd := make(chan error)
scanner := &testScanner{
scanf: func(src any) error {
scanStart <- src
return <-scanEnd
},
}
// Start a query, and pause it mid-scan.
want := []byte("Alice")
r, err := db.QueryContext(ctx, "SELECT|people|name|name=?", string(want))
if err != nil {
t.Fatal(err)
}
if !r.Next() {
t.Fatalf("r.Next() = false, want true")
}
go func() {
r.Scan(scanner)
}()
got := <-scanStart
defer close(scanEnd)
gotBytes, ok := got.([]byte)
if !ok {
t.Fatalf("r.Scan returned %T, want []byte", got)
}
if !bytes.Equal(gotBytes, want) {
t.Fatalf("before cancel: r.Scan returned %q, want %q", gotBytes, want)
}
// Cancel the query.
// Sleep to give it a chance to finish canceling.
cancel()
time.Sleep(10 * time.Millisecond)
// Cancelling the query should not have changed the result.
if !bytes.Equal(gotBytes, want) {
t.Fatalf("after cancel: r.Scan result is now %q, want %q", gotBytes, want)
}
}
func TestNilErrorAfterClose(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
@@ -4562,10 +4614,6 @@ func TestRawBytesReuse(t *testing.T) {
db := newTestDB(t, "people")
defer closeDB(t, db)
if _, err := db.Exec("USE_RAWBYTES"); err != nil {
t.Fatal(err)
}
var raw RawBytes
// The RawBytes in this query aliases driver-owned memory.

View File

@@ -98,7 +98,12 @@ func readCOFFSymbols(fh *FileHeader, r io.ReadSeeker) ([]COFFSymbol, error) {
// isSymNameOffset checks symbol name if it is encoded as offset into string table.
func isSymNameOffset(name [8]byte) (bool, uint32) {
if name[0] == 0 && name[1] == 0 && name[2] == 0 && name[3] == 0 {
return true, binary.LittleEndian.Uint32(name[4:])
offset := binary.LittleEndian.Uint32(name[4:])
if offset == 0 {
// symbol has no name
return false, 0
}
return true, offset
}
return false, 0
}

View File

@@ -22,6 +22,7 @@ package asn1
import (
"errors"
"fmt"
"internal/saferio"
"math"
"math/big"
"reflect"
@@ -666,10 +667,17 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type
offset += t.length
numElements++
}
ret = reflect.MakeSlice(sliceType, numElements, numElements)
elemSize := uint64(elemType.Size())
safeCap := saferio.SliceCapWithSize(elemSize, uint64(numElements))
if safeCap < 0 {
err = SyntaxError{fmt.Sprintf("%s slice too big: %d elements of %d bytes", elemType.Kind(), numElements, elemSize)}
return
}
ret = reflect.MakeSlice(sliceType, 0, safeCap)
params := fieldParameters{}
offset := 0
for i := 0; i < numElements; i++ {
ret = reflect.Append(ret, reflect.Zero(elemType))
offset, err = parseField(ret.Index(i), bytes, offset, params)
if err != nil {
return

View File

@@ -7,10 +7,12 @@ package asn1
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"math"
"math/big"
"reflect"
"runtime"
"strings"
"testing"
"time"
@@ -1216,3 +1218,39 @@ func TestImplicitTypeRoundtrip(t *testing.T) {
t.Fatalf("Unexpected diff after roundtripping struct\na: %#v\nb: %#v", a, b)
}
}
func TestParsingMemoryConsumption(t *testing.T) {
// Craft a syntatically valid, but empty, ~10 MB DER bomb. A successful
// unmarshal of this bomb should yield ~280 MB. However, the parsing should
// fail due to the empty content; and, in such cases, we want to make sure
// that we do not unnecessarily allocate memories.
derBomb := make([]byte, 10_000_000)
for i := range derBomb {
derBomb[i] = 0x30
}
derBomb = append([]byte{0x30, 0x83, 0x98, 0x96, 0x80}, derBomb...)
var m runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m)
memBefore := m.TotalAlloc
var out []struct {
Id []int
Critical bool `asn1:"optional"`
Value []byte
}
_, err := Unmarshal(derBomb, &out)
if !errors.As(err, &SyntaxError{}) {
t.Fatalf("Incorrect error result: want (%v), but got (%v) instead", &SyntaxError{}, err)
}
runtime.ReadMemStats(&m)
memDiff := m.TotalAlloc - memBefore
// Ensure that the memory allocated does not exceed 10<<21 (~20 MB) when
// the parsing fails.
if memDiff > 10<<21 {
t.Errorf("Too much memory allocated while parsing DER: %v MiB", memDiff/1024/1024)
}
}

View File

@@ -37,7 +37,7 @@ type Block struct {
// line bytes. The remainder of the byte array (also not including the new line
// bytes) is also returned and this will always be smaller than the original
// argument.
func getLine(data []byte) (line, rest []byte) {
func getLine(data []byte) (line, rest []byte, consumed int) {
i := bytes.IndexByte(data, '\n')
var j int
if i < 0 {
@@ -49,7 +49,7 @@ func getLine(data []byte) (line, rest []byte) {
i--
}
}
return bytes.TrimRight(data[0:i], " \t"), data[j:]
return bytes.TrimRight(data[0:i], " \t"), data[j:], j
}
// removeSpacesAndTabs returns a copy of its input with all spaces and tabs
@@ -90,20 +90,32 @@ func Decode(data []byte) (p *Block, rest []byte) {
// pemStart begins with a newline. However, at the very beginning of
// the byte array, we'll accept the start string without it.
rest = data
for {
if bytes.HasPrefix(rest, pemStart[1:]) {
rest = rest[len(pemStart)-1:]
} else if _, after, ok := bytes.Cut(rest, pemStart); ok {
rest = after
} else {
// Find the first END line, and then find the last BEGIN line before
// the end line. This lets us skip any repeated BEGIN lines that don't
// have a matching END.
endIndex := bytes.Index(rest, pemEnd)
if endIndex < 0 {
return nil, data
}
endTrailerIndex := endIndex + len(pemEnd)
beginIndex := bytes.LastIndex(rest[:endIndex], pemStart[1:])
if beginIndex < 0 || beginIndex > 0 && rest[beginIndex-1] != '\n' {
return nil, data
}
rest = rest[beginIndex+len(pemStart)-1:]
endIndex -= beginIndex + len(pemStart) - 1
endTrailerIndex -= beginIndex + len(pemStart) - 1
var typeLine []byte
typeLine, rest = getLine(rest)
var consumed int
typeLine, rest, consumed = getLine(rest)
if !bytes.HasSuffix(typeLine, pemEndOfLine) {
continue
}
endIndex -= consumed
endTrailerIndex -= consumed
typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)]
p = &Block{
@@ -117,7 +129,7 @@ func Decode(data []byte) (p *Block, rest []byte) {
if len(rest) == 0 {
return nil, data
}
line, next := getLine(rest)
line, next, consumed := getLine(rest)
key, val, ok := bytes.Cut(line, colon)
if !ok {
@@ -129,21 +141,13 @@ func Decode(data []byte) (p *Block, rest []byte) {
val = bytes.TrimSpace(val)
p.Headers[string(key)] = string(val)
rest = next
endIndex -= consumed
endTrailerIndex -= consumed
}
var endIndex, endTrailerIndex int
// If there were no headers, the END line might occur
// immediately, without a leading newline.
if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) {
endIndex = 0
endTrailerIndex = len(pemEnd) - 1
} else {
endIndex = bytes.Index(rest, pemEnd)
endTrailerIndex = endIndex + len(pemEnd)
}
if endIndex < 0 {
// If there were headers, there must be a newline between the headers
// and the END line, so endIndex should be >= 0.
if len(p.Headers) > 0 && endIndex < 0 {
continue
}
@@ -163,21 +167,24 @@ func Decode(data []byte) (p *Block, rest []byte) {
}
// The line must end with only whitespace.
if s, _ := getLine(restOfEndLine); len(s) != 0 {
if s, _, _ := getLine(restOfEndLine); len(s) != 0 {
continue
}
base64Data := removeSpacesAndTabs(rest[:endIndex])
p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
if err != nil {
continue
p.Bytes = []byte{}
if endIndex > 0 {
base64Data := removeSpacesAndTabs(rest[:endIndex])
p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data)))
n, err := base64.StdEncoding.Decode(p.Bytes, base64Data)
if err != nil {
continue
}
p.Bytes = p.Bytes[:n]
}
p.Bytes = p.Bytes[:n]
// the -1 is because we might have only matched pemEnd without the
// leading newline if the PEM block was empty.
_, rest = getLine(rest[endIndex+len(pemEnd)-1:])
_, rest, _ = getLine(rest[endIndex+len(pemEnd)-1:])
return p, rest
}
}

View File

@@ -34,7 +34,7 @@ var getLineTests = []GetLineTest{
func TestGetLine(t *testing.T) {
for i, test := range getLineTests {
x, y := getLine([]byte(test.in))
x, y, _ := getLine([]byte(test.in))
if string(x) != test.out1 || string(y) != test.out2 {
t.Errorf("#%d got:%+v,%+v want:%s,%s", i, x, y, test.out1, test.out2)
}
@@ -46,6 +46,7 @@ func TestDecode(t *testing.T) {
if !reflect.DeepEqual(result, certificate) {
t.Errorf("#0 got:%#v want:%#v", result, certificate)
}
result, remainder = Decode(remainder)
if !reflect.DeepEqual(result, privateKey) {
t.Errorf("#1 got:%#v want:%#v", result, privateKey)
@@ -68,7 +69,7 @@ func TestDecode(t *testing.T) {
}
result, remainder = Decode(remainder)
if result == nil || result.Type != "HEADERS" || len(result.Headers) != 1 {
if result == nil || result.Type != "VALID HEADERS" || len(result.Headers) != 1 {
t.Errorf("#5 expected single header block but got :%v", result)
}
@@ -381,15 +382,15 @@ ZWAaUoVtWIQ52aKS0p19G99hhb+IVANC4akkdHV4SP8i7MVNZhfUmg==
# This shouldn't be recognised because of the missing newline after the
headers.
-----BEGIN HEADERS-----
-----BEGIN INVALID HEADERS-----
Header: 1
-----END HEADERS-----
-----END INVALID HEADERS-----
# This should be valid, however.
-----BEGIN HEADERS-----
-----BEGIN VALID HEADERS-----
Header: 1
-----END HEADERS-----`)
-----END VALID HEADERS-----`)
var certificate = &Block{Type: "CERTIFICATE",
Headers: map[string]string{},

View File

@@ -235,7 +235,6 @@ var depsRules = `
internal/types/errors,
mime/quotedprintable,
net/internal/socktest,
net/url,
runtime/trace,
text/scanner,
text/tabwriter;
@@ -298,6 +297,12 @@ var depsRules = `
FMT
< text/template/parse;
internal/bytealg, internal/itoa, math/bits, slices, strconv, unique
< net/netip;
FMT, net/netip
< net/url;
net/url, text/template/parse
< text/template
< internal/lazytemplate;
@@ -412,9 +417,6 @@ var depsRules = `
< golang.org/x/net/dns/dnsmessage,
golang.org/x/net/lif;
internal/bytealg, internal/itoa, math/bits, slices, strconv, unique
< net/netip;
os, net/netip
< internal/routebsd;
@@ -557,7 +559,7 @@ var depsRules = `
# CRYPTO-MATH is crypto that exposes math/big APIs - no cgo, net; fmt now ok.
CRYPTO, FMT, math/big
CRYPTO, FMT, math/big, internal/saferio
< crypto/internal/boring/bbig
< crypto/internal/fips140cache
< crypto/rand

View File

@@ -85,7 +85,7 @@ func gofips140() string {
}
// isFIPSVersion reports whether v is a valid FIPS version,
// of the form vX.Y.Z.
// of the form vX.Y.Z or vX.Y.Z-hash.
func isFIPSVersion(v string) bool {
if !strings.HasPrefix(v, "v") {
return false
@@ -99,7 +99,8 @@ func isFIPSVersion(v string) bool {
return false
}
v, ok = skipNum(v[len("."):])
return ok && v == ""
hasHash := strings.HasPrefix(v, "-") && len(v) == len("-")+8
return ok && (v == "" || hasHash)
}
// skipNum skips the leading text matching [0-9]+

View File

@@ -42,6 +42,7 @@ var All = []Info{
{Name: "http2client", Package: "net/http"},
{Name: "http2debug", Package: "net/http", Opaque: true},
{Name: "http2server", Package: "net/http"},
{Name: "httpcookiemaxnum", Package: "net/http", Changed: 24, Old: "0"},
{Name: "httplaxcontentlength", Package: "net/http", Changed: 22, Old: "1"},
{Name: "httpmuxgo121", Package: "net/http", Changed: 22, Old: "1"},
{Name: "httpservecontentkeepheaders", Package: "net/http", Changed: 23, Old: "1"},

View File

@@ -636,12 +636,22 @@ func (fd *FD) Pread(b []byte, off int64) (int, error) {
fd.l.Lock()
defer fd.l.Unlock()
curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent)
if err != nil {
return 0, err
if fd.isBlocking {
curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent)
if err != nil {
return 0, err
}
defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart)
defer fd.setOffset(curoffset)
} else {
// Overlapped handles don't have the file pointer updated
// when performing I/O operations, so there is no need to
// call Seek to reset the file pointer.
// Also, some overlapped file handles don't support seeking.
// See https://go.dev/issues/74951.
curoffset := fd.offset
defer fd.setOffset(curoffset)
}
defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart)
defer fd.setOffset(curoffset)
o := &fd.rop
o.InitBuf(b)
fd.setOffset(off)
@@ -852,12 +862,22 @@ func (fd *FD) Pwrite(buf []byte, off int64) (int, error) {
fd.l.Lock()
defer fd.l.Unlock()
curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent)
if err != nil {
return 0, err
if fd.isBlocking {
curoffset, err := syscall.Seek(fd.Sysfd, 0, io.SeekCurrent)
if err != nil {
return 0, err
}
defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart)
defer fd.setOffset(curoffset)
} else {
// Overlapped handles don't have the file pointer updated
// when performing I/O operations, so there is no need to
// call Seek to reset the file pointer.
// Also, some overlapped file handles don't support seeking.
// See https://go.dev/issues/74951.
curoffset := fd.offset
defer fd.setOffset(curoffset)
}
defer syscall.Seek(fd.Sysfd, curoffset, io.SeekStart)
defer fd.setOffset(curoffset)
var ntotal int
for {
@@ -1106,6 +1126,12 @@ func (fd *FD) Seek(offset int64, whence int) (int64, error) {
fd.l.Lock()
defer fd.l.Unlock()
if !fd.isBlocking && whence == io.SeekCurrent {
// Windows doesn't keep the file pointer for overlapped file handles.
// We do it ourselves in case to account for any read or write
// operations that may have occurred.
offset += fd.offset
}
n, err := syscall.Seek(fd.Sysfd, offset, whence)
fd.setOffset(n)
return n, err

View File

@@ -383,57 +383,59 @@ func TestChannelMovedOutOfBubble(t *testing.T) {
for _, test := range []struct {
desc string
f func(chan struct{})
wantPanic string
wantFatal string
}{{
desc: "receive",
f: func(ch chan struct{}) {
<-ch
},
wantPanic: "receive on synctest channel from outside bubble",
wantFatal: "receive on synctest channel from outside bubble",
}, {
desc: "send",
f: func(ch chan struct{}) {
ch <- struct{}{}
},
wantPanic: "send on synctest channel from outside bubble",
wantFatal: "send on synctest channel from outside bubble",
}, {
desc: "close",
f: func(ch chan struct{}) {
close(ch)
},
wantPanic: "close of synctest channel from outside bubble",
wantFatal: "close of synctest channel from outside bubble",
}} {
t.Run(test.desc, func(t *testing.T) {
// Bubbled channel accessed from outside any bubble.
t.Run("outside_bubble", func(t *testing.T) {
donec := make(chan struct{})
ch := make(chan chan struct{})
go func() {
defer close(donec)
defer wantPanic(t, test.wantPanic)
test.f(<-ch)
}()
synctest.Run(func() {
ch <- make(chan struct{})
wantFatal(t, test.wantFatal, func() {
donec := make(chan struct{})
ch := make(chan chan struct{})
go func() {
defer close(donec)
test.f(<-ch)
}()
synctest.Run(func() {
ch <- make(chan struct{})
})
<-donec
})
<-donec
})
// Bubbled channel accessed from a different bubble.
t.Run("different_bubble", func(t *testing.T) {
donec := make(chan struct{})
ch := make(chan chan struct{})
go func() {
defer close(donec)
c := <-ch
wantFatal(t, test.wantFatal, func() {
donec := make(chan struct{})
ch := make(chan chan struct{})
go func() {
defer close(donec)
c := <-ch
synctest.Run(func() {
test.f(c)
})
}()
synctest.Run(func() {
defer wantPanic(t, test.wantPanic)
test.f(c)
ch <- make(chan struct{})
})
}()
synctest.Run(func() {
ch <- make(chan struct{})
<-donec
})
<-donec
})
})
}
@@ -443,39 +445,40 @@ func TestTimerFromInsideBubble(t *testing.T) {
for _, test := range []struct {
desc string
f func(tm *time.Timer)
wantPanic string
wantFatal string
}{{
desc: "read channel",
f: func(tm *time.Timer) {
<-tm.C
},
wantPanic: "receive on synctest channel from outside bubble",
wantFatal: "receive on synctest channel from outside bubble",
}, {
desc: "Reset",
f: func(tm *time.Timer) {
tm.Reset(1 * time.Second)
},
wantPanic: "reset of synctest timer from outside bubble",
wantFatal: "reset of synctest timer from outside bubble",
}, {
desc: "Stop",
f: func(tm *time.Timer) {
tm.Stop()
},
wantPanic: "stop of synctest timer from outside bubble",
wantFatal: "stop of synctest timer from outside bubble",
}} {
t.Run(test.desc, func(t *testing.T) {
donec := make(chan struct{})
ch := make(chan *time.Timer)
go func() {
defer close(donec)
defer wantPanic(t, test.wantPanic)
test.f(<-ch)
}()
synctest.Run(func() {
tm := time.NewTimer(1 * time.Second)
ch <- tm
wantFatal(t, test.wantFatal, func() {
donec := make(chan struct{})
ch := make(chan *time.Timer)
go func() {
defer close(donec)
test.f(<-ch)
}()
synctest.Run(func() {
tm := time.NewTimer(1 * time.Second)
ch <- tm
})
<-donec
})
<-donec
})
}
}
@@ -776,6 +779,28 @@ func TestWaitGroupHeapAllocated(t *testing.T) {
})
}
// Issue #75134: Many racing bubble associations.
func TestWaitGroupManyBubbles(t *testing.T) {
var wg sync.WaitGroup
for range 100 {
wg.Go(func() {
synctest.Run(func() {
cancelc := make(chan struct{})
var wg2 sync.WaitGroup
for range 100 {
wg2.Go(func() {
<-cancelc
})
}
synctest.Wait()
close(cancelc)
wg2.Wait()
})
})
}
wg.Wait()
}
func TestHappensBefore(t *testing.T) {
// Use two parallel goroutines accessing different vars to ensure that
// we correctly account for multiple goroutines in the bubble.

View File

@@ -7,6 +7,7 @@ package http
import (
"errors"
"fmt"
"internal/godebug"
"log"
"net"
"net/http/internal/ascii"
@@ -16,6 +17,8 @@ import (
"time"
)
var httpcookiemaxnum = godebug.New("httpcookiemaxnum")
// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
//
@@ -58,16 +61,37 @@ const (
)
var (
errBlankCookie = errors.New("http: blank cookie")
errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
errInvalidCookieName = errors.New("http: invalid cookie name")
errInvalidCookieValue = errors.New("http: invalid cookie value")
errBlankCookie = errors.New("http: blank cookie")
errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
errInvalidCookieName = errors.New("http: invalid cookie name")
errInvalidCookieValue = errors.New("http: invalid cookie value")
errCookieNumLimitExceeded = errors.New("http: number of cookies exceeded limit")
)
const defaultCookieMaxNum = 3000
func cookieNumWithinMax(cookieNum int) bool {
withinDefaultMax := cookieNum <= defaultCookieMaxNum
if httpcookiemaxnum.Value() == "" {
return withinDefaultMax
}
if customMax, err := strconv.Atoi(httpcookiemaxnum.Value()); err == nil {
withinCustomMax := customMax == 0 || cookieNum <= customMax
if withinDefaultMax != withinCustomMax {
httpcookiemaxnum.IncNonDefault()
}
return withinCustomMax
}
return withinDefaultMax
}
// ParseCookie parses a Cookie header value and returns all the cookies
// which were set in it. Since the same cookie name can appear multiple times
// the returned Values can contain more than one value for a given key.
func ParseCookie(line string) ([]*Cookie, error) {
if !cookieNumWithinMax(strings.Count(line, ";") + 1) {
return nil, errCookieNumLimitExceeded
}
parts := strings.Split(textproto.TrimString(line), ";")
if len(parts) == 1 && parts[0] == "" {
return nil, errBlankCookie
@@ -197,11 +221,21 @@ func ParseSetCookie(line string) (*Cookie, error) {
// readSetCookies parses all "Set-Cookie" values from
// the header h and returns the successfully parsed Cookies.
//
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
// GODEBUG option is not explicitly turned off, this function will silently
// fail and return an empty slice.
func readSetCookies(h Header) []*Cookie {
cookieCount := len(h["Set-Cookie"])
if cookieCount == 0 {
return []*Cookie{}
}
// Cookie limit was unfortunately introduced at a later point in time.
// As such, we can only fail by returning an empty slice rather than
// explicit error.
if !cookieNumWithinMax(cookieCount) {
return []*Cookie{}
}
cookies := make([]*Cookie, 0, cookieCount)
for _, line := range h["Set-Cookie"] {
if cookie, err := ParseSetCookie(line); err == nil {
@@ -329,13 +363,28 @@ func (c *Cookie) Valid() error {
// readCookies parses all "Cookie" values from the header h and
// returns the successfully parsed Cookies.
//
// if filter isn't empty, only cookies of that name are returned.
// If filter isn't empty, only cookies of that name are returned.
//
// If the amount of cookies exceeds CookieNumLimit, and httpcookielimitnum
// GODEBUG option is not explicitly turned off, this function will silently
// fail and return an empty slice.
func readCookies(h Header, filter string) []*Cookie {
lines := h["Cookie"]
if len(lines) == 0 {
return []*Cookie{}
}
// Cookie limit was unfortunately introduced at a later point in time.
// As such, we can only fail by returning an empty slice rather than
// explicit error.
cookieCount := 0
for _, line := range lines {
cookieCount += strings.Count(line, ";") + 1
}
if !cookieNumWithinMax(cookieCount) {
return []*Cookie{}
}
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
for _, line := range lines {
line = textproto.TrimString(line)

View File

@@ -11,6 +11,7 @@ import (
"log"
"os"
"reflect"
"slices"
"strings"
"testing"
"time"
@@ -255,16 +256,17 @@ func TestAddCookie(t *testing.T) {
}
var readSetCookiesTests = []struct {
Header Header
Cookies []*Cookie
header Header
cookies []*Cookie
godebug string
}{
{
Header{"Set-Cookie": {"Cookie-1=v$1"}},
[]*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
header: Header{"Set-Cookie": {"Cookie-1=v$1"}},
cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
},
{
Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
[]*Cookie{{
header: Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
cookies: []*Cookie{{
Name: "NID",
Value: "99=YsDT5i3E-CXax-",
Path: "/",
@@ -276,8 +278,8 @@ var readSetCookiesTests = []struct {
}},
},
{
Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
[]*Cookie{{
header: Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
cookies: []*Cookie{{
Name: ".ASPXAUTH",
Value: "7E3AA",
Path: "/",
@@ -288,8 +290,8 @@ var readSetCookiesTests = []struct {
}},
},
{
Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
[]*Cookie{{
header: Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
cookies: []*Cookie{{
Name: "ASP.NET_SessionId",
Value: "foo",
Path: "/",
@@ -298,8 +300,8 @@ var readSetCookiesTests = []struct {
}},
},
{
Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
[]*Cookie{{
header: Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
cookies: []*Cookie{{
Name: "samesitedefault",
Value: "foo",
SameSite: SameSiteDefaultMode,
@@ -307,8 +309,8 @@ var readSetCookiesTests = []struct {
}},
},
{
Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}},
[]*Cookie{{
header: Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}},
cookies: []*Cookie{{
Name: "samesiteinvalidisdefault",
Value: "foo",
SameSite: SameSiteDefaultMode,
@@ -316,8 +318,8 @@ var readSetCookiesTests = []struct {
}},
},
{
Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
[]*Cookie{{
header: Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
cookies: []*Cookie{{
Name: "samesitelax",
Value: "foo",
SameSite: SameSiteLaxMode,
@@ -325,8 +327,8 @@ var readSetCookiesTests = []struct {
}},
},
{
Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
[]*Cookie{{
header: Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
cookies: []*Cookie{{
Name: "samesitestrict",
Value: "foo",
SameSite: SameSiteStrictMode,
@@ -334,8 +336,8 @@ var readSetCookiesTests = []struct {
}},
},
{
Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}},
[]*Cookie{{
header: Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}},
cookies: []*Cookie{{
Name: "samesitenone",
Value: "foo",
SameSite: SameSiteNoneMode,
@@ -345,47 +347,66 @@ var readSetCookiesTests = []struct {
// Make sure we can properly read back the Set-Cookie headers we create
// for values containing spaces or commas:
{
Header{"Set-Cookie": {`special-1=a z`}},
[]*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
header: Header{"Set-Cookie": {`special-1=a z`}},
cookies: []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
},
{
Header{"Set-Cookie": {`special-2=" z"`}},
[]*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
header: Header{"Set-Cookie": {`special-2=" z"`}},
cookies: []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
},
{
Header{"Set-Cookie": {`special-3="a "`}},
[]*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
header: Header{"Set-Cookie": {`special-3="a "`}},
cookies: []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
},
{
Header{"Set-Cookie": {`special-4=" "`}},
[]*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
header: Header{"Set-Cookie": {`special-4=" "`}},
cookies: []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
},
{
Header{"Set-Cookie": {`special-5=a,z`}},
[]*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
header: Header{"Set-Cookie": {`special-5=a,z`}},
cookies: []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
},
{
Header{"Set-Cookie": {`special-6=",z"`}},
[]*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
header: Header{"Set-Cookie": {`special-6=",z"`}},
cookies: []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
},
{
Header{"Set-Cookie": {`special-7=a,`}},
[]*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
header: Header{"Set-Cookie": {`special-7=a,`}},
cookies: []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
},
{
Header{"Set-Cookie": {`special-8=","`}},
[]*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
header: Header{"Set-Cookie": {`special-8=","`}},
cookies: []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
},
// Make sure we can properly read back the Set-Cookie headers
// for names containing spaces:
{
Header{"Set-Cookie": {`special-9 =","`}},
[]*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
header: Header{"Set-Cookie": {`special-9 =","`}},
cookies: []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
},
// Quoted values (issue #46443)
{
Header{"Set-Cookie": {`cookie="quoted"`}},
[]*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
header: Header{"Set-Cookie": {`cookie="quoted"`}},
cookies: []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
},
{
header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
cookies: []*Cookie{},
},
{
header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, 10)},
cookies: []*Cookie{},
godebug: "httpcookiemaxnum=5",
},
{
header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
godebug: "httpcookiemaxnum=0",
},
{
header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
},
// TODO(bradfitz): users have reported seeing this in the
@@ -405,79 +426,103 @@ func toJSON(v any) string {
func TestReadSetCookies(t *testing.T) {
for i, tt := range readSetCookiesTests {
t.Setenv("GODEBUG", tt.godebug)
for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input
c := readSetCookies(tt.Header)
if !reflect.DeepEqual(c, tt.Cookies) {
t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies))
c := readSetCookies(tt.header)
if !reflect.DeepEqual(c, tt.cookies) {
t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.cookies))
}
}
}
}
var readCookiesTests = []struct {
Header Header
Filter string
Cookies []*Cookie
header Header
filter string
cookies []*Cookie
godebug string
}{
{
Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
"",
[]*Cookie{
header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
filter: "",
cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1"},
{Name: "c2", Value: "v2"},
},
},
{
Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
"c2",
[]*Cookie{
header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
filter: "c2",
cookies: []*Cookie{
{Name: "c2", Value: "v2"},
},
},
{
Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
"",
[]*Cookie{
header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
filter: "",
cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1"},
{Name: "c2", Value: "v2"},
},
},
{
Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
"c2",
[]*Cookie{
header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
filter: "c2",
cookies: []*Cookie{
{Name: "c2", Value: "v2"},
},
},
{
Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
"",
[]*Cookie{
header: Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
filter: "",
cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1", Quoted: true},
{Name: "c2", Value: "v2", Quoted: true},
},
},
{
Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
"",
[]*Cookie{
header: Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
filter: "",
cookies: []*Cookie{
{Name: "Cookie-1", Value: "v$1", Quoted: true},
{Name: "c2", Value: "v2"},
},
},
{
Header{"Cookie": {``}},
"",
[]*Cookie{},
header: Header{"Cookie": {``}},
filter: "",
cookies: []*Cookie{},
},
// GODEBUG=httpcookiemaxnum should work regardless if all cookies are sent
// via one "Cookie" field, or multiple fields.
{
header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
cookies: []*Cookie{},
},
{
header: Header{"Cookie": slices.Repeat([]string{"a="}, 10)},
cookies: []*Cookie{},
godebug: "httpcookiemaxnum=5",
},
{
header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
godebug: "httpcookiemaxnum=0",
},
{
header: Header{"Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
},
}
func TestReadCookies(t *testing.T) {
for i, tt := range readCookiesTests {
t.Setenv("GODEBUG", tt.godebug)
for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input
c := readCookies(tt.Header, tt.Filter)
if !reflect.DeepEqual(c, tt.Cookies) {
t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies))
c := readCookies(tt.header, tt.filter)
if !reflect.DeepEqual(c, tt.cookies) {
t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.cookies))
}
}
}
@@ -689,6 +734,7 @@ func TestParseCookie(t *testing.T) {
line string
cookies []*Cookie
err error
godebug string
}{
{
line: "Cookie-1=v$1",
@@ -722,8 +768,28 @@ func TestParseCookie(t *testing.T) {
line: "k1=\\",
err: errInvalidCookieValue,
},
{
line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
err: errCookieNumLimitExceeded,
},
{
line: strings.Repeat(";a=", 10)[1:],
err: errCookieNumLimitExceeded,
godebug: "httpcookiemaxnum=5",
},
{
line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
godebug: "httpcookiemaxnum=0",
},
{
line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
},
}
for i, tt := range tests {
t.Setenv("GODEBUG", tt.godebug)
gotCookies, gotErr := ParseCookie(tt.line)
if !errors.Is(gotErr, tt.err) {
t.Errorf("#%d ParseCookie got error %v, want error %v", i, gotErr, tt.err)

View File

@@ -77,13 +77,21 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error {
return nil
}
var noopHandler = HandlerFunc(func(w ResponseWriter, r *Request) {})
type noopHandler struct{}
func (noopHandler) ServeHTTP(ResponseWriter, *Request) {}
var sentinelHandler Handler = &noopHandler{}
// AddInsecureBypassPattern permits all requests that match the given pattern.
// The pattern syntax and precedence rules are the same as [ServeMux].
//
// AddInsecureBypassPattern can be called concurrently with other methods
// or request handling, and applies to future requests.
// The pattern syntax and precedence rules are the same as [ServeMux]. Only
// requests that match the pattern directly are permitted. Those that ServeMux
// would redirect to a pattern (e.g. after cleaning the path or adding a
// trailing slash) are not.
//
// AddInsecureBypassPattern can be called concurrently with other methods or
// request handling, and applies to future requests.
func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
var bypass *ServeMux
@@ -99,7 +107,7 @@ func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
}
}
bypass.Handle(pattern, noopHandler)
bypass.Handle(pattern, sentinelHandler)
}
// SetDenyHandler sets a handler to invoke when a request is rejected.
@@ -172,7 +180,7 @@ var (
// be deferred until the last moment.
func (c *CrossOriginProtection) isRequestExempt(req *Request) bool {
if bypass := c.bypass.Load(); bypass != nil {
if _, pattern := bypass.Handler(req); pattern != "" {
if h, _ := bypass.Handler(req); h == sentinelHandler {
// The request matches a bypass pattern.
return true
}

View File

@@ -113,6 +113,11 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) {
protection := http.NewCrossOriginProtection()
protection.AddInsecureBypassPattern("/bypass/")
protection.AddInsecureBypassPattern("/only/{foo}")
protection.AddInsecureBypassPattern("/no-trailing")
protection.AddInsecureBypassPattern("/yes-trailing/")
protection.AddInsecureBypassPattern("PUT /put-only/")
protection.AddInsecureBypassPattern("GET /get-only/")
protection.AddInsecureBypassPattern("POST /post-only/")
handler := protection.Handler(okHandler)
tests := []struct {
@@ -126,13 +131,23 @@ func TestCrossOriginProtectionPatternBypass(t *testing.T) {
{"non-bypass path without sec-fetch-site", "/api/", "", http.StatusForbidden},
{"non-bypass path with cross-site", "/api/", "cross-site", http.StatusForbidden},
{"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusOK},
{"redirect to bypass path with trailing slash", "/bypass", "", http.StatusOK},
{"redirect to bypass path without ..", "/foo/../bypass/bar", "", http.StatusForbidden},
{"redirect to bypass path with trailing slash", "/bypass", "", http.StatusForbidden},
{"redirect to non-bypass path with ..", "/foo/../api/bar", "", http.StatusForbidden},
{"redirect to non-bypass path with trailing slash", "/api", "", http.StatusForbidden},
{"wildcard bypass", "/only/123", "", http.StatusOK},
{"non-wildcard", "/only/123/foo", "", http.StatusForbidden},
// https://go.dev/issue/75054
{"no trailing slash exact match", "/no-trailing", "", http.StatusOK},
{"no trailing slash with slash", "/no-trailing/", "", http.StatusForbidden},
{"yes trailing slash exact match", "/yes-trailing/", "", http.StatusOK},
{"yes trailing slash without slash", "/yes-trailing", "", http.StatusForbidden},
{"method-specific hit", "/post-only/", "", http.StatusOK},
{"method-specific miss (PUT)", "/put-only/", "", http.StatusForbidden},
{"method-specific miss (GET)", "/get-only/", "", http.StatusForbidden},
}
for _, tc := range tests {

View File

@@ -1372,7 +1372,10 @@ func (w *wantConn) cancel(t *Transport) {
w.done = true
w.mu.Unlock()
if pc != nil {
// HTTP/2 connections (pc.alt != nil) aren't removed from the idle pool on use,
// and should not be added back here. If the pconn isn't in the idle pool,
// it's because we removed it due to an error.
if pc != nil && pc.alt == nil {
t.putOrCloseIdleConn(pc)
}
}

View File

@@ -7559,3 +7559,35 @@ func TestTransportServerProtocols(t *testing.T) {
})
}
}
func TestIssue61474(t *testing.T) {
run(t, testIssue61474, []testMode{http2Mode})
}
func testIssue61474(t *testing.T, mode testMode) {
if testing.Short() {
return
}
// This test reliably exercises the condition causing #61474,
// but requires many iterations to do so.
// Keep the test around for now, but don't run it by default.
t.Skip("test is too large")
cst := newClientServerTest(t, mode, HandlerFunc(func(rw ResponseWriter, req *Request) {
}), func(tr *Transport) {
tr.MaxConnsPerHost = 1
})
var wg sync.WaitGroup
defer wg.Wait()
for range 100000 {
wg.Go(func() {
ctx, cancel := context.WithTimeout(t.Context(), 1*time.Millisecond)
defer cancel()
req, _ := NewRequestWithContext(ctx, "GET", cst.ts.URL, nil)
resp, err := cst.c.Do(req)
if err == nil {
resp.Body.Close()
}
})
}
}

View File

@@ -237,8 +237,12 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e
func addrPortToSockaddrInet4(ap netip.AddrPort) (syscall.SockaddrInet4, error) {
// ipToSockaddrInet4 has special handling here for zero length slices.
// We do not, because netip has no concept of a generic zero IP address.
//
// addr is allowed to be an IPv4-mapped IPv6 address.
// As4 will unmap it to an IPv4 address.
// The error message is kept consistent with ipToSockaddrInet4.
addr := ap.Addr()
if !addr.Is4() {
if !addr.Is4() && !addr.Is4In6() {
return syscall.SockaddrInet4{}, &AddrError{Err: "non-IPv4 address", Addr: addr.String()}
}
sa := syscall.SockaddrInet4{

View File

@@ -724,7 +724,8 @@ func (p *addrParser) consumeDomainLiteral() (string, error) {
}
// Parse the dtext
var dtext string
dtext := p.s
dtextLen := 0
for {
if p.empty() {
return "", errors.New("mail: unclosed domain-literal")
@@ -741,9 +742,10 @@ func (p *addrParser) consumeDomainLiteral() (string, error) {
return "", fmt.Errorf("mail: bad character in domain-literal: %q", r)
}
dtext += p.s[:size]
dtextLen += size
p.s = p.s[size:]
}
dtext = dtext[:dtextLen]
// Skip the trailing ]
if !p.consume(']') {

View File

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

View File

@@ -705,3 +705,40 @@ func TestIPv6WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) {
t.Fatal(err)
}
}
// TestIPv4WriteMsgUDPAddrPortTargetAddrIPVersion verifies that
// WriteMsgUDPAddrPort accepts IPv4 and IPv4-mapped IPv6 destination addresses,
// and rejects IPv6 destination addresses on a "udp4" connection.
func TestIPv4WriteMsgUDPAddrPortTargetAddrIPVersion(t *testing.T) {
switch runtime.GOOS {
case "plan9":
t.Skipf("not supported on %s", runtime.GOOS)
}
if !testableNetwork("udp4") {
t.Skipf("skipping: udp4 not available")
}
conn, err := ListenUDP("udp4", &UDPAddr{IP: IPv4(127, 0, 0, 1)})
if err != nil {
t.Fatal(err)
}
defer conn.Close()
daddr4 := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), 12345)
daddr4in6 := netip.AddrPortFrom(netip.MustParseAddr("::ffff:127.0.0.1"), 12345)
daddr6 := netip.AddrPortFrom(netip.MustParseAddr("::1"), 12345)
buf := make([]byte, 8)
if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr4); err != nil {
t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr4) failed: %v", err)
}
if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr4in6); err != nil {
t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr4in6) failed: %v", err)
}
if _, _, err = conn.WriteMsgUDPAddrPort(buf, nil, daddr6); err == nil {
t.Errorf("conn.WriteMsgUDPAddrPort(buf, nil, daddr6) should have failed, but got no error")
}
}

View File

@@ -16,6 +16,7 @@ import (
"errors"
"fmt"
"maps"
"net/netip"
"path"
"slices"
"strconv"
@@ -626,40 +627,61 @@ func parseAuthority(authority string) (user *Userinfo, host string, err error) {
// parseHost parses host as an authority without user
// information. That is, as host[:port].
func parseHost(host string) (string, error) {
if strings.HasPrefix(host, "[") {
if openBracketIdx := strings.LastIndex(host, "["); openBracketIdx != -1 {
// Parse an IP-Literal in RFC 3986 and RFC 6874.
// E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80".
i := strings.LastIndex(host, "]")
if i < 0 {
closeBracketIdx := strings.LastIndex(host, "]")
if closeBracketIdx < 0 {
return "", errors.New("missing ']' in host")
}
colonPort := host[i+1:]
colonPort := host[closeBracketIdx+1:]
if !validOptionalPort(colonPort) {
return "", fmt.Errorf("invalid port %q after host", colonPort)
}
unescapedColonPort, err := unescape(colonPort, encodeHost)
if err != nil {
return "", err
}
hostname := host[openBracketIdx+1 : closeBracketIdx]
var unescapedHostname string
// RFC 6874 defines that %25 (%-encoded percent) introduces
// the zone identifier, and the zone identifier can use basically
// any %-encoding it likes. That's different from the host, which
// can only %-encode non-ASCII bytes.
// We do impose some restrictions on the zone, to avoid stupidity
// like newlines.
zone := strings.Index(host[:i], "%25")
if zone >= 0 {
host1, err := unescape(host[:zone], encodeHost)
zoneIdx := strings.Index(hostname, "%25")
if zoneIdx >= 0 {
hostPart, err := unescape(hostname[:zoneIdx], encodeHost)
if err != nil {
return "", err
}
host2, err := unescape(host[zone:i], encodeZone)
zonePart, err := unescape(hostname[zoneIdx:], encodeZone)
if err != nil {
return "", err
}
host3, err := unescape(host[i:], encodeHost)
unescapedHostname = hostPart + zonePart
} else {
var err error
unescapedHostname, err = unescape(hostname, encodeHost)
if err != nil {
return "", err
}
return host1 + host2 + host3, nil
}
// Per RFC 3986, only a host identified by a valid
// IPv6 address can be enclosed by square brackets.
// This excludes any IPv4 or IPv4-mapped addresses.
addr, err := netip.ParseAddr(unescapedHostname)
if err != nil {
return "", fmt.Errorf("invalid host: %w", err)
}
if addr.Is4() || addr.Is4In6() {
return "", errors.New("invalid IPv6 host")
}
return "[" + unescapedHostname + "]" + unescapedColonPort, nil
} else if i := strings.LastIndex(host, ":"); i != -1 {
colonPort := host[i:]
if !validOptionalPort(colonPort) {

View File

@@ -383,6 +383,16 @@ var urltests = []URLTest{
},
"",
},
// valid IPv6 host with port and path
{
"https://[2001:db8::1]:8443/test/path",
&URL{
Scheme: "https",
Host: "[2001:db8::1]:8443",
Path: "/test/path",
},
"",
},
// host subcomponent; IPv6 address with zone identifier in RFC 6874
{
"http://[fe80::1%25en0]/", // alphanum zone identifier
@@ -707,6 +717,24 @@ var parseRequestURLTests = []struct {
// RFC 6874.
{"http://[fe80::1%en0]/", false},
{"http://[fe80::1%en0]:8080/", false},
// Tests exercising RFC 3986 compliance
{"https://[1:2:3:4:5:6:7:8]", true}, // full IPv6 address
{"https://[2001:db8::a:b:c:d]", true}, // compressed IPv6 address
{"https://[fe80::1%25eth0]", true}, // link-local address with zone ID (interface name)
{"https://[fe80::abc:def%254]", true}, // link-local address with zone ID (interface index)
{"https://[2001:db8::1]/path", true}, // compressed IPv6 address with path
{"https://[fe80::1%25eth0]/path?query=1", true}, // link-local with zone, path, and query
{"https://[::ffff:192.0.2.1]", false},
{"https://[:1] ", false},
{"https://[1:2:3:4:5:6:7:8:9]", false},
{"https://[1::1::1]", false},
{"https://[1:2:3:]", false},
{"https://[ffff::127.0.0.4000]", false},
{"https://[0:0::test.com]:80", false},
{"https://[2001:db8::test.com]", false},
{"https://[test.com]", false},
}
func TestParseRequestURI(t *testing.T) {
@@ -1643,6 +1671,17 @@ func TestParseErrors(t *testing.T) {
{"cache_object:foo", true},
{"cache_object:foo/bar", true},
{"cache_object/:foo/bar", false},
{"http://[192.168.0.1]/", true}, // IPv4 in brackets
{"http://[192.168.0.1]:8080/", true}, // IPv4 in brackets with port
{"http://[::ffff:192.168.0.1]/", true}, // IPv4-mapped IPv6 in brackets
{"http://[::ffff:192.168.0.1]:8080/", true}, // IPv4-mapped IPv6 in brackets with port
{"http://[::ffff:c0a8:1]/", true}, // IPv4-mapped IPv6 in brackets (hex)
{"http://[not-an-ip]/", true}, // invalid IP string in brackets
{"http://[fe80::1%foo]/", true}, // invalid zone format in brackets
{"http://[fe80::1", true}, // missing closing bracket
{"http://fe80::1]/", true}, // missing opening bracket
{"http://[test.com]/", true}, // domain name in brackets
}
for _, tt := range tests {
u, err := Parse(tt.in)

View File

@@ -177,4 +177,48 @@ func TestLookPath(t *testing.T) {
}
}
})
checker := func(test string) func(t *testing.T) {
return func(t *testing.T) {
t.Helper()
t.Logf("PATH=%s", os.Getenv("PATH"))
p, err := LookPath(test)
if err == nil {
t.Errorf("%q: error expected, got nil", test)
}
if p != "" {
t.Errorf("%q: path returned should be \"\". Got %q", test, p)
}
}
}
// Reference behavior for the next test
t.Run(pathVar+"=$OTHER2", func(t *testing.T) {
t.Run("empty", checker(""))
t.Run("dot", checker("."))
t.Run("dotdot1", checker("abc/.."))
t.Run("dotdot2", checker(".."))
})
// Test the behavior when PATH contains an executable file which is not a directory
t.Run(pathVar+"=exe", func(t *testing.T) {
// Inject an executable file (not a directory) in PATH.
// Use our own binary os.Args[0].
t.Setenv(pathVar, testenv.Executable(t))
t.Run("empty", checker(""))
t.Run("dot", checker("."))
t.Run("dotdot1", checker("abc/.."))
t.Run("dotdot2", checker(".."))
})
// Test the behavior when PATH contains an executable file which is not a directory
t.Run(pathVar+"=exe/xx", func(t *testing.T) {
// Inject an executable file (not a directory) in PATH.
// Use our own binary os.Args[0].
t.Setenv(pathVar, filepath.Join(testenv.Executable(t), "xx"))
t.Run("empty", checker(""))
t.Run("dot", checker("."))
t.Run("dotdot1", checker("abc/.."))
t.Run("dotdot2", checker(".."))
})
}

View File

@@ -1328,3 +1328,13 @@ func addCriticalEnv(env []string) []string {
// Code should use errors.Is(err, ErrDot), not err == ErrDot,
// to test whether a returned error err is due to this condition.
var ErrDot = errors.New("cannot run executable found relative to current directory")
// validateLookPath excludes paths that can't be valid
// executable names. See issue #74466 and CVE-2025-47906.
func validateLookPath(s string) error {
switch s {
case "", ".", "..":
return ErrNotFound
}
return nil
}

View File

@@ -36,6 +36,10 @@ func findExecutable(file string) error {
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
func LookPath(file string) (string, error) {
if err := validateLookPath(filepath.Clean(file)); err != nil {
return "", &Error{file, err}
}
// skip the path lookup for these prefixes
skip := []string{"/", "#", "./", "../"}

View File

@@ -54,6 +54,10 @@ func LookPath(file string) (string, error) {
// (only bypass the path if file begins with / or ./ or ../)
// but that would not match all the Unix shells.
if err := validateLookPath(file); err != nil {
return "", &Error{file, err}
}
if strings.Contains(file, "/") {
err := findExecutable(file)
if err == nil {

View File

@@ -67,6 +67,10 @@ func findExecutable(file string, exts []string) (string, error) {
// As of Go 1.19, LookPath will instead return that path along with an error satisfying
// [errors.Is](err, [ErrDot]). See the package documentation for more details.
func LookPath(file string) (string, error) {
if err := validateLookPath(file); err != nil {
return "", &Error{file, err}
}
return lookPath(file, pathExt())
}
@@ -80,6 +84,10 @@ func LookPath(file string) (string, error) {
// "C:\foo\example.com" would be returned as-is even if the
// program is actually "C:\foo\example.com.exe".
func lookExtensions(path, dir string) (string, error) {
if err := validateLookPath(path); err != nil {
return "", &Error{path, err}
}
if filepath.Base(path) == path {
path = "." + string(filepath.Separator) + path
}

View File

@@ -1845,6 +1845,72 @@ func TestFile(t *testing.T) {
}
}
func TestFileOverlappedSeek(t *testing.T) {
t.Parallel()
name := filepath.Join(t.TempDir(), "foo")
f := newFileOverlapped(t, name, true)
content := []byte("foo")
if _, err := f.Write(content); err != nil {
t.Fatal(err)
}
// Check that the file pointer is at the expected offset.
n, err := f.Seek(0, io.SeekCurrent)
if err != nil {
t.Fatal(err)
}
if n != int64(len(content)) {
t.Errorf("expected file pointer to be at offset %d, got %d", len(content), n)
}
// Set the file pointer to the start of the file.
if _, err := f.Seek(0, io.SeekStart); err != nil {
t.Fatal(err)
}
// Read the first byte.
var buf [1]byte
if _, err := f.Read(buf[:]); err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf[:], content[:len(buf)]) {
t.Errorf("expected %q, got %q", content[:len(buf)], buf[:])
}
// Check that the file pointer is at the expected offset.
n, err = f.Seek(0, io.SeekCurrent)
if err != nil {
t.Fatal(err)
}
if n != int64(len(buf)) {
t.Errorf("expected file pointer to be at offset %d, got %d", len(buf), n)
}
}
func TestFileOverlappedReadAtVolume(t *testing.T) {
// Test that we can use File.ReadAt with an overlapped volume handle.
// See https://go.dev/issues/74951.
t.Parallel()
name := `\\.\` + filepath.VolumeName(t.TempDir())
namep, err := syscall.UTF16PtrFromString(name)
if err != nil {
t.Fatal(err)
}
h, err := syscall.CreateFile(namep,
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
nil, syscall.OPEN_ALWAYS, syscall.FILE_FLAG_OVERLAPPED, 0)
if err != nil {
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
t.Skip("skipping test: access denied")
}
t.Fatal(err)
}
f := os.NewFile(uintptr(h), name)
defer f.Close()
var buf [0]byte
if _, err := f.ReadAt(buf[:], 0); err != nil {
t.Fatal(err)
}
}
func TestPipe(t *testing.T) {
t.Parallel()
r, w, err := os.Pipe()

View File

@@ -131,7 +131,9 @@ func rootMkdirAll(r *Root, fullname string, perm FileMode) error {
if try > 0 || !IsNotExist(err) {
return 0, &PathError{Op: "openat", Err: err}
}
if err := mkdirat(parent, name, perm); err != nil {
// Try again on EEXIST, because the directory may have been created
// by another process or thread between the rootOpenDir and mkdirat calls.
if err := mkdirat(parent, name, perm); err != nil && err != syscall.EEXIST {
return 0, &PathError{Op: "mkdirat", Err: err}
}
}

View File

@@ -1919,3 +1919,36 @@ func TestRootWriteReadFile(t *testing.T) {
t.Fatalf("root.ReadFile(%q) = %q, %v; want %q, nil", name, got, err, want)
}
}
func TestRootName(t *testing.T) {
dir := t.TempDir()
root, err := os.OpenRoot(dir)
if err != nil {
t.Fatal(err)
}
defer root.Close()
if got, want := root.Name(), dir; got != want {
t.Errorf("root.Name() = %q, want %q", got, want)
}
f, err := root.Create("file")
if err != nil {
t.Fatal(err)
}
defer f.Close()
if got, want := f.Name(), filepath.Join(dir, "file"); got != want {
t.Errorf(`root.Create("file").Name() = %q, want %q`, got, want)
}
if err := root.Mkdir("dir", 0o777); err != nil {
t.Fatal(err)
}
subroot, err := root.OpenRoot("dir")
if err != nil {
t.Fatal(err)
}
defer subroot.Close()
if got, want := subroot.Name(), filepath.Join(dir, "dir"); got != want {
t.Errorf(`root.OpenRoot("dir").Name() = %q, want %q`, got, want)
}
}

View File

@@ -75,7 +75,7 @@ func openRootInRoot(r *Root, name string) (*Root, error) {
if err != nil {
return nil, &PathError{Op: "openat", Path: name, Err: err}
}
return newRoot(fd, name)
return newRoot(fd, joinPath(r.Name(), name))
}
// rootOpenFileNolog is Root.OpenFile.

View File

@@ -123,7 +123,7 @@ func openRootInRoot(r *Root, name string) (*Root, error) {
if err != nil {
return nil, &PathError{Op: "openat", Path: name, Err: err}
}
return newRoot(fd, name)
return newRoot(fd, joinPath(r.Name(), name))
}
// rootOpenFileNolog is Root.OpenFile.

View File

@@ -7,6 +7,7 @@ package user
import (
"crypto/rand"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"internal/syscall/windows"
@@ -16,11 +17,92 @@ import (
"runtime"
"slices"
"strconv"
"strings"
"syscall"
"testing"
"unicode"
"unicode/utf8"
"unsafe"
)
// addUserAccount creates a local user account.
// It returns the name and password of the new account.
// Multiple programs or goroutines calling addUserAccount simultaneously will not choose the same directory.
func addUserAccount(t *testing.T) (name, password string) {
t.TempDir()
pattern := t.Name()
// Windows limits the user name to 20 characters,
// leave space for a 4 digits random suffix.
const maxNameLen, suffixLen = 20, 4
pattern = pattern[:min(len(pattern), maxNameLen-suffixLen)]
// Drop unusual characters from the account name.
mapper := func(r rune) rune {
if r < utf8.RuneSelf {
if '0' <= r && r <= '9' ||
'a' <= r && r <= 'z' ||
'A' <= r && r <= 'Z' {
return r
}
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
return r
}
return -1
}
pattern = strings.Map(mapper, pattern)
// Generate a long random password.
var pwd [33]byte
rand.Read(pwd[:])
// Add special chars to ensure it satisfies password requirements.
password = base64.StdEncoding.EncodeToString(pwd[:]) + "_-As@!%*(1)4#2"
password16, err := syscall.UTF16PtrFromString(password)
if err != nil {
t.Fatal(err)
}
try := 0
for {
// Calculate a random suffix to append to the user name.
var suffix [2]byte
rand.Read(suffix[:])
suffixStr := strconv.FormatUint(uint64(binary.LittleEndian.Uint16(suffix[:])), 10)
name := pattern + suffixStr[:min(len(suffixStr), suffixLen)]
name16, err := syscall.UTF16PtrFromString(name)
if err != nil {
t.Fatal(err)
}
// Create user.
userInfo := windows.UserInfo1{
Name: name16,
Password: password16,
Priv: windows.USER_PRIV_USER,
}
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
t.Skip("skipping test; don't have permission to create user")
}
// If the user already exists, try again with a different name.
if errors.Is(err, windows.NERR_UserExists) {
if try++; try < 1000 {
t.Log("user already exists, trying again with a different name")
continue
}
}
if err != nil {
t.Fatalf("NetUserAdd failed: %v", err)
}
// Delete the user when the test is done.
t.Cleanup(func() {
if err := windows.NetUserDel(nil, name16); err != nil {
if !errors.Is(err, windows.NERR_UserNotFound) {
t.Fatal(err)
}
}
})
return name, password
}
}
// windowsTestAccount creates a test user and returns a token for that user.
// If the user already exists, it will be deleted and recreated.
// The caller is responsible for closing the token.
@@ -32,47 +114,15 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
// See https://dev.go/issue/70396.
t.Skip("skipping non-hermetic test outside of Go builders")
}
const testUserName = "GoStdTestUser01"
var password [33]byte
rand.Read(password[:])
// Add special chars to ensure it satisfies password requirements.
pwd := base64.StdEncoding.EncodeToString(password[:]) + "_-As@!%*(1)4#2"
name, err := syscall.UTF16PtrFromString(testUserName)
name, password := addUserAccount(t)
name16, err := syscall.UTF16PtrFromString(name)
if err != nil {
t.Fatal(err)
}
pwd16, err := syscall.UTF16PtrFromString(pwd)
pwd16, err := syscall.UTF16PtrFromString(password)
if err != nil {
t.Fatal(err)
}
userInfo := windows.UserInfo1{
Name: name,
Password: pwd16,
Priv: windows.USER_PRIV_USER,
}
// Create user.
err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil)
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
t.Skip("skipping test; don't have permission to create user")
}
if errors.Is(err, windows.NERR_UserExists) {
// User already exists, delete and recreate.
if err = windows.NetUserDel(nil, name); err != nil {
t.Fatal(err)
}
if err = windows.NetUserAdd(nil, 1, (*byte)(unsafe.Pointer(&userInfo)), nil); err != nil {
t.Fatal(err)
}
} else if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err = windows.NetUserDel(nil, name); err != nil {
if !errors.Is(err, windows.NERR_UserNotFound) {
t.Fatal(err)
}
}
})
domain, err := syscall.UTF16PtrFromString(".")
if err != nil {
t.Fatal(err)
@@ -80,13 +130,13 @@ func windowsTestAccount(t *testing.T) (syscall.Token, *User) {
const LOGON32_PROVIDER_DEFAULT = 0
const LOGON32_LOGON_INTERACTIVE = 2
var token syscall.Token
if err = windows.LogonUser(name, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
if err = windows.LogonUser(name16, domain, pwd16, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
token.Close()
})
usr, err := Lookup(testUserName)
usr, err := Lookup(name)
if err != nil {
t.Fatal(err)
}

View File

@@ -191,7 +191,7 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
}
if c.bubble != nil && getg().bubble != c.bubble {
panic(plainError("send on synctest channel from outside bubble"))
fatal("send on synctest channel from outside bubble")
}
// Fast path: check for failed non-blocking operation without acquiring the lock.
@@ -318,7 +318,7 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
if c.bubble != nil && getg().bubble != c.bubble {
unlockf()
panic(plainError("send on synctest channel from outside bubble"))
fatal("send on synctest channel from outside bubble")
}
if raceenabled {
if c.dataqsiz == 0 {
@@ -416,7 +416,7 @@ func closechan(c *hchan) {
panic(plainError("close of nil channel"))
}
if c.bubble != nil && getg().bubble != c.bubble {
panic(plainError("close of synctest channel from outside bubble"))
fatal("close of synctest channel from outside bubble")
}
lock(&c.lock)
@@ -538,7 +538,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
}
if c.bubble != nil && getg().bubble != c.bubble {
panic(plainError("receive on synctest channel from outside bubble"))
fatal("receive on synctest channel from outside bubble")
}
if c.timer != nil {
@@ -702,7 +702,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
if c.bubble != nil && getg().bubble != c.bubble {
unlockf()
panic(plainError("receive on synctest channel from outside bubble"))
fatal("receive on synctest channel from outside bubble")
}
if c.dataqsiz == 0 {
if raceenabled {

View File

@@ -0,0 +1,72 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"os"
"regexp"
"runtime"
"testing"
)
func validateMapLabels(t *testing.T, labels []string) {
// These are the specific region labels that need get added during the
// runtime phase. Hence they are the ones that need to be confirmed as
// present at the time the test reads its own region labels, which
// is sufficient to validate that the default `decoratemappings` value
// (enabled) was set early enough in the init process.
regions := map[string]bool{
"allspans array": false,
"gc bits": false,
"heap": false,
"heap index": false,
"heap reservation": false,
"immortal metadata": false,
"page alloc": false,
"page alloc index": false,
"page summary": false,
"scavenge index": false,
}
for _, label := range labels {
if _, ok := regions[label]; !ok {
t.Logf("unexpected region label found: \"%s\"", label)
}
regions[label] = true
}
for label, found := range regions {
if !found {
t.Logf("region label missing: \"%s\"", label)
}
}
}
func TestDecorateMappings(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("decoratemappings is only supported on Linux")
// /proc/self/maps is also Linux-specific
}
var labels []string
if rawMaps, err := os.ReadFile("/proc/self/maps"); err != nil {
t.Fatalf("failed to read /proc/self/maps: %v", err)
} else {
t.Logf("maps:%s\n", string(rawMaps))
matches := regexp.MustCompile("[^[]+ \\[anon: Go: (.+)\\]\n").FindAllSubmatch(rawMaps, -1)
for _, match_pair := range matches {
// match_pair consists of the matching substring and the parenthesized group
labels = append(labels, string(match_pair[1]))
}
}
t.Logf("DebugDecorateMappings: %v", *runtime.DebugDecorateMappings)
if *runtime.DebugDecorateMappings != 0 && runtime.SetVMANameSupported() {
validateMapLabels(t, labels)
} else {
if len(labels) > 0 {
t.Errorf("unexpected mapping labels present: %v", labels)
} else {
t.Skip("mapping labels absent as expected")
}
}
}

View File

@@ -1927,3 +1927,7 @@ func (t *TraceStackTable) Reset() {
func TraceStack(gp *G, tab *TraceStackTable) {
traceStack(0, gp, (*traceStackTable)(tab))
}
var DebugDecorateMappings = &debug.decoratemappings
func SetVMANameSupported() bool { return setVMANameSupported() }

View File

@@ -282,6 +282,11 @@ Below is the full list of supported metrics, ordered lexicographically.
The number of non-default behaviors executed by the net/http
package due to a non-default GODEBUG=http2server=... setting.
/godebug/non-default-behavior/httpcookiemaxnum:events
The number of non-default behaviors executed by the net/http
package due to a non-default GODEBUG=httpcookiemaxnum=...
setting.
/godebug/non-default-behavior/httplaxcontentlength:events
The number of non-default behaviors executed by the net/http
package due to a non-default GODEBUG=httplaxcontentlength=...

View File

@@ -789,7 +789,9 @@ func cpuinit(env string) {
// getGodebugEarly extracts the environment variable GODEBUG from the environment on
// Unix-like operating systems and returns it. This function exists to extract GODEBUG
// early before much of the runtime is initialized.
func getGodebugEarly() string {
//
// Returns nil, false if OS doesn't provide env vars early in the init sequence.
func getGodebugEarly() (string, bool) {
const prefix = "GODEBUG="
var env string
switch GOOS {
@@ -807,12 +809,16 @@ func getGodebugEarly() string {
s := unsafe.String(p, findnull(p))
if stringslite.HasPrefix(s, prefix) {
env = gostring(p)[len(prefix):]
env = gostringnocopy(p)[len(prefix):]
break
}
}
break
default:
return "", false
}
return env
return env, true
}
// The bootstrap sequence is:
@@ -859,11 +865,14 @@ func schedinit() {
// The world starts stopped.
worldStopped()
godebug, parsedGodebug := getGodebugEarly()
if parsedGodebug {
parseRuntimeDebugVars(godebug)
}
ticks.init() // run as early as possible
moduledataverify()
stackinit()
mallocinit()
godebug := getGodebugEarly()
cpuinit(godebug) // must run before alginit
randinit() // must run before alginit, mcommoninit
alginit() // maps, hash, rand must not be used before this call
@@ -880,7 +889,12 @@ func schedinit() {
goenvs()
secure()
checkfds()
parsedebugvars()
if !parsedGodebug {
// Some platforms, e.g., Windows, didn't make env vars available "early",
// so try again now.
parseRuntimeDebugVars(gogetenv("GODEBUG"))
}
finishDebugVarsSetup()
gcinit()
// Allocate stack space that can be used when crashing due to bad stack

View File

@@ -402,7 +402,7 @@ var dbgvars = []*dbgVar{
{name: "updatemaxprocs", value: &debug.updatemaxprocs, def: 1},
}
func parsedebugvars() {
func parseRuntimeDebugVars(godebug string) {
// defaults
debug.cgocheck = 1
debug.invalidptr = 1
@@ -420,12 +420,6 @@ func parsedebugvars() {
}
debug.traceadvanceperiod = defaultTraceAdvancePeriod
godebug := gogetenv("GODEBUG")
p := new(string)
*p = godebug
godebugEnv.Store(p)
// apply runtime defaults, if any
for _, v := range dbgvars {
if v.def != 0 {
@@ -437,7 +431,6 @@ func parsedebugvars() {
}
}
}
// apply compile-time GODEBUG settings
parsegodebug(godebugDefault, nil)
@@ -463,6 +456,12 @@ func parsedebugvars() {
if debug.gccheckmark > 0 {
debug.asyncpreemptoff = 1
}
}
func finishDebugVarsSetup() {
p := new(string)
*p = gogetenv("GODEBUG")
godebugEnv.Store(p)
setTraceback(gogetenv("GOTRACEBACK"))
traceback_env = traceback_cache

View File

@@ -178,7 +178,7 @@ func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, blo
if cas.c.bubble != nil {
if getg().bubble != cas.c.bubble {
panic(plainError("select on synctest channel from outside bubble"))
fatal("select on synctest channel from outside bubble")
}
} else {
allSynctest = false

View File

@@ -14,9 +14,13 @@ import (
var prSetVMAUnsupported atomic.Bool
func setVMANameSupported() bool {
return !prSetVMAUnsupported.Load()
}
// setVMAName calls prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, start, len, name)
func setVMAName(start unsafe.Pointer, length uintptr, name string) {
if debug.decoratemappings == 0 || prSetVMAUnsupported.Load() {
if debug.decoratemappings == 0 || !setVMANameSupported() {
return
}

View File

@@ -10,3 +10,5 @@ import "unsafe"
// setVMAName isnt implemented
func setVMAName(start unsafe.Pointer, len uintptr, name string) {}
func setVMANameSupported() bool { return false }

View File

@@ -410,7 +410,9 @@ func getOrSetBubbleSpecial(p unsafe.Pointer, bubbleid uint64, add bool) (assoc i
} else if add {
// p is not associated with a bubble,
// and we've been asked to add an association.
lock(&mheap_.speciallock)
s := (*specialBubble)(mheap_.specialBubbleAlloc.alloc())
unlock(&mheap_.speciallock)
s.bubbleid = bubbleid
s.special.kind = _KindSpecialBubble
s.special.offset = offset

View File

@@ -415,7 +415,7 @@ func newTimer(when, period int64, f func(arg any, seq uintptr, delay int64), arg
//go:linkname stopTimer time.stopTimer
func stopTimer(t *timeTimer) bool {
if t.isFake && getg().bubble == nil {
panic("stop of synctest timer from outside bubble")
fatal("stop of synctest timer from outside bubble")
}
return t.stop()
}
@@ -430,7 +430,7 @@ func resetTimer(t *timeTimer, when, period int64) bool {
racerelease(unsafe.Pointer(&t.timer))
}
if t.isFake && getg().bubble == nil {
panic("reset of synctest timer from outside bubble")
fatal("reset of synctest timer from outside bubble")
}
return t.reset(when, period)
}

View File

@@ -232,7 +232,7 @@ func (x *Uintptr) Add(delta uintptr) (new uintptr) { return AddUintptr(&x.v, del
func (x *Uintptr) And(mask uintptr) (old uintptr) { return AndUintptr(&x.v, mask) }
// Or atomically performs a bitwise OR operation on x using the bitmask
// provided as mask and returns the updated value after the OR operation.
// provided as mask and returns the old value.
func (x *Uintptr) Or(mask uintptr) (old uintptr) { return OrUintptr(&x.v, mask) }
// noCopy may be added to structs which must not be copied

View File

@@ -0,0 +1,77 @@
// run
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
func fff(a []int, b bool, p, q *int) {
outer:
n := a[0]
a = a[1:]
switch n {
case 1:
goto one
case 2:
goto two
case 3:
goto three
case 4:
goto four
}
one:
goto inner
two:
goto outer
three:
goto inner
four:
goto innerSideEntry
inner:
n = a[0]
a = a[1:]
switch n {
case 1:
goto outer
case 2:
goto inner
case 3:
goto innerSideEntry
default:
return
}
innerSideEntry:
n = a[0]
a = a[1:]
switch n {
case 1:
goto outer
case 2:
goto inner
case 3:
goto inner
}
ggg(p, q)
goto inner
}
var b bool
func ggg(p, q *int) {
n := *p + 5 // this +5 ends up in the entry block, well before the *p load
if b {
*q = 0
}
*p = n
}
func main() {
var x, y int
fff([]int{4, 4, 4}, false, &x, &y)
if x != 5 {
panic(x)
}
}