Closed dmitretretre closed 1 month ago
Looks like a СhatGPT.
no ddos attack - gas for internal messages is paid by the sender
checking in the loop body will be N times more expensive than checking after the loop, but the result is the same
gas limit overrun error - that's okay.
we don’t need update code function by design
self-destruct send mode is ok. you code is wrong btw.
banning zero addresses is the closest thing to a useful suggestion you've offered so far). However, it's debatable whether it's necessary to do so much foolproofing
replay protection is done with seqno. in TON it is not recommended to use dictionaries in the way you described.
Ok, thx for reviewing. Chat GPT not used here, btw I agree with the claims about some errors. Will I get a reward for a defect taking into gas limit optimisation?
I found three potential bugs in the smart contact https://github.com/ton-blockchain/wallet-contract-v5 . Let's look at the problem of each of them in order and solve them. First problem - The possibility of a DoS attack in the vulnerability of the 'revc_internal' function: The
recv_internal
function accepts and processes messages even if they do not match the expected format. This can be used for a DoS attack by sending a lot of incorrect messages. The original version of the function:The problem is that even if the message does not match the expected format, the contract still spends Gas processing it. To fix this error, you need to add stricter checks. It is also worth considering the limit on the number of messages processed. Corrected version:
Now the 'recv_internal' function has checks for the size of the message with "throw_if", a check for bounced messages has been added, a limit on the number of actions has been introduced, a counter has been added to track processed actions and a check for exceeding the action limit has been added. The first error was sorted out, now the code does not have the opportunity to fall under a DoS attack, we go to the second error. Second Problem - No overflow check. The
verify_c5_actions
function has a check forcount <= 255
, but there is no explicit check for overflow when increasing thecount
. The original version of the function:In order to get rid of the overflow error, you need to refine the function code. Here is the corrected version:
Now the function works correctly, due to the addition of the constant
MAX_ACTIONS
, which sets the maximum number of actions, a new error codeerror::too_many_actions
has been added, thethrow_if' construct to generate an error when the limit is exceeded, Instead of checking
count <= 255at the end of the function, we now check
count > MAX_ACTIONSafter each increment of the counter. Now we proceed to the third error. Third Problem - Potential risk with Gas Limit. In
while (true)loops, there is no explicit limit on the number of iterations in the
process_actions` function. This can lead to the exhaustion of the gas limit when processing a large number of actions. The original version on function:Correct version:
The main improvement in this version is the introduction of a limit on the number of actions to be processed, which prevents potential gas exhaustion when processing too many actions. This makes the contract more resistant to attacks and errors associated with a large number of operations. So, we fixed everything, but there are a couple more nuances that need to be said:
(slice) on_upgrade(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure method_id (0x0101) { return in_msg_body; }
() process_transactions( slice sender_address, slice in_msg_body_original, int my_balance, int msg_value, cell in_msg_full, slice in_msg_body, int just_inited ) impure method_id(0x111) { throw(0xfff); }
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { slice cs = in_msg_full.begin_parse(); int flags = cs~load_uint(4); slice sender_address = cs~load_msg_addr();
}
() process_actions(slice cs, int is_external, int is_extension) impure inline_ref { cell c5_actions = cs~load_maybe_ref(); ifnot (cell_null?(c5_actions)) { set_c5_actions(c5_actions.verify_c5_actions(is_external)); } if (cs~load_int(1) == 0) { ;; has_other_actions return (); }
const int MAX_ACTIONS = 20; int action_count = 0;
const int MIN_BALANCE = 100000000;
while (action_count < MAX_ACTIONS) { ;; existing action processing code
}
throw_if(error::too_many_actions, action_count >= MAX_ACTIONS);
int final_balance = get_balance().pair_first(); throw_if(error::insufficient_balance, final_balance < MIN_BALANCE); }
() recv_internal(cell in_msg_full, slice in_msg_body) impure inline { ;; previous code
if (op == prefix::extension_action) { ;; previous code
} ;; previous code }
() process_signed_request(slice in_msg_body, int is_external) impure inline { slice signature = in_msg_body.get_last_bits(size::signature); slice signed_slice = in_msg_body.remove_last_bits(size::signature);
slice cs = signed_slice.skip_bits(size::message_operation_prefix); (int wallet_id, int valid_until, int seqno) = (cs~load_uint(size::wallet_id), cs~load_uint(size::valid_until), cs~load_uint(size::seqno));
slice data_slice = get_data().begin_parse(); int is_signature_allowed = data_slice~load_int(size::bool); int stored_seqno = data_slice~load_uint(size::seqno); slice data_tail = data_slice; int stored_wallet_id = data_slice~load_uint(size::wallet_id); int public_key = data_slice~load_uint(size::public_key); int is_extensions_not_empty = data_slice.preload_int(1);
throw_if(error::expired, valid_until <= now());
throw_unless(error::invalid_wallet_id, wallet_id == stored_wallet_id);
throw_unless(error::invalid_seqno, seqno == stored_seqno);
int is_signature_valid = check_signature(slice_hash(signed_slice), signature, public_key); ifnot (is_signature_valid) { if (is_external) { throw(error::invalid_signature); } else { return (); } }
throw_if(error::signature_disabled, (~ is_signature_allowed) & is_extensions_not_empty);
if (is_external) { accept_message(); }
;; Increasing the seqno to prevent message reuse stored_seqno = stored_seqno + 1; set_data(begin_cell() .store_int(true, size::bool) .store_uint(stored_seqno, size::seqno) .store_slice(data_tail) .end_cell());
if (is_external) { ;; Commit changes to seqno for external messages commit(); }
;; Adding a unique message identifier to the processed message store cell processed_msgs = get_processed_msgs(); int msg_id = slice_hash(signed_slice); (processed_msgs, int success) = processed_msgs.udict_add_builder?(256, msg_id, begin_cell().store_uint(now(), 32)); throw_unless(error::msg_already_processed, success); set_processed_msgs(processed_msgs);
process_actions(cs, is_external, false); }
cell get_processed_msgs() inline { slice cs = get_data().begin_parse(); cs~skip_bits(size::bool + size::seqno + size::wallet_id + size::public_key); cs~load_dict(); ;; skip extensions return cs~load_dict(); }
() set_processed_msgs(cell processed_msgs) impure inline { slice cs = get_data().begin_parse(); slice data_head = cs~load_bits(size::bool + size::seqno + size::wallet_id + size::public_key); cell extensions = cs~load_dict(); set_data(begin_cell() .store_slice(data_head) .store_dict(extensions) .store_dict(processed_msgs) .end_cell()); }