Skip to content

Commit 3f200c7

Browse files
authored
Merge pull request #4768 from weiznich/fix/4765
Fix yet another mysql_time issue
2 parents a96cf71 + 2f72e6d commit 3f200c7

File tree

1 file changed

+79
-8
lines changed

1 file changed

+79
-8
lines changed

diesel/src/mysql/value.rs

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use super::types::date_and_time::MysqlTime;
22
use super::MysqlType;
3+
34
use crate::deserialize;
45
use std::error::Error;
6+
use std::mem::MaybeUninit;
57

68
/// Raw mysql value as received from the database
79
#[derive(Clone, Debug)]
@@ -35,17 +37,56 @@ impl<'a> MysqlValue<'a> {
3537

3638
/// Checks that the type code is valid, and interprets the data as a
3739
/// `MysqlTime` pointer
38-
// We use `ptr.read_unaligned()` to read the potential unaligned ptr,
39-
// so clippy is clearly wrong here
40-
// https://github.com/rust-lang/rust-clippy/issues/2881
41-
#[allow(dead_code, clippy::cast_ptr_alignment)]
42-
#[allow(unsafe_code)] // pointer cast
40+
// We use `ptr::copy` to read the actual data
41+
// and copy it over to the returned `MysqlTime` instance
42+
#[allow(unsafe_code)] // MaybeUninit + ptr copy
4343
pub(crate) fn time_value(&self) -> deserialize::Result<MysqlTime> {
4444
match self.tpe {
4545
MysqlType::Time | MysqlType::Date | MysqlType::DateTime | MysqlType::Timestamp => {
46-
self.too_short_buffer(std::mem::size_of::<MysqlTime>(), "Timestamp")?;
47-
let ptr = self.raw.as_ptr() as *const MysqlTime;
48-
let result = unsafe { ptr.read_unaligned() };
46+
// we check for the size of the `MYSQL_TIME` type from `mysqlclient_sys` here as
47+
// certain older libmysqlclient and newer libmariadb versions do not have all the
48+
// same fields (and size) as the `MysqlTime` type from diesel. The later one is modeled after
49+
// the type from newer libmysqlclient
50+
self.too_short_buffer(
51+
#[cfg(feature = "mysql")]
52+
std::mem::size_of::<mysqlclient_sys::MYSQL_TIME>(),
53+
#[cfg(not(feature = "mysql"))]
54+
std::mem::size_of::<MysqlTime>(),
55+
"timestamp",
56+
)?;
57+
// To ensure we copy the right number of bytes we need to make sure to copy not more bytes than needed
58+
// for `MysqlTime` and not more bytes than inside of the buffer
59+
let len = std::cmp::min(std::mem::size_of::<MysqlTime>(), self.raw.len());
60+
// Zero is a valid pattern for this type so we are fine with initializing all fields to zero
61+
// If the provided byte buffer is too short we just use 0 as default value
62+
let mut out = MaybeUninit::<MysqlTime>::zeroed();
63+
// Make sure to check that the boolean is an actual bool value, so 0 or 1
64+
// as anything else is UB in rust
65+
let neg_offset = std::mem::offset_of!(MysqlTime, neg);
66+
if neg_offset < self.raw.len()
67+
&& self.raw[neg_offset] != 0
68+
&& self.raw[neg_offset] != 1
69+
{
70+
return Err(
71+
"Received invalid value for `neg` in the `MysqlTime` datastructure".into(),
72+
);
73+
}
74+
let result = unsafe {
75+
// SAFETY: We copy over the bytes from our raw buffer to the `MysqlTime` instance
76+
// This type is correctly aligned and we ensure that we do not copy more bytes than are there
77+
// We are also sure that these ptr do not overlap as they are completely different
78+
// instances
79+
std::ptr::copy_nonoverlapping(
80+
self.raw.as_ptr(),
81+
out.as_mut_ptr() as *mut u8,
82+
len,
83+
);
84+
// SAFETY: all zero is a valid pattern for this type
85+
// Otherwise any other bit pattern is also valid, beside
86+
// neg being something other than 0 or 1
87+
// We check for that above by looking at the byte before copying
88+
out.assume_init()
89+
};
4990
if result.neg {
5091
Err("Negative dates/times are not yet supported".into())
5192
} else {
@@ -133,10 +174,40 @@ pub enum NumericRepresentation<'a> {
133174
}
134175

135176
#[test]
177+
#[allow(unsafe_code, reason = "Test code")]
136178
fn invalid_reads() {
179+
use crate::data_types::MysqlTimestampType;
180+
137181
assert!(MysqlValue::new_internal(&[1], MysqlType::Timestamp)
138182
.time_value()
139183
.is_err());
184+
let v = MysqlTime {
185+
year: 2025,
186+
month: 9,
187+
day: 15,
188+
hour: 22,
189+
minute: 3,
190+
second: 10,
191+
second_part: 0,
192+
neg: false,
193+
time_type: MysqlTimestampType::MYSQL_TIMESTAMP_DATETIME,
194+
time_zone_displacement: 0,
195+
};
196+
let mut bytes = [0; std::mem::size_of::<MysqlTime>()];
197+
unsafe {
198+
// SAFETY: Test code
199+
// also the size matches and we want to get raw bytes
200+
std::ptr::copy(
201+
&v as *const MysqlTime as *const u8,
202+
bytes.as_mut_ptr(),
203+
bytes.len(),
204+
);
205+
}
206+
let offset = std::mem::offset_of!(MysqlTime, neg);
207+
bytes[offset] = 42;
208+
assert!(MysqlValue::new_internal(&bytes, MysqlType::Timestamp)
209+
.time_value()
210+
.is_err());
140211

141212
assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Long)
142213
.numeric_value()

0 commit comments

Comments
 (0)