diff --git a/backport-0033-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch b/backport-0033-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch new file mode 100644 index 0000000000000000000000000000000000000000..34fb5187aad059c7acaee08f0b74e9b7329ea6a1 --- /dev/null +++ b/backport-0033-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch @@ -0,0 +1,328 @@ +From e80914f45fff764897dda5187af2ded7142406d6 Mon Sep 17 00:00:00 2001 +From: Damien Neil +Date: Wed, 23 Jul 2025 14:26:54 -0700 +Subject: [PATCH 1/2] database/sql: avoid closing Rows while scan is in + progress + +CVE: CVE-2025-47907 +Confict: as follows +- Affected file: src/database/sql/sql.go, src/database/sql/sql_test.go + - Patch adaptation due to context changes +Reference: https://go-review.googlesource.com/c/go/+/693616 + +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. + +Fixes #74831. + +Change-Id: Ice45b5fad51b679c38e3e1d21ef39156b56d6037 +Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2540 +Reviewed-by: Roland Shoemaker +Reviewed-by: Neal Patel +Reviewed-on: https://go-review.googlesource.com/c/go/+/693735 +Auto-Submit: Dmitri Shuralyov +Reviewed-by: Roland Shoemaker +LUCI-TryBot-Result: Go LUCI +Reviewed-by: Dmitri Shuralyov +Signed-off-by: yangjiaqi +--- + src/database/sql/convert.go | 2 -- + src/database/sql/fakedb_test.go | 47 ++++++++++++-------------- + src/database/sql/sql.go | 25 ++++++-------- + src/database/sql/sql_test.go | 60 ++++++++++++++++++++++++++++++--- + 4 files changed, 89 insertions(+), 45 deletions(-) + +diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go +index ffc4e49..6c1d7d4 100644 +--- a/src/database/sql/convert.go ++++ b/src/database/sql/convert.go +@@ -325,7 +325,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) {}, +@@ -341,7 +340,6 @@ func convertAssignRows(dest, src any, rows *Rows) error { + parentCancel() + } + } +- rows.closemu.Unlock() + return nil + } + } +diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go +index cfeb3b3..8f4efa7 100644 +--- a/src/database/sql/fakedb_test.go ++++ b/src/database/sql/fakedb_test.go +@@ -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 +@@ -700,8 +698,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": +@@ -805,9 +801,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 +@@ -1090,10 +1083,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. +@@ -1110,9 +1102,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 + } +@@ -1143,6 +1145,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 +@@ -1150,20 +1154,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 + } +diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go +index 836fe83..4496ee3 100644 +--- a/src/database/sql/sql.go ++++ b/src/database/sql/sql.go +@@ -3295,37 +3295,34 @@ func (rs *Rows) Scan(dest ...any) error { + // without calling Next. + return fmt.Errorf("sql: Scan called without calling Next (closemuScanHold)") + } ++ + rs.closemu.RLock() ++ err := rs.scanLocked(dest...) ++ if err == nil && scanArgsContainRawBytes(dest) { ++ rs.closemuScanHold = true ++ } else { ++ rs.closemu.RUnlock() ++ } ++ return err ++} + ++func (rs *Rows) scanLocked(dest ...any) error { + 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.closemuScanHold = true +- } else { +- rs.closemu.RUnlock() ++ 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) + } + } +diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go +index e6a5cd9..e66fc19 100644 +--- a/src/database/sql/sql_test.go ++++ b/src/database/sql/sql_test.go +@@ -5,6 +5,7 @@ + package sql + + import ( ++ "bytes" + "context" + "database/sql/driver" + "errors" +@@ -4398,10 +4399,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()) +@@ -4493,6 +4490,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) +-- +2.33.0 + diff --git a/backport-0034-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch b/backport-0034-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch new file mode 100644 index 0000000000000000000000000000000000000000..1091d005765d7974e5396a887bb6ca768ebaa566 --- /dev/null +++ b/backport-0034-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch @@ -0,0 +1,196 @@ +From 5e2f852313b0f0debfaf91b63fbb19aeb5791e85 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Olivier=20Mengu=C3=A9?= +Date: Mon, 30 Jun 2025 16:58:59 +0200 +Subject: [PATCH 2/2] os/exec: fix incorrect expansion of "", "." and ".." in + LookPath + +CVE: CVE-2025-47906 +Confict: as follows +- affected file: src/os/exec/lp_plan9.go,src/os/exec/lp_windows.go + - Position offset & Missing high version functions +- affected file: src/internal/testenv/exec.go + - Missing high version functions +Reference: https://go-review.googlesource.com/c/go/+/691875 + +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 #74804 + +Change-Id: Ie50cc0a660fce8fbdc952a7f2e05c36062dcb50e +Reviewed-on: https://go-review.googlesource.com/c/go/+/685755 +LUCI-TryBot-Result: Go LUCI +Auto-Submit: Damien Neil +Reviewed-by: Roland Shoemaker +Reviewed-by: Damien Neil +(cherry picked from commit e0b07dc22eaab1b003d98ad6d63cdfacc76c5c70) +Reviewed-on: https://go-review.googlesource.com/c/go/+/691875 +Reviewed-by: Michael Knyszek +Signed-off-by: yangjiaqi +--- + src/internal/testenv/exec.go | 18 +++++++++++++++ + src/os/exec/dot_test.go | 44 ++++++++++++++++++++++++++++++++++++ + src/os/exec/exec.go | 10 ++++++++ + src/os/exec/lp_plan9.go | 4 ++++ + src/os/exec/lp_unix.go | 4 ++++ + src/os/exec/lp_windows.go | 3 +++ + 6 files changed, 83 insertions(+) + +diff --git a/src/internal/testenv/exec.go b/src/internal/testenv/exec.go +index 50d3b0d..5015a4e 100644 +--- a/src/internal/testenv/exec.go ++++ b/src/internal/testenv/exec.go +@@ -79,6 +79,24 @@ func tryExec() error { + return cmd.Run() + } + ++// Executable is a wrapper around [MustHaveExec] and [os.Executable]. ++// It returns the path name for the executable that started the current process, ++// or skips the test if the current system can't start new processes, ++// or fails the test if the path can not be obtained. ++func Executable(t testing.TB) string { ++ MustHaveExec(t) ++ ++ exe, err := os.Executable() ++ if err != nil { ++ msg := fmt.Sprintf("os.Executable error: %v", err) ++ if t == nil { ++ panic(msg) ++ } ++ t.Fatal(msg) ++ } ++ return exe ++} ++ + var execPaths sync.Map // path -> error + + // MustHaveExecPath checks that the current system can start the named executable +diff --git a/src/os/exec/dot_test.go b/src/os/exec/dot_test.go +index 66c92f7..a6ba1a0 100644 +--- a/src/os/exec/dot_test.go ++++ b/src/os/exec/dot_test.go +@@ -189,4 +189,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("..")) ++ }) + } +diff --git a/src/os/exec/exec.go b/src/os/exec/exec.go +index 138be29..1fce9be 100644 +--- a/src/os/exec/exec.go ++++ b/src/os/exec/exec.go +@@ -1301,3 +1301,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 ++} +diff --git a/src/os/exec/lp_plan9.go b/src/os/exec/lp_plan9.go +index 9344b14..ba0b228 100644 +--- a/src/os/exec/lp_plan9.go ++++ b/src/os/exec/lp_plan9.go +@@ -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(file); err != nil { ++ return "", &Error{file, err} ++ } ++ + // skip the path lookup for these prefixes + skip := []string{"/", "#", "./", "../"} + +diff --git a/src/os/exec/lp_unix.go b/src/os/exec/lp_unix.go +index fd2c6ef..3e5877f 100644 +--- a/src/os/exec/lp_unix.go ++++ b/src/os/exec/lp_unix.go +@@ -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 { +diff --git a/src/os/exec/lp_windows.go b/src/os/exec/lp_windows.go +index 066d38d..27ce790 100644 +--- a/src/os/exec/lp_windows.go ++++ b/src/os/exec/lp_windows.go +@@ -63,6 +63,9 @@ 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} ++ } + var exts []string + x := os.Getenv(`PATHEXT`) + if x != "" { +-- +2.33.0 + diff --git a/golang.spec b/golang.spec index fc8388bd7b6feb4e3ae9e7acdb45fd35b704105a..1169f2f8b4cc2b43724ab2d5bd4d9e3f9e35a41b 100644 --- a/golang.spec +++ b/golang.spec @@ -66,7 +66,7 @@ Name: golang Version: 1.21.4 -Release: 33 +Release: 34 Summary: The Go Programming Language License: BSD and Public Domain URL: https://golang.org/ @@ -152,6 +152,8 @@ Patch6029: backport-0029-CVE-2024-45341-crypto-x509-properly-check-for-IPv6-h.pa Patch6030: backport-0030-CVE-2024-45336-net-http-persist-header-stripping-acr.patch Patch6031: backport-0031-CVE-2025-22870.patch Patch6032: backport-0032-CVE-2025-4673-net-http-strip-sensitive-proxy-headers.patch +Patch6033: backport-0033-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch +Patch6034: backport-0034-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch ExclusiveArch: %{golang_arches} @@ -391,6 +393,12 @@ fi %files devel -f go-tests.list -f go-misc.list -f go-src.list %changelog +* Wed Aug 20 2025 verbir - 1.21.4-34 +- Type:CVE +- CVE:CVE-2025-47907 CVE-2025-47906 +- SUG:NA +- DESC:fix CVE-2025-47907 CVE-2025-47906 + * Mon Jun 23 2025 wujichao - 1.21.4-33 - Type:CVE - CVE:CVE-2025-4673