-
Notifications
You must be signed in to change notification settings - Fork 319
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
fix(sns): Avoid producing overly large errors upon UpgradeSnsControlledCanister
proposal invalidation
#2877
base: master
Are you sure you want to change the base?
Conversation
if new_canister_wasm.len() < 4 | ||
|| new_canister_wasm[..4] != RAW_WASM_HEADER[..] | ||
&& new_canister_wasm[..3] != GZIPPED_WASM_HEADER[..] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems orthogonal to the problem(s) mentioned in the PR title+description. Perhaps, mention that there are some additional small entrained fixes?
defects.push(format!("the maximum canister WASM and argument size for UpgradeSnsControlledCanister is {} bytes.", MAX_INSTALL_CODE_WASM_AND_ARG_SIZE)); | ||
defects.push(format!( | ||
"the maximum canister WASM and argument size \ | ||
for UpgradeSnsControlledCanister is {} bytes.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
align. rustfmt is great sometimes. All kneel before the robots!
/// Return an Err whose inner value describes (in detail) what is wrong with a | ||
/// field value, and where within some (Protocol Buffers message) struct. | ||
fn field_err(field_name: &str, field_value: impl Debug, defect: &str) -> Result<(), String> { | ||
/// Return an Err whose inner value describes (in detail) what is wrong with a field value (should |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Return an Err whose inner value describes (in detail) what is wrong with a field value (should | |
/// Return an Err whose inner value describes (in detail) what is wrong with a field value (e.g. should |
?
let mut bounded_field_value = String::new(); | ||
// Searching for the longest byte prefix that is a valid string is expensive in the worst case. | ||
// This is because unicode characters are unbounded. Instead, concatenate characters one-by-one, | ||
// until we either run out of characters or exceed the limit (in which case we pop the last | ||
// character to still comply with the limits). Note that a `char` is always 4 bytes, but | ||
// pushing it to a string does not always increase a string's byte size by 4 bytes. | ||
// For example: | ||
// ``` | ||
// println!("bytes = {}", std::mem::size_of_val(&'\u{200D}')); // bytes = 4 | ||
// println!("bytes = {}", std::mem::size_of_val("\u{200D}")); // bytes = 3 | ||
// ``` | ||
for c in field_value.chars() { | ||
bounded_field_value.push(c); | ||
if bounded_field_value.len() > MAX_SCALAR_FIELD_LEN_BYTES { | ||
bounded_field_value.pop(); | ||
break; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh. You are just trying to limit the length of the defect description, right? How about this: https://sourcegraph.com/github.com/dfinity/ic/-/blob/rs/nervous_system/string/src/lib.rs?L25:8-25:23
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That function doesn't seem safe for arbitrary unicode strings. For example, it would panic in the following case:
clamp_string_len("\u{200D}\u{200D}\u{200D}", 2)
Err(format!( | ||
"The value in field {} is {}: {:?}", | ||
field_name, defect, field_value | ||
"The first {} characters of the value in field `{}` are {}: `{}`", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new wording doesn't make sense to me. It should say what bad property the field value has. E.g. "The value in foo is not prime: 42".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll improve the wording.
fn field_err(field_name: &str, field_value: String, defect: &str) -> Result<(), String> { | ||
let mut bounded_field_value = String::new(); | ||
// Searching for the longest byte prefix that is a valid string is expensive in the worst case. | ||
// This is because unicode characters are unbounded. Instead, concatenate characters one-by-one, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unicode characters are unbounded
Whaa??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, this comment is misleading. Actually there are ligatures of unbounded size, but they would be represented as multiple char
s in Rust's utf-8 encoding. I'll adjust the comment.
(input_value, observer_err) | ||
}; | ||
|
||
// Scenario A: maximum size that still fits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Scenario A: maximum size that still fits. | |
// Value is (barely) not too large to be fully included in the defect description. |
assert!(observer_err.contains(&input_value)); | ||
} | ||
|
||
// Scenario B: minimum size that no longer fits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Scenario B: minimum size that no longer fits. | |
// Value is too large to be fully included in the defect description. |
let input_value: String = (0..value_size).map(|_| '🤝').collect(); | ||
// Sanity check: We construct a string in which each character is encoded as 4 bytes. | ||
assert_eq!(input_value.len(), 4 * input_value.chars().count()); | ||
let observer_err = field_err("foo", input_value.clone(), "bar").unwrap_err(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let observer_err = field_err("foo", input_value.clone(), "bar").unwrap_err(); | |
let observer_err = field_err("field_name", input_value.clone(), "ugly").unwrap_err(); | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I see the point to have these dummy values over others?
|
||
#[test] | ||
fn test_giant_field_err() { | ||
let expected_upper_bound = 12_500; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let expected_upper_bound = 12_500; | |
let barely_not_too_large_len = 12_500; |
If a governance error is too large (over 2 MiB), it cannot be sent back to the client, and the replica just drops the error with the following symptoms, e.g.:
This PR bounds the maximum size of errors that can be produced upon
UpgradeSnsControlledCanister
proposal invalidation. In particular, SNS Governance no longer incorporates the entire (invalidated) WASM bytes string into validation errors.Additional measures are taken to bound the maximal byte size of common proposal fields, such as title, url, motion text, etc., while still attempting to provide useful error messages.