Halborn Logo

icon

NGL Bridge + Gorples Bridge - Entangle Labs


Prepared by:

Halborn Logo

HALBORN

Last Updated 07/23/2024

Date of Engagement by: May 23rd, 2024 - June 21st, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

4

Critical

1

High

1

Medium

0

Low

1

Informational

1


1. Introduction

Entangle Labs team engaged Halborn to conduct a security assessment on their ngl-core, ngl-bridge and gorples-bridge Solana programs beginning on May 23rd, 2024 and ending on June 21st, 2024. The security assessment was scoped to the Solana Programs provided in ngl-core, ngl-bridge , and gorples-bridge GitHub repositories. Commit hashes and further details can be found in the Scope section of this report.

    • Ngl-core is a token owner program, used by ngl-bridge program to mint and burn tokens

    • Ngl-bridge is a bridge program, called by Photon CCM endpoint, to receive tokens from other networks or the user to send tokens to other networks.

    • Gorples Bridge program is a token bridge compatible with Entangle CCM

2. Assessment Summary

Halborn was provided 4 weeks for the engagement and assigned one full-time security engineer to review the security of the Solana Programs in scope. The engineer is a blockchain and smart contract security expert with advanced smart contract hacking skills, and deep knowledge of multiple blockchain protocols.

The purpose of the assessment is to:

    • Identify potential security issues within the Solana Programs.

    • Ensure that smart contract functionality operates as intended.

In summary, Halborn identified some improvements to reduce the likelihood and impact of multiple risks, which has been partially addressed by Entangle Labs team. The main ones were the following:

    • Gorples Bridge Program initializer can be front-run

    • Fee Collector Vault check missing can lead to DoS in PhotonMsg


Note: Testing is a crucial component of our methodology for conducting security assessments. It enables us to identify potential security risks and program malfunctions, as well as to emulate the exploitation of these risks.

However, during this security assessment, the Entangle Labs team did not provide a test suite that facilitates interaction with the in-scope bridge programs. This significantly limited our ability to perform security tests on certain parts of the program, such as bridge and photon msg. Consequently, we were unable to execute our methodology fully and were restricted to conducting only an exhaustive code review of these components. This limitation has prevented us from guaranteeing 100% code security.

3. Test Approach and Methodology

Halborn performed a combination of a manual review of the source code and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of the program assessment. While manual testing is recommended to uncover flaws in business logic, processes, and implementation; automated testing techniques help enhance coverage of programs and can quickly identify items that do not follow security best practices.

The following phases and associated tools were used throughout the term of the assessment:

- Research into the architecture, purpose, and use of the platform.

- Manual program source code review to identify business logic issues.

- Mapping out possible attack vectors

- Thorough assessment of safety and usage of critical Rust variables and functions in scope that could lead to arithmetic vulnerabilities.

- Scanning dependencies for known vulnerabilities (`cargo audit`).

- Local runtime testing (`solana-test-framework`)


4. RISK METHODOLOGY

Every vulnerability and issue observed by Halborn is ranked based on two sets of Metrics and a Severity Coefficient. This system is inspired by the industry standard Common Vulnerability Scoring System.
The two Metric sets are: Exploitability and Impact. Exploitability captures the ease and technical means by which vulnerabilities can be exploited and Impact describes the consequences of a successful exploit.
The Severity Coefficients is designed to further refine the accuracy of the ranking with two factors: Reversibility and Scope. These capture the impact of the vulnerability on the environment as well as the number of users and smart contracts affected.
The final score is a value between 0-10 rounded up to 1 decimal place and 10 corresponding to the highest security risk. This provides an objective and accurate rating of the severity of security vulnerabilities in smart contracts.
The system is designed to assist in identifying and prioritizing vulnerabilities based on their level of risk to address the most critical issues in a timely manner.

4.1 EXPLOITABILITY

Attack Origin (AO):
Captures whether the attack requires compromising a specific account.
Attack Cost (AC):
Captures the cost of exploiting the vulnerability incurred by the attacker relative to sending a single transaction on the relevant blockchain. Includes but is not limited to financial and computational cost.
Attack Complexity (AX):
Describes the conditions beyond the attacker’s control that must exist in order to exploit the vulnerability. Includes but is not limited to macro situation, available third-party liquidity and regulatory challenges.
Metrics:
EXPLOITABILIY METRIC (mem_e)METRIC VALUENUMERICAL VALUE
Attack Origin (AO)Arbitrary (AO:A)
Specific (AO:S)
1
0.2
Attack Cost (AC)Low (AC:L)
Medium (AC:M)
High (AC:H)
1
0.67
0.33
Attack Complexity (AX)Low (AX:L)
Medium (AX:M)
High (AX:H)
1
0.67
0.33
Exploitability EE is calculated using the following formula:

E=meE = \prod m_e

4.2 IMPACT

Confidentiality (C):
Measures the impact to the confidentiality of the information resources managed by the contract due to a successfully exploited vulnerability. Confidentiality refers to limiting access to authorized users only.
Integrity (I):
Measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of data stored and/or processed on-chain. Integrity impact directly affecting Deposit or Yield records is excluded.
Availability (A):
Measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability. This metric refers to smart contract features and functionality, not state. Availability impact directly affecting Deposit or Yield is excluded.
Deposit (D):
Measures the impact to the deposits made to the contract by either users or owners.
Yield (Y):
Measures the impact to the yield generated by the contract for either users or owners.
Metrics:
IMPACT METRIC (mIm_I)METRIC VALUENUMERICAL VALUE
Confidentiality (C)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Integrity (I)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Availability (A)None (A:N)
Low (A:L)
Medium (A:M)
High (A:H)
Critical (A:C)
0
0.25
0.5
0.75
1
Deposit (D)None (D:N)
Low (D:L)
Medium (D:M)
High (D:H)
Critical (D:C)
0
0.25
0.5
0.75
1
Yield (Y)None (Y:N)
Low (Y:L)
Medium (Y:M)
High (Y:H)
Critical (Y:C)
0
0.25
0.5
0.75
1
Impact II is calculated using the following formula:

I=max(mI)+mImax(mI)4I = max(m_I) + \frac{\sum{m_I} - max(m_I)}{4}

4.3 SEVERITY COEFFICIENT

Reversibility (R):
Describes the share of the exploited vulnerability effects that can be reversed. For upgradeable contracts, assume the contract private key is available.
Scope (S):
Captures whether a vulnerability in one vulnerable contract impacts resources in other contracts.
Metrics:
SEVERITY COEFFICIENT (CC)COEFFICIENT VALUENUMERICAL VALUE
Reversibility (rr)None (R:N)
Partial (R:P)
Full (R:F)
1
0.5
0.25
Scope (ss)Changed (S:C)
Unchanged (S:U)
1.25
1
Severity Coefficient CC is obtained by the following product:

C=rsC = rs

The Vulnerability Severity Score SS is obtained by:

S=min(10,EIC10)S = min(10, EIC * 10)

The score is rounded up to 1 decimal places.
SeverityScore Value Range
Critical9 - 10
High7 - 8.9
Medium4.5 - 6.9
Low2 - 4.4
Informational0 - 1.9

5. SCOPE

Files and Repository
(a) Repository: ngl-bridge
(b) Assessed Commit ID: e1c7fe8
(c) Items in scope:
  • programs/ngl-core/src/lib.rs
  • programs/ngl-bridge/lib.rs
  • programs/ngl-bridge/states.rs
↓ Expand ↓
Out-of-Scope:
Files and Repository
(a) Repository: gorples-solana
(c) Items in scope:
  • borpa-bridge/src/instructions/initialize.rs
  • borpa-bridge/src/instructions/set_bridge_config.rs
  • borpa-bridge/src/instructions/bridge.rs
↓ Expand ↓
Out-of-Scope:
Remediation Commit ID:
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

1

High

1

Medium

0

Low

1

Informational

1

Security analysisRisk levelRemediation Date
GORPLES BRIDGE PROGRAM INITIALIZER CAN BE FRONT-RUNCriticalSolved - 06/23/2024
FEE COLLECTOR VAULT CHECK MISSING CAN LEAD TO DOS IN PHOTONMSGHighPartially Solved - 07/17/2024
UNVALIDATED BRIDGE AUTHORITY COULD LEAD TO IRREVERSIBLE ERRORS AND DOSLowRisk Accepted
NEW ADMIN NOT VALIDATEDInformationalAcknowledged

7. Findings & Tech Details

7.1 GORPLES BRIDGE PROGRAM INITIALIZER CAN BE FRONT-RUN

// Critical

Description

The current implementation of the Initialize instruction in the gorples-bridge program lacks restrictions to ensure that it is signed by a known and trusted address, such as the program update authority.

This lack of validation could allow an attacker to execute the initialization process prematurely, potentially configuring the program with accounts under their control. This unauthorized access to the program configuration can have serious consequences, including:

  • Setting the bridge_router_address to an invalid account, causing users to lose the tokens provided in the bridge call. This happens because the new message initiated through the photon program will not reach the Bridge Router contract in Entangle Oracle Bridge, preventing the subsequent message to the destination chain where tokens are minted to the specified address.

  • Configuring the fee_collector_vault account to an account under their control, calling SetBridgeConfig instruction to collect commissions earned in PhotonMsg.

Additionally, the program lacks a mechanism for ownership transfer by a known higher authority, making such an attack irreversible.

initialize.rs

pub struct Initialize<'info> {
    /// Bridge admin
    #[account(mut, signer)]
    pub admin: Signer<'info>,

    /// Bridge config
    #[account(init, payer = admin, space = Config::LEN, seeds = [BRIDGE_ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,

    pub system_program: Program<'info, System>,
}

pub fn handle_initialize(
    ctx: Context<Initialize>,
    bridge_router_address: Vec<u8>,
    min_bridge_amount: u64,
) -> Result<()> {
    ctx.accounts.config.bridge_router_address = bridge_router_address.try_into().unwrap();
    ctx.accounts.config.admin = ctx.accounts.admin.key();
    ctx.accounts.config.min_bridge_amount = min_bridge_amount;
    Ok(())

set_bridge_config.rs

pub struct SetBridgeConfig<'info> {
    /// Bridge admin
    #[account(mut, signer, address = config.admin)]
    pub admin: Signer<'info>,

    /// Bridge config
    #[account(seeds = [BRIDGE_ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,
}

pub fn handle_set_bridge_config(
    ctx: Context<SetBridgeConfig>,
    bridge_router_address: Vec<u8>,
    min_bridge_amount: u64,
    fee_collector_vault: Pubkey,
) -> Result<()> {
    ctx.accounts.config.bridge_router_address = bridge_router_address.try_into().unwrap();
    ctx.accounts.config.min_bridge_amount = min_bridge_amount;
    ctx.accounts.config.fee_collector_vault = fee_collector_vault;
    Ok(())
Proof of Concept
  1. A random user calls Initialize providing an invalid bridge_router_address

  2. The user calls SetBridgeConfig providing an account under its control as fee_collector_vault


poc1.png
BVSS
Recommendation

To address this issue, it is recommended to implement the following measures:

  • Ensure that the Initialize instruction can only be executed by a known and trusted address, such as the program upgrade authority.

  • Introduce a mechanism for ownership transfer, allowing a higher authority to reclaim and reassign control in case of unauthorized or malicious configurations.

  • Implement a validation for bridge_router_address.


Remediation Plan

SOLVED: The Entangle Labs team solved this issue by modifying the initialization instruction to ensure that it can only be called by the deployer. The deployer's address is now hardcoded, guaranteeing that the deployer is responsible for securing and providing a valid and correct bridge_router_address address. This modification prevents the described vulnerability by restricting access and ensuring proper initialization.

Remediation Hash

7.2 FEE COLLECTOR VAULT CHECK MISSING CAN LEAD TO DOS IN PHOTONMSG

// High

Description

The Initialize statement of the gorples-bridge program allows the initialization of the bridge config account. This requires two parameters: bridge_router_address and min_bridge_amount.

initialize.rs

pub struct Initialize<'info> {
    /// Bridge admin
    #[account(mut, signer)]
    pub admin: Signer<'info>,

    /// Bridge config
    #[account(init, payer = admin, space = Config::LEN, seeds = [BRIDGE_ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,

    pub system_program: Program<'info, System>,
}

pub fn handle_initialize(
    ctx: Context<Initialize>,
    bridge_router_address: Vec<u8>,
    min_bridge_amount: u64,
) -> Result<()> {
    ctx.accounts.config.bridge_router_address = bridge_router_address.try_into().unwrap();
    ctx.accounts.config.admin = ctx.accounts.admin.key();
    ctx.accounts.config.min_bridge_amount = min_bridge_amount;
    Ok(())
}

The SetBridgeConfig instruction enables the bridge config administrator to modify the bridge config by providing values for the same fields as in the initialization, along with an additional fee_collector_vault address.

set_bridge_config.rs

pub struct SetBridgeConfig<'info> {
    /// Bridge admin
    #[account(mut, signer, address = config.admin)]
    pub admin: Signer<'info>,

    /// Bridge config
    #[account(seeds = [BRIDGE_ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,
}

pub fn handle_set_bridge_config(
    ctx: Context<SetBridgeConfig>,
    bridge_router_address: Vec<u8>,
    min_bridge_amount: u64,
    fee_collector_vault: Pubkey,
) -> Result<()> {
    ctx.accounts.config.bridge_router_address = bridge_router_address.try_into().unwrap();
    ctx.accounts.config.min_bridge_amount = min_bridge_amount;
    ctx.accounts.config.fee_collector_vault = fee_collector_vault;
    Ok(())

The fee_collector_vault is intended to be the account where fees obtained in photon_msg will be collected. However, this address, like the other two, is not validated. As a result, it is possible to provide an invalid account or a token account without the corresponding mint associated to match the mint of the core config. This issue could prevent fee collection in photon_msg, causing the user to be unable to receive mined tokens, effectively resulting in a denial of service for this instruction until a valid fee_collector_vault is assigned.

photon_msg.rs

    /// User vault for token
    #[account(
        init_if_needed,
        payer = executor,
        associated_token::authority = user,
        associated_token::mint = mint,
        associated_token::token_program = token_program
    )]
    pub user_vault: Box<InterfaceAccount<'info, TokenAccount>>,

    /// Fee collector vault for token
    #[account(
        mut,
        address = config.fee_collector_vault,
        token::mint = mint,
        token::token_program = token_program
    )]
    pub fee_collector_vault: Box<InterfaceAccount<'info, TokenAccount>>,

By default, this scenario occurs if SetBridgeConfig is not called to assign a correct address, as the zero address is assigned during initialization.

In the ngl-bridge program, this address is also not validated but is provided during initialization and managed by the deployer, thus mitigating the level of security risk. However, since gorples-bridge is more susceptible to front-running attacks as it is explained in HAL-01, the following security risks are possible:

  • Invalid fee_collector_vault addresses could lead to uncollected fees and disrupted operations.

  • Users may experience denial of service until the configuration is corrected.

  • Security risks increase if malicious actors exploit the non-validated fields.


Proof of Concept
  1. Initialize bridge config

  2. Initialize Core Config

  3. Set bridge config with an invalid account as fee collector vault or with a token account whose mint does not match the core config mint

poc2.png
poc3.png
BVSS
Recommendation

To address this issue, it is recommended to implement the following measures:

  • Include the fee_collector_vault address as a parameter at initialization to prevent it from being assigned to the default address.

  • Add a validation to ensure that fee_collector_vault is a token account with an associated mint that matches the mint in the core configuration.



Remediation Plan

PARTIALLY SOLVED: The Entangle Labs team partially solved this issue by modifying the initialization instruction handler. Now, it requires the fee_collector_vault address to be provided as a parameter, ensuring it is not initialized by default. This change ensures that the address is properly configured. Following the remediation plan for HAL_01, the deployer is responsible for ensuring and providing a valid and correct address.

Remediation Hash

7.3 UNVALIDATED BRIDGE AUTHORITY COULD LEAD TO IRREVERSIBLE ERRORS AND DOS

// Low

Description

The Initialize instruction of the ngl-core program allows the deployer to initialize the core config account. To achieve this, the bridge_authority address must be provided as a parameter along with other values. However, this address is not validated, which could lead to the accidental setting of an unintentional or invalid address. Since the ngl-core program does not have a functionality to change the core config account values (except for setting a new admin via SetAdmin), this mistake would be irreversible.

This could result in denial of service for mining and token burning operations if the bridge_authority was invalid, or misuse of Mint operations if an incorrect address was assigned.

lib.rs (ngl-core)

    pub fn initialize(
        ctx: Context<Initialize>,
        bridge_authority: Pubkey,
        name: String,
        symbol: String,
        uri: String,
    ) -> Result<()> {
        ctx.accounts.config.admin = ctx.accounts.admin.key();
        ctx.accounts.config.contracts[0] = bridge_authority;
        ctx.accounts.config.mint = ctx.accounts.mint.key();
#[derive(Accounts)]
pub struct MintToken<'info> {
    /// Mint authority
    /// CHECK: not loaded
    #[account(
        signer,
        constraint = 
            mint_authority.key() != Pubkey::default() && config.contracts.contains(&mint_authority.key())
                @ CustomError::Unauthorized
    )]
    pub mint_authority: Signer<'info>,

    /// Token authority
    /// CHECK: not loaded
    #[account(seeds = [ROOT, b"AUTHORITY"], bump)]
    pub authority: UncheckedAccount<'info>,

lib.rs (ngl-core)

#[derive(Accounts)]
pub struct BurnToken<'info> {
    #[account(signer)]
    pub vault_owner: Signer<'info>,

    /// Burn authority
    /// CHECK: not loaded
    #[account(
        signer,
        constraint = 
            burn_authority.key() != Pubkey::default() && config.contracts.contains(&burn_authority.key())
                @ CustomError::Unauthorized
    )]
    pub burn_authority: Signer<'info>,

On the other hand, the Bridge and PhotonMsg instructions use as mint_authority and burn_authority the token authority, so if this authority is not the one provided as bridge_authority in the initialization of the core config, it will result in a denial of service in both instructions.


bridge.rs

pub struct Bridge<'info> {
    /// Sender wallet
    #[account(signer)]
    pub sender: Signer<'info>,

    /// Token authority
    /// CHECK: not loaded
    #[account(seeds = [ROOT, b"AUTHORITY"], bump)]
    pub authority: AccountInfo<'info>,

    /// Token mint
    #[account(
        mut,
        address = core_config.mint @ CustomError::InvalidMint,
        mint::token_program = token_program
    )]
    pub mint: Box<InterfaceAccount<'info, Mint>>,

    /// Sender vault
    #[account(
        mut,
        token::mint = mint,
        token::authority = sender,
        token::token_program = token_program
    )]
    pub sender_vault: Box<InterfaceAccount<'info, TokenAccount>>,

    /// Bridge config
    #[account(seeds = [ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,

    /// Core config
    pub core_config: Box<Account<'info, ngl_core::Config>>,
   require_gte!(amount, ctx.accounts.config.min_bridge_amount, CustomError::AmountTooLow);
    let bump = &[ctx.bumps.authority];
    let seed = &[ROOT, &b"AUTHORITY"[..], bump][..];
    let seeds = &[seed];
    // Burn
    let accounts = BurnToken {
        vault_owner: ctx.accounts.sender.to_account_info(),
        burn_authority: ctx.accounts.authority.to_account_info(),
        config: ctx.accounts.core_config.to_account_info(),
        mint: ctx.accounts.mint.to_account_info(),
        vault: ctx.accounts.sender_vault.to_account_info(),
        token_program: ctx.accounts.token_program.to_account_info(),
    };


photon_msg.rs

pub struct PhotonMsg<'info> {
    /// Executor wallet
    #[account(mut, signer)]
    pub executor: Signer<'info>,

    /// Protocol call authority (from photon program)
    #[account(
        signer,
        seeds = [entangle_photon_sol/*photon*/::photon::ROOT, b"CALL_AUTHORITY", NGL_PROTOCOL_ID],
        bump,
        seeds::program = Photon::id()
    )]
    pub call_authority: Signer<'info>,

    /// Provided by photon program
    pub op_info: Box<Account<'info, OpInfo>>,

    /// Token authority
    /// CHECK: not loaded
    #[account(seeds = [ROOT, b"AUTHORITY"], bump)]
    pub authority: UncheckedAccount<'info>,

    /// Token mint (checked by core)
    #[account(mut)]
    pub mint: Box<InterfaceAccount<'info, Mint>>,

    /// CHECK: User wallet
    pub user: UncheckedAccount<'info>,

    /// User vault for token
    #[account(
        init_if_needed,
        payer = executor,
        associated_token::authority = user,
        associated_token::mint = mint,
        associated_token::token_program = token_program
    )]
    pub user_vault: Box<InterfaceAccount<'info, TokenAccount>>,

    /// Fee collector vault for token
    #[account(
        mut,
        address = config.fee_collector_vault,
        token::mint = mint,
        token::token_program = token_program
    )]
    pub fee_collector_vault: Box<InterfaceAccount<'info, TokenAccount>>,

    /// Bridge config
    #[account(seeds = [ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,

    /// Core config
    /// CHECK: by core program
    pub core_config: UncheckedAccount<'info>,

 let accounts = MintToken {
                mint_authority: ctx.accounts.authority.to_account_info(),
                authority: ctx.accounts.core_authority.to_account_info(),
                config: ctx.accounts.core_config.to_account_info(),
                mint: ctx.accounts.mint.to_account_info(),
                vault: ctx.accounts.fee_collector_vault.to_account_info(),
                token_program: ctx.accounts.token_program.to_account_info(),
            };
            let cpi_ctx = CpiContext::new_with_signer(
                ctx.accounts.core_program.to_account_info(),
                accounts,
                seeds,
            );
            mint_token(cpi_ctx, fee)?;
            // Mint for user
            let bump = &[ctx.bumps.authority];
            let seed = &[ROOT, &b"AUTHORITY"[..], bump][..];
            let seeds = &[seed];
            let accounts = MintToken {
                mint_authority: ctx.accounts.authority.to_account_info(),
                authority: ctx.accounts.core_authority.to_account_info(),
                config: ctx.accounts.core_config.to_account_info(),
                mint: ctx.accounts.mint.to_account_info(),
                vault: ctx.accounts.user_vault.to_account_info(),
                token_program: ctx.accounts.token_program.to_account_info(),
            };

Conversely, in the initialize instruction handler of the ngl-bridge program, the values passed by parameters are also not validated. However, this program includes a SetBridgeConfig instruction that allows the administrator (initially the deployer) to correct any values of the bridge config at any time in case of an error, although those values are not validated in it either.


initialize.rs (ngl-bridge)

#[derive(Accounts)]
pub struct Initialize<'info> {
    /// Bridge admin
    #[account(mut, signer, address = DEPLOYER.parse().expect("Deployer key not set"))]
    pub admin: Signer<'info>,

    /// Bridge config
    #[account(init, payer = admin, space = Config::LEN, seeds = [ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,

    pub system_program: Program<'info, System>,
}

pub fn handle_initialize(
    ctx: Context<Initialize>,
    bridge_router_address: Vec<u8>,
    min_bridge_amount: u64,
    fee_collector_vault: Pubkey,
) -> Result<()> {
    ctx.accounts.config.bridge_router_address = bridge_router_address.try_into().unwrap();
    ctx.accounts.config.admin = ctx.accounts.admin.key();
    ctx.accounts.config.min_bridge_amount = min_bridge_amount;
    ctx.accounts.config.fee_collector_vault = fee_collector_vault;
    Ok(())

BVSS
Recommendation

To address this issue, the following modifications are recommended:

  • Change the authority used in bridge and photon_msg of the ngl-bridge program that will be used as burn_authority and mint_authority to be a pda with the corresponding bridge authority seeds.

  • Add a check in the initialize instruction handler of the ngl-core program to verify that the provided bridge_authority address is not an invalid address and that it corresponds to the expected bridge_authority pda which should match the one from the previous point.


It is also recommended to validate the values passed by parameter in the instruction handler initialize and set_bridge_config of the ngl-bridge program to check that they are valid to avoid a situation like the one described in HAL-01 and HAL-02 by error.

Remediation Plan

RISK ACCEPTED: The Entangle Labs team accepted the risk of this finding.

7.4 NEW ADMIN NOT VALIDATED

// Informational

Description

The SetAdmin instruction in the ngl-bridge and ngl-core programs allows the deployer to set a new config admin once the config is initialized, provided as a parameter. This new admin will hold critical privileges for managing the config account. However, the provided admin address is not validated to ensure it is a valid address. Additionally, the instruction handler uses a one-step procedure for admin delegation.

set_admin.rs

pub struct SetAdmin<'info> {
    /// Deployer address
    #[account(signer, address = DEPLOYER.parse().expect("Deployer key not set"))]
    pub deployer: Signer<'info>,

    /// Bridge config
    #[account(mut, seeds = [ROOT, b"CONFIG"], bump)]
    pub config: Box<Account<'info, Config>>,
}

pub fn handle_set_admin(ctx: Context<SetAdmin>, admin: Pubkey) -> Result<()> {
    ctx.accounts.config.admin = admin;
    Ok(())
}

lib.rs


    pub fn set_admin(ctx: Context<SetAdmin>, admin: Pubkey) -> Result<()> {
        ctx.accounts.config.admin = admin;
        Ok(())
    }

This may lead to issues where the MintByAdmin and SetBridgeConfig statements cannot be called if the admin address is invalid. Moreover, mistakenly setting an incorrect address could allow unauthorized use of high privileges by the new malicious admin, posing security risks until the deployer calls SetAdmin again to set a valid and trusted administrator.

BVSS
Recommendation

Although this issue does not pose an immediate security risk, as the deployer can reassign a new config admin, it is advisable to verify that the new admin address provided is valid to prevent potential issues.


Remediation Plan

ACKNOWLEDGED: The Entangle Labs team acknowledged this finding.

8. Automated Testing

Static Analysis Report
Description

Halborn used automated security scanners to assist with detection of well-known security issues and vulnerabilities. Among the tools used was cargo audit, a security scanner for vulnerabilities reported to the RustSec Advisory Database. All vulnerabilities published in https://crates.io are stored in a repository named The RustSec Advisory Database. cargo audit is a human-readable version of the advisory database which performs a scanning on Cargo.lock. Security Detections are only in scope. All vulnerabilities shown here were already disclosed in the above report. However, to better assist the developers maintaining this code, the auditors are including the output with the dependencies tree, and this is included in the cargo audit output to better know the dependencies affected by unmaintained and vulnerable crates.

Cargo Audit Results


ID

Crate

Desccription

RUSTSEC-2022-0093

ed25519-dalek

Double Public Key Signing Function Oracle Attack on ed255109-dalek




Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.