summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPraveen Kumar <praveen.kumar@windriver.com>2025-08-26 09:27:43 +0530
committerSteve Sakoman <steve@sakoman.com>2025-09-01 08:30:56 -0700
commit766dfe511580a9e69f9ddca465b859404abdb060 (patch)
treeb145bd8d4ec1ff5b0e97b59bfd318e92686232b8
parentf584b357c3b4bd8cc48668caece670c1b929c8ce (diff)
downloadpoky-766dfe511580a9e69f9ddca465b859404abdb060.tar.gz
go: fix CVE-2025-47907
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. Reference: https://nvd.nist.gov/vuln/detail/CVE-2025-47907 Upstream-patch: https://github.com/golang/go/commit/8a924caaf348fdc366bab906424616b2974ad4e9 (From OE-Core rev: 22d8ac9884208b8f9b2a69ec6a257c62e1f2f8d2) Signed-off-by: Praveen Kumar <praveen.kumar@windriver.com> Signed-off-by: Steve Sakoman <steve@sakoman.com>
-rw-r--r--meta/recipes-devtools/go/go-1.22.12.inc2
-rw-r--r--meta/recipes-devtools/go/go/CVE-2025-47907-pre.patch233
-rw-r--r--meta/recipes-devtools/go/go/CVE-2025-47907.patch328
3 files changed, 563 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.22.12.inc b/meta/recipes-devtools/go/go-1.22.12.inc
index 12d7539017..d0ce333117 100644
--- a/meta/recipes-devtools/go/go-1.22.12.inc
+++ b/meta/recipes-devtools/go/go-1.22.12.inc
@@ -18,6 +18,8 @@ SRC_URI += "\
18 file://CVE-2025-22871.patch \ 18 file://CVE-2025-22871.patch \
19 file://CVE-2025-4673.patch \ 19 file://CVE-2025-4673.patch \
20 file://CVE-2025-4674.patch \ 20 file://CVE-2025-4674.patch \
21 file://CVE-2025-47907-pre.patch \
22 file://CVE-2025-47907.patch \
21" 23"
22SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71" 24SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71"
23 25
diff --git a/meta/recipes-devtools/go/go/CVE-2025-47907-pre.patch b/meta/recipes-devtools/go/go/CVE-2025-47907-pre.patch
new file mode 100644
index 0000000000..dafa878e73
--- /dev/null
+++ b/meta/recipes-devtools/go/go/CVE-2025-47907-pre.patch
@@ -0,0 +1,233 @@
1From c23579f031ecd09bf37c644723b33736dffa8b92 Mon Sep 17 00:00:00 2001
2From: Damien Neil <dneil@google.com>
3Date: Tue, 23 Jan 2024 15:59:47 -0800
4Subject: [PATCH 1/2] database/sql: avoid clobbering driver-owned memory in
5 RawBytes
6
7Depending on the query, a RawBytes can contain memory owned by the
8driver or by database/sql:
9
10If the driver provides the column as a []byte,
11RawBytes aliases that []byte.
12
13If the driver provides the column as any other type,
14RawBytes contains memory allocated by database/sql.
15Prior to this CL, Rows.Scan will reuse existing capacity in a
16RawBytes to permit a single allocation to be reused across rows.
17
18When a RawBytes is reused across queries, this can result
19in database/sql writing to driver-owned memory.
20
21Add a buffer to Rows to store RawBytes data, and reuse this
22buffer across calls to Rows.Scan.
23
24Fixes #65201
25
26Change-Id: Iac640174c7afa97eeb39496f47dec202501b2483
27Reviewed-on: https://go-review.googlesource.com/c/go/+/557917
28Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
29Reviewed-by: Roland Shoemaker <roland@golang.org>
30LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
31
32CVE: CVE-2025-47907
33
34Upstream-Status: Backport [https://github.com/golang/go/commit/c23579f031ecd09bf37c644723b33736dffa8b92]
35
36Signed-off-by: Praveen Kumar <praveen.kumar@windriver.com>
37---
38 src/database/sql/convert.go | 8 +++---
39 src/database/sql/convert_test.go | 12 ++++++--
40 src/database/sql/sql.go | 34 +++++++++++++++++++++++
41 src/database/sql/sql_test.go | 47 ++++++++++++++++++++++++++++++++
42 4 files changed, 94 insertions(+), 7 deletions(-)
43
44diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go
45index cca5d15..999b8f1 100644
46--- a/src/database/sql/convert.go
47+++ b/src/database/sql/convert.go
48@@ -237,7 +237,7 @@ func convertAssignRows(dest, src any, rows *Rows) error {
49 if d == nil {
50 return errNilPtr
51 }
52- *d = append((*d)[:0], s...)
53+ *d = rows.setrawbuf(append(rows.rawbuf(), s...))
54 return nil
55 }
56 case []byte:
57@@ -285,7 +285,7 @@ func convertAssignRows(dest, src any, rows *Rows) error {
58 if d == nil {
59 return errNilPtr
60 }
61- *d = s.AppendFormat((*d)[:0], time.RFC3339Nano)
62+ *d = rows.setrawbuf(s.AppendFormat(rows.rawbuf(), time.RFC3339Nano))
63 return nil
64 }
65 case decimalDecompose:
66@@ -366,8 +366,8 @@ func convertAssignRows(dest, src any, rows *Rows) error {
67 }
68 case *RawBytes:
69 sv = reflect.ValueOf(src)
70- if b, ok := asBytes([]byte(*d)[:0], sv); ok {
71- *d = RawBytes(b)
72+ if b, ok := asBytes(rows.rawbuf(), sv); ok {
73+ *d = rows.setrawbuf(b)
74 return nil
75 }
76 case *bool:
77diff --git a/src/database/sql/convert_test.go b/src/database/sql/convert_test.go
78index 6d09fa1..f94db8e 100644
79--- a/src/database/sql/convert_test.go
80+++ b/src/database/sql/convert_test.go
81@@ -354,9 +354,10 @@ func TestRawBytesAllocs(t *testing.T) {
82 {"time", time.Unix(2, 5).UTC(), "1970-01-01T00:00:02.000000005Z"},
83 }
84
85- buf := make(RawBytes, 10)
86+ var buf RawBytes
87+ rows := &Rows{}
88 test := func(name string, in any, want string) {
89- if err := convertAssign(&buf, in); err != nil {
90+ if err := convertAssignRows(&buf, in, rows); err != nil {
91 t.Fatalf("%s: convertAssign = %v", name, err)
92 }
93 match := len(buf) == len(want)
94@@ -375,6 +376,7 @@ func TestRawBytesAllocs(t *testing.T) {
95
96 n := testing.AllocsPerRun(100, func() {
97 for _, tt := range tests {
98+ rows.raw = rows.raw[:0]
99 test(tt.name, tt.in, tt.want)
100 }
101 })
102@@ -383,7 +385,11 @@ func TestRawBytesAllocs(t *testing.T) {
103 // and gc. With 32-bit words there are more convT2E allocs, and
104 // with gccgo, only pointers currently go in interface data.
105 // So only care on amd64 gc for now.
106- measureAllocs := runtime.GOARCH == "amd64" && runtime.Compiler == "gc"
107+ measureAllocs := false
108+ switch runtime.GOARCH {
109+ case "amd64", "arm64":
110+ measureAllocs = runtime.Compiler == "gc"
111+ }
112
113 if n > 0.5 && measureAllocs {
114 t.Fatalf("allocs = %v; want 0", n)
115diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go
116index 4f1197d..da0f52c 100644
117--- a/src/database/sql/sql.go
118+++ b/src/database/sql/sql.go
119@@ -2936,6 +2936,13 @@ type Rows struct {
120 // not to be called concurrently.
121 lastcols []driver.Value
122
123+ // raw is a buffer for RawBytes that persists between Scan calls.
124+ // This is used when the driver returns a mismatched type that requires
125+ // a cloning allocation. For example, if the driver returns a *string and
126+ // the user is scanning into a *RawBytes, we need to copy the string.
127+ // The raw buffer here lets us reuse the memory for that copy across Scan calls.
128+ raw []byte
129+
130 // closemuScanHold is whether the previous call to Scan kept closemu RLock'ed
131 // without unlocking it. It does that when the user passes a *RawBytes scan
132 // target. In that case, we need to prevent awaitDone from closing the Rows
133@@ -3130,6 +3137,32 @@ func (rs *Rows) Err() error {
134 return rs.lasterrOrErrLocked(nil)
135 }
136
137+// rawbuf returns the buffer to append RawBytes values to.
138+// This buffer is reused across calls to Rows.Scan.
139+//
140+// Usage:
141+//
142+// rawBytes = rows.setrawbuf(append(rows.rawbuf(), value...))
143+func (rs *Rows) rawbuf() []byte {
144+ if rs == nil {
145+ // convertAssignRows can take a nil *Rows; for simplicity handle it here
146+ return nil
147+ }
148+ return rs.raw
149+}
150+
151+// setrawbuf updates the RawBytes buffer with the result of appending a new value to it.
152+// It returns the new value.
153+func (rs *Rows) setrawbuf(b []byte) RawBytes {
154+ if rs == nil {
155+ // convertAssignRows can take a nil *Rows; for simplicity handle it here
156+ return RawBytes(b)
157+ }
158+ off := len(rs.raw)
159+ rs.raw = b
160+ return RawBytes(rs.raw[off:])
161+}
162+
163 var errRowsClosed = errors.New("sql: Rows are closed")
164 var errNoRows = errors.New("sql: no Rows available")
165
166@@ -3337,6 +3370,7 @@ func (rs *Rows) Scan(dest ...any) error {
167
168 if scanArgsContainRawBytes(dest) {
169 rs.closemuScanHold = true
170+ rs.raw = rs.raw[:0]
171 } else {
172 rs.closemu.RUnlock()
173 }
174diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go
175index c38a348..c3b4822 100644
176--- a/src/database/sql/sql_test.go
177+++ b/src/database/sql/sql_test.go
178@@ -4531,6 +4531,53 @@ func TestNilErrorAfterClose(t *testing.T) {
179 }
180 }
181
182+// Issue #65201.
183+//
184+// If a RawBytes is reused across multiple queries,
185+// subsequent queries shouldn't overwrite driver-owned memory from previous queries.
186+func TestRawBytesReuse(t *testing.T) {
187+ db := newTestDB(t, "people")
188+ defer closeDB(t, db)
189+
190+ if _, err := db.Exec("USE_RAWBYTES"); err != nil {
191+ t.Fatal(err)
192+ }
193+
194+ var raw RawBytes
195+
196+ // The RawBytes in this query aliases driver-owned memory.
197+ rows, err := db.Query("SELECT|people|name|")
198+ if err != nil {
199+ t.Fatal(err)
200+ }
201+ rows.Next()
202+ rows.Scan(&raw) // now raw is pointing to driver-owned memory
203+ name1 := string(raw)
204+ rows.Close()
205+
206+ // The RawBytes in this query does not alias driver-owned memory.
207+ rows, err = db.Query("SELECT|people|age|")
208+ if err != nil {
209+ t.Fatal(err)
210+ }
211+ rows.Next()
212+ rows.Scan(&raw) // this must not write to the driver-owned memory in raw
213+ rows.Close()
214+
215+ // Repeat the first query. Nothing should have changed.
216+ rows, err = db.Query("SELECT|people|name|")
217+ if err != nil {
218+ t.Fatal(err)
219+ }
220+ rows.Next()
221+ rows.Scan(&raw) // raw points to driver-owned memory again
222+ name2 := string(raw)
223+ rows.Close()
224+ if name1 != name2 {
225+ t.Fatalf("Scan read name %q, want %q", name2, name1)
226+ }
227+}
228+
229 // badConn implements a bad driver.Conn, for TestBadDriver.
230 // The Exec method panics.
231 type badConn struct{}
232--
2332.40.0
diff --git a/meta/recipes-devtools/go/go/CVE-2025-47907.patch b/meta/recipes-devtools/go/go/CVE-2025-47907.patch
new file mode 100644
index 0000000000..a556c3dd68
--- /dev/null
+++ b/meta/recipes-devtools/go/go/CVE-2025-47907.patch
@@ -0,0 +1,328 @@
1From 8a924caaf348fdc366bab906424616b2974ad4e9 Mon Sep 17 00:00:00 2001
2From: Damien Neil <dneil@google.com>
3Date: Wed, 23 Jul 2025 14:26:54 -0700
4Subject: [PATCH 2/2] database/sql: avoid closing Rows while scan is in
5 progress
6
7A database/sql/driver.Rows can return database-owned data
8from Rows.Next. The driver.Rows documentation doesn't explicitly
9document the lifetime guarantees for this data, but a reasonable
10expectation is that the caller of Next should only access it
11until the next call to Rows.Close or Rows.Next.
12
13Avoid violating that constraint when a query is cancelled while
14a call to database/sql.Rows.Scan (note the difference between
15the two different Rows types!) is in progress. We previously
16took care to avoid closing a driver.Rows while the user has
17access to driver-owned memory via a RawData, but we could still
18close a driver.Rows while a Scan call was in the process of
19reading previously-returned driver-owned data.
20
21Update the fake DB used in database/sql tests to invalidate
22returned data to help catch other places we might be
23incorrectly retaining it.
24
25Updates #74831
26Fixes #74832
27
28Change-Id: Ice45b5fad51b679c38e3e1d21ef39156b56d6037
29Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2540
30Reviewed-by: Roland Shoemaker <bracewell@google.com>
31Reviewed-by: Neal Patel <nealpatel@google.com>
32Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2601
33Reviewed-on: https://go-review.googlesource.com/c/go/+/693558
34TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
35Reviewed-by: Mark Freeman <markfreeman@google.com>
36Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
37Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
38
39CVE: CVE-2025-47907
40
41Upstream-Status: Backport [https://github.com/golang/go/commit/8a924caaf348fdc366bab906424616b2974ad4e9]
42
43Signed-off-by: Praveen Kumar <praveen.kumar@windriver.com>
44---
45 src/database/sql/convert.go | 2 --
46 src/database/sql/fakedb_test.go | 47 ++++++++++++--------------
47 src/database/sql/sql.go | 26 ++++++--------
48 src/database/sql/sql_test.go | 60 ++++++++++++++++++++++++++++++---
49 4 files changed, 89 insertions(+), 46 deletions(-)
50
51diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go
52index 999b8f1..2869a3b 100644
53--- a/src/database/sql/convert.go
54+++ b/src/database/sql/convert.go
55@@ -324,7 +324,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
56 if rows == nil {
57 return errors.New("invalid context to convert cursor rows, missing parent *Rows")
58 }
59- rows.closemu.Lock()
60 *d = Rows{
61 dc: rows.dc,
62 releaseConn: func(error) {},
63@@ -340,7 +339,6 @@ func convertAssignRows(dest, src any, rows *Rows) error {
64 parentCancel()
65 }
66 }
67- rows.closemu.Unlock()
68 return nil
69 }
70 }
71diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go
72index c6c3172..95c0fa3 100644
73--- a/src/database/sql/fakedb_test.go
74+++ b/src/database/sql/fakedb_test.go
75@@ -5,6 +5,7 @@
76 package sql
77
78 import (
79+ "bytes"
80 "context"
81 "database/sql/driver"
82 "errors"
83@@ -15,7 +16,6 @@ import (
84 "strconv"
85 "strings"
86 "sync"
87- "sync/atomic"
88 "testing"
89 "time"
90 )
91@@ -91,8 +91,6 @@ func (cc *fakeDriverCtx) OpenConnector(name string) (driver.Connector, error) {
92 type fakeDB struct {
93 name string
94
95- useRawBytes atomic.Bool
96-
97 mu sync.Mutex
98 tables map[string]*table
99 badConn bool
100@@ -700,8 +698,6 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm
101 switch cmd {
102 case "WIPE":
103 // Nothing
104- case "USE_RAWBYTES":
105- c.db.useRawBytes.Store(true)
106 case "SELECT":
107 stmt, err = c.prepareSelect(stmt, parts)
108 case "CREATE":
109@@ -805,9 +801,6 @@ func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (d
110 case "WIPE":
111 db.wipe()
112 return driver.ResultNoRows, nil
113- case "USE_RAWBYTES":
114- s.c.db.useRawBytes.Store(true)
115- return driver.ResultNoRows, nil
116 case "CREATE":
117 if err := db.createTable(s.table, s.colName, s.colType); err != nil {
118 return nil, err
119@@ -1090,10 +1083,9 @@ type rowsCursor struct {
120 errPos int
121 err error
122
123- // a clone of slices to give out to clients, indexed by the
124- // original slice's first byte address. we clone them
125- // just so we're able to corrupt them on close.
126- bytesClone map[*byte][]byte
127+ // Data returned to clients.
128+ // We clone and stash it here so it can be invalidated by Close and Next.
129+ driverOwnedMemory [][]byte
130
131 // Every operation writes to line to enable the race detector
132 // check for data races.
133@@ -1110,9 +1102,19 @@ func (rc *rowsCursor) touchMem() {
134 rc.line++
135 }
136
137+func (rc *rowsCursor) invalidateDriverOwnedMemory() {
138+ for _, buf := range rc.driverOwnedMemory {
139+ for i := range buf {
140+ buf[i] = 'x'
141+ }
142+ }
143+ rc.driverOwnedMemory = nil
144+}
145+
146 func (rc *rowsCursor) Close() error {
147 rc.touchMem()
148 rc.parentMem.touchMem()
149+ rc.invalidateDriverOwnedMemory()
150 rc.closed = true
151 return rc.closeErr
152 }
153@@ -1143,6 +1145,8 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
154 if rc.posRow >= len(rc.rows[rc.posSet]) {
155 return io.EOF // per interface spec
156 }
157+ // Corrupt any previously returned bytes.
158+ rc.invalidateDriverOwnedMemory()
159 for i, v := range rc.rows[rc.posSet][rc.posRow].cols {
160 // TODO(bradfitz): convert to subset types? naah, I
161 // think the subset types should only be input to
162@@ -1150,20 +1154,13 @@ func (rc *rowsCursor) Next(dest []driver.Value) error {
163 // a wider range of types coming out of drivers. all
164 // for ease of drivers, and to prevent drivers from
165 // messing up conversions or doing them differently.
166- dest[i] = v
167-
168- if bs, ok := v.([]byte); ok && !rc.db.useRawBytes.Load() {
169- if rc.bytesClone == nil {
170- rc.bytesClone = make(map[*byte][]byte)
171- }
172- clone, ok := rc.bytesClone[&bs[0]]
173- if !ok {
174- clone = make([]byte, len(bs))
175- copy(clone, bs)
176- rc.bytesClone[&bs[0]] = clone
177- }
178- dest[i] = clone
179+ if bs, ok := v.([]byte); ok {
180+ // Clone []bytes and stash for later invalidation.
181+ bs = bytes.Clone(bs)
182+ rc.driverOwnedMemory = append(rc.driverOwnedMemory, bs)
183+ v = bs
184 }
185+ dest[i] = v
186 }
187 return nil
188 }
189diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go
190index da0f52c..41130d9 100644
191--- a/src/database/sql/sql.go
192+++ b/src/database/sql/sql.go
193@@ -3357,37 +3357,33 @@ func (rs *Rows) Scan(dest ...any) error {
194 return fmt.Errorf("sql: Scan called without calling Next (closemuScanHold)")
195 }
196 rs.closemu.RLock()
197-
198- if rs.lasterr != nil && rs.lasterr != io.EOF {
199+ rs.raw = rs.raw[:0]
200+ err := rs.scanLocked(dest...)
201+ if err == nil && scanArgsContainRawBytes(dest) {
202+ rs.closemuScanHold = true
203+ } else {
204 rs.closemu.RUnlock()
205+ }
206+ return err
207+}
208+func (rs *Rows) scanLocked(dest ...any) error {
209+ if rs.lasterr != nil && rs.lasterr != io.EOF {
210 return rs.lasterr
211 }
212 if rs.closed {
213- err := rs.lasterrOrErrLocked(errRowsClosed)
214- rs.closemu.RUnlock()
215- return err
216- }
217-
218- if scanArgsContainRawBytes(dest) {
219- rs.closemuScanHold = true
220- rs.raw = rs.raw[:0]
221- } else {
222- rs.closemu.RUnlock()
223+ return rs.lasterrOrErrLocked(errRowsClosed)
224 }
225
226 if rs.lastcols == nil {
227- rs.closemuRUnlockIfHeldByScan()
228 return errors.New("sql: Scan called without calling Next")
229 }
230 if len(dest) != len(rs.lastcols) {
231- rs.closemuRUnlockIfHeldByScan()
232 return fmt.Errorf("sql: expected %d destination arguments in Scan, not %d", len(rs.lastcols), len(dest))
233 }
234
235 for i, sv := range rs.lastcols {
236 err := convertAssignRows(dest[i], sv, rs)
237 if err != nil {
238- rs.closemuRUnlockIfHeldByScan()
239 return fmt.Errorf(`sql: Scan error on column index %d, name %q: %w`, i, rs.rowsi.Columns()[i], err)
240 }
241 }
242diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go
243index c3b4822..ee65b1b 100644
244--- a/src/database/sql/sql_test.go
245+++ b/src/database/sql/sql_test.go
246@@ -5,6 +5,7 @@
247 package sql
248
249 import (
250+ "bytes"
251 "context"
252 "database/sql/driver"
253 "errors"
254@@ -4411,10 +4412,6 @@ func testContextCancelDuringRawBytesScan(t *testing.T, mode string) {
255 db := newTestDB(t, "people")
256 defer closeDB(t, db)
257
258- if _, err := db.Exec("USE_RAWBYTES"); err != nil {
259- t.Fatal(err)
260- }
261-
262 // cancel used to call close asynchronously.
263 // This test checks that it waits so as not to interfere with RawBytes.
264 ctx, cancel := context.WithCancel(context.Background())
265@@ -4506,6 +4503,61 @@ func TestContextCancelBetweenNextAndErr(t *testing.T) {
266 }
267 }
268
269+type testScanner struct {
270+ scanf func(src any) error
271+}
272+
273+func (ts testScanner) Scan(src any) error { return ts.scanf(src) }
274+
275+func TestContextCancelDuringScan(t *testing.T) {
276+ db := newTestDB(t, "people")
277+ defer closeDB(t, db)
278+
279+ ctx, cancel := context.WithCancel(context.Background())
280+ defer cancel()
281+
282+ scanStart := make(chan any)
283+ scanEnd := make(chan error)
284+ scanner := &testScanner{
285+ scanf: func(src any) error {
286+ scanStart <- src
287+ return <-scanEnd
288+ },
289+ }
290+
291+ // Start a query, and pause it mid-scan.
292+ want := []byte("Alice")
293+ r, err := db.QueryContext(ctx, "SELECT|people|name|name=?", string(want))
294+ if err != nil {
295+ t.Fatal(err)
296+ }
297+ if !r.Next() {
298+ t.Fatalf("r.Next() = false, want true")
299+ }
300+ go func() {
301+ r.Scan(scanner)
302+ }()
303+ got := <-scanStart
304+ defer close(scanEnd)
305+ gotBytes, ok := got.([]byte)
306+ if !ok {
307+ t.Fatalf("r.Scan returned %T, want []byte", got)
308+ }
309+ if !bytes.Equal(gotBytes, want) {
310+ t.Fatalf("before cancel: r.Scan returned %q, want %q", gotBytes, want)
311+ }
312+
313+ // Cancel the query.
314+ // Sleep to give it a chance to finish canceling.
315+ cancel()
316+ time.Sleep(10 * time.Millisecond)
317+
318+ // Cancelling the query should not have changed the result.
319+ if !bytes.Equal(gotBytes, want) {
320+ t.Fatalf("after cancel: r.Scan result is now %q, want %q", gotBytes, want)
321+ }
322+}
323+
324 func TestNilErrorAfterClose(t *testing.T) {
325 db := newTestDB(t, "people")
326 defer closeDB(t, db)
327--
3282.40.0