Fixes for issues reported in pentesting audit#338
Conversation
| // Validate all provided values before mutating any metadata fields. | ||
| if (option::is_some(&name)){ | ||
| let new_name = option::borrow(&name); | ||
| assert!(string::length(new_name) <= MAX_NAME_LENGTH, error::out_of_range(ENAME_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&symbol)){ | ||
| let new_symbol = option::borrow(&symbol); | ||
| assert!(string::length(new_symbol) <= MAX_SYMBOL_LENGTH, error::out_of_range(ESYMBOL_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&decimals)){ | ||
| let new_decimals = option::borrow(&decimals); | ||
| assert!(*new_decimals <= MAX_DECIMALS, error::out_of_range(EDECIMALS_TOO_LARGE)); | ||
| }; | ||
| if (option::is_some(&icon_uri)){ | ||
| let new_icon_uri = option::borrow(&icon_uri); | ||
| assert!(string::length(new_icon_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&project_uri)){ | ||
| let new_project_uri = option::borrow(&project_uri); | ||
| assert!(string::length(new_project_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); | ||
| }; | ||
|
|
There was a problem hiding this comment.
Rather than 2 conditional statement for a same cause
Can we just update the values after the assert
For example
if (option::is_some(&name)){ let new_name = option::extract(&mut name); assert!(string::length(new_name) <= MAX_NAME_LENGTH, error::out_of_range(ENAME_TOO_LONG)); mutable_metadata.name = new_name; };
Can we use destroy_some instead of extract Dr @sjoshisupra ?
There was a problem hiding this comment.
In that case, if the 2nd or 3rd assertion fails, the 1st metadata would've been set incorrectly already. Therefore, checking for all the metadata limits before assigning them might be better, no?
I am not sure how the Move compiler will process this flow.
There was a problem hiding this comment.
@supra-yoga if one of the asserts gets failed then the txn will get failed so there wont be any partial state update
There was a problem hiding this comment.
@anshuman-supraoracles , @supra-yoga ,
Yes we can use destroy_some instead of extract but we need to know that the caller has no use of the same after the call. extract will make it option::none, whereas destroy_some would destroy the option altogether.
There was a problem hiding this comment.
I agree, no need to perform option::is_some check twice for every field. If anything fails, the whole tx reverts anyway.
There was a problem hiding this comment.
Understood. Will update it as per the suggestion and also use option::destroy_some. Thanks @anshuman-supraoracles.
aregng
left a comment
There was a problem hiding this comment.
Approved automation_registry changes.
Left couple of suggestions to be fixed based on applicability.
| } | ||
|
|
||
| /// Ensures removing exactly one member keeps the committee type invariants valid. | ||
| fun validate_committee_type_after_member_removal(committee: &CommitteeInfo) { |
There was a problem hiding this comment.
Suggestion for the function name: assert_removal_allowed
| } else if (committee_type == CLAN) { | ||
| // 2f+1, number of nodes in a clan committee should be odd and greater than 3 | ||
| // 2f+1, number of nodes in a clan committee should be odd and greater than or equal to 3 | ||
| assert!(num_of_nodes >= 3 && num_of_nodes % 2 == 1, INVALID_NODE_NUMBERS); |
There was a problem hiding this comment.
INVALID_NODE_NUMBERS sound to generic, maybe error per committee type will be more descriptive. e.g. INVALID_CLAN_NODE_NUMBERS, INVALID_FAMILIY_NODE_NUMBERS
There was a problem hiding this comment.
ECLAN_TOO_SMALL_OR_EVEN_NODES_IN_CLAN
| /// Ensures removing exactly one member keeps the committee type invariants valid. | ||
| fun validate_committee_type_after_member_removal(committee: &CommitteeInfo) { | ||
| let current_num_of_nodes = simple_map::length(&committee.map); | ||
| assert!(current_num_of_nodes > 0, INVALID_NODE_NUMBERS); |
There was a problem hiding this comment.
and here INVALID_NODE_NUMBERS ZERO_COMMITTE_NODE_NUMBER
There was a problem hiding this comment.
EZERO_NUM_NODE let us use E prefix for error codes.
| /// | ||
| /// Typically used in `X::on_new_epoch()` where X is an on-chaon config. | ||
| public fun extract<T: store>(): T acquires PendingConfigs { | ||
| public(friend) fun extract<T: store>(): T acquires PendingConfigs { |
There was a problem hiding this comment.
With this we can't upgrade I think.
we need to have another interface for the same.
which is included in fwk upgrade PR LINK
There was a problem hiding this comment.
@dhaval-supraoracles , has the necessary changes been done to the calling code? Are they now calling extract_v2? I do not see that in the PR link you mentioned above.
There was a problem hiding this comment.
yes all relevant module also uses extract_v2 on the link that I share.
There was a problem hiding this comment.
@dhaval-supraoracles , I see multiple modules where config_buffer:extract is being called. Can you please change them all to extract_v2 and compile/test ? supra_config.move, keyless_account.move and many others.
| /// | ||
| /// Typically used in `X::on_new_epoch()` where X is an on-chaon config. | ||
| public fun extract<T: store>(): T acquires PendingConfigs { | ||
| public(friend) fun extract<T: store>(): T acquires PendingConfigs { |
There was a problem hiding this comment.
All modules that call this function need to be marked as friends of this one. Have they already been marked as such? Also, I'm not sure if this might prevent us from using this function in scripts.
I think a simpler change would be to add system_addresses::assert_supra_framework(framework); as the first line of the function instead.
There was a problem hiding this comment.
Ah, since the method does not take a signer it is not possible to do assert_supra_framework, we have to make changes to all the callers.
There was a problem hiding this comment.
@dhaval-supraoracles Can we make this change in the upcoming FWK upgrade?
There was a problem hiding this comment.
It's already updated on FWK upgrade changes. @supra-yoga
| /// The maximum length of the input string for derived string snapshot. | ||
| /// If we want to increase this, we need to modify BITS_FOR_SIZE in types/src/delayed_fields.rs. | ||
| pub const DERIVED_STRING_INPUT_MAX_LENGTH: usize = 1024; | ||
| pub const DERIVED_STRING_INPUT_MAX_LENGTH: usize = 256; |
There was a problem hiding this comment.
This is a breaking change to the execution semantics and needs to be feature gated. It would be simpler to update the docs to reflect the actual 1024 byte limit, if that is the only place that it is referenced. This will also avoid breaking any objects that have already been created in mainnet with a length between 256 and 1024 bytes. We need to check if any other code expects 256 bytes before doing this.
| let committee_store = borrow_global_mut<CommitteeInfoStore>(com_store_addr); | ||
| let event_handler = borrow_global_mut<SupraCommitteeEventHandler>(get_committeeInfo_address(owner_signer)); | ||
|
|
||
| // If the node is already associated with another committee, remove the stale entry first. |
There was a problem hiding this comment.
I'm not sure how we're using this module, but I know that when we eventually add support for Clans and Families (sub-committees) to the L1 that a node will be able to participate in multiple committees simultaneously. If we intend to use this module for that use case then node_to_committee_map should be updated from SimpleMap<address, u64> to SimpleMap<address, vector<u64>> instead. Perhaps Dr @sjoshisupra knows more about the intended purpose of this module.
There was a problem hiding this comment.
For now, this is used for DORA nodes for discovery @dhaval-supraoracles , but yes, we can modify it to make it more general.
sjoshisupra
left a comment
There was a problem hiding this comment.
@aregng are other issues from the pentest report wrt, gas tracking and estimate handled already?
| /// | ||
| /// Typically used in `X::on_new_epoch()` where X is an on-chaon config. | ||
| public fun extract<T: store>(): T acquires PendingConfigs { | ||
| public(friend) fun extract<T: store>(): T acquires PendingConfigs { |
There was a problem hiding this comment.
@dhaval-supraoracles , has the necessary changes been done to the calling code? Are they now calling extract_v2? I do not see that in the PR link you mentioned above.
| /// | ||
| /// Typically used in `X::on_new_epoch()` where X is an on-chaon config. | ||
| public fun extract<T: store>(): T acquires PendingConfigs { | ||
| public(friend) fun extract<T: store>(): T acquires PendingConfigs { |
| } else if (committee_type == CLAN) { | ||
| // 2f+1, number of nodes in a clan committee should be odd and greater than 3 | ||
| // 2f+1, number of nodes in a clan committee should be odd and greater than or equal to 3 | ||
| assert!(num_of_nodes >= 3 && num_of_nodes % 2 == 1, INVALID_NODE_NUMBERS); |
There was a problem hiding this comment.
ECLAN_TOO_SMALL_OR_EVEN_NODES_IN_CLAN
| /// Ensures removing exactly one member keeps the committee type invariants valid. | ||
| fun validate_committee_type_after_member_removal(committee: &CommitteeInfo) { | ||
| let current_num_of_nodes = simple_map::length(&committee.map); | ||
| assert!(current_num_of_nodes > 0, INVALID_NODE_NUMBERS); |
There was a problem hiding this comment.
EZERO_NUM_NODE let us use E prefix for error codes.
| let committee_store = borrow_global_mut<CommitteeInfoStore>(com_store_addr); | ||
| let event_handler = borrow_global_mut<SupraCommitteeEventHandler>(get_committeeInfo_address(owner_signer)); | ||
|
|
||
| // If the node is already associated with another committee, remove the stale entry first. |
There was a problem hiding this comment.
For now, this is used for DORA nodes for discovery @dhaval-supraoracles , but yes, we can modify it to make it more general.
| // Validate all provided values before mutating any metadata fields. | ||
| if (option::is_some(&name)){ | ||
| let new_name = option::borrow(&name); | ||
| assert!(string::length(new_name) <= MAX_NAME_LENGTH, error::out_of_range(ENAME_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&symbol)){ | ||
| let new_symbol = option::borrow(&symbol); | ||
| assert!(string::length(new_symbol) <= MAX_SYMBOL_LENGTH, error::out_of_range(ESYMBOL_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&decimals)){ | ||
| let new_decimals = option::borrow(&decimals); | ||
| assert!(*new_decimals <= MAX_DECIMALS, error::out_of_range(EDECIMALS_TOO_LARGE)); | ||
| }; | ||
| if (option::is_some(&icon_uri)){ | ||
| let new_icon_uri = option::borrow(&icon_uri); | ||
| assert!(string::length(new_icon_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&project_uri)){ | ||
| let new_project_uri = option::borrow(&project_uri); | ||
| assert!(string::length(new_project_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); | ||
| }; | ||
|
|
There was a problem hiding this comment.
@anshuman-supraoracles , @supra-yoga ,
Yes we can use destroy_some instead of extract but we need to know that the caller has no use of the same after the call. extract will make it option::none, whereas destroy_some would destroy the option altogether.
| // Validate all provided values before mutating any metadata fields. | ||
| if (option::is_some(&name)){ | ||
| let new_name = option::borrow(&name); | ||
| assert!(string::length(new_name) <= MAX_NAME_LENGTH, error::out_of_range(ENAME_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&symbol)){ | ||
| let new_symbol = option::borrow(&symbol); | ||
| assert!(string::length(new_symbol) <= MAX_SYMBOL_LENGTH, error::out_of_range(ESYMBOL_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&decimals)){ | ||
| let new_decimals = option::borrow(&decimals); | ||
| assert!(*new_decimals <= MAX_DECIMALS, error::out_of_range(EDECIMALS_TOO_LARGE)); | ||
| }; | ||
| if (option::is_some(&icon_uri)){ | ||
| let new_icon_uri = option::borrow(&icon_uri); | ||
| assert!(string::length(new_icon_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); | ||
| }; | ||
| if (option::is_some(&project_uri)){ | ||
| let new_project_uri = option::borrow(&project_uri); | ||
| assert!(string::length(new_project_uri) <= MAX_URI_LENGTH, error::out_of_range(EURI_TOO_LONG)); | ||
| }; | ||
|
|
There was a problem hiding this comment.
I agree, no need to perform option::is_some check twice for every field. If anything fails, the whole tx reverts anyway.
| /// | ||
| /// Typically used in `X::on_new_epoch()` where X is an on-chaon config. | ||
| public fun extract<T: store>(): T acquires PendingConfigs { | ||
| public(friend) fun extract<T: store>(): T acquires PendingConfigs { |
There was a problem hiding this comment.
@dhaval-supraoracles , I see multiple modules where config_buffer:extract is being called. Can you please change them all to extract_v2 and compile/test ? supra_config.move, keyless_account.move and many others.
| /// | ||
| /// Typically used in `X::on_new_epoch()` where X is an on-chaon config. | ||
| public(friend) fun extract<T: store>(): T acquires PendingConfigs { | ||
| public fun extract<T: store>(): T acquires PendingConfigs { |
There was a problem hiding this comment.
Isn't keeping it without public(friend) a vulnerability that was pointed out? I believe that since public interface can not be changed, we decided to keep the interface but use assert!(false, ENOT_SUPPORTED) for this and we should use a separate public(friend) that performs this buffer extraction, or use a public interface that takes a signer and check that it is framework signer. @supra-yoga
| let no_votes = proposal.no_votes; | ||
|
|
||
| if (yes_votes > no_votes && | ||
| yes_votes + no_votes >= proposal.min_vote_threshold) { |
There was a problem hiding this comment.
here we want to consider yes+no as min vote threshold right?
not yes > min vote threshold.
| /// 10. `u64` - The time at which the task was registered (timestamp). | ||
| /// 11. `u8` - The state of the task (e.g., active, cancelled, completed). | ||
| /// 12. `u64` - The locked fee reserved for the next epoch execution. | ||
| public fun deconstruct_task_metadata( |
There was a problem hiding this comment.
I think this will break the upgrade as public function signature is being changed.
There was a problem hiding this comment.
Yes @dhaval Purohit, that change was intended. Just curious, can you kindly explain how updating the return types will break the upgrade?
I am assuming the users of these methods should mandatorily update their code to avoid this breakage.
There was a problem hiding this comment.
Changing a public function's return type is considered a breaking change because:
External modules or scripts may already depend on the existing return type
The Move type system enforces strict type safety at the call site
Existing callers would break at runtime if the return type changed unexpectedly
sjoshisupra
left a comment
There was a problem hiding this comment.
What finally happened to extract and extract_v2? I do not see those in the diff. @supra-yoga
| /// 9. `u64` - The time at which the task was registered (timestamp). | ||
| /// 10. `u8` - The state of the task (e.g., active, cancelled, completed). | ||
| /// 11. `u64` - The locked fee reserved for the next epoch execution. | ||
| public fun deconstruct_task_metadata( |
There was a problem hiding this comment.
deconstruct_task_metadata signature can not be changed during upgrade. Not sure if that is what is happening since I see above in the diff that decontruct_task_metadata is deleted and deconstruct_task_metadata_v2 is added. Can you confirm that original signature is being preserved? @supra-yoga
Resolves all issues in #330