#45674 [SC-Insight] `executeMinting()` allows impersonation of minter during chain-reorg due to deterministic `crtId` and lack of minter binding

Submitted on May 18th 2025 at 23:06:32 UTC by @danvinci_20 for Audit Comp | Flare | FAssets

  • Report ID: #45674

  • Report Type: Smart Contract

  • Report severity: Insight

  • Target: https://github.com/flare-foundation/fassets/blob/main/contracts/assetManager/facets/MintingFacet.sol

  • Impacts:

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Description

An attacker can front-run a legitimate collateral reservation during a reorg and copy the crtId by ensuring their reservation gets the same randomizedIdSkip() result. By setting the executor field in their reservation to the real user’s executor and other data, they create a situation where the real executor, when executing with a valid payment proof, unintentionally mints tokens for the attacker’s address.

   function reserveCollateral(
        address _minter,
        address _agentVault,
        uint64 _lots,
        uint64 _maxMintingFeeBIPS,
        address payable _executor,
        string[] calldata _minterUnderlyingAddresses
    )
        internal
    {
        Agent.State storage agent = Agent.get(_agentVault);
        Agents.requireWhitelistedAgentVaultOwner(agent);

        Collateral.CombinedData memory collateralData = AgentCollateral.combinedData(agent);
        AssetManagerState.State storage state = AssetManagerState.get();

...............

        require(msg.value >= reservationFee, "inappropriate fee amount");
        // create new crt id - pre-increment, so that id can never be 0
       @>> state.newCrtId += PaymentReference.randomizedIdSkip();
        uint64 crtId = state.newCrtId;
        // create in-memory cr and then put it to storage to not go out-of-stack
        CollateralReservation.Data memory cr;
        cr.valueAMG = valueAMG;
        cr.underlyingFeeUBA = Conversion.convertAmgToUBA(valueAMG).mulBips(agent.feeBIPS).toUint128();
        cr.reservationFeeNatWei = reservationFee.toUint128();
        // 1 is added for backward compatibility where 0 means "value not stored" - it is subtracted when used
        cr.poolFeeShareBIPS = agent.poolFeeShareBIPS + 1;
        cr.agentVault = _agentVault;
        cr.minter = _minter;
        cr.executor = _executor;
        cr.executorFeeNatGWei = ((msg.value - reservationFee) / Conversion.GWEI).toUint64();

........
         }

The reserveCollateral() flow assigns a crtId using a weak randomization mechanism:

Since this logic is block-dependent and lacks any entropy from the caller, it is predictable during reorgs. This enables an attacker to force a collision with a legitimate crtId reservation.

Because executeMinting() trusts that the caller (minter, executor, or agent) corresponds to the original reservation, and does not validate the minter address against an expected value, the following sequence is possible:

  1. Legitimate user submits a reservation.

  2. Attacker front-runs in a reorged block and creates their own reservation with same crtId (by controlling timing).

  3. Attacker sets executor to the known executor used by the real user.

  4. Real executor (most likely an offchain relayer) calls executeMinting() believing they're minting for the legit user, but ends up minting to attacker-controlled address.

Impact

Likelihood: Low – reorgs are rare, but deterministic ID generation enables predictability.

Impact: High - The legitimate minter loses funds permanently. The attacker gains tokens minted using their own reservation.

Recommendation

To prevent this attack, bind executeMinting() to a validated minter identity:

  1. Add an expectedMinter parameter to executeMinting().

  2. Add require(crt.minter == expectedMinter) to validate against impersonation.

This ensures the executor cannot be tricked into executing a minting for a spoofed reservation.

References

https://github.com/flare-foundation/fassets/blob/fc727ee70a6d36a3d8dec81892d76d01bb22e7f1/contracts/assetManager/facets/CollateralReservationsFacet.sol#L36-L51

https://github.com/flare-foundation/fassets/blob/fc727ee70a6d36a3d8dec81892d76d01bb22e7f1/contracts/assetManager/facets/MintingFacet.sol#L24-L32

Proof of Concept

Proof of Concept

The attacker can follow this part:

  1. Legitimate user submits a reservation.

  2. Attacker front-runs in a reorged block and creates their own reservation with same crtId (by controlling timing).

  3. Attacker sets executor to the known executor used by the real user.

  4. Real executor (most likely an offchain relayer) calls executeMinting() believing they're minting for the legit user, but ends up minting to attacker-controlled address.

Was this helpful?