This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 5fc06f1db fix(rust): handle panics from fuzz-found edge cases (#3481)
5fc06f1db is described below
commit 5fc06f1db45337346db4ed380906c013f1e2f3f7
Author: Uğur Tafralı <[email protected]>
AuthorDate: Sun Mar 15 06:04:37 2026 +0300
fix(rust): handle panics from fuzz-found edge cases (#3481)
Closes #3480
Cargo-fuzz was hitting several panics in the serialization code.
Switched from unwrap/unreachable to proper error handling:
- field_id calculation now uses checked_add to catch overflows instead
of panicking
- field name decoding switched from unwrap() to Result propagation
- byte_to_encoding now returns Result for unknown encoding bytes instead
of using unreachable!()
- MetaStringBytes::new() updated to return Result so callers can handle
invalid inputs
All of these return the same error type so the call sites already handle
it correctly.
## Why?
Fuzzing found inputs that triggered panics. Better to return an error
than crash.
## What does this PR do?
Replaces panics with proper error returns in the metadata serialization
paths.
## Related issues
#3480
## AI Contribution Checklist
N/A
## Does this PR introduce any user-facing change?
No. Valid inputs behave identically. Invalid/malformed data now returns
an error instead of panicking.
## Benchmark
N/A
---
rust/fory-core/src/meta/type_meta.rs | 8 ++--
.../fory-core/src/resolver/meta_string_resolver.rs | 49 +++++++++++++---------
2 files changed, 33 insertions(+), 24 deletions(-)
diff --git a/rust/fory-core/src/meta/type_meta.rs
b/rust/fory-core/src/meta/type_meta.rs
index 368de1e18..d4ab3a956 100644
--- a/rust/fory-core/src/meta/type_meta.rs
+++ b/rust/fory-core/src/meta/type_meta.rs
@@ -277,7 +277,9 @@ impl FieldInfo {
// Field ID mode: | 0b11:2bits | field_id_low:4bits |
nullable:1bit | track_ref:1bit |
let mut field_id = ((header >> 2) & FIELD_NAME_SIZE_THRESHOLD as
u8) as i16;
if field_id == SMALL_FIELD_ID_THRESHOLD {
- field_id += reader.read_varuint32()? as i16;
+ field_id = field_id
+ .checked_add(reader.read_varuint32()? as i16)
+ .ok_or_else(|| Error::invalid_data("field_id overflow"))?;
}
let mut field_type = FieldType::from_bytes(reader, false,
Option::from(nullable))?;
@@ -302,9 +304,7 @@ impl FieldInfo {
let field_name_bytes = reader.read_bytes(name_size)?;
- let field_name = FIELD_NAME_DECODER
- .decode(field_name_bytes, encoding)
- .unwrap();
+ let field_name = FIELD_NAME_DECODER.decode(field_name_bytes,
encoding)?;
Ok(FieldInfo {
field_id: -1i16,
field_name: field_name.original,
diff --git a/rust/fory-core/src/resolver/meta_string_resolver.rs
b/rust/fory-core/src/resolver/meta_string_resolver.rs
index f174db634..fb47acc88 100644
--- a/rust/fory-core/src/resolver/meta_string_resolver.rs
+++ b/rust/fory-core/src/resolver/meta_string_resolver.rs
@@ -38,14 +38,16 @@ pub struct MetaStringBytes {
const HEADER_MASK: i64 = 0xff;
-fn byte_to_encoding(byte: u8) -> Encoding {
+fn byte_to_encoding(byte: u8) -> Result<Encoding, Error> {
match byte {
- 0 => Encoding::Utf8,
- 1 => Encoding::LowerSpecial,
- 2 => Encoding::LowerUpperDigitSpecial,
- 3 => Encoding::FirstToLowerSpecial,
- 4 => Encoding::AllToLowerSpecial,
- _ => unreachable!(),
+ 0 => Ok(Encoding::Utf8),
+ 1 => Ok(Encoding::LowerSpecial),
+ 2 => Ok(Encoding::LowerUpperDigitSpecial),
+ 3 => Ok(Encoding::FirstToLowerSpecial),
+ 4 => Ok(Encoding::AllToLowerSpecial),
+ _ => Err(Error::invalid_data(format!(
+ "unknown encoding byte: {byte}"
+ ))),
}
}
@@ -54,22 +56,22 @@ static EMPTY: OnceLock<MetaStringBytes> = OnceLock::new();
impl MetaStringBytes {
pub const DEFAULT_DYNAMIC_WRITE_STRING_ID: i16 = -1;
- pub fn new(bytes: Vec<u8>, hash_code: i64) -> Self {
+ pub fn new(bytes: Vec<u8>, hash_code: i64) -> Result<Self, Error> {
let header = (hash_code & HEADER_MASK) as u8;
- let encoding = byte_to_encoding(header);
+ let encoding = byte_to_encoding(header)?;
let mut data = bytes.clone();
if bytes.len() < 16 {
data.resize(16, 0);
}
let first8 = u64::from_le_bytes(data[0..8].try_into().unwrap());
let second8 = u64::from_le_bytes(data[8..16].try_into().unwrap());
- MetaStringBytes {
+ Ok(MetaStringBytes {
bytes,
hash_code,
encoding,
first8,
second8,
- }
+ })
}
pub fn to_meta_string(&self) -> Result<MetaString, Error> {
@@ -88,7 +90,7 @@ impl MetaStringBytes {
let encoding = meta_string.encoding;
let header = encoding as i64 & HEADER_MASK;
hash_code |= header;
- Ok(Self::new(bytes, hash_code))
+ Self::new(bytes, hash_code)
}
pub fn get_empty() -> &'static MetaStringBytes {
@@ -219,6 +221,9 @@ impl MetaStringReaderResolver {
self.read_big_meta_string_bytes_and_update(reader, len,
hash_code)
}
} else {
+ if len == 0 {
+ return Err(Error::invalid_data("dynamic string id cannot be
zero"));
+ }
let idx = len - 1;
self.dynamic_read
.get(idx)
@@ -243,6 +248,9 @@ impl MetaStringReaderResolver {
self.read_small_meta_string_bytes_and_update(reader, len)
}
} else {
+ if len == 0 {
+ return Err(Error::invalid_data("dynamic string id cannot be
zero"));
+ }
let idx = len - 1;
self.dynamic_read
.get(idx)
@@ -265,7 +273,7 @@ impl MetaStringReaderResolver {
}
Entry::Vacant(entry) => {
let bytes = reader.read_bytes(len)?.to_vec();
- let mb = MetaStringBytes::new(bytes, hash_code);
+ let mb = MetaStringBytes::new(bytes, hash_code)?;
entry.insert(mb)
}
};
@@ -319,7 +327,7 @@ impl MetaStringReaderResolver {
let hash_code = (murmurhash3_x64_128(&data, 47).0 as
i64).abs();
let hash_code =
(hash_code as u64 & 0xffffffffffffff00_u64) as i64 |
(encoding_val as i64);
- let mb = MetaStringBytes::new(data, hash_code);
+ let mb = MetaStringBytes::new(data, hash_code)?;
entry.insert(mb)
}
};
@@ -360,13 +368,14 @@ impl MetaStringReaderResolver {
let mb_ref = self.read_meta_string_bytes(reader)?;
mb_ref as *const MetaStringBytes
};
- let ms_ref = self
- .meta_string_bytes_to_string
- .entry(ptr)
- .or_insert_with(|| {
+ let ms_ref = match self.meta_string_bytes_to_string.entry(ptr) {
+ Entry::Occupied(o) => o.into_mut(),
+ Entry::Vacant(v) => {
let mb_ref = unsafe { &*ptr };
- mb_ref.to_meta_string().unwrap()
- });
+ let ms = mb_ref.to_meta_string()?;
+ v.insert(ms)
+ }
+ };
Ok(ms_ref)
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]