code-423n4 / 2024-07-karak-validation

0 stars 0 forks source link

`NativeNode` can't receive the Eth from the beacon chain validators, make the Native Restaking not working #272

Closed c4-bot-8 closed 2 months ago

c4-bot-8 commented 2 months ago

Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeNode.sol#L17

Vulnerability details

Impact

The NativeNode can't receive the Eth from the beacon chain validators, which breaks the Native Restaking functionality of the protocol and user lose their ETH of his validator.

Proof of Concept

User can use Native Restaking on Karak to restake their ETH from their beacon chain validator, it use the NativeNode address as a withdrawal address, created from a NativeVault. The NativeNiode doesn't have fallback() or receive() functions and therefore cannot receive the ETH sended from the validator.

On the beacon chain validator, when user set the withdrawal address, he can't change it, and because the NativeNode can't receive ETH, the user user lose his funds.

POC

You can see this Poc with the NativeNode and a modified NativeNode with fallback() and receive() functions. The logs of the Poc:

  -------------- NativeNode --------------
  Balance Ether: 0
  Balance Ether After Call: 0
  -------------- NativeNode with fallback() and receive() --------------
  Balance Ether: 0
  Balance Ether After Call: 3000000000000000000

You can add this PoC to the ./test/nativeRestaking/nativeVault.t.sol :

    function test_sendEtherToNativeNode() public {
        //Setup
        NativeVault vaultContract;
        DSSContract dss = new DSSContract();

        // Give Eth to Bob
        address bob = address(500);
        vm.deal(bob, 20 ether);

        vm.startPrank(address(dss));
        core.registerDSS(100000000000000000000);
        vm.stopPrank();       
        // Setup NativeNode implementation
        address nativeNodeImpl = address(new NativeNode());

        // Deploy Vaults
        VaultLib.Config[] memory vaultConfigs = new VaultLib.Config[](1);
        vaultConfigs[0] = VaultLib.Config({
            asset: Constants.DEAD_BEEF,
            decimals: 18,
            operator: operator,
            name: "NativeTestVault",
            symbol: "NTV",
            extraData: abi.encode(address(manager), slashStore, address(nativeNodeImpl))
        });

        vm.startPrank(operator);
        IDSS dssInterface = IDSS(address(dss));
        core.registerOperatorToDSS(dssInterface, bytes(""));
        IKarakBaseVault[] memory vaults = core.deployVaults(vaultConfigs, address(0));
        vaultContract = NativeVault(address(vaults[0]));
        vm.stopPrank();

        vm.startPrank(bob);

        // Try send ether on a NativeNode
        address addrNativeNode = vaultContract.createNode();
        uint256 natNodeBal = addrNativeNode.balance;
        console.log("-------------- NativeNode --------------");
        console.log("Balance Ether: %s", natNodeBal);
        (bool success, bytes memory data) = addrNativeNode.call{value: 3 ether}("");
        natNodeBal = addrNativeNode.balance;
        console.log("Balance Ether After Call: %s", natNodeBal);

        // Try send ether on a CustomNativeNode with fallback() and receive()
        NativeNodeReceive natNodeReceive = new NativeNodeReceive(); 
        uint256 natNodeBalReceive = address(natNodeReceive).balance;
        console.log("-------------- NativeNode with fallback() and receive() --------------");
        console.log("Balance Ether: %s", natNodeBalReceive);
        (bool success2, bytes memory _data) = address(natNodeReceive).call{value: 3 ether}("");
        natNodeBalReceive = address(natNodeReceive).balance;
        console.log("Balance Ether After Call: %s", natNodeBalReceive);

        vm.stopPrank();
    }

Also add this on the top of thefile import "../../src/NativeNodeReceive.sol"; Also copy the NativeNode contract and rename it NativeNodeReceive, add theses line at the end of the contract :

    fallback() external payable {}
    receive() external payable {}

Now execute this command: forge test --mt test_sendEtherToNativeNode -vvv

Tools Used

Manual Review

Recommended Mitigation Steps

Add fallback() or/and receive() functions in the NativeNode.

Assessed type

ETH-Transfer

MiloTruck commented 1 month ago

https://ethereum.stackexchange.com/questions/148579/what-happens-when-a-validators-withdrawal-address-is-a-smart-contract-with-a-fa

Withdrawals from the beacon chain do not trigger code, so receive or fallback isn't needed.