|
1 | 1 | use super::types::date_and_time::MysqlTime; |
2 | 2 | use super::MysqlType; |
| 3 | + |
3 | 4 | use crate::deserialize; |
4 | 5 | use std::error::Error; |
| 6 | +use std::mem::MaybeUninit; |
5 | 7 |
|
6 | 8 | /// Raw mysql value as received from the database |
7 | 9 | #[derive(Clone, Debug)] |
@@ -35,17 +37,56 @@ impl<'a> MysqlValue<'a> { |
35 | 37 |
|
36 | 38 | /// Checks that the type code is valid, and interprets the data as a |
37 | 39 | /// `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 |
43 | 43 | pub(crate) fn time_value(&self) -> deserialize::Result<MysqlTime> { |
44 | 44 | match self.tpe { |
45 | 45 | 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 | + }; |
49 | 90 | if result.neg { |
50 | 91 | Err("Negative dates/times are not yet supported".into()) |
51 | 92 | } else { |
@@ -133,10 +174,40 @@ pub enum NumericRepresentation<'a> { |
133 | 174 | } |
134 | 175 |
|
135 | 176 | #[test] |
| 177 | +#[allow(unsafe_code, reason = "Test code")] |
136 | 178 | fn invalid_reads() { |
| 179 | + use crate::data_types::MysqlTimestampType; |
| 180 | + |
137 | 181 | assert!(MysqlValue::new_internal(&[1], MysqlType::Timestamp) |
138 | 182 | .time_value() |
139 | 183 | .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()); |
140 | 211 |
|
141 | 212 | assert!(MysqlValue::new_internal(&[1, 2], MysqlType::Long) |
142 | 213 | .numeric_value() |
|
0 commit comments