diff --git a/0076-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch b/0076-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch new file mode 100644 index 0000000000000000000000000000000000000000..a58b12549be2bb782f39c67de0b3443abae6d469 --- /dev/null +++ b/0076-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch @@ -0,0 +1,248 @@ +From a7622f9a2d7fee0256cccf9744789ce1b99df2d4 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/fakedb_test.go, 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 | 36 ++++++++++++--------- + src/database/sql/sql.go | 11 ++++--- + src/database/sql/sql_test.go | 56 +++++++++++++++++++++++++++++++++ + 4 files changed, 83 insertions(+), 22 deletions(-) + +diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go +index b966ef9..de2a17d 100644 +--- a/src/database/sql/convert.go ++++ b/src/database/sql/convert.go +@@ -324,7 +324,6 @@ func convertAssignRows(dest, src interface{}, 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) {}, +@@ -340,7 +339,6 @@ func convertAssignRows(dest, src interface{}, 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 4b68f1c..4ad2740 100644 +--- a/src/database/sql/fakedb_test.go ++++ b/src/database/sql/fakedb_test.go +@@ -1062,10 +1062,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. +@@ -1079,9 +1078,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 nil + } +@@ -1112,6 +1121,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 +@@ -1119,20 +1130,15 @@ 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 { +- if rc.bytesClone == nil { +- rc.bytesClone = make(map[*byte][]byte) ++ // Clone []bytes and stash for later invalidation. ++ if bs != nil { ++ bs = append([]byte{}, bs...) + } +- clone, ok := rc.bytesClone[&bs[0]] +- if !ok { +- clone = make([]byte, len(bs)) +- copy(clone, bs) +- rc.bytesClone[&bs[0]] = clone +- } +- dest[i] = clone ++ 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 68fb392..f25819b 100644 +--- a/src/database/sql/sql.go ++++ b/src/database/sql/sql.go +@@ -3224,17 +3224,18 @@ func rowsColumnInfoSetupConnLocked(rowsi driver.Rows) []*ColumnType { + // that error will be wrapped in the returned error + func (rs *Rows) Scan(dest ...interface{}) error { + rs.closemu.RLock() ++ err := rs.scanLocked(dest...) ++ rs.closemu.RUnlock() ++ return err ++} + ++func (rs *Rows) scanLocked(dest ...interface{}) 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 ++ return rs.lasterrOrErrLocked(errRowsClosed) + } +- rs.closemu.RUnlock() + + if rs.lastcols == nil { + return errors.New("sql: Scan called without calling Next") +diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go +index f771dee..373aaec 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" +@@ -4255,6 +4256,61 @@ func TestRowsScanProperlyWrapsErrors(t *testing.T) { + } + } + ++type testScanner struct { ++ scanf func(src interface{}) error ++} ++ ++func (ts testScanner) Scan(src interface{}) 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 interface{}) ++ scanEnd := make(chan error) ++ scanner := &testScanner{ ++ scanf: func(src interface{}) 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) ++ } ++} ++ + // badConn implements a bad driver.Conn, for TestBadDriver. + // The Exec method panics. + type badConn struct{} +-- +2.33.0 + diff --git a/0077-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch b/0077-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch new file mode 100644 index 0000000000000000000000000000000000000000..f0b18a5a342cdecc3af494f429a23ec7338632b5 --- /dev/null +++ b/0077-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch @@ -0,0 +1,285 @@ +From f01f6d2940be357dde934bb31194551b5782569c 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_unix.go,src/os/exec/lp_windows.go + - Position offset +- affected file: src/os/exec/lp_windows_test.go + - Test function with the same name as dot_test.go +- affected file: os/exec/dot_test.go + - The lower version does not have this test case, so the new test case is retained. +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 #74803 + +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/+/691855 +Reviewed-by: Michael Knyszek +Signed-off-by: yangjiaqi +--- + src/internal/testenv/testenv.go | 19 +++++++ + src/os/exec/dot_test.go | 96 +++++++++++++++++++++++++++++++++ + src/os/exec/exec.go | 14 +++++ + src/os/exec/lp_plan9.go | 4 ++ + src/os/exec/lp_unix.go | 4 ++ + src/os/exec/lp_windows.go | 4 ++ + src/os/exec/lp_windows_test.go | 2 +- + 7 files changed, 142 insertions(+), 1 deletion(-) + create mode 100644 src/os/exec/dot_test.go + +diff --git a/src/internal/testenv/testenv.go b/src/internal/testenv/testenv.go +index c902b14..92eddb3 100644 +--- a/src/internal/testenv/testenv.go ++++ b/src/internal/testenv/testenv.go +@@ -13,6 +13,7 @@ package testenv + import ( + "errors" + "flag" ++ "fmt" + "internal/cfg" + "os" + "os/exec" +@@ -142,6 +143,24 @@ func MustHaveExec(t testing.TB) { + } + } + ++// 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 +new file mode 100644 +index 0000000..20b61ff +--- /dev/null ++++ b/src/os/exec/dot_test.go +@@ -0,0 +1,96 @@ ++// Copyright 2022 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 exec_test ++ ++import ( ++ "internal/testenv" ++ "os" ++ . "os/exec" ++ "path/filepath" ++ "runtime" ++ "testing" ++) ++ ++var pathVar string = func() string { ++ if runtime.GOOS == "plan9" { ++ return "path" ++ } ++ return "PATH" ++}() ++ ++func TestLookPath(t *testing.T) { ++ testenv.MustHaveExec(t) ++ ++ tmpDir := filepath.Join(t.TempDir(), "testdir") ++ if err := os.Mkdir(tmpDir, 0777); err != nil { ++ t.Fatal(err) ++ } ++ ++ executable := "execabs-test" ++ if runtime.GOOS == "windows" { ++ executable += ".exe" ++ } ++ if err := os.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0777); err != nil { ++ t.Fatal(err) ++ } ++ cwd, err := os.Getwd() ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer func() { ++ if err := os.Chdir(cwd); err != nil { ++ panic(err) ++ } ++ }() ++ if err = os.Chdir(tmpDir); err != nil { ++ t.Fatal(err) ++ } ++ t.Setenv("PWD", tmpDir) ++ t.Logf(". is %#q", tmpDir) ++ ++ 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 6f5c61b..c63794f 100644 +--- a/src/os/exec/exec.go ++++ b/src/os/exec/exec.go +@@ -345,6 +345,10 @@ func (c *Cmd) Run() error { + // It uses LookPath to try appropriate extensions. + // lookExtensions does not search PATH, instead it converts `prog` into `.\prog`. + func lookExtensions(path, dir string) (string, error) { ++ if err := validateLookPath(path); err != nil { ++ return "", &Error{path, err} ++ } ++ + if filepath.Base(path) == path { + path = filepath.Join(".", path) + } +@@ -793,6 +797,16 @@ func addCriticalEnv(env []string) []string { + return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT")) + } + ++// 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 ++} ++ + // environ returns a best-effort copy of the environment in which the command + // would be run as it is currently configured. If an error occurs in computing + // the environment, it is returned alongside the best-effort copy. +diff --git a/src/os/exec/lp_plan9.go b/src/os/exec/lp_plan9.go +index e8826a5..a4fb95d 100644 +--- a/src/os/exec/lp_plan9.go ++++ b/src/os/exec/lp_plan9.go +@@ -32,6 +32,10 @@ func findExecutable(file string) error { + // directly and the path is not consulted. + // The result may be an absolute path or a path relative to the current directory. + 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 d1d246a..1b27f2b 100644 +--- a/src/os/exec/lp_unix.go ++++ b/src/os/exec/lp_unix.go +@@ -38,6 +38,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 e7a2cdf..7a1d6fb 100644 +--- a/src/os/exec/lp_windows.go ++++ b/src/os/exec/lp_windows.go +@@ -58,6 +58,10 @@ func findExecutable(file string, exts []string) (string, error) { + // a suitable candidate. + // The result may be an absolute path or a path relative to the current directory. + 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 != "" { +diff --git a/src/os/exec/lp_windows_test.go b/src/os/exec/lp_windows_test.go +index bbf6a9b..7d84564 100644 +--- a/src/os/exec/lp_windows_test.go ++++ b/src/os/exec/lp_windows_test.go +@@ -305,7 +305,7 @@ var lookPathTests = []lookPathTest{ + }, + } + +-func TestLookPath(t *testing.T) { ++func TestLookPathWindows(t *testing.T) { + tmp := t.TempDir() + printpathExe := buildPrintPathExe(t, tmp) + +-- +2.33.0 + diff --git a/golang.spec b/golang.spec index 22ce28e261a4c28b69f4b7e2a49891355dd55f59..e4941b640c385a23438e86138dcaac5215e6ac30 100644 --- a/golang.spec +++ b/golang.spec @@ -63,7 +63,7 @@ Name: golang Version: 1.17.3 -Release: 40 +Release: 41 Summary: The Go Programming Language License: BSD and Public Domain URL: https://golang.org/ @@ -225,6 +225,8 @@ Patch6072: 0072-CVE-2024-45336-net-http-persist-header-stripping-acr.patch Patch6073: 0073-crypto-tls-fix-Config.Time-in-tests-using-expir.patch Patch6074: 0074-CVE-2025-22870-do-not-mismatch-IPv6-zone-ids-ag.patch Patch6075: 0075-CVE-2025-4673-net-http-strip-sensitive-proxy-headers.patch +Patch6076: 0076-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch +Patch6077: 0077-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch ExclusiveArch: %{golang_arches} @@ -463,6 +465,12 @@ fi %files devel -f go-tests.list -f go-misc.list -f go-src.list %changelog +* Wed Aug 20 2025 verbir - 1.17.3-41 +- 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.17.3-40 - Type:CVE - CVE:CVE-2025-4673