diff --git a/0130-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch b/0130-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch new file mode 100644 index 0000000000000000000000000000000000000000..8d2071247c0f8150b795823e45faa330f0a31e7e --- /dev/null +++ b/0130-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch @@ -0,0 +1,250 @@ +From ed29cee49dd30aabcbba9812e82eaa9e6077550f 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. + +Updates #74831 +Fixes #74832 + +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-internal-review.googlesource.com/c/go/+/2601 +Reviewed-on: https://go-review.googlesource.com/c/go/+/693558 +TryBot-Bypass: Dmitri Shuralyov +Reviewed-by: Mark Freeman +Reviewed-by: Dmitri Shuralyov +Auto-Submit: Dmitri Shuralyov +Signed-off-by: vegbir +--- + 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 b966ef970c..de2a17d518 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 7605a2a6d2..e925b99484 100644 +--- a/src/database/sql/fakedb_test.go ++++ b/src/database/sql/fakedb_test.go +@@ -1053,10 +1053,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. +@@ -1070,9 +1069,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 + } +@@ -1103,6 +1112,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 +@@ -1110,20 +1121,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 3d1367d28f..87e890cc8b 100644 +--- a/src/database/sql/sql.go ++++ b/src/database/sql/sql.go +@@ -3112,17 +3112,18 @@ func rowsColumnInfoSetupConnLocked(rowsi driver.Rows) []*ColumnType { + // select query will close any cursor *Rows if the parent *Rows is closed. + 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 d7d4642608..51880de607 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" +@@ -4197,6 +4198,61 @@ func (bc badConn) Exec(query string, args []driver.Value) (driver.Result, error) + panic("badConn.Exec") + } + ++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) ++ } ++} ++ + // badDriver is a driver.Driver that uses badConn. + type badDriver struct{} + +-- +2.33.0 + diff --git a/0131-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch b/0131-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch new file mode 100644 index 0000000000000000000000000000000000000000..102f0170a15513d1d07bc57705de3790414d57e7 --- /dev/null +++ b/0131-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch @@ -0,0 +1,281 @@ +From 66cecb12bcd79d86ac2de37d30a4b2f4d98ea005 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_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 #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: vegbir +--- + src/internal/testenv/testenv.go | 19 +++++++ + src/os/exec/dot_test.go | 96 +++++++++++++++++++++++++++++++++ + src/os/exec/exec.go | 13 +++++ + 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, 141 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 309b2702ed..fa8fbbef92 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" +@@ -152,6 +153,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 0000000000..20b61ff9c7 +--- /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 6f5c61b2f6..76a4e7507e 100644 +--- a/src/os/exec/exec.go ++++ b/src/os/exec/exec.go +@@ -345,6 +345,9 @@ 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) + } +@@ -815,3 +818,13 @@ func (c *Cmd) Environ() []string { + env, _ := c.environ() + return env + } ++ ++// 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 5860cbca4d..a7bf5ad341 100644 +--- a/src/os/exec/lp_plan9.go ++++ b/src/os/exec/lp_plan9.go +@@ -31,6 +31,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 93793e0eee..a8f99aaada 100644 +--- a/src/os/exec/lp_unix.go ++++ b/src/os/exec/lp_unix.go +@@ -36,6 +36,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 9ea3d76575..68c97d72da 100644 +--- a/src/os/exec/lp_windows.go ++++ b/src/os/exec/lp_windows.go +@@ -57,6 +57,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 59b5f1c2c7..3dd32a848f 100644 +--- a/src/os/exec/lp_windows_test.go ++++ b/src/os/exec/lp_windows_test.go +@@ -306,7 +306,7 @@ var lookPathTests = []lookPathTest{ + }, + } + +-func TestLookPath(t *testing.T) { ++func TestLookPathWindows(t *testing.T) { + tmp, err := ioutil.TempDir("", "TestLookPath") + if err != nil { + t.Fatal("TempDir failed: ", err) +-- +2.33.0 + diff --git a/golang.spec b/golang.spec index 76005af43f2aa92bbb1d22b96b27b6c6c78b63b7..17269333e8bd0aa1a2ded205c2e471f190f7bd96 100644 --- a/golang.spec +++ b/golang.spec @@ -58,7 +58,7 @@ Name: golang Version: 1.15.7 -Release: 52 +Release: 53 Summary: The Go Programming Language License: BSD and Public Domain URL: https://golang.org/ @@ -271,6 +271,8 @@ Patch6126: 0126-CVE-2024-45341-crypto-x509-properly-check-for-IPv6-h.patch Patch6127: 0127-CVE-2024-45336-net-http-persist-header-stripping-acr.patch Patch6128: 0128-CVE-2025-22870-do-not-mismatch-IPv6-zone-ids-ag.patch Patch6129: 0129-CVE-2025-4673-net-http-strip-sensitive-proxy-headers.patch +Patch6130: 0130-CVE-2025-47907-database-sql-avoid-closing-Rows-while-scan-is-in-pro.patch +Patch6131: 0131-CVE-2025-47906-os-exec-fix-incorrect-expansion-of-.-and-.-in-LookPa.patch Patch9002: 0002-fix-patch-cmd-go-internal-modfetch-do-not-sho.patch @@ -509,6 +511,12 @@ fi %files devel -f go-tests.list -f go-misc.list -f go-src.list %changelog +* Tue Aug 19 2025 verbir - 1.15.7-53 +- 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.15.7-52 - Type:CVE - CVE:CVE-2025-4673