Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sui-types] Update bounded visitor to take an environment variable override at startup #18175

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 willing 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
Loading