fix: audit sqlx_postgres::types::hstore for bad casts

This commit is contained in:
Austin Bonander 2024-08-16 14:30:37 -07:00
parent 2a9b85889e
commit bf13a7706b

View file

@ -2,11 +2,9 @@ use std::{
collections::{btree_map, BTreeMap}, collections::{btree_map, BTreeMap},
mem::size_of, mem::size_of,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
str::from_utf8, str,
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
decode::Decode, decode::Decode,
encode::{Encode, IsNull}, encode::{Encode, IsNull},
@ -14,6 +12,8 @@ use crate::{
types::Type, types::Type,
PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres, PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres,
}; };
use serde::{Deserialize, Serialize};
use sqlx_core::bytes::Buf;
/// Key-value support (`hstore`) for Postgres. /// Key-value support (`hstore`) for Postgres.
/// ///
@ -143,41 +143,64 @@ impl<'r> Decode<'r, Postgres> for PgHstore {
let mut buf = <&[u8] as Decode<Postgres>>::decode(value)?; let mut buf = <&[u8] as Decode<Postgres>>::decode(value)?;
let len = read_length(&mut buf)?; let len = read_length(&mut buf)?;
if len < 0 { let len =
Err(format!("hstore, invalid entry count: {len}"))?; usize::try_from(len).map_err(|_| format!("PgHstore: length out of range: {len}"))?;
}
let mut result = Self::default(); let mut result = Self::default();
while !buf.is_empty() { for i in 0..len {
let key_len = read_length(&mut buf)?; let key = read_string(&mut buf)
let key = read_value(&mut buf, key_len)?.ok_or("hstore, key not found")?; .map_err(|e| format!("PgHstore: error reading {i}th key: {e}"))?
.ok_or_else(|| format!("PgHstore: expected {i}th key, got nothing"))?;
let value_len = read_length(&mut buf)?; let value = read_string(&mut buf)
let value = read_value(&mut buf, value_len)?; .map_err(|e| format!("PgHstore: error reading value for key {key:?}: {e}"))?;
result.insert(key, value); result.insert(key, value);
} }
if !buf.is_empty() {
tracing::warn!("{} unread bytes at the end of HSTORE value", buf.len());
}
Ok(result) Ok(result)
} }
} }
impl Encode<'_, Postgres> for PgHstore { impl Encode<'_, Postgres> for PgHstore {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend_from_slice(&i32::to_be_bytes(self.0.len() as i32)); buf.extend_from_slice(&i32::to_be_bytes(
self.0
.len()
.try_into()
.map_err(|_| format!("PgHstore length out of range: {}", self.0.len()))?,
));
for (key, val) in &self.0 { for (i, (key, val)) in self.0.iter().enumerate() {
let key_bytes = key.as_bytes(); let key_bytes = key.as_bytes();
buf.extend_from_slice(&i32::to_be_bytes(key_bytes.len() as i32)); let key_len = i32::try_from(key_bytes.len()).map_err(|_| {
// Doesn't make sense to print the key itself: it's more than 2 GiB long!
format!(
"PgHstore: length of {i}th key out of range: {} bytes",
key_bytes.len()
)
})?;
buf.extend_from_slice(&i32::to_be_bytes(key_len));
buf.extend_from_slice(key_bytes); buf.extend_from_slice(key_bytes);
match val { match val {
Some(val) => { Some(val) => {
let val_bytes = val.as_bytes(); let val_bytes = val.as_bytes();
buf.extend_from_slice(&i32::to_be_bytes(val_bytes.len() as i32)); let val_len = i32::try_from(val_bytes.len()).map_err(|_| {
format!(
"PgHstore: value length for key {key:?} out of range: {} bytes",
val_bytes.len()
)
})?;
buf.extend_from_slice(&i32::to_be_bytes(val_len));
buf.extend_from_slice(val_bytes); buf.extend_from_slice(val_bytes);
} }
None => { None => {
@ -190,30 +213,36 @@ impl Encode<'_, Postgres> for PgHstore {
} }
} }
fn read_length(buf: &mut &[u8]) -> Result<i32, BoxDynError> { fn read_length(buf: &mut &[u8]) -> Result<i32, String> {
let (bytes, rest) = buf.split_at(size_of::<i32>()); if buf.len() < size_of::<i32>() {
return Err(format!(
"expected {} bytes, got {}",
size_of::<i32>(),
buf.len()
));
}
*buf = rest; Ok(buf.get_i32())
Ok(i32::from_be_bytes(
bytes
.try_into()
.map_err(|err| format!("hstore, reading length: {err}"))?,
))
} }
fn read_value(buf: &mut &[u8], len: i32) -> Result<Option<String>, BoxDynError> { fn read_string(buf: &mut &[u8]) -> Result<Option<String>, String> {
match len { let len = read_length(buf)?;
len if len <= 0 => Ok(None),
len => {
let (val, rest) = buf.split_at(len as usize);
match len {
-1 => Ok(None),
len => {
let len =
usize::try_from(len).map_err(|_| format!("string length out of range: {len}"))?;
if buf.len() < len {
return Err(format!("expected {len} bytes, got {}", buf.len()));
}
let (val, rest) = buf.split_at(len);
*buf = rest; *buf = rest;
Ok(Some( Ok(Some(
from_utf8(val) str::from_utf8(val).map_err(|e| e.to_string())?.to_string(),
.map_err(|err| format!("hstore, reading value: {err}"))?
.to_string(),
)) ))
} }
} }
@ -258,7 +287,7 @@ mod test {
} }
#[test] #[test]
#[should_panic(expected = "hstore, invalid entry count: -5")] #[should_panic(expected = "PgHstore: length out of range: -5")]
fn hstore_deserialize_buffer_length_error() { fn hstore_deserialize_buffer_length_error() {
let buf = PgValueRef { let buf = PgValueRef {
value: Some(&[255, 255, 255, 251]), value: Some(&[255, 255, 255, 251]),