The Open Network - Audit 1 (Phase 2)

CertiKCode version: not recorded (see the report)

CertiK reviewed TON (The Open Network - Audit 1 (Phase 2)) on 1 Aug 2022: 22 findings, 0 critical; all marked fixed.

No open critical findings
Open the reportOpen TON’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings22

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High001
Medium002
Low0019
High`block->data->deps` can be altered by a maliciousFixed

Malicious actor can receive a valid block, alter the `block->data->deps` array and rebroadcast the block to other nodes. New block will be valid for catchain nodes but invalid for BCP. The block validation works this way: 1. Overlay gets the block and calls `CatChainReceiverImpl::receive_message_from_overlay(src, data)` 2. `catchain_block` is fetched from `data`, the rest is treated `payload` 3. `CatChainReceiverImpl::receive_block(src, catchain_block, payload)` is called 4. `CatChainReceiverImpl::validate_block_sync(block, payload)` is called 5. `CatChainReceivedBlock::pre_validate_block(block, payload)` is called - `block->incarnation` is verified - `block->src` is verified - `block->data->deps.size()` is verified to be not more than `max_deps` - `block->data->prev->src` is verified to be the same as `block->src` - `block->data->prev->height + 1 == block->height` is verified - it is verified that there are no `deps` from the same source - it is verified that `payload` is not empty - **`block->data->deps` array is not checked and not covered by signature** 6. For each `deps` and `prev` as argument `CatChainReceiverImpl::validate_block_sync(dep)` is called - Signature (by `dep.src`) of `block_dep_id` is checked. `block_dep_id = { incarnation, source_hash(dep.src), dep.height, dep.data_hash }` 8. Signature (by `src`) of `block_id` is checked. `block_id = { incarnation, source_hash(src), height, hash(payload) }` of original block 9. `CatChainReceiverImpl::create_block(block, payload)` is called and updates `blocks_[hash(block_id)]` This can lead to network stalling without ability to punish the malicious actor. For example: 1. Malicious actor can clear the `deps` array of received block and force `CatChainReceiverImpl::synchronize_with()` all other nodes. They will accept the modified block and `CatChainReceiverImpl::deliver_block()` immediately to `ValidatorSessionImpl::preprocess_block()` with undefined result. 2. Malicious actor can replace one `dep` with unexisting correctly signed `block_id` and push it to other nodes. They will accept it, will never be able to deliver that block to `ValidatorSession`, and will reject the same block with correct `deps` from other nodes as duplicate.

MediumCatchain DoS can be provoked by valid blocks flooFixed

Malicious actor can create many valid blocks to overflow internal structures of honest nodes and provoke their crash. 1. Many blocks with increasing `height` can be formed and sent by the malicious actor 2. Honest node Overlay gets the block and calls `CatChainReceiverImpl::receive_message_from_overlay(src, data)` 3. `CatChainReceiverImpl::receive_block()` is called 4. `CatChainReceiverImpl::create_block()` is called 5. `CatChainReceivedBlock::create()` allocates memory in heap Old blocks unloading is not implemented in general.

MediumCycles in `deps` are not preventedFixed

According to [Whitepaper](https://test.ton.org/catchain.pdf) the Catchain blocks form a directed acyclic graph using their `deps`. The Catchain code doesn't ensure that. Let's assume: 1. Malicious actor A creates a BlockA and shares its `block_id_A (incarnation, hash(src), height, hash(payload)` with malicious actor B 2. Malicious actor B creates a BlockB, shares its `block_id_B` with A 3. Malicious actor A uses `block_id_B` as part of `BlockA->data->deps` and broadcasts the block in the Catchain 4. Malicious actor B uses `block_id_A` as part of `BlockB->data->deps` and broadcasts the block in the Catchain 5. Honest node C gets two blocks that depend on each other Currently the code works correctly in this situation.

Low`CatChainReceivedBlockImpl::deps_` should be nameFixed

`CatChainReceivedBlockImpl` has fields `block_deps_`, `deps_`, `rev_deps_`, `pending_deps_`. - `block_deps_` holds pointers to blocks from other nodes - `deps_` holds **heights** of this block source chain pieces - `rev_deps_` holds pointers to blocks that depend on this one - `pending_deps_` holds the number of full blocks required to `CatChainReceiverImpl::run_block()` `deps_` name is misleading.

Low`data_hash_` should be named `payload_hash_`Fixed

`CatChainReceivedBlockImpl` has fields `data_`, `hash_`, `payload_` and `data_hash_`. `data_` field is unused. `data_hash_` in fact holds `payload_` hash.

Low`deps` size is not limited for outgoing blocksFixed

Catchain expects every full block received to have not more than `max_deps` dependencies. `max_deps` is 4 at this moment. However, when full block is sent to network, the `deps` size is not checked.

Low`empty()` can be used instead of `!size()`Fixed

```cpp=85 if (!blocks_.size()) { ``` `blocks_.empty()` can be used instead of `!blocks_.size()`.

Low`map::emplace()` result can be used instead of `mFixed

`map::emplace()` returns `std::pair<iterator,bool>`. `iterator` can be used to avoid the call to `map::find()`.

Low`src_id` bounds are not checked before indexing `Fixed

Indexing `blamed_sources_[src_id]` is done without validation of `src_id` correctness.

Low`top_blocks_` element source bounds are not checkFixed

```cpp=35 auto B = *top_blocks_.get_random(); CHECK(B != nullptr); top_blocks_.remove(B->hash()); if (B->source() == sources_.size() || !blamed_sources_[B->source()]) { ``` The code allows `B->source() == sources_.size()`, however, all the insertions to `top_blocks_` ensure the valid `src_`.

Show 10 more
Low`validate_dep_sync()` is never usedFixed

`CatChainReceiverSourceImpl::validate_dep_sync()` is never used. `CatChainReceiverImpl::validate_block_sync(dep)` is used instead.

Low`vector::reserve()` can minimize relocationsFixed

The resulting size of `ids` vector is known in advance. Using of `vector::push_back()` leads to unnecessary relocations.

LowCatchain hangs if can't `td::RocksDb::destroy()`Fixed

`CatChainReceiverImpl::destroy()` is supposed to stop trying after 10 failed attempts. However, the counter is not incremented - it will try endlessly. The same problem affects also `validator/db/archive-slice.cpp` (out of audit scope).

LowMagic numbersFixed

Magic numbers are used directly in code.

LowPointer dereferenced before checkFixed

```cpp=568 B->written(); CHECK(B); ``` The pointer `B` is dereferenced before it is checked.

LowPointer is not checked before dereferencing in `sFixed

The code assumes `dst` argument is valid and source `S` is non-zero. No check is performed before dereferencing.

LowRedundant StatementsFixed

The linked statement does not affect the functionality of the codebase. Both `r` and `my_vt[i] - vt[i]` are checked to be positive.

LowThe code assumes the hash is 32 bytes longFixed

```cpp=283 td::BufferSlice raw_data{32}; raw_data.as_slice().copy_from(as_slice(id)); ``` Magic number 32 assumes the hash value fits 32 bytes.

LowTypoFixed

"syncronize" is supposed to be "synchronize".

LowUnmodified arguments should be declared as `constFixed

If the function doesn't modify the argument, it should accept it as `const *` or `const &`. ```cpp=362 td::Status CatChainReceivedBlock::pre_validate_block(CatChainReceiver *chain, tl_object_ptr<ton_api::catchain_block> &block, td::Slice payload) { ``` This and some other functions don't modify the arguments. Methods that don't modify the object state should be declared as `const`. If cycle doesn't modify the container elements, the variable `X` should be declared as `const`: ```cpp=400 for (auto &X : block->data_->deps_) { ```

Show 2 more
LowUnnecessary structures copyingFixed

In many functions non-trivial arguments are passed by value. This leads to unnecessary copying. Non-trivial arguments are `PublicKeyHash`, `CatChainSessionId`, `td::Slice`, `td::SharedSlice` and others.

LowVariable name `l` is not recommendedFixed

It is not recommended to use `l`, `o` and some other variable names for better code readability.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Part of TON’s security record

TON
Audits6 audits by 2 firmsLast audit Mar 2024, by Trail of Bits.
Critical findingsNo open critical findingsAcross 6 reports.
Open TON’s security record

Security firm

CertiK

1,045 projects reviewed · 1,376 reports · latest May 2025

See CertiK’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us