From 25a54340e8ed5e936f6f7cddd8b39c72cbc6c110 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Wed, 10 Jun 2026 14:45:37 -0400 Subject: [PATCH] Slate: length-check ed25519/uuid fields in the JSON serde path The serde deserialize helpers copied fixed-length byte arrays (b.copy_from_slice(&bytes[0..N])) without checking the decoded length, so a slatepack with a short pubkey/signature/uuid field panicked the wallet during deserialization. The binary path was hardened in 25dab9f; do the same for the JSON modules: reject any field whose decoded length is not exactly N. Adds a regression test that exercised the panic. --- libwallet/src/slate_versions/ser.rs | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/libwallet/src/slate_versions/ser.rs b/libwallet/src/slate_versions/ser.rs index 28c67d0..8d97ac3 100644 --- a/libwallet/src/slate_versions/ser.rs +++ b/libwallet/src/slate_versions/ser.rs @@ -207,6 +207,9 @@ pub mod dalek_xpubkey_serde { String::deserialize(deserializer) .and_then(|string| from_hex(&string).map_err(|err| Error::custom(err.to_string()))) .and_then(|bytes: Vec| { + if bytes.len() != 32 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 32]; b.copy_from_slice(&bytes[0..32]); Ok(xDalekPublicKey::from(b)) @@ -271,6 +274,9 @@ pub mod option_dalek_pubkey_base64 { Some(string) => base64::decode(&string) .map_err(|err| Error::custom(err.to_string())) .and_then(|bytes: Vec| { + if bytes.len() != 32 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 32]; b.copy_from_slice(&bytes[0..32]); DalekPublicKey::from_bytes(&b) @@ -313,6 +319,9 @@ pub mod option_dalek_pubkey_serde { Some(string) => from_hex(&string) .map_err(|err| Error::custom(err.to_string())) .and_then(|bytes: Vec| { + if bytes.len() != 32 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 32]; b.copy_from_slice(&bytes[0..32]); DalekPublicKey::from_bytes(&b) @@ -352,6 +361,9 @@ pub mod option_xdalek_pubkey_serde { Some(string) => from_hex(&string) .map_err(|err| Error::custom(err.to_string())) .and_then(|bytes: Vec| { + if bytes.len() != 32 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 32]; b.copy_from_slice(&bytes[0..32]); Ok(Some(xDalekPublicKey::from(b))) @@ -386,6 +398,9 @@ pub mod dalek_sig_serde { String::deserialize(deserializer) .and_then(|string| from_hex(&string).map_err(|err| Error::custom(err.to_string()))) .and_then(|bytes: Vec| { + if bytes.len() != 64 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 64]; b.copy_from_slice(&bytes[0..64]); DalekSignature::try_from(b).map_err(|err| Error::custom(err.to_string())) @@ -422,6 +437,9 @@ pub mod option_dalek_sig_serde { Some(string) => from_hex(&string) .map_err(|err| Error::custom(err.to_string())) .and_then(|bytes: Vec| { + if bytes.len() != 64 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 64]; b.copy_from_slice(&bytes[0..64]); DalekSignature::try_from(b) @@ -461,6 +479,9 @@ pub mod option_dalek_sig_base64 { Some(string) => base64::decode(&string) .map_err(|err| Error::custom(err.to_string())) .and_then(|bytes: Vec| { + if bytes.len() != 64 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 64]; b.copy_from_slice(&bytes[0..64]); DalekSignature::try_from(b) @@ -671,6 +692,9 @@ pub mod uuid_base64 { base64::decode(&string).map_err(|err| Error::custom(err.to_string())) }) .and_then(|bytes: Vec| { + if bytes.len() != 16 { + return Err(Error::custom("unexpected byte length")); + } let mut b = [0u8; 16]; b.copy_from_slice(&bytes[0..16]); Ok(Uuid::from_bytes(b)) @@ -744,4 +768,22 @@ mod test { assert_eq!(s, deserialized); } } + + #[test] + fn rejects_malformed_dalek_fields() { + // A too-short fixed-length field used to panic inside copy_from_slice during + // deserialization; it must now surface as an error instead. + let json = serde_json::to_string(&SerTest::random()).unwrap(); + let value: serde_json::Value = serde_json::from_str(&json).unwrap(); + + // Signature field shorter than 64 bytes (exercises the dalek_sig_serde guard). + let mut bad = value.clone(); + bad["sig"] = serde_json::Value::String("00".to_string()); + assert!(serde_json::from_value::(bad).is_err()); + + // Public-key field shorter than 32 bytes. + let mut bad = value; + bad["pub_key"] = serde_json::Value::String("00".to_string()); + assert!(serde_json::from_value::(bad).is_err()); + } }