Skip to content

Commit

Permalink
[sui-types] Update bounded visitor to take an environment variable ov…
Browse files Browse the repository at this point in the history
…erride at startup
  • Loading branch information
tzakian committed Jun 10, 2024
1 parent da6677f commit 6669f4b
Showing 1 changed file with 95 additions and 5 deletions.
100 changes: 95 additions & 5 deletions crates/sui-types/src/object/bounded_visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use move_core_types::{
language_storage::TypeTag,
u256::U256,
};
use once_cell::sync::Lazy;
use tracing::info;

/// Visitor to deserialize annotated values or structs, bounding the size budgeted for types and
/// field names in the output. The visitor does not bound the size of values, because they are
Expand All @@ -27,12 +29,46 @@ pub enum Error {
OutOfBudget,
}

/// Initial budget for deserialization -- we're okay to spend an extra ~1MiB on types and field
/// Environment variable to override the default budget for deserialization. This can be set at
/// runtime to change the maximum size of values that can be deserialized.
const MAX_BOUND_VAR_NAME: &str = "MAX_ANNOTATED_VALUE_SIZE";

/// Default budget for deserialization -- we're okay to spend an extra ~1MiB on types and field
/// information per value.
const DEFAULT_MAX_BOUND: usize = 1024 * 1024;

/// Budget for deserialization into an annotated Move value. This sets the numbers of bytes that we
/// are willy to spend on field names, type names (etc) when deserializing a Move value into an
/// annotated Move value.
///
/// Bounded deserialization is intended for use outside of the validator, and so uses a fixed bound
/// that needs to be set at startup rather than one that is configured as part of the protocol.
///
/// Bounded deserialization is intended for use outside of the validator, and so uses a fixed bound,
/// rather than one that is configured as part of the protocol.
const MAX_BOUND: usize = 1024 * 1024;
/// If the environment variable `MAX_ANNOTATED_VALUE_SIZE` is unset we default to
/// `DEFAULT_MAX_BOUND` which allows ~1MiB additional space usage on types and field information
/// per value.
///
/// This is read only once and after that the value is cached. To change this value you will need
/// to restart the process with the new value set (or the value unset if you wish to use the
/// `DEFAULT_MAX_BOUND` value).
static MAX_BOUND: Lazy<usize> = Lazy::new(|| {
let max_bound_opt = std::env::var(MAX_BOUND_VAR_NAME)
.ok()
.and_then(|s| s.parse().ok());
if let Some(max_bound) = max_bound_opt {
info!(
"Using custom value for '{}' max bound: {}",
MAX_BOUND_VAR_NAME, max_bound
);
max_bound
} else {
info!(
"Using default value for '{}' -- max bound: {}",
MAX_BOUND_VAR_NAME, DEFAULT_MAX_BOUND
);
DEFAULT_MAX_BOUND
}
});

impl BoundedVisitor {
fn new(bound: usize) -> Self {
Expand Down Expand Up @@ -221,7 +257,7 @@ impl Visitor for BoundedVisitor {

impl Default for BoundedVisitor {
fn default() -> Self {
Self::new(MAX_BOUND)
Self::new(*MAX_BOUND)
}
}

Expand Down Expand Up @@ -264,6 +300,60 @@ mod tests {
assert_eq!(value, deser);
}

#[test]
fn test_env_variable_override() {
use A::MoveTypeLayout as T;
use A::MoveValue as V;

let type_layout = layout_(
"0x0::foo::Bar",
vec![
("a", T::U64),
("b", T::Vector(Box::new(T::U64))),
("c", layout_("0x0::foo::Baz", vec![("d", T::U64)])),
],
);

let value = value_(
"0x0::foo::Bar",
vec![
("a", V::U64(42)),
("b", V::Vector(vec![V::U64(43)])),
("c", value_("0x0::foo::Baz", vec![("d", V::U64(44))])),
],
);

let bytes = serialize(value.clone());

let before_value = std::env::var(MAX_BOUND_VAR_NAME).ok();

std::env::set_var(MAX_BOUND_VAR_NAME, "10");
let mut visitor = BoundedVisitor::default();
let err = A::MoveValue::visit_deserialize(&bytes, &type_layout, &mut visitor).unwrap_err();
let expect = expect!["Deserialized value too large"];
expect.assert_eq(&err.to_string());

// Should be unaffected as we already set the value, so this should still fail.
std::env::set_var(MAX_BOUND_VAR_NAME, "1000");
let mut visitor = BoundedVisitor::default();
let err = A::MoveValue::visit_deserialize(&bytes, &type_layout, &mut visitor).unwrap_err();
let expect = expect!["Deserialized value too large"];
expect.assert_eq(&err.to_string());

// set the value back to what it was before if it was previously set, otherwise unset it.
if let Some(previous_value) = before_value {
std::env::set_var(MAX_BOUND_VAR_NAME, previous_value);
} else {
std::env::remove_var(MAX_BOUND_VAR_NAME);
}

// Should still fail as the static value is already set.
let mut visitor = BoundedVisitor::default();
let err = A::MoveValue::visit_deserialize(&bytes, &type_layout, &mut visitor).unwrap_err();
let expect = expect!["Deserialized value too large"];
expect.assert_eq(&err.to_string());
}

#[test]
fn test_too_deep() {
use A::MoveTypeLayout as T;
Expand Down

0 comments on commit 6669f4b

Please sign in to comment.