aboutsummaryrefslogtreecommitdiff
path: root/src/database/sql/fakedb_test.go
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@golang.org>2023-05-23 15:12:47 -0700
committerGopher Robot <gobot@golang.org>2023-05-24 04:00:19 +0000
commit298fe517a9333c05143a8a8e1f9d5499f0c6e59b (patch)
treef485c44300f3f0f9957d8b36190bdf71dd23d877 /src/database/sql/fakedb_test.go
parentf0d575c2662229b6e4c51f2d0483b56542ae7661 (diff)
downloadgo-298fe517a9333c05143a8a8e1f9d5499f0c6e59b.tar.xz
database/sql: make RawBytes safely usable with contexts
sql.RawBytes was added the very first Go release, Go 1. Its docs say: > RawBytes is a byte slice that holds a reference to memory owned by > the database itself. After a Scan into a RawBytes, the slice is only > valid until the next call to Next, Scan, or Close. That "only valid until the next call" bit was true at the time, until contexts were added to database/sql in Go 1.8. In the past ~dozen releases it's been unsafe to use QueryContext with a context that might become Done to get an *sql.Rows that's scanning into a RawBytes. The Scan can succeed, but then while the caller's reading the memory, a database/sql-managed goroutine can see the context becoming done and call Close on the database/sql/driver and make the caller's view of the RawBytes memory no longer valid, introducing races, crashes, or database corruption. See #60304 and #53970 for details. This change does the minimal surgery on database/sql to make it safe again: Rows.Scan was already acquiring a mutex to check whether the rows had been closed, so this change make Rows.Scan notice whether *RawBytes was used and, if so, doesn't release the mutex on exit before returning. That mean it's still locked while the user code operates on the RawBytes memory and the concurrent context-watching goroutine to close the database still runs, but if it fires, it then gets blocked on the mutex until the next call to a Rows method (Next, NextResultSet, Err, Close). Updates #60304 Updates #53970 (earlier one I'd missed) Change-Id: Ie41c0c6f32c24887b2f53ec3686c2aab73a1bfff Reviewed-on: https://go-review.googlesource.com/c/go/+/497675 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@google.com> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> Auto-Submit: Ian Lance Taylor <iant@google.com> Reviewed-by: Russ Cox <rsc@golang.org>
Diffstat (limited to 'src/database/sql/fakedb_test.go')
-rw-r--r--src/database/sql/fakedb_test.go13
1 files changed, 12 insertions, 1 deletions
diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go
index 2fe5ea42da..cfeb3b3437 100644
--- a/src/database/sql/fakedb_test.go
+++ b/src/database/sql/fakedb_test.go
@@ -15,6 +15,7 @@ import (
"strconv"
"strings"
"sync"
+ "sync/atomic"
"testing"
"time"
)
@@ -90,6 +91,8 @@ 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
@@ -697,6 +700,8 @@ 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":
@@ -800,6 +805,9 @@ 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
@@ -929,6 +937,7 @@ func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (
txStatus = "transaction"
}
cursor := &rowsCursor{
+ db: s.c.db,
parentMem: s.c,
posRow: -1,
rows: [][]*row{
@@ -1025,6 +1034,7 @@ func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (
}
cursor := &rowsCursor{
+ db: s.c.db,
parentMem: s.c,
posRow: -1,
rows: setMRows,
@@ -1067,6 +1077,7 @@ func (tx *fakeTx) Rollback() error {
}
type rowsCursor struct {
+ db *fakeDB
parentMem memToucher
cols [][]string
colType [][]string
@@ -1141,7 +1152,7 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
// messing up conversions or doing them differently.
dest[i] = v
- if bs, ok := v.([]byte); ok {
+ if bs, ok := v.([]byte); ok && !rc.db.useRawBytes.Load() {
if rc.bytesClone == nil {
rc.bytesClone = make(map[*byte][]byte)
}