mirror of
https://git.yoctoproject.org/poky
synced 2026-03-09 16:59:40 +01:00
Cancelling a query (e.g. by cancelling the context passed to one of
the query methods) during a call to the Scan method of the returned
Rows can result in unexpected results if other queries are being made
in parallel. This can result in a race condition that may overwrite
the expected results with those of another query, causing the call to
Scan to return either unexpected results from the other query or an
error.
Made below changes for Go 1.17 backport:
- Replaced `atomic.Pointer[error]` with `atomic.Value`, since
atomic pointers are not supported in Go 1.17.
- Used errp.(*error) to retrieve and dereference
the stored *error, Without this, build fails with:
invalid indirect of errp (type interface{}).
- Replaced Go 1.18 `any` keyword with `interface{}` for backward
compatibility with Go 1.17.
Reference:
https://nvd.nist.gov/vuln/detail/CVE-2025-47907
Upstream-patch:
8a924caaf3
298fe517a9
c23579f031
(From OE-Core rev: af9c43c39764ce9ce37785c44dfb83e25cb24703)
Signed-off-by: Praveen Kumar <praveen.kumar@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
328 lines
9.5 KiB
Diff
328 lines
9.5 KiB
Diff
From 8a924caaf348fdc366bab906424616b2974ad4e9 Mon Sep 17 00:00:00 2001
|
|
From: Damien Neil <dneil@google.com>
|
|
Date: Wed, 23 Jul 2025 14:26:54 -0700
|
|
Subject: [PATCH] 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 #74832
|
|
|
|
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/+/2601
|
|
Reviewed-on: https://go-review.googlesource.com/c/go/+/693558
|
|
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
|
|
Reviewed-by: Mark Freeman <markfreeman@google.com>
|
|
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
|
|
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
|
|
|
|
CVE: CVE-2025-47907
|
|
|
|
Upstream-Status: Backport [https://github.com/golang/go/commit/8a924caaf348fdc366bab906424616b2974ad4e9]
|
|
|
|
Signed-off-by: Praveen Kumar <praveen.kumar@windriver.com>
|
|
---
|
|
src/database/sql/convert.go | 2 --
|
|
src/database/sql/fakedb_test.go | 47 ++++++++++++--------------
|
|
src/database/sql/sql.go | 26 +++++++-------
|
|
src/database/sql/sql_test.go | 60 ++++++++++++++++++++++++++++++---
|
|
4 files changed, 90 insertions(+), 45 deletions(-)
|
|
|
|
diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go
|
|
index 3a581f6..5b0c6f0 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 33c57b9..9f3d517 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
|
|
@@ -683,8 +681,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":
|
|
@@ -788,9 +784,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
|
|
@@ -1073,10 +1066,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.
|
|
@@ -1090,9 +1082,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
|
|
}
|
|
@@ -1123,6 +1125,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
|
|
@@ -1130,20 +1134,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 e25447c..a428e29 100644
|
|
--- a/src/database/sql/sql.go
|
|
+++ b/src/database/sql/sql.go
|
|
@@ -3294,38 +3294,36 @@ func (rs *Rows) Scan(dest ...interface{}) error {
|
|
// without calling Next.
|
|
return fmt.Errorf("sql: Scan called without calling Next (closemuScanHold)")
|
|
}
|
|
+
|
|
rs.closemu.RLock()
|
|
+ rs.raw = rs.raw[:0]
|
|
+ err := rs.scanLocked(dest...)
|
|
+ if err == nil && scanArgsContainRawBytes(dest) {
|
|
+ rs.closemuScanHold = true
|
|
+ } else {
|
|
+ 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
|
|
- }
|
|
-
|
|
- if scanArgsContainRawBytes(dest) {
|
|
- rs.closemuScanHold = true
|
|
- rs.raw = rs.raw[:0]
|
|
- } 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 6aa9bf0..6aec7ec 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"
|
|
@@ -4321,10 +4322,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.
|
|
@@ -4469,6 +4466,61 @@ func TestTypedString(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 BenchmarkConcurrentDBExec(b *testing.B) {
|
|
b.ReportAllocs()
|
|
ct := new(concurrentDBExecTest)
|