This is an automated email from the ASF dual-hosted git repository.

martin-g pushed a commit to branch 06-empty-compression-level
in repository https://gitbox.apache.org/repos/asf/avro-rs.git

commit 6f3a2d8b80e7bb36cea9847fe8014930bd170a4d
Author: Martin Tzvetanov Grigorov <[email protected]>
AuthorDate: Tue Aug 25 16:33:14 2026 +0300

    fix: Empty avro.codec.compression_level metadata panics reader via bytes[0]
    
    Header parsing returns Err, never panics, on malformed metadata: all three 
sites
    use bytes.first(); an empty value yields the existing 
Details::BadCodecMetadata error.
    An explicit error (rather than silently falling back to the codec default 
level) was
    chosen because an empty level is malformed input, and failing closed on 
malformed
    attacker headers is the safer contract; the absent-key case still falls 
back to defaults as before.
    
    Reported-by: Security scans
---
 avro/src/reader/block.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 51 insertions(+), 3 deletions(-)

diff --git a/avro/src/reader/block.rs b/avro/src/reader/block.rs
index b860e58..536a157 100644
--- a/avro/src/reader/block.rs
+++ b/avro/src/reader/block.rs
@@ -333,7 +333,10 @@ fn read_codec(metadata: &HashMap<String, Value>) -> 
AvroResult<Codec> {
                         if let Some(Value::Bytes(bytes)) =
                             metadata.get("avro.codec.compression_level")
                         {
-                            Ok(Codec::Bzip2(Bzip2Settings::new(bytes[0])))
+                            match bytes.first() {
+                                Some(&level) => 
Ok(Codec::Bzip2(Bzip2Settings::new(level))),
+                                None => Err(Details::BadCodecMetadata.into()),
+                            }
                         } else {
                             Ok(codec)
                         }
@@ -344,7 +347,10 @@ fn read_codec(metadata: &HashMap<String, Value>) -> 
AvroResult<Codec> {
                         if let Some(Value::Bytes(bytes)) =
                             metadata.get("avro.codec.compression_level")
                         {
-                            Ok(Codec::Xz(XzSettings::new(bytes[0])))
+                            match bytes.first() {
+                                Some(&level) => 
Ok(Codec::Xz(XzSettings::new(level))),
+                                None => Err(Details::BadCodecMetadata.into()),
+                            }
                         } else {
                             Ok(codec)
                         }
@@ -355,7 +361,10 @@ fn read_codec(metadata: &HashMap<String, Value>) -> 
AvroResult<Codec> {
                         if let Some(Value::Bytes(bytes)) =
                             metadata.get("avro.codec.compression_level")
                         {
-                            
Ok(Codec::Zstandard(ZstandardSettings::new(bytes[0])))
+                            match bytes.first() {
+                                Some(&level) => 
Ok(Codec::Zstandard(ZstandardSettings::new(level))),
+                                None => Err(Details::BadCodecMetadata.into()),
+                            }
                         } else {
                             Ok(codec)
                         }
@@ -377,6 +386,45 @@ mod tests {
     use crate::{Codec, Schema};
     use apache_avro_test_helper::TestResult;
 
+    #[cfg(any(feature = "bzip", feature = "xz", feature = "zstandard"))]
+    fn empty_compression_level_errors_for(codec_name: &str) {
+        use crate::types::Value;
+        use std::collections::HashMap;
+
+        let mut metadata = HashMap::new();
+        metadata.insert(
+            "avro.codec".to_string(),
+            Value::Bytes(codec_name.as_bytes().to_vec()),
+        );
+        metadata.insert(
+            "avro.codec.compression_level".to_string(),
+            Value::Bytes(vec![]),
+        );
+
+        // An empty compression_level in attacker-controlled metadata must be
+        // a clean error, not an index-out-of-bounds panic.
+        let err = super::read_codec(&metadata).unwrap_err().into_details();
+        assert!(matches!(err, Details::BadCodecMetadata), "{err:?}");
+    }
+
+    #[cfg(feature = "bzip")]
+    #[test]
+    fn avro_rs_644_empty_bzip2_compression_level_is_rejected() {
+        empty_compression_level_errors_for("bzip2");
+    }
+
+    #[cfg(feature = "xz")]
+    #[test]
+    fn avro_rs_644_empty_xz_compression_level_is_rejected() {
+        empty_compression_level_errors_for("xz");
+    }
+
+    #[cfg(feature = "zstandard")]
+    #[test]
+    fn avro_rs_644_empty_zstandard_compression_level_is_rejected() {
+        empty_compression_level_errors_for("zstandard");
+    }
+
     #[test]
     fn avro_rs_643_huge_message_count_on_empty_block_is_rejected() -> 
TestResult {
         // Block header claiming 2^62 objects with a block size of 0: with a

Reply via email to