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]

Reply via email to