Protect against OOB offset in variable list SSZ decoding (#974)

* Add "pretty-ssz" tool to lcli

* Protect against OOB SSZ offset

* Add more work on decoding

* Fix benches

* Add more decode fixes

* Rename fixed_ptr

* Add, fix tests

* Add extra test

* Increase SSZ decode error granularity

* Ripples new error types across ssz crate

* Add comment to `sanitize_offset`

* Introduce max_len to SSZ list decoding

* Restrict FixedVector, check for zero-len items

* Double check for empty list

* Address Michael's comment
This commit is contained in:
Paul Hauner
2020-04-20 15:35:47 +10:00
committed by GitHub
parent 32074f0d09
commit b374ead24b
8 changed files with 263 additions and 82 deletions

View File

@@ -29,6 +29,14 @@ pub fn parse_path_with_default_in_home_dir(
})
}
pub fn parse_path(matches: &ArgMatches, name: &'static str) -> Result<PathBuf, String> {
matches
.value_of(name)
.ok_or_else(|| format!("{} not specified", name))?
.parse::<PathBuf>()
.map_err(|e| format!("Unable to parse {}: {}", name, e))
}
pub fn parse_u64(matches: &ArgMatches, name: &'static str) -> Result<u64, String> {
matches
.value_of(name)

View File

@@ -9,6 +9,7 @@ mod helpers;
mod interop_genesis;
mod new_testnet;
mod parse_hex;
mod parse_ssz;
mod refund_deposit_contract;
mod transition_blocks;
@@ -36,7 +37,6 @@ fn main() {
.long("spec")
.value_name("STRING")
.takes_value(true)
.required(true)
.possible_values(&["minimal", "mainnet"])
.default_value("mainnet")
)
@@ -94,6 +94,27 @@ fn main() {
.help("Path to output a SSZ file."),
),
)
.subcommand(
SubCommand::with_name("pretty-ssz")
.about("Parses a file of raw (not hex-encoded) SSZ bytes")
.arg(
Arg::with_name("type")
.index(1)
.value_name("TYPE")
.takes_value(true)
.required(true)
.possible_values(&["SignedBeaconBlock"])
.help("The schema of the supplied SSZ."),
)
.arg(
Arg::with_name("path")
.index(2)
.value_name("SSZ_FILE")
.takes_value(true)
.required(true)
.help("A file contains SSZ bytes"),
),
)
.subcommand(
SubCommand::with_name("pretty-hex")
.about("Parses SSZ encoded as ASCII 0x-prefixed hex")
@@ -452,6 +473,14 @@ fn run<T: EthSpec>(env_builder: EnvironmentBuilder<T>, matches: &ArgMatches) {
}
("transition-blocks", Some(matches)) => run_transition_blocks::<T>(matches)
.unwrap_or_else(|e| error!("Failed to transition blocks: {}", e)),
("pretty-ssz", Some(sub_matches)) => {
let result = match matches.value_of("spec").expect("spec is required by slog") {
"minimal" => parse_ssz::run::<MinimalEthSpec>(sub_matches),
"mainnet" => parse_ssz::run::<MainnetEthSpec>(sub_matches),
_ => unreachable!("guarded by slog possible_values"),
};
result.unwrap_or_else(|e| error!("Failed to run eth1-genesis command: {}", e))
}
("pretty-hex", Some(matches)) => run_parse_hex::<T>(matches)
.unwrap_or_else(|e| error!("Failed to pretty print hex: {}", e)),
("deploy-deposit-contract", Some(matches)) => {

40
lcli/src/parse_ssz.rs Normal file
View File

@@ -0,0 +1,40 @@
use crate::helpers::parse_path;
use clap::ArgMatches;
use serde::Serialize;
use ssz::Decode;
use std::fs::File;
use std::io::Read;
use types::{EthSpec, SignedBeaconBlock};
pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
let type_str = matches
.value_of("type")
.ok_or_else(|| "No type supplied".to_string())?;
let path = parse_path(matches, "path")?;
info!("Type: {:?}", type_str);
let mut bytes = vec![];
let mut file = File::open(&path).map_err(|e| format!("Unable to open {:?}: {}", path, e))?;
file.read_to_end(&mut bytes)
.map_err(|e| format!("Unable to read {:?}: {}", path, e))?;
match type_str {
"SignedBeaconBlock" => decode_and_print::<SignedBeaconBlock<T>>(&bytes)?,
other => return Err(format!("Unknown type: {}", other)),
};
Ok(())
}
fn decode_and_print<T: Decode + Serialize>(bytes: &[u8]) -> Result<(), String> {
let item = T::from_ssz_bytes(&bytes).map_err(|e| format!("Ssz decode failed: {:?}", e))?;
println!(
"{}",
serde_yaml::to_string(&item)
.map_err(|e| format!("Unable to write object to YAML: {:?}", e))?
);
Ok(())
}