diesel-async may expose uninitialized padding bytes for MySQL temporal columns
Summary
diesel-async exposes uninitialized stack padding to safe code on every read of a MySQL DATE, TIME, DATETIME, or TIMESTAMP column. Reading that buffer is undefined behavior, and the leaked bytes can contain stale heap/stack contents, so this is both a soundness bug and a potential information-disclosure vector.
Details
In diesel-async/src/mysql/row.rs (lines 65-103), MysqlRow::get builds a MysqlTime from the parsed mysql_async::Value and then fabricates the byte buffer that downstream FromSql impls expect like this:
let date = MysqlTime::new(/* fields from Value::Date / Value::Time */);
let buffer = unsafe {
let ptr = &date as *const MysqlTime as *const u8;
let slice = std::slice::from_raw_parts(ptr, std::mem::size_of::());
slice.to_vec()
};
MysqlTime is #[repr(C)] with 3 bytes of padding after bool neg (Linux x86_64, offsets 0x21..0x23). The literal construction leaves that padding uninitialized, and to_vec() carries it into a Vec that becomes the MysqlValue's backing buffer, reachable from safe code via MysqlValue::as_bytes() -> &[u8].
diesel itself avoids this by going through MaybeUninit::::zeroed() + ptr::copy_nonoverlapping (see diesel/src/mysql/value.rs:43-94); the same pattern would fix this. Alternatively, write the bytes diesel's FromSql reads without round-tripping through a MysqlTime value.
PoC
Cargo.toml:
[dependencies]
diesel = { version = "~2.3.0", default-features = false, features = ["mysql_backend"] }
diesel-async = { version = "=0.8.0", features = ["mysql"] }
mysql_common = { version = "0.35", default-features = false }
src/main.rs:
use diesel::row::{Field, Row};
use diesel_async::{AsyncConnectionCore, AsyncMysqlConnection};
use mysql_common::{constants::ColumnType, packets::Column, prelude::FromRow, value::Value};
type MysqlRow = ::Row;
fn main() {
let cols = std::sync::Arc::from([Column::new(ColumnType::MYSQL_TYPE_DATE)]);
let raw = mysql_common::row::new_row(vec![Value::Date(2024, 1, 1, 0, 0, 0, 0)], cols);
let row: MysqlRow = FromRow::from_row(raw);
let field = row.get(0).unwrap();
let bytes = field.value().unwrap().as_bytes();
let _: u64 = bytes.iter().map(|&b| b as u64).sum(); // UB: hits padding
}
Miri output:
error: Undefined Behavior: reading memory at alloc844[0x21..0x22], but memory is uninitialized at [0x21..0x22], and this operation requires initialized memory
--> src/main.rs:14:37
|
14 | let _: u64 = bytes.iter().map(|&b| b as u64).sum(); // UB: hits padding
| ^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: stack backtrace:
0: main::{closure#0}
at src/main.rs:14:37: 14:38
1: std::iter::adapters::map::map_fold:: as std::iter::Iterator>::fold::, {closure@src/main.rs:14:35: 14:39}> as std::iter::Iterator>::fold::, {closure@src/main.rs:14:35: 14:39}>>
at /home/paolobarbolini/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/accum.rs:52:17: 56:18
5: