CoinFabrik/scout-substrate huggingface dataset:
Audit finding: Vulnerable dependencies in the Substrate parachain
Project: Parallel
Auditor: Trail of Bits
Impact: Medium
Likelihood: Difficulty-High
Vulnerability class (audit): Patching
Vulnerability class (Scout): Dependency

1. Vulnerable dependencies in the Substrate parachain
Severity: Medium
Difficulty: High
Type: Patching
Finding ID: TOB-PLF-1
Target: parallel repository
Description
The Parallel Finance parachain node uses the following dependencies with known vulnerabilities. (All of the dependencies listed are inherited from the Substrate framework.)
DependencyVersionIDDescription
chrono 0.4.19 RUSTSEC-2020-0159 Potential segfault in localtime_r invocations
lru 0.6.6 RUSTSEC-2021-0130 Use after free in lru crate
time 0.1.44 RUSTSEC-2020-0071 Potential segfault in the time crate
net2 0.2.37 RUSTSEC-2020-0016 net2 crate has been deprecated; use socket2 instead
Other than chrono, all the dependencies can simply be updated to their newest versions to fix the vulnerabilities. The chrono crate issue has not been mitigated and remains problematic. A specific sequence of calls must occur to trigger the vulnerability, which is discussed in this GitHub thread in the chrono repository.
Exploit Scenario
An attacker exploits a known vulnerability in the Parallel Finance node and performs a denial-of-service attack on the network by taking down all nodes in the network.
Recommendations
Short term, update all dependencies to their newest versions. Monitor the referenced GitHub thread regarding the chrono crate segfault issue.
Long term, run cargo-audit as part of the CI/CD pipeline and ensure that the team is alerted to any vulnerable dependencies that are detected.

Summary: The Substrate parachain uses dependencies with known vulnerabilities, including chrono, lru, time, and net2, which may lead to potential segfaults or memory issues.
Audit finding: Missing validation in Pallet::force_update_market
Project: Parallel
Auditor: Trail of Bits
Location: pallets/loans/src/lib.rs:539-556
Impact: Informational
Likelihood: Difficulty-High
Vulnerability class (audit): Data Validation
Vulnerability class (Scout): Error Handling and Validation

3. Missing validation in Pallet::force_update_market
Severity: Informational
Difficulty: High
Type: Data Validation
Finding ID: TOB-PLF-3
Target: pallets/loans/src/lib.rs
Description
The Pallet::force_update_market method can be used to replace the stored market instance for a given asset. Other methods used to update market parameters perform extensive validation of the market parameters, but force_update_market checks only the rate model.
pub fn force_update_market(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
market: Market<BalanceOf<T>>,
) -> DispatchResultWithPostInfo {
T::UpdateOrigin::ensure_origin(origin)?;
ensure!(
market.rate_model.check_model(),
Error::<T>::InvalidRateModelParam
);
let updated_market = Self::mutate_market(asset_id, |stored_market| {
*stored_market = market;
stored_market.clone()
})?;
Self::deposit_event(Event::<T>::UpdatedMarket(updated_market));
Ok(().into())
}
Figure 3.1: pallets/loans/src/lib.rs:539-556
This means that the caller (who is either the root account or half of the general council) could inadvertently change immutable market parameters like ptoken_id by mistake.
Exploit Scenario
The root account calls force_update_market to update a set of market parameters. By mistake, the ptoken_id market parameter is updated, which means that Pallet::ptoken_id and Pallet::underlying_id are no longer inverses.
Recommendations
Short term, consider adding more input validation to the force_update_market extrinsic. In particular, it may make sense to ensure that the ptoken_id market parameter has not changed. Alternatively, add validation to check whether the ptoken_id market parameter is updated and to update the UnderlyingAssetId map to ensure that the value matches the Markets storage map.

Summary: The force_update_market method lacks sufficient validation, potentially allowing unintended changes to immutable market parameters.
Audit finding: Failed XCM requests left in storage
Project: Parallel
Auditor: Trail of Bits
Location: pallets/liquid-staking/src/lib.rs:1071-1159
Impact: Low
Likelihood: Difficulty-High
Vulnerability class (audit): Data Validation
Vulnerability class (Scout): Error Handling and Validation

5. Failed XCM requests left in storage
Severity: Low
Difficulty: High
Type: Data Validation
Finding ID: TOB-PLF-5
Target: pallets/liquid-staking/src/lib.rs
Description
When the liquid-staking pallet generates an XCM request for the parent chain, the corresponding XCM response triggers a call to Pallet::notification_received. If the response is of the Response::ExecutionResult type, this method calls Pallet::do_notification_received to handle the result.
The Pallet::do_notification_received method checks whether the request was successful and then updates the local state according to the corresponding XCM request, which is obtained from the XcmRequests storage map.
fn do_notification_received(
query_id: QueryId,
request: XcmRequest<T>,
res: Option<(u32, XcmError)>,
) -> DispatchResult {
use ArithmeticKind::*;
use XcmRequest::*;
let executed = res.is_none();
if !executed {
return Ok(());
}
match request {
Bond {
index: derivative_index,
amount,
} => {
ensure!(
!StakingLedgers::<T>::contains_key(&derivative_index),
Error::<T>::AlreadyBonded
);
let staking_ledger =
<StakingLedger<T::AccountId, BalanceOf<T>>>::new(
Self::derivative_sovereign_account_id(derivative_index),
amount,
);
StakingLedgers::<T>::insert(derivative_index, staking_ledger);
MatchingPool::<T>::try_mutate(|p| -> DispatchResult {
p.update_total_stake_amount(amount, Subtraction)
})?;
T::Assets::burn_from(
Self::staking_currency()?,
&Self::account_id(),
Amount
)?;
}
// ... <redacted>
}
XcmRequests::<T>::remove(&query_id);
Ok(())
}
Figure 5.1: pallets/liquid-staking/src/lib.rs:1071-1159
If the method completes without errors, the XCM request is removed from storage via a call to XcmRequests<T>::remove(query_id). However, if any of the following conditions are true, the corresponding XCM request is left in storage indefinitely:
1. The request fails and Pallet::do_notification_received exits early.
2. Pallet::do_notification_received fails.
3. The response type is not Response::ExecutionResult.
These three cases are currently unhandled by the codebase. The same issue is present in the crowdloans pallet implementation of Pallet::do_notification_received.
Recommendations
Short term, ensure that failed XCM requests are handled correctly by the crowdloans and liquid-staking pallets.

Summary: Failed XCM requests are left in storage indefinitely if not handled properly by the notification_received method.
Audit finding: Risk of using stale oracle prices in loans pallet
Project: Parallel
Auditor: Trail of Bits
Location: pallets/loans/src/lib.rs:1430-1441
Impact: Low
Likelihood: Difficulty-High
Vulnerability class (audit): Data Validation
Vulnerability class (Scout): Business Logic

6. Risk of using stale oracle prices in loans pallet
Severity: Low
Difficulty: High
Type: Data Validation
Finding ID: TOB-PLF-6
Target: pallets/loans/src/lib.rs
Description
The loans pallet uses oracle prices to find a USD value of assets using the get_price function. The get_price function internally uses the T::PriceFeeder::get_price function, which returns a timestamp and the price. However, the returned timestamp is ignored.
pub fn get_price(asset_id: AssetIdOf<T>) -> Result<Price, DispatchError> {
let (price, _) = T::PriceFeeder::get_price(&asset_id).ok_or(Error::<T>::PriceOracleNotReady)?;
if price.is_zero() {
return Err(Error::<T>::PriceIsZero.into());
}
log::trace!(target: "loans::get_price", "price: {:?}", price.into_inner());
Ok(price)
}
Figure 6.1: pallets/loans/src/lib.rs:1430-1441
Exploit Scenario
The price feeding oracles fail to deliver prices for an extended period of time. The get_price function returns stale prices, causing the get_asset_value function to return a non-market asset value.
Recommendations
Short term, modify the code so that it compares the returned timestamp from the T::PriceFeeder::get_price function with the current timestamp, returns an error if the price is too old, and handles the emergency price, which currently has a timestamp of zero. This will stop the market if stale prices are returned and allow the governance process to intervene with an emergency price.

Summary: The loans pallet may use stale oracle prices, leading to incorrect asset valuations.
Audit finding: Missing calculations in crowdloans extrinsics
Project: Parallel
Auditor: Trail of Bits
Location: pallets/crowdloans/src/lib.rs:718-765
Impact: Undetermined
Likelihood: Difficulty-High
Vulnerability class (audit): Undefined Behavior
Vulnerability class (Scout): Business Logic

7. Missing calculations in crowdloans extrinsics
Severity: Undetermined
Difficulty: High
Type: Undefined Behavior
Finding ID: TOB-PLF-7
Target: pallets/crowdloans/src/lib.rs
Description
The claim extrinsic in the crowdloans pallet is missing code to subtract the claimed amount from vault.contributed to update the total contribution amount. A similar bug exists in the refund extrinsic: there is no subtraction from vault.contributed after the Self::contribution_kill call.
pub fn claim(
origin: OriginFor<T>,
crowdloan: ParaId,
lease_start: LeasePeriod,
lease_end: LeasePeriod,
) -> DispatchResult {
// ... <redacted>
Self::contribution_kill(
vault.trie_index,
&who,
ChildStorageKind::Contributed
);
Self::deposit_event(Event::<T>::VaultClaimed(
crowdloan,
(lease_start, lease_end),
ctoken,
who,
amount,
VaultPhase::Succeeded,
));
Ok(())
}
Figure 7.1: pallets/crowdloans/src/lib.rs:718-765
Exploit Scenario
The claim extrinsic is called, but the total amount in vault.contributed is not updated, leading to incorrect calculations in other places.
Recommendations
Short term, update the claim and refund extrinsics so that they subtract the amount from vault.contributed.
Long term, add a test suite to ensure that the vault state stays consistent after the claim and refund extrinsics are called.

Summary: The claim and refund extrinsics do not update the vault's total contribution, leading to incorrect calculations.
Audit finding: Event emitted when update_vault and set_vrf calls do not make updates
Project: Parallel
Auditor: Trail of Bits
Location: pallets/crowdloans/src/lib.rs:424-472,599-616
Impact: Informational
Likelihood: Difficulty-High
Vulnerability class (audit): Auditing and Logging
Vulnerability class (Scout): Code Quality

8. Event emitted when update_vault and set_vrf calls do not make updates
Severity: Informational
Difficulty: High
Type: Auditing and Logging
Finding ID: TOB-PLF-8
Target: pallets/crowdloans/src/lib.rs
Description
The update_vault extrinsic in the crowdloans pallet is responsible for updating the three values shown. It is possible to call update_vault in such a way that no update is performed, but the function emits an event regardless of whether an update occurred. The same situation occurs in the set_vrfs extrinsic.
pub fn update_vault(
origin: OriginFor<T>,
crowdloan: ParaId,
cap: Option<BalanceOf<T>>,
end_block: Option<BlockNumberFor<T>>,
contribution_strategy: Option<ContributionStrategy>,
) -> DispatchResult {
T::UpdateVaultOrigin::ensure_origin(origin)?;
let mut vault = Self::current_vault(crowdloan).ok_or(Error::<T>::VaultDoesNotExist)?;
if let Some(cap) = cap {
// ... <redacted>
}
if let Some(end_block) = end_block {
// ... <redacted>
}
if let Some(contribution_strategy) = contribution_strategy {
// ... <redacted>
}
Self::deposit_event(Event::<T>::VaultUpdated(crowdloan,(lease_start, lease_end),contribution_strategy,cap,end_block));
Ok(())
}
Figure 8.1: pallets/crowdloans/src/lib.rs:424-472
pub fn set_vrfs(origin: OriginFor<T>, vrfs: Vec<ParaId>) -> DispatchResult {
T::VrfOrigin::ensure_origin(origin)?;
log::trace!(target: "crowdloans::set_vrfs", "pre-toggle. vrfs: {:?}",vrfs);
Vrfs::<T>::try_mutate(|b| -> Result<(), DispatchError> {
*b = vrfs.try_into().map_err(|_| Error::<T>::MaxVrfsExceeded)?;
Ok(())
})?;
Self::deposit_event(Event::<T>::VrfsUpdated(Self::vrfs()));
Ok(())
}
Figure 8.2: pallets/crowdloans/src/lib.rs:599-616
Exploit Scenario
A system observes that the VaultUpdate event was emitted even though the vault state did not actually change. Based on this observation, it performs logic that should be executed only when the state has been updated.
Recommendations
Short term, modify the VaultUpdate event so that it is emitted only when the update_vault extrinsic makes an actual update. Optionally, have the update_vault extrinsic return an error to the caller when calling it results in no updates.

Summary: The update_vault and set_vrfs extrinsics emit events even when no actual updates are made.
Audit finding: The referral code is a sequence of arbitrary bytes
Project: Parallel
Auditor: Trail of Bits
Location: pallets/crowdloans/src/lib.rs:502-594
Impact: Informational
Likelihood: Difficulty-High
Vulnerability class (audit): Data Validation
Vulnerability class (Scout): Error Handling and Validation

9. The referral code is a sequence of arbitrary bytes
Severity: Informational
Difficulty: High
Type: Data Validation
Finding ID: TOB-PLF-9
Target: pallets/crowdloans/src/lib.rs
Description
The referral code is used in a number of extrinsic calls in the crowdloans pallet. Because the referral code is never validated, it can be a sequence of arbitrary bytes. The referral code is logged by a number of extrinsics. However, it is currently impossible to perform log injection because the referral code is printed as a hexadecimal string rather than raw bytes (using the debug representation).
pub fn contribute(
origin: OriginFor<T>,
crowdloan: ParaId,
#[pallet::compact] amount: BalanceOf<T>,
referral_code: Vec<u8>,
) -> DispatchResultWithPostInfo {
// ... <redacted>
log::trace!(
target: "crowdloans::contribute",
"who: {:?}, para_id: {:?}, amount: {:?}, referral_code: {:?}",
&who,
&crowdloan,
&amount,
&referral_code
);
Ok(().into())
}
Figure 9.1: pallets/crowdloans/src/lib.rs:502-594
Exploit Scenario
The referral code is rendered as raw bytes in a vulnerable environment, introducing an opportunity to perform a log injection attack.
Recommendations
Short term, choose and implement a data type that models the referral code semantics as closely as possible.

Summary: The referral code in the crowdloans pallet is arbitrary and unvalidated, potentially allowing log injection attacks.
Audit finding: Missing validation of referral code size
Project: Parallel
Auditor: Trail of Bits
Location: pallets/crowdloans/src/lib.rs:1429-1464
Impact: Low
Likelihood: Difficulty-Low
Vulnerability class (audit): Data Validation
Vulnerability class (Scout): Error Handling and Validation

10. Missing validation of referral code size
Severity: Low
Difficulty: Low
Type: Data Validation
Finding ID: TOB-PLF-10
Target: pallets/crowdloans/src/lib.rs
Description
The length of the referral code is not validated by the contribute extrinsic defined by the crowdloans pallet. Since the referral code is stored by the node, a malicious user could call contribute multiple times with a very large referral code. This would increase the memory pressure on the node, potentially leading to memory exhaustion.
fn do_contribute(
who: &AccountIdOf<T>,
crowdloan: ParaId,
vault_id: VaultId,
amount: BalanceOf<T>,
referral_code: Vec<u8>,
) -> Result<(), DispatchError> {
XcmRequests::<T>::insert(
query_id,
XcmRequest::Contribute {
crowdloan,
vault_id,
who: who.clone(),
amount,
referral_code: referral_code.clone(),
},
);
Ok(())
}
Figure 10.1: pallets/crowdloans/src/lib.rs:1429-1464
Exploit Scenario
A malicious user calls the contribute extrinsic multiple times with a very large referral code. This increases the memory pressure on the validator nodes and eventually causes all parachain nodes to run out of memory and crash.
Recommendations
Short term, add validation that limits the size of the referral code argument to the contribute extrinsic.

Summary: The contribute extrinsic lacks validation on the referral code size, potentially leading to memory exhaustion.
Audit finding: Code duplication in crowdloans pallet
Project: Parallel
Auditor: Trail of Bits
Location: pallets/crowdloans/src/lib.rs
Impact: Informational
Likelihood: Difficulty-High
Vulnerability class (audit): Patching
Vulnerability class (Scout): Code Quality

11. Code duplication in crowdloans pallet
Severity: Informational
Difficulty: High
Type: Patching
Finding ID: TOB-PLF-11
Target: pallets/crowdloans/src/lib.rs
Description
A number of extrinsics in the crowdloans pallet have duplicate code. The close, reopen, and auction_succeeded extrinsics have virtually identical logic. The migrate_pending and refund extrinsics are also fairly similar.
Exploit Scenario
A vulnerability is found in the duplicate code, but it is patched in only one place.
Recommendations
Short term, refactor the close, reopen, and auction_succeeded extrinsics into one function, to be called with values specific to the extrinsics. Refactor common pieces of logic in the migrate_pending and refund extrinsics.
Long term, avoid code duplication, as it makes the system harder to review and update. Perform regular code reviews and track any logic that is duplicated.

Summary: Code duplication in the crowdloans pallet increases the risk of inconsistent patches and complicates maintenance.
Audit finding: Need to upgrade the module
Project: Parallel
Auditor: Slow Mist
Impact: low-risk
Vulnerability class (Scout): Dependency
Status: Fixed in: https://github.com/parallel-finance/parallel/pull/210

5.1 Need to upgrade the module [low-risk]
ID:RUSTSEC-2021-0067
Crate:cranelift-codegen
Version: 0.71.0
Date:2021-05-21
URL:https://rustsec.org/advisories/RUSTSEC-2021-0067
Title: Memory access due to code generation flaw in Cranelift module
Solution: upgrade to >= 0.73.1 OR >= 0.74
Dependency tree: cranelift-codegen 0.71.0
Fixed in: https://github.com/parallel-finance/parallel/pull/210

Summary: Memory access vulnerability in Cranelift module due to code generation flaw, fixed by upgrading to >= 0.73.1 or >= 0.74.
Audit finding: Numeric overflow
Project: Parallel
Auditor: Slow Mist
Location: parallel/pallets/loans/src/lib.rs
Impact: enhancement
Vulnerability class (Scout): Arithmetic
Status: Fixed in: https://github.com/parallel-finance/parallel/pull/241

5.2 Numeric overflow[enhancement]
parallel/pallets/loans/src/lib.rs
let total_reserves_new = total_reserves - reduce_amount;
//...snip code...//
let total_reserves_new = total_reserves + add_amount;
It is recommended to use `checked_add/checked_sub` to prevent numerical overflow.
Fixed in: https://github.com/parallel-finance/parallel/pull/241

Summary: Numeric overflow vulnerability due to lack of checked_add/checked_sub, fixed by adding these methods.
Audit finding: Lack of bounds checking
Project: Parallel
Auditor: Slow Mist
Location: parallel/pallets/loans/src/lib.rs
Impact: weakness
Vulnerability class (Scout): Error Handling and Validation
Status: Fixed in: https://github.com/parallel-finance/parallel/pull/258

5.3 Lack of bounds checking[weakness]
`mint_amount` has no boundary limit, it is recommended to enhance.
Fixed in: https://github.com/parallel-finance/parallel/pull/258

Summary: Lack of bounds checking for mint_amount, recommended to enhance.
Audit finding: Oracle price feed risk
Project: Parallel
Auditor: Slow Mist
Location: parallel/pallets/prices/src/lib.rs
Impact: weakness
Vulnerability class (Scout): Business Logic

5.4 Oracle price feed risk[weakness]
Due to the lack of time parameter, if the price is not feed in time, the price may be inaccurate.
parallel/pallets/prices/src/lib.rs
pub enum Event<T: Config> {
/// Set emergency price. [currency_id, price_detail]
SetPrice(CurrencyId, PriceWithDecimal),
/// Reset emergency price. [currency_id]
ResetPrice(CurrencyId),
}
Feedback: At present, the source of price feeding is controlled by authority and credible. The range of trustworthiness includes the accuracy and real-time of the price, that is, outdated prices will not be sent to the chain, but the price feeding transaction sent is through operational on the chain. Transaction level guarantees will be packaged in real time, that is, the current block.

Summary: Oracle price feed may be inaccurate due to lack of a time parameter.
Audit finding: Calculate inaccurate risk
Project: AvaProtocol
Auditor: Slow Mist
Location: pallets/automation-time/src/lib.rs
Impact: Suggestion
Vulnerability class (audit): Integer Overflow
Vulnerability class (Scout): Arithmetic
Status: Fixed

[N1] [Suggestion] Calculate inaccurate risk
Category: Integer Overflow Audit
Content
pallets/automation-time/src/lib.rs
There are some risks of value overflow.
saturating_mul, saturating_sub, saturating_add and +-*/, +=, -=
saturating at the numeric bounds instead of overflowing, The returned result is inaccurate.
Solution
Use checked_add/checked_sub/checked_mul/checked_div instead of
saturating_add/saturating_sub/saturating_mul/saturating_div and +-*/, +=, -= .
Status
Fixed

Summary: Inaccurate calculation risk due to potential integer overflow; replace saturating operations with checked operations.
Audit finding: User balance is not checked before transfer
Project: AvaProtocol
Auditor: Slow Mist
Location: pallets/automation-time/src/lib.rs
Impact: Suggestion
Vulnerability class (audit): Others
Vulnerability class (Scout): Error Handling and Validation
Status: Ignored; This is expected behaviour.

[N2] [Suggestion] User balance is not checked
Category: Others
Content
pallets/automation-time/src/lib.rs
The amount transferred by the user is not compared with the user's balance here, and the user may not have enough NativeToken.
pub fn schedule_native_transfer_task(
origin: OriginFor<T>,
provided_id: Vec<u8>,
execution_times: Vec<UnixTime>,
recipient_id: T::AccountId,
#[pallet::compact] amount: BalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
// check for greater than existential deposit
if amount < T::NativeTokenExchange::minimum_balance() {
Err(<Error<T>>::InvalidAmount)?
}
// check not sent to self
if who == recipient_id {
Err(<Error<T>>::TransferToSelf)?
}
let action =
Action::NativeTransfer { sender: who.clone(), recipient: recipient_id, amount };
Self::validate_and_schedule_task(action, who, provided_id, execution_times)?;
Ok(().into())
}
Solution
Compare the user's transfer amount with the balance
Status
Ignored; This is expected behaviour.

Summary: User balance is not checked before transfer, allowing potential insufficient funds.
Audit finding: Return value not checked
Project: AvaProtocol
Auditor: Slow Mist
Location: pallets/parachain-staking/src/lib.rs:962,1004,1390
pallets/parachain-staking/src/types.rs:463
pallets/parachain-staking/src/delegation_requests.rs:279,648-651
pallets/parachain-staking/src/migrations.rs:404
Impact: Low
Vulnerability class (audit): Others
Vulnerability class (Scout): Error Handling and Validation
Status: Ignored; This is unlikely to happen, and no specific handling is required.

[N3] [Low] Return Value Not Checked
Category: Others
Content
pallets/parachain-staking/src/lib.rs
//#L962
T::Currency::unreserve(&bond.owner, bond.amount);
//#L1004
T::Currency::unreserve(&candidate, state.bond);
//#L1390
T::Currency::unreserve(&delegator, amount);
pallets/parachain-staking/src/types.rs
//#L463
T::Currency::unreserve(&who, request.amount.into());
//#L648:651
T::Currency::unreserve(&lowest_bottom_to_be_kicked.owner, lowest_bottom_to_be_kicked.amount);
pallets/parachain-staking/src/delegation_requests.rs
//#L279
T::Currency::unreserve(&delegator, amount);
pallets/parachain-staking/src/migrations.rs
//#L404
T::Currency::unreserve(&owner, *amount);
The return value of unreserve needs to be checked.
Solution
Check the return value.
Status
Ignored; If the account has less than that locked up. Not only is this unlikely to happen, there's nothing for parachain-staking to do if it occurs.

Summary: Return value of unreserve function is not checked, risking unhandled errors.