Skip to content

Commit e72f822

Browse files
Lossily coerce invalid UTF-8 in sqlite collation callback (#4219)
The safe Fn(&str, &str) -> Ordering signature exposed by SqliteConnectOptions::collation() and LockedSqliteHandle::create_collation() was backed by from_utf8_unchecked, so a database containing invalid UTF-8 text could reach the user callback and materialize &str values that violate Rust's UTF-8 invariant inside a safe API. SQLite explicitly documents that invalid UTF-8 may be passed into application-defined collating sequences, so the FFI shim must not assume well-formed bytes. Replace from_utf8_unchecked with String::from_utf8_lossy, which matches the sqlite3_create_collation_v2 SQLITE_UTF8 flag and keeps the safe signature sound without changing correct-UTF-8 behavior. Fixes #4194 Co-authored-by: Joaquin Hui Gomez <joaquinhuigomez@users.noreply.github.com>
1 parent 69ee0df commit e72f822

File tree

1 file changed

+7
-4
lines changed

1 file changed

+7
-4
lines changed

sqlx-sqlite/src/connection/collation.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::ffi::CString;
33
use std::fmt::{self, Debug, Formatter};
44
use std::os::raw::{c_int, c_void};
55
use std::slice;
6-
use std::str::from_utf8_unchecked;
76
use std::sync::Arc;
87

98
use libsqlite3_sys::{sqlite3_create_collation_v2, SQLITE_OK, SQLITE_UTF8};
@@ -137,15 +136,19 @@ where
137136
let right_len = usize::try_from(right_len)
138137
.unwrap_or_else(|_| panic!("right_len out of range: {right_len}"));
139138

139+
// SQLite explicitly documents that invalid UTF-8 may be passed into
140+
// application-defined collating sequences. The safe `Fn(&str, &str)`
141+
// signature exposed to users must never observe invalid UTF-8, so
142+
// lossily coerce the raw bytes here.
140143
let s1 = {
141144
let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len);
142-
from_utf8_unchecked(c_slice)
145+
String::from_utf8_lossy(c_slice)
143146
};
144147
let s2 = {
145148
let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len);
146-
from_utf8_unchecked(c_slice)
149+
String::from_utf8_lossy(c_slice)
147150
};
148-
let t = (*boxed_f)(s1, s2);
151+
let t = (*boxed_f)(&s1, &s2);
149152

150153
match t {
151154
Ordering::Less => -1,

0 commit comments

Comments
 (0)