Skip to content

Commit e9ffde4

Browse files
committed
fix(sqlite): audit for bad casts
1 parent 3dfc305 commit e9ffde4

File tree

10 files changed

+116
-33
lines changed

10 files changed

+116
-33
lines changed

sqlx-sqlite/src/connection/collation.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,23 @@ where
127127
C: Fn(&str, &str) -> Ordering,
128128
{
129129
let boxed_f: *mut C = data as *mut C;
130-
debug_assert!(!boxed_f.is_null());
130+
131+
// Note: unwinding is now caught at the FFI boundary:
132+
// https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding
133+
assert!(!boxed_f.is_null());
134+
135+
let left_len =
136+
usize::try_from(left_len).unwrap_or_else(|_| panic!("left_len out of range: {left_len}"));
137+
138+
let right_len = usize::try_from(right_len)
139+
.unwrap_or_else(|_| panic!("right_len out of range: {right_len}"));
140+
131141
let s1 = {
132-
let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len as usize);
142+
let c_slice = slice::from_raw_parts(left_ptr as *const u8, left_len);
133143
from_utf8_unchecked(c_slice)
134144
};
135145
let s2 = {
136-
let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len as usize);
146+
let c_slice = slice::from_raw_parts(right_ptr as *const u8, right_len);
137147
from_utf8_unchecked(c_slice)
138148
};
139149
let t = (*boxed_f)(s1, s2);

sqlx-sqlite/src/connection/explain.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
// Bad casts in this module SHOULD NOT result in a SQL injection
2+
// https://github.com/launchbadge/sqlx/issues/3440
3+
#![allow(
4+
clippy::cast_possible_truncation,
5+
clippy::cast_possible_wrap,
6+
clippy::cast_sign_loss
7+
)]
18
use crate::connection::intmap::IntMap;
29
use crate::connection::{execute, ConnectionState};
310
use crate::error::Error;

sqlx-sqlite/src/connection/intmap.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
// Bad casts in this module SHOULD NOT result in a SQL injection
2+
// https://github.com/launchbadge/sqlx/issues/3440
3+
#![allow(
4+
clippy::cast_possible_truncation,
5+
clippy::cast_possible_wrap,
6+
clippy::cast_sign_loss
7+
)]
18
use std::cmp::Ordering;
29
use std::{fmt::Debug, hash::Hash};
310

sqlx-sqlite/src/logger.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
// Bad casts in this module SHOULD NOT result in a SQL injection
2+
// https://github.com/launchbadge/sqlx/issues/3440
3+
#![allow(
4+
clippy::cast_possible_truncation,
5+
clippy::cast_possible_wrap,
6+
clippy::cast_sign_loss
7+
)]
8+
19
use crate::connection::intmap::IntMap;
210
use std::collections::HashSet;
311
use std::fmt::Debug;

sqlx-sqlite/src/statement/handle.rs

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ pub(crate) struct StatementHandle(NonNull<sqlite3_stmt>);
3434

3535
unsafe impl Send for StatementHandle {}
3636

37+
macro_rules! expect_ret_valid {
38+
($fn_name:ident($($args:tt)*)) => {{
39+
let val = $fn_name($($args)*);
40+
41+
TryFrom::try_from(val)
42+
.unwrap_or_else(|_| panic!("{}() returned invalid value: {val:?}", stringify!($fn_name)))
43+
}}
44+
}
45+
46+
macro_rules! check_col_idx {
47+
($idx:ident) => {
48+
c_int::try_from($idx).unwrap_or_else(|_| panic!("invalid column index: {}", $idx))
49+
};
50+
}
51+
3752
// might use some of this later
3853
#[allow(dead_code)]
3954
impl StatementHandle {
@@ -71,22 +86,22 @@ impl StatementHandle {
7186
#[inline]
7287
pub(crate) fn column_count(&self) -> usize {
7388
// https://sqlite.org/c3ref/column_count.html
74-
unsafe { sqlite3_column_count(self.0.as_ptr()) as usize }
89+
unsafe { expect_ret_valid!(sqlite3_column_count(self.0.as_ptr())) }
7590
}
7691

7792
#[inline]
7893
pub(crate) fn changes(&self) -> u64 {
7994
// returns the number of changes of the *last* statement; not
8095
// necessarily this statement.
8196
// https://sqlite.org/c3ref/changes.html
82-
unsafe { sqlite3_changes(self.db_handle()) as u64 }
97+
unsafe { expect_ret_valid!(sqlite3_changes(self.db_handle())) }
8398
}
8499

85100
#[inline]
86101
pub(crate) fn column_name(&self, index: usize) -> &str {
87102
// https://sqlite.org/c3ref/column_name.html
88103
unsafe {
89-
let name = sqlite3_column_name(self.0.as_ptr(), index as c_int);
104+
let name = sqlite3_column_name(self.0.as_ptr(), check_col_idx!(index));
90105
debug_assert!(!name.is_null());
91106

92107
from_utf8_unchecked(CStr::from_ptr(name).to_bytes())
@@ -107,7 +122,7 @@ impl StatementHandle {
107122
#[inline]
108123
pub(crate) fn column_decltype(&self, index: usize) -> Option<SqliteTypeInfo> {
109124
unsafe {
110-
let decl = sqlite3_column_decltype(self.0.as_ptr(), index as c_int);
125+
let decl = sqlite3_column_decltype(self.0.as_ptr(), check_col_idx!(index));
111126
if decl.is_null() {
112127
// If the Nth column of the result set is an expression or subquery,
113128
// then a NULL pointer is returned.
@@ -123,16 +138,18 @@ impl StatementHandle {
123138

124139
pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> {
125140
unsafe {
141+
let index = check_col_idx!(index);
142+
126143
// https://sqlite.org/c3ref/column_database_name.html
127144
//
128145
// ### Note
129146
// The returned string is valid until the prepared statement is destroyed using
130147
// sqlite3_finalize() or until the statement is automatically reprepared by the
131148
// first call to sqlite3_step() for a particular run or until the same information
132149
// is requested again in a different encoding.
133-
let db_name = sqlite3_column_database_name(self.0.as_ptr(), index as c_int);
134-
let table_name = sqlite3_column_table_name(self.0.as_ptr(), index as c_int);
135-
let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index as c_int);
150+
let db_name = sqlite3_column_database_name(self.0.as_ptr(), index);
151+
let table_name = sqlite3_column_table_name(self.0.as_ptr(), index);
152+
let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index);
136153

137154
if db_name.is_null() || table_name.is_null() || origin_name.is_null() {
138155
return Ok(None);
@@ -174,7 +191,7 @@ impl StatementHandle {
174191
#[inline]
175192
pub(crate) fn bind_parameter_count(&self) -> usize {
176193
// https://www.sqlite.org/c3ref/bind_parameter_count.html
177-
unsafe { sqlite3_bind_parameter_count(self.0.as_ptr()) as usize }
194+
unsafe { expect_ret_valid!(sqlite3_bind_parameter_count(self.0.as_ptr())) }
178195
}
179196

180197
// Name Of A Host Parameter
@@ -183,7 +200,7 @@ impl StatementHandle {
183200
pub(crate) fn bind_parameter_name(&self, index: usize) -> Option<&str> {
184201
unsafe {
185202
// https://www.sqlite.org/c3ref/bind_parameter_name.html
186-
let name = sqlite3_bind_parameter_name(self.0.as_ptr(), index as c_int);
203+
let name = sqlite3_bind_parameter_name(self.0.as_ptr(), check_col_idx!(index));
187204
if name.is_null() {
188205
return None;
189206
}
@@ -200,7 +217,7 @@ impl StatementHandle {
200217
unsafe {
201218
sqlite3_bind_blob64(
202219
self.0.as_ptr(),
203-
index as c_int,
220+
check_col_idx!(index),
204221
v.as_ptr() as *const c_void,
205222
v.len() as u64,
206223
SQLITE_TRANSIENT(),
@@ -210,76 +227,85 @@ impl StatementHandle {
210227

211228
#[inline]
212229
pub(crate) fn bind_text(&self, index: usize, v: &str) -> c_int {
230+
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
231+
let encoding = SQLITE_UTF8 as u8;
232+
213233
unsafe {
214234
sqlite3_bind_text64(
215235
self.0.as_ptr(),
216-
index as c_int,
236+
check_col_idx!(index),
217237
v.as_ptr() as *const c_char,
218238
v.len() as u64,
219239
SQLITE_TRANSIENT(),
220-
SQLITE_UTF8 as u8,
240+
encoding,
221241
)
222242
}
223243
}
224244

225245
#[inline]
226246
pub(crate) fn bind_int(&self, index: usize, v: i32) -> c_int {
227-
unsafe { sqlite3_bind_int(self.0.as_ptr(), index as c_int, v as c_int) }
247+
unsafe { sqlite3_bind_int(self.0.as_ptr(), check_col_idx!(index), v as c_int) }
228248
}
229249

230250
#[inline]
231251
pub(crate) fn bind_int64(&self, index: usize, v: i64) -> c_int {
232-
unsafe { sqlite3_bind_int64(self.0.as_ptr(), index as c_int, v) }
252+
unsafe { sqlite3_bind_int64(self.0.as_ptr(), check_col_idx!(index), v) }
233253
}
234254

235255
#[inline]
236256
pub(crate) fn bind_double(&self, index: usize, v: f64) -> c_int {
237-
unsafe { sqlite3_bind_double(self.0.as_ptr(), index as c_int, v) }
257+
unsafe { sqlite3_bind_double(self.0.as_ptr(), check_col_idx!(index), v) }
238258
}
239259

240260
#[inline]
241261
pub(crate) fn bind_null(&self, index: usize) -> c_int {
242-
unsafe { sqlite3_bind_null(self.0.as_ptr(), index as c_int) }
262+
unsafe { sqlite3_bind_null(self.0.as_ptr(), check_col_idx!(index)) }
243263
}
244264

245265
// result values from the query
246266
// https://www.sqlite.org/c3ref/column_blob.html
247267

248268
#[inline]
249269
pub(crate) fn column_type(&self, index: usize) -> c_int {
250-
unsafe { sqlite3_column_type(self.0.as_ptr(), index as c_int) }
270+
unsafe { sqlite3_column_type(self.0.as_ptr(), check_col_idx!(index)) }
251271
}
252272

253273
#[inline]
254274
pub(crate) fn column_int(&self, index: usize) -> i32 {
255-
unsafe { sqlite3_column_int(self.0.as_ptr(), index as c_int) as i32 }
275+
unsafe { sqlite3_column_int(self.0.as_ptr(), check_col_idx!(index)) as i32 }
256276
}
257277

258278
#[inline]
259279
pub(crate) fn column_int64(&self, index: usize) -> i64 {
260-
unsafe { sqlite3_column_int64(self.0.as_ptr(), index as c_int) as i64 }
280+
unsafe { sqlite3_column_int64(self.0.as_ptr(), check_col_idx!(index)) as i64 }
261281
}
262282

263283
#[inline]
264284
pub(crate) fn column_double(&self, index: usize) -> f64 {
265-
unsafe { sqlite3_column_double(self.0.as_ptr(), index as c_int) }
285+
unsafe { sqlite3_column_double(self.0.as_ptr(), check_col_idx!(index)) }
266286
}
267287

268288
#[inline]
269289
pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value {
270-
unsafe { sqlite3_column_value(self.0.as_ptr(), index as c_int) }
290+
unsafe { sqlite3_column_value(self.0.as_ptr(), check_col_idx!(index)) }
271291
}
272292

273293
pub(crate) fn column_blob(&self, index: usize) -> &[u8] {
274-
let index = index as c_int;
275-
let len = unsafe { sqlite3_column_bytes(self.0.as_ptr(), index) } as usize;
294+
let len = unsafe { sqlite3_column_bytes(self.0.as_ptr(), check_col_idx!(index)) };
295+
296+
// This likely means UB in SQLite itself or our usage of it;
297+
// signed integer overflow is UB in the C standard.
298+
let len = usize::try_from(len).unwrap_or_else(|_| {
299+
panic!("sqlite3_value_bytes() returned value out of range for usize: {len}")
300+
});
276301

277302
if len == 0 {
278303
// empty blobs are NULL so just return an empty slice
279304
return &[];
280305
}
281306

282-
let ptr = unsafe { sqlite3_column_blob(self.0.as_ptr(), index) } as *const u8;
307+
let ptr =
308+
unsafe { sqlite3_column_blob(self.0.as_ptr(), check_col_idx!(index)) } as *const u8;
283309
debug_assert!(!ptr.is_null());
284310

285311
unsafe { from_raw_parts(ptr, len) }

sqlx-sqlite/src/statement/unlock_notify.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ pub unsafe fn wait(conn: *mut sqlite3) -> Result<(), SqliteError> {
2727

2828
unsafe extern "C" fn unlock_notify_cb(ptr: *mut *mut c_void, len: c_int) {
2929
let ptr = ptr as *mut &Notify;
30-
let slice = slice::from_raw_parts(ptr, len as usize);
30+
// We don't have a choice; we can't panic and unwind into FFI here.
31+
let slice = slice::from_raw_parts(ptr, usize::try_from(len).unwrap_or(0));
3132

3233
for notify in slice {
3334
notify.fire();

sqlx-sqlite/src/statement/virtual.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,13 @@ fn prepare(
163163
let mut tail: *const c_char = null();
164164

165165
let query_ptr = query.as_ptr() as *const c_char;
166-
let query_len = query.len() as i32;
166+
let query_len = i32::try_from(query.len()).map_err(|_| {
167+
err_protocol!(
168+
"query string too large for SQLite3 API ({} bytes); \
169+
try breaking it into smaller chunks (< 2 GiB), executed separately",
170+
query.len()
171+
)
172+
})?;
167173

168174
// <https://www.sqlite.org/c3ref/prepare.html>
169175
let status = unsafe {

sqlx-sqlite/src/types/chrono.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,20 @@ fn decode_datetime_from_float(value: f64) -> Option<DateTime<FixedOffset>> {
167167
let epoch_in_julian_days = 2_440_587.5;
168168
let seconds_in_day = 86400.0;
169169
let timestamp = (value - epoch_in_julian_days) * seconds_in_day;
170-
let seconds = timestamp as i64;
171-
let nanos = (timestamp.fract() * 1E9) as u32;
172170

173-
Utc.fix().timestamp_opt(seconds, nanos).single()
171+
if !timestamp.is_finite() {
172+
return None;
173+
}
174+
175+
// We don't really have a choice but to do lossy casts for this conversion
176+
// We checked above if the value is infinite or NaN which could otherwise cause problems
177+
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
178+
{
179+
let seconds = timestamp.trunc() as i64;
180+
let nanos = (timestamp.fract() * 1E9).abs() as u32;
181+
182+
Utc.fix().timestamp_opt(seconds, nanos).single()
183+
}
174184
}
175185

176186
impl<'r> Decode<'r, Sqlite> for NaiveDateTime {

sqlx-sqlite/src/types/float.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ impl<'q> Encode<'q, Sqlite> for f32 {
2424

2525
impl<'r> Decode<'r, Sqlite> for f32 {
2626
fn decode(value: SqliteValueRef<'r>) -> Result<f32, BoxDynError> {
27+
// Truncation is intentional
28+
#[allow(clippy::cast_possible_truncation)]
2729
Ok(value.double() as f32)
2830
}
2931
}

sqlx-sqlite/src/value.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,13 @@ impl SqliteValue {
120120
}
121121

122122
fn blob(&self) -> &[u8] {
123-
let len = unsafe { sqlite3_value_bytes(self.handle.0.as_ptr()) } as usize;
123+
let len = unsafe { sqlite3_value_bytes(self.handle.0.as_ptr()) };
124+
125+
// This likely means UB in SQLite itself or our usage of it;
126+
// signed integer overflow is UB in the C standard.
127+
let len = usize::try_from(len).unwrap_or_else(|_| {
128+
panic!("sqlite3_value_bytes() returned value out of range for usize: {len}")
129+
});
124130

125131
if len == 0 {
126132
// empty blobs are NULL so just return an empty slice

0 commit comments

Comments
 (0)