Skip to content

Commit

Permalink
Merge pull request #148 from Chia-Network/sanitize-int
Browse files Browse the repository at this point in the history
sanitize_uint improvement
  • Loading branch information
arvidn authored Mar 15, 2023
2 parents 738ff1d + 7dfc2bc commit 5ff7c31
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 341 deletions.
6 changes: 6 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,9 @@ name = "parse-spends"
path = "fuzz_targets/parse-spends.rs"
test = false
doc = false

[[bin]]
name = "sanitize-uint"
path = "fuzz_targets/sanitize-uint.rs"
test = false
doc = false
48 changes: 48 additions & 0 deletions fuzz/fuzz_targets/sanitize-uint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#![no_main]
use libfuzzer_sys::fuzz_target;

use clvmr::allocator::Allocator;
use chia::gen::sanitize_int::{sanitize_uint, SanitizedUint};
use chia::gen::validation_error::{ErrorCode, ValidationErr};

fuzz_target!(|data: &[u8]| {
let mut a = Allocator::new();
let atom = a.new_atom(data).unwrap();
match sanitize_uint(&a, atom, 8, ErrorCode::InvalidCoinAmount) {
Ok(SanitizedUint::Ok(_)) => {
assert!(data.len() <= 9);
if data.len() == 9 {
assert!(data[0] == 0);
}
},
Ok(SanitizedUint::NegativeOverflow) => {
assert!(data.len() > 0 && (data[0] & 0x80) != 0);
},
Ok(SanitizedUint::PositiveOverflow) => {
assert!(data.len() > 8);
},
Err(ValidationErr(n, c)) => {
assert!(n == atom);
assert!(c == ErrorCode::InvalidCoinAmount);
}
}

match sanitize_uint(&a, atom, 4, ErrorCode::InvalidCoinAmount) {
Ok(SanitizedUint::Ok(_)) => {
assert!(data.len() <= 5);
if data.len() == 5 {
assert!(data[0] == 0);
}
},
Ok(SanitizedUint::NegativeOverflow) => {
assert!(data.len() > 0 && (data[0] & 0x80) != 0);
},
Ok(SanitizedUint::PositiveOverflow) => {
assert!(data.len() > 4);
},
Err(ValidationErr(n, c)) => {
assert!(n == atom);
assert!(c == ErrorCode::InvalidCoinAmount);
}
}
});
275 changes: 5 additions & 270 deletions src/gen/condition_sanitizers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::sanitize_int::sanitize_uint;
use super::sanitize_int::{sanitize_uint, SanitizedUint};
use super::validation_error::{atom, ErrorCode, ValidationErr};
use clvmr::allocator::{Allocator, NodePtr};
use clvmr::op_utils::u64_from_bytes;

pub fn sanitize_hash(
a: &Allocator,
Expand All @@ -20,76 +19,10 @@ pub fn sanitize_hash(

pub fn parse_amount(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result<u64, ValidationErr> {
// amounts are not allowed to exceed 2^64. i.e. 8 bytes
match sanitize_uint(a, n, 8, code) {
Err(ValidationErr(n, ErrorCode::NegativeAmount)) => Err(ValidationErr(n, code)),
Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)),
Err(r) => Err(r),
Ok(r) => Ok(u64_from_bytes(r)),
}
}

pub fn parse_create_coin_amount(a: &Allocator, n: NodePtr) -> Result<u64, ValidationErr> {
// amounts are not allowed to exceed 2^64. i.e. 8 bytes
let buf = sanitize_uint(a, n, 8, ErrorCode::InvalidCoinAmount)?;
Ok(u64_from_bytes(buf))
}

// a negative height is always true. In this case the
// condition can be ignored and this functon returns 0
pub fn parse_height(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result<u32, ValidationErr> {
// heights are not allowed to exceed 2^32. i.e. 4 bytes
match sanitize_uint(a, n, 4, code) {
// Height is always positive, so a negative requirement is always true,
// just like 0.
Err(ValidationErr(_, ErrorCode::NegativeAmount)) => Ok(0),
Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)),
Err(r) => Err(r),
Ok(r) => Ok(u64_from_bytes(r) as u32),
}
}

// negative seconds are always valid conditions, and will return 0
pub fn parse_seconds(a: &Allocator, n: NodePtr, code: ErrorCode) -> Result<u64, ValidationErr> {
// seconds are not allowed to exceed 2^64. i.e. 8 bytes
match sanitize_uint(a, n, 8, code) {
// seconds is always positive, so a negative requirement is always true,
// we don't need to include this condition
Err(ValidationErr(_, ErrorCode::NegativeAmount)) => Ok(0),
Err(ValidationErr(n, ErrorCode::AmountExceedsMaximum)) => Err(ValidationErr(n, code)),
Err(r) => Err(r),
Ok(r) => Ok(u64_from_bytes(r)),
}
}

// a negative height is a failure, but exceeding the max is a no-op.
// This is used for parsing assert_before_height conditions
pub fn parse_positive_height(
a: &Allocator,
n: NodePtr,
code: ErrorCode,
) -> Result<Option<u32>, ValidationErr> {
// heights are not allowed to exceed 2^32. i.e. 4 bytes
match sanitize_uint(a, n, 4, code) {
Err(ValidationErr(n, ErrorCode::NegativeAmount)) => Err(ValidationErr(n, code)),
Err(ValidationErr(_, ErrorCode::AmountExceedsMaximum)) => Ok(None),
Err(r) => Err(r),
Ok(r) => Ok(Some(u64_from_bytes(r) as u32)),
}
}

// negative seconds are a failure, exceeding the max limit is no-op.
// this is used for parsing assert_before_seconds conditions.
pub fn parse_positive_seconds(
a: &Allocator,
n: NodePtr,
code: ErrorCode,
) -> Result<Option<u64>, ValidationErr> {
// seconds are not allowed to exceed 2^64. i.e. 8 bytes
match sanitize_uint(a, n, 8, code) {
Err(ValidationErr(n, ErrorCode::NegativeAmount)) => Err(ValidationErr(n, code)),
Err(ValidationErr(_, ErrorCode::AmountExceedsMaximum)) => Ok(None),
Err(r) => Err(r),
Ok(r) => Ok(Some(u64_from_bytes(r))),
match sanitize_uint(a, n, 8, code)? {
SanitizedUint::NegativeOverflow => Err(ValidationErr(n, code)),
SanitizedUint::PositiveOverflow => Err(ValidationErr(n, code)),
SanitizedUint::Ok(r) => Ok(r),
}
}

Expand All @@ -107,9 +40,6 @@ pub fn sanitize_announce_msg(
}
}

#[cfg(test)]
use rstest::rstest;

#[cfg(test)]
fn zero_vec(len: usize) -> Vec<u8> {
let mut ret = Vec::<u8>::new();
Expand Down Expand Up @@ -231,198 +161,3 @@ fn test_sanitize_amount() {
Ok(0xffffffffffffffff)
);
}

#[cfg(test)]
fn create_amount_tester(buf: &[u8]) -> Result<u64, ValidationErr> {
let mut a = Allocator::new();
let n = a.new_atom(buf).unwrap();

parse_create_coin_amount(&mut a, n)
}

#[test]
fn test_sanitize_create_coin_amount() {
// negative coin amounts are not allowed
assert_eq!(
create_amount_tester(&[0xff]).unwrap_err().1,
ErrorCode::NegativeAmount
);
// amounts aren't allowed to be too big
let large_buf = [0x7f, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
create_amount_tester(&large_buf).unwrap_err().1,
ErrorCode::AmountExceedsMaximum
);

// this is small enough though
assert_eq!(
create_amount_tester(&[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
Ok(0xffffffffffffffff)
);
}

#[cfg(test)]
fn height_tester(buf: &[u8]) -> Result<Option<u64>, ValidationErr> {
let mut a = Allocator::new();
let n = a.new_atom(buf).unwrap();
Ok(Some(
parse_height(&mut a, n, ErrorCode::AssertHeightAbsolute)? as u64,
))
}

#[cfg(test)]
fn seconds_tester(buf: &[u8]) -> Result<Option<u64>, ValidationErr> {
let mut a = Allocator::new();
let n = a.new_atom(buf).unwrap();
Ok(Some(parse_seconds(
&mut a,
n,
ErrorCode::AssertSecondsAbsolute,
)?))
}

#[cfg(test)]
fn positive_height_tester(buf: &[u8]) -> Result<Option<u64>, ValidationErr> {
let mut a = Allocator::new();
let n = a.new_atom(buf).unwrap();
Ok(parse_positive_height(&mut a, n, ErrorCode::AssertBeforeHeightAbsolute)?.map(|v| v as u64))
}

#[cfg(test)]
fn positive_seconds_tester(buf: &[u8]) -> Result<Option<u64>, ValidationErr> {
let mut a = Allocator::new();
let n = a.new_atom(buf).unwrap();
parse_positive_seconds(&mut a, n, ErrorCode::AssertBeforeSecondsAbsolute)
}

#[cfg(test)]
#[rstest]
// == parse_height
#[case(height_tester, &[0x80], Some(0))]
// negative values are no-ops
#[case(height_tester, &[0xff], Some(0))]
#[case(height_tester, &[0xff, 0], Some(0))]
// leading zeros are sometimes necessary to make values positive
#[case(height_tester, &[0, 0xff], Some(0xff))]
// this is small enough
#[case(height_tester, &[0, 0xff, 0xff, 0xff, 0xff], Some(0xffffffff))]
// == parse_seconds
#[case(seconds_tester, &[0x80], Some(0))]
// negative values are no-ops
#[case(seconds_tester, &[0xff], Some(0))]
#[case(seconds_tester, &[0xff, 0], Some(0))]
// leading zeros are sometimes necessary to make values positive
#[case(seconds_tester, &[0, 0xff], Some(0xff))]
// this is small enough
#[case(seconds_tester, &[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], Some(0xffffffffffffffff))]
// == parse_positive_height
// leading zeros are sometimes necessary to make values positive
#[case(positive_height_tester, &[0, 0xff], Some(0xff))]
// this is small enough
#[case(positive_height_tester, &[0, 0xff, 0xff, 0xff, 0xff], Some(0xffffffff))]
// positive heights are allowed to be > 2^32 (i.e. 5 bytes). it's a no-op
#[case(positive_height_tester, &[0x01, 0xff, 0xff, 0xff, 0xff], None)]
// == parse_positive_seconds
// leading zeros are sometimes necessary to make values positive
#[case(positive_seconds_tester, &[0, 0xff], Some(0xff))]
// this is small enough
#[case(positive_seconds_tester, &[0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], Some(0xffffffffffffffff))]
// before-seconds are allowed to be > 2^64 (i.e. 9 bytes)
#[case(positive_seconds_tester, &[0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], None)]
fn test_parse_ok(
#[case] fun: impl Fn(&[u8]) -> Result<Option<u64>, ValidationErr>,
#[case] buf: &[u8],
#[case] expected: Option<u64>,
) {
println!("test case: {:?} expect: {:?}", buf, expected);
// negative heights can be ignored
assert_eq!(fun(buf).unwrap(), expected);
}

#[cfg(test)]
#[rstest]
// == parse_height
#[case(height_tester, &[0, 0, 0, 0xff], ErrorCode::AssertHeightAbsolute)]
#[case(height_tester, &[0, 0, 0, 0x80], ErrorCode::AssertHeightAbsolute)]
#[case(height_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertHeightAbsolute)]
#[case(height_tester, &[0, 0, 0], ErrorCode::AssertHeightAbsolute)]
#[case(height_tester, &[0], ErrorCode::AssertHeightAbsolute)]
// heights aren't allowed to be > 2^32 (i.e. 5 bytes)
#[case(height_tester, &[0x01, 0xff, 0xff, 0xff, 0xff], ErrorCode::AssertHeightAbsolute)]
// == parse_seconds
#[case(seconds_tester, &[0, 0, 0, 0xff], ErrorCode::AssertSecondsAbsolute)]
#[case(seconds_tester, &[0, 0, 0, 0x80], ErrorCode::AssertSecondsAbsolute)]
#[case(seconds_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertSecondsAbsolute)]
#[case(seconds_tester, &[0, 0, 0], ErrorCode::AssertSecondsAbsolute)]
#[case(seconds_tester, &[0], ErrorCode::AssertSecondsAbsolute)]
// positive seconds are allowed to be > 2^64 (i.e. 9 bytes)
#[case(seconds_tester, &[0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], ErrorCode::AssertSecondsAbsolute)]
// == parse_positive_height
#[case(positive_height_tester, &[0, 0, 0, 0xff], ErrorCode::AssertBeforeHeightAbsolute)]
#[case(positive_height_tester, &[0, 0, 0, 0x80], ErrorCode::AssertBeforeHeightAbsolute)]
#[case(positive_height_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertBeforeHeightAbsolute)]
#[case(positive_height_tester, &[0, 0, 0], ErrorCode::AssertBeforeHeightAbsolute)]
#[case(positive_height_tester, &[0], ErrorCode::AssertBeforeHeightAbsolute)]
// negative values are failures
#[case(positive_height_tester, &[0x80], ErrorCode::AssertBeforeHeightAbsolute)]
#[case(positive_height_tester, &[0xff], ErrorCode::AssertBeforeHeightAbsolute)]
#[case(positive_height_tester, &[0xff, 0], ErrorCode::AssertBeforeHeightAbsolute)]
// == parse_positive_seconds
#[case(positive_seconds_tester, &[0, 0, 0, 0xff], ErrorCode::AssertBeforeSecondsAbsolute)]
#[case(positive_seconds_tester, &[0, 0, 0, 0x80], ErrorCode::AssertBeforeSecondsAbsolute)]
#[case(positive_seconds_tester, &[0, 0, 0, 0x7f], ErrorCode::AssertBeforeSecondsAbsolute)]
#[case(positive_seconds_tester, &[0, 0, 0], ErrorCode::AssertBeforeSecondsAbsolute)]
#[case(positive_seconds_tester, &[0], ErrorCode::AssertBeforeSecondsAbsolute)]
// negative values are failures
#[case(positive_seconds_tester, &[0x80], ErrorCode::AssertBeforeSecondsAbsolute)]
#[case(positive_seconds_tester, &[0xff], ErrorCode::AssertBeforeSecondsAbsolute)]
#[case(positive_seconds_tester, &[0xff, 0], ErrorCode::AssertBeforeSecondsAbsolute)]
fn test_parse_fail(
#[case] fun: impl Fn(&[u8]) -> Result<Option<u64>, ValidationErr>,
#[case] buf: &[u8],
#[case] expected: ErrorCode,
) {
println!("test case: {:?} expect: {:?}", buf, expected);
// negative heights can be ignored
assert_eq!(fun(buf).unwrap_err().1, expected);
}

#[test]
fn test_parse_height_pair() {
let mut a = Allocator::new();
let pair = a.new_pair(a.null(), a.null()).unwrap();
assert_eq!(
parse_height(&mut a, pair, ErrorCode::AssertHeightAbsolute),
Err(ValidationErr(pair, ErrorCode::AssertHeightAbsolute))
);
}

#[test]
fn test_parse_seconds_pair() {
let mut a = Allocator::new();
let pair = a.new_pair(a.null(), a.null()).unwrap();
assert_eq!(
parse_seconds(&mut a, pair, ErrorCode::AssertSecondsAbsolute),
Err(ValidationErr(pair, ErrorCode::AssertSecondsAbsolute))
);
}

#[test]
fn test_parse_positive_height_pair() {
let mut a = Allocator::new();
let pair = a.new_pair(a.null(), a.null()).unwrap();
assert_eq!(
parse_positive_height(&mut a, pair, ErrorCode::AssertHeightAbsolute),
Err(ValidationErr(pair, ErrorCode::AssertHeightAbsolute))
);
}

#[test]
fn test_parse_positive_seconds_pair() {
let mut a = Allocator::new();
let pair = a.new_pair(a.null(), a.null()).unwrap();
assert_eq!(
parse_positive_seconds(&mut a, pair, ErrorCode::AssertSecondsAbsolute),
Err(ValidationErr(pair, ErrorCode::AssertSecondsAbsolute))
);
}
Loading

0 comments on commit 5ff7c31

Please sign in to comment.