diff options
-rw-r--r-- | meta/recipes-devtools/go/go-1.22.12.inc | 2 | ||||
-rw-r--r-- | meta/recipes-devtools/go/go/CVE-2025-47907-pre.patch | 233 | ||||
-rw-r--r-- | meta/recipes-devtools/go/go/CVE-2025-47907.patch | 328 |
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 | " |
22 | SRC_URI[main.sha256sum] = "012a7e1f37f362c0918c1dfa3334458ac2da1628c4b9cf4d9ca02db986e17d71" | 24 | SRC_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 @@ | |||
1 | From c23579f031ecd09bf37c644723b33736dffa8b92 Mon Sep 17 00:00:00 2001 | ||
2 | From: Damien Neil <dneil@google.com> | ||
3 | Date: Tue, 23 Jan 2024 15:59:47 -0800 | ||
4 | Subject: [PATCH 1/2] database/sql: avoid clobbering driver-owned memory in | ||
5 | RawBytes | ||
6 | |||
7 | Depending on the query, a RawBytes can contain memory owned by the | ||
8 | driver or by database/sql: | ||
9 | |||
10 | If the driver provides the column as a []byte, | ||
11 | RawBytes aliases that []byte. | ||
12 | |||
13 | If the driver provides the column as any other type, | ||
14 | RawBytes contains memory allocated by database/sql. | ||
15 | Prior to this CL, Rows.Scan will reuse existing capacity in a | ||
16 | RawBytes to permit a single allocation to be reused across rows. | ||
17 | |||
18 | When a RawBytes is reused across queries, this can result | ||
19 | in database/sql writing to driver-owned memory. | ||
20 | |||
21 | Add a buffer to Rows to store RawBytes data, and reuse this | ||
22 | buffer across calls to Rows.Scan. | ||
23 | |||
24 | Fixes #65201 | ||
25 | |||
26 | Change-Id: Iac640174c7afa97eeb39496f47dec202501b2483 | ||
27 | Reviewed-on: https://go-review.googlesource.com/c/go/+/557917 | ||
28 | Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> | ||
29 | Reviewed-by: Roland Shoemaker <roland@golang.org> | ||
30 | LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> | ||
31 | |||
32 | CVE: CVE-2025-47907 | ||
33 | |||
34 | Upstream-Status: Backport [https://github.com/golang/go/commit/c23579f031ecd09bf37c644723b33736dffa8b92] | ||
35 | |||
36 | Signed-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 | |||
44 | diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go | ||
45 | index 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: | ||
77 | diff --git a/src/database/sql/convert_test.go b/src/database/sql/convert_test.go | ||
78 | index 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) | ||
115 | diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go | ||
116 | index 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 | } | ||
174 | diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go | ||
175 | index 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 | -- | ||
233 | 2.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 @@ | |||
1 | From 8a924caaf348fdc366bab906424616b2974ad4e9 Mon Sep 17 00:00:00 2001 | ||
2 | From: Damien Neil <dneil@google.com> | ||
3 | Date: Wed, 23 Jul 2025 14:26:54 -0700 | ||
4 | Subject: [PATCH 2/2] database/sql: avoid closing Rows while scan is in | ||
5 | progress | ||
6 | |||
7 | A database/sql/driver.Rows can return database-owned data | ||
8 | from Rows.Next. The driver.Rows documentation doesn't explicitly | ||
9 | document the lifetime guarantees for this data, but a reasonable | ||
10 | expectation is that the caller of Next should only access it | ||
11 | until the next call to Rows.Close or Rows.Next. | ||
12 | |||
13 | Avoid violating that constraint when a query is cancelled while | ||
14 | a call to database/sql.Rows.Scan (note the difference between | ||
15 | the two different Rows types!) is in progress. We previously | ||
16 | took care to avoid closing a driver.Rows while the user has | ||
17 | access to driver-owned memory via a RawData, but we could still | ||
18 | close a driver.Rows while a Scan call was in the process of | ||
19 | reading previously-returned driver-owned data. | ||
20 | |||
21 | Update the fake DB used in database/sql tests to invalidate | ||
22 | returned data to help catch other places we might be | ||
23 | incorrectly retaining it. | ||
24 | |||
25 | Updates #74831 | ||
26 | Fixes #74832 | ||
27 | |||
28 | Change-Id: Ice45b5fad51b679c38e3e1d21ef39156b56d6037 | ||
29 | Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2540 | ||
30 | Reviewed-by: Roland Shoemaker <bracewell@google.com> | ||
31 | Reviewed-by: Neal Patel <nealpatel@google.com> | ||
32 | Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/2601 | ||
33 | Reviewed-on: https://go-review.googlesource.com/c/go/+/693558 | ||
34 | TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org> | ||
35 | Reviewed-by: Mark Freeman <markfreeman@google.com> | ||
36 | Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> | ||
37 | Auto-Submit: Dmitri Shuralyov <dmitshur@google.com> | ||
38 | |||
39 | CVE: CVE-2025-47907 | ||
40 | |||
41 | Upstream-Status: Backport [https://github.com/golang/go/commit/8a924caaf348fdc366bab906424616b2974ad4e9] | ||
42 | |||
43 | Signed-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 | |||
51 | diff --git a/src/database/sql/convert.go b/src/database/sql/convert.go | ||
52 | index 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 | } | ||
71 | diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go | ||
72 | index 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 | } | ||
189 | diff --git a/src/database/sql/sql.go b/src/database/sql/sql.go | ||
190 | index 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 | } | ||
242 | diff --git a/src/database/sql/sql_test.go b/src/database/sql/sql_test.go | ||
243 | index 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 | -- | ||
328 | 2.40.0 | ||