mirror of
https://github.com/launchbadge/sqlx
synced 2024-11-10 06:24:16 +00:00
fix: audit sqlx_postgres::types::hstore
for bad casts
This commit is contained in:
parent
2a9b85889e
commit
bf13a7706b
1 changed files with 62 additions and 33 deletions
|
@ -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]),
|
||||||
|
|
Loading…
Reference in a new issue