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 | ||
