From f29fc9352f32f13150daf0ecc26abf9d9574da2a Mon Sep 17 00:00:00 2001
From: Nym Seddon <64070857+unseddd@users.noreply.github.com>
Date: Wed, 12 Aug 2020 09:09:46 +0000
Subject: [PATCH] Check Slatepack file size before loading contents (#495)

Check Slatepack file size is within valid range before loading contents
into memory

Co-authored-by: Nym Seddon <unseddd@shh.xyz>
---
 impls/src/adapters/slatepack.rs   | 97 ++++++++++++++++++++++++++++++-
 libwallet/src/lib.rs              |  2 +-
 libwallet/src/slatepack/armor.rs  | 16 ++++-
 libwallet/src/slatepack/mod.rs    |  4 +-
 libwallet/src/slatepack/packer.rs |  9 +--
 5 files changed, 119 insertions(+), 9 deletions(-)

diff --git a/impls/src/adapters/slatepack.rs b/impls/src/adapters/slatepack.rs
index 2aa32494..f47b3852 100644
--- a/impls/src/adapters/slatepack.rs
+++ b/impls/src/adapters/slatepack.rs
@@ -13,11 +13,11 @@
 // limitations under the License.
 
 /// Slatepack Output 'plugin' implementation
-use std::fs::File;
+use std::fs::{metadata, File};
 use std::io::{Read, Write};
 use std::path::PathBuf;
 
-use crate::libwallet::{Error, ErrorKind, Slate, Slatepack, SlatepackBin, Slatepacker};
+use crate::libwallet::{slatepack, Error, ErrorKind, Slate, Slatepack, SlatepackBin, Slatepacker};
 use crate::{SlateGetter, SlatePutter};
 use grin_wallet_util::byte_ser;
 
@@ -39,6 +39,17 @@ impl<'a> PathToSlatepack<'a> {
 	}
 
 	pub fn get_slatepack_file_contents(&self) -> Result<Vec<u8>, Error> {
+		let metadata = metadata(&self.pathbuf)?;
+		let len = metadata.len();
+		let min_len = slatepack::min_size();
+		let max_len = slatepack::max_size();
+		if len < min_len || len > max_len {
+			let msg = format!(
+				"Data is invalid length: {} | min: {}, max: {} |",
+				len, min_len, max_len
+			);
+			return Err(ErrorKind::SlatepackDeser(msg).into());
+		}
 		let mut pub_tx_f = File::open(&self.pathbuf)?;
 		let mut data = Vec::new();
 		pub_tx_f.read_to_end(&mut data)?;
@@ -84,3 +95,85 @@ impl<'a> SlateGetter for PathToSlatepack<'a> {
 		Ok((self.packer.get_slate(&slatepack)?, true))
 	}
 }
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use std::fs;
+
+	use grin_wallet_util::grin_core::global;
+
+	fn clean_output_dir(test_dir: &str) {
+		let _ = fs::remove_dir_all(test_dir);
+	}
+
+	fn setup(test_dir: &str) {
+		clean_output_dir(test_dir);
+	}
+
+	const SLATEPACK_DIR: &'static str = "target/test_output/slatepack";
+
+	#[test]
+	fn pathbuf_get_file_contents() {
+		global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
+		setup(SLATEPACK_DIR);
+
+		fs::create_dir_all(SLATEPACK_DIR).unwrap();
+		let sp_path = PathBuf::from(SLATEPACK_DIR).join("pack_file");
+
+		// set Slatepack file to minimum allowable size
+		{
+			let f = File::create(sp_path.clone()).unwrap();
+			f.set_len(slatepack::min_size()).unwrap();
+		}
+
+		let args = slatepack::SlatepackerArgs {
+			sender: None,
+			recipients: vec![],
+			dec_key: None,
+		};
+		let packer = Slatepacker::new(args);
+
+		let mut pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
+		assert!(pack_path.get_slatepack_file_contents().is_ok());
+
+		pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
+		assert!(pack_path.get_slatepack_file_contents().is_ok());
+
+		// set Slatepack file to maximum allowable size
+		{
+			let f = File::create(sp_path.clone()).unwrap();
+			f.set_len(slatepack::max_size()).unwrap();
+		}
+
+		pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
+		assert!(pack_path.get_slatepack_file_contents().is_ok());
+
+		pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
+		assert!(pack_path.get_slatepack_file_contents().is_ok());
+
+		// set Slatepack file below minimum allowable size
+		{
+			let f = File::create(sp_path.clone()).unwrap();
+			f.set_len(slatepack::min_size() - 1).unwrap();
+		}
+
+		pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
+		assert!(pack_path.get_slatepack_file_contents().is_err());
+
+		pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
+		assert!(pack_path.get_slatepack_file_contents().is_err());
+
+		// set Slatepack file above maximum allowable size
+		{
+			let f = File::create(sp_path.clone()).unwrap();
+			f.set_len(slatepack::max_size() + 1).unwrap();
+		}
+
+		pack_path = PathToSlatepack::new(sp_path.clone(), &packer, true);
+		assert!(pack_path.get_slatepack_file_contents().is_err());
+
+		pack_path = PathToSlatepack::new(sp_path.clone(), &packer, false);
+		assert!(pack_path.get_slatepack_file_contents().is_err());
+	}
+}
diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs
index 5121c52c..a9dce0b1 100644
--- a/libwallet/src/lib.rs
+++ b/libwallet/src/lib.rs
@@ -52,7 +52,7 @@ mod error;
 mod internal;
 mod slate;
 pub mod slate_versions;
-mod slatepack;
+pub mod slatepack;
 mod types;
 
 pub use crate::error::{Error, ErrorKind};
diff --git a/libwallet/src/slatepack/armor.rs b/libwallet/src/slatepack/armor.rs
index 74120c1b..7988ce86 100644
--- a/libwallet/src/slatepack/armor.rs
+++ b/libwallet/src/slatepack/armor.rs
@@ -21,7 +21,7 @@
 // Finally add armor framing and space/newline formatting as desired
 
 use crate::{Error, ErrorKind};
-use grin_wallet_util::byte_ser;
+use grin_wallet_util::{byte_ser, grin_core::global::max_tx_weight};
 use regex::Regex;
 use sha2::{Digest, Sha256};
 use std::str;
@@ -33,6 +33,20 @@ pub static HEADER: &str = "BEGINSLATEPACK.";
 static FOOTER: &str = ". ENDSLATEPACK.";
 const WORD_LENGTH: usize = 15;
 const WORDS_PER_LINE: usize = 200;
+const WEIGHT_RATIO: u64 = 32;
+
+/// Maximum size for an armored Slatepack file
+pub fn max_size() -> u64 {
+	max_tx_weight()
+		.saturating_mul(WEIGHT_RATIO)
+		.saturating_add(HEADER.len() as u64)
+		.saturating_add(FOOTER.len() as u64)
+}
+
+/// Minimum size for an armored Slatepack file or stream
+pub fn min_size() -> u64 {
+	HEADER.len() as u64
+}
 
 lazy_static! {
 	static ref HEADER_REGEX: Regex =
diff --git a/libwallet/src/slatepack/mod.rs b/libwallet/src/slatepack/mod.rs
index ab72b94b..c2dbcafd 100644
--- a/libwallet/src/slatepack/mod.rs
+++ b/libwallet/src/slatepack/mod.rs
@@ -12,12 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//! Functions and types for handling Slatepack transactions
+
 mod address;
 mod armor;
 mod packer;
 mod types;
 
 pub use self::address::SlatepackAddress;
-pub use self::armor::SlatepackArmor;
+pub use self::armor::{max_size, min_size, SlatepackArmor};
 pub use self::packer::{Slatepacker, SlatepackerArgs};
 pub use self::types::{Slatepack, SlatepackBin};
diff --git a/libwallet/src/slatepack/packer.rs b/libwallet/src/slatepack/packer.rs
index ac7578b1..f42b4853 100644
--- a/libwallet/src/slatepack/packer.rs
+++ b/libwallet/src/slatepack/packer.rs
@@ -16,11 +16,11 @@ use std::convert::TryFrom;
 use std::str;
 
 use super::armor::HEADER;
-use crate::{Error, ErrorKind};
 use crate::{
-	Slate, SlateVersion, Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin,
+	slatepack, Slate, SlateVersion, Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin,
 	VersionedBinSlate, VersionedSlate,
 };
+use crate::{Error, ErrorKind};
 
 use grin_wallet_util::byte_ser;
 
@@ -50,8 +50,9 @@ impl<'a> Slatepacker<'a> {
 	/// return slatepack
 	pub fn deser_slatepack(&self, data: &[u8], decrypt: bool) -> Result<Slatepack, Error> {
 		// check if data is armored, if so, remove and continue
-		if data.len() < super::armor::HEADER.len() {
-			let msg = format!("Data too short");
+		let data_len = data.len() as u64;
+		if data_len < slatepack::min_size() || data_len > slatepack::max_size() {
+			let msg = format!("Data invalid length");
 			return Err(ErrorKind::SlatepackDeser(msg).into());
 		}