xanoutas/solidity-security-findings huggingface dataset:
Operators can deploy malicious NativeVault via changing extraData params
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L161-L168
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/CoreLib.sol#L124-L142


# Vulnerability details

Relevant files: [Core::deployVaults](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L161-L168), [CoreLib::deployVaults](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/CoreLib.sol#L124-L142)
### Impact
Karak protocol allows Operators to deploy special type of vault called `NativeVault`, which allows protocol users to re-stake their natively staked ether. Due to the way `NativeVault` works, it is required to receive some addresses upon initialization, namely: `manager`, `slashStore`, `nodeImplementation`. 
```js
(address manager, address slashStore, address nodeImplementation) = abi.decode(_extraData, (address, address, address));
```

The issue is that no checks are performed for these operator-supplied addresses, which opens the `NativeVault` to a multitude of issues:

1. A malicious manager can change the `NativeNode` implementation at any point. This means that the `withdraw` function can be re-written to transfer funds to attacker's address.
2. A malicious manager can pause functions in `NativeNode` and `NativeVault` making certain actions temporarily DoSed.
3. A different `slashStore` from what is registered in `Core` can be supplied, which will cause `slashAssets` function to revert. This means that to slash such `NativeVault` the assets allow list has to be temporarily updated and then brought back to normal. This puts a strain on normal protocol operations.
4. A malicious `nodeImplementation` can be supplied from the get-go with the same worst-case impact as point **1**.

### Root Cause
Arbitrary addresses are given to `NativeVault` which lack necessary checks.
### PoC
This PoC demonstrates the following impacts:
1. Supplying arbitrary `NativeNode` implementation upon `NativeVault` init
2. Malicious manager changing node implementation
3. `NativeNode` withdrawals redirecting funds to attackers' address
4. Attempts to slash will revert

Create a file `test/nativeRestaking/MaliciousNativeVault.t.sol` with the following content:
```js
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import "forge-std/Vm.sol";
import "forge-std/Test.sol";

import "../../src/Core.sol";
import "../../src/SlashStore.sol";
import "../../src/NativeNode.sol";
import "../../src/NativeVault.sol";
import "../helpers/ProxyDeployment.sol";
import "../../src/interfaces/Constants.sol";
import "../../src/entities/NativeVaultLib.sol";

import {INativeNode} from "../../src/interfaces/INativeNode.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ReentrancyGuard} from "solady/src/utils/Reentra
Lack of input validation in deployVaults function can lead to malicious use
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L161-L168
https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/CoreLib.sol#L77-L87
https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/CoreLib.sol#L118-L147


# Vulnerability details

## Impact
An operator can create a vault with a certain asset that exhibits unexpected behavior, which can result in errors for the operator or, worse, malicious use.

## Proof of Concept
When an operator creates a vault, they can do so with any allowlisted vault and any allowlisted asset.

````
    function validateVaultConfigs(Storage storage self, VaultLib.Config[] calldata vaultConfigs, address implementation)
        public
        view
    {
        if (!(implementation == address(0) || isVaultImplAllowlisted(self, implementation))) {
            revert VaultImplNotAllowlisted();
        }
        for (uint256 i = 0; i < vaultConfigs.length; i++) {
            if (self.assetSlashingHandlers[vaultConfigs[i].asset] == address(0)) revert AssetNotAllowlisted();
        }
    }
````
This function in the CoreLib library (L-77) will cause the call to the deployVaults function to revert. It reverts if the vault or the asset is not allowlisted. However, the function does not check the compatibility between the asset and the vault.

This means that an operator can, for instance, deploy a vault with the native vault implementation but with an ERC20 token as the asset. This can lead to unexpected behavior depending on what will be the allowlisted vaults or the allowlisted assets in the future.

You can copy and paste this test in the vault.t.sol :

````
function test_deployVaultsWithNativeAssets() public {
        VaultLib.Config[] memory vaultConfigs = new VaultLib.Config[](1);
        vaultConfigs[0] = VaultLib.Config({
            asset: Constants.DEAD_BEEF,
            decimals: 18,
            operator: operator,
            name: "TestVault",
            symbol: "TV",
            extraData: bytes("")
        });
         address[] memory assets = new address[](1);
        assets[0] = Constants.DEAD_BEEF;
        address[] memory slashingHandlers = new address[](1);
        slashingHandlers[0] = slashingHandler;
        core.allowlistAssets(assets, slashingHandlers);
        vm.prank(operator);
        IKarakBaseVault[] memory vaultsDeployed = core.deployVaults(vaultConfigs, core.implementation( address(vault)) );
        VaultLib.Config memory config = vaultsDeployed[0].vaultConfig();
// we can see that the asset of the vault is DeadBeef wich indicate the native asset but with the implementation for the ERC20 tokens.
        assertEq(config.asset, Constants.DEAD_BEEF);
        assertEq(core.implementation(address(vaultsDeployed[0])), core.implementation(address(vault)));
       
    }

````

This test show that an operator can deploy a vault with any implementation allowlisted and any allowlisted asset. 



## Tools Used
Unit Tests

## Recommended Mitigat
Breaks invariant: Only DSSs an operator is registered with can slash said operator
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L248


# Vulnerability details

## Impact
This breaks one of the main invariants: `Only DSSs an operator is registered with can slash said operator`.

## Proof of Concept
When DSS calls the finalizeSlashing, there's a possibilty that the operator was already unregistered.
Before DSS calls `finalizeSlashing`, operator can finalize the unstaking of its vault and unregister from DSS successfully. This can be possible if the operator `already pre-scheduled the unstaking` early enough, so it can bypass the `MIN_STAKE_UPDATE_DELAY`.
As `finalizeSlashing` doesn't check the registration status of operator, slashing can still happen towards the given operator and vault.

Below is an example scenario:
1. Operator registers with DSS
2. Operator requests a vault staking into the DSS
3. Operator completes the vault staking after `MIN_STAKE_UPDATE_DELAY`
4. Operator requests unstaking of the given vault. Time passes enough and operator is ready to complete unstaking.
5. DSS finds a wrong behavior of the operator and request a slashing of the given vault.
6. Operator finds out a slashing to be done towards the vault and front-run `finalizeSlashing` by completing unstaking and unregisters from the DSS
7. Operator is unregistered from the vault, but DSS goes on to slash the vault.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Check the registration status of operator in `finalizeSlashing`
```solidity
    function finalizeSlashing(SlasherLib.QueuedSlashing memory queuedSlashing)
        external
        nonReentrant
        whenFunctionNotPaused(Constants.PAUSE_CORE_FINALIZE_SLASHING)
    {
+       self.checkIfOperatorIsRegInRegDSS(queuedSlashing.operator, queuedSlashing.dss);
        _self().finalizeSlashing(queuedSlashing);

        emit FinalizedSlashing(msg.sender, queuedSlashing);
    }


## Assessed type

Other
Operator can avoid slashing by pre-scheduling unstake request
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/SlasherLib.sol#L52
https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L149


# Vulnerability details

## Impact
Gives malicious operators an opportunity of front-running to disrupt the DSS operation and they effectively dodge the slashing event.

## Proof of Concept
Operator can avoid slashing by pre-scheduling an unstake request and front-running the `requestSlashing`.
DSS can slash any vault as long it belongs to a registered operator and fully staked into the DSS. Operator can `pre-schedule an unstake` of the vault early enough to pass the delay (`MIN_STAKE_UPDATE_DELAY`) and get ready to finalize the unstaking at any moment. Basically, they can request an unstaking right after they staked.

If a slashing event comes up targeting the vault, operator can finalize the unstaking by front-running `requestSlashing`. Hence, the slashing would then fail since it's targeting an unstaked vault. If the slashing targets multiple vaults, operator just need to unstake any one of them to prevent slashing.
```solidity
    function validateVaultsAndSlashPercentages(
        CoreLib.Storage storage self,
        SlashRequest memory slashingRequest,
        IDSS dss
    ) internal view {
        if (slashingRequest.vaults.hasDuplicates()) revert DuplicateSlashingVaults();

        uint256 maxSlashPercentageWad = getDSSMaxSlashablePercentageWad(self, dss);
        for (uint256 i = 0; i < slashingRequest.vaults.length; i++) {
            if (!self.operatorState[slashingRequest.operator].isVaultStakeToDSS(dss, slashingRequest.vaults[i])) {
>               revert VaultNotStakedToDSS();
            }
            if (slashingRequest.slashPercentagesWad[i] == 0) revert ZeroSlashPercentageWad();
            if (slashingRequest.slashPercentagesWad[i] > maxSlashPercentageWad) revert MaxSlashPercentageWadBreached();
        }
    }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
1. Let the unstaking request expire if it stays too long.
2. Give the DSS ability to communicate with core to pause the vault on unstaking. Map index: Constants.PAUSE_CORE_FINALIZE_STAKE_UPDATE


## Assessed type

Other
Malicious attacker can disguise as operator via finishUpdateStakeHook
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L126


# Vulnerability details

## Impact
With the wrong operator, it can mislead to wrong DSS operations like distributing awards.

## Proof of Concept
`finalizeUpdateVaultStakeInDSS` forwards msg.sender in place of operator via finishUpdateStakeHook where a malicious attacker can disguise as operator. `finalizeUpdateVaultStakeInDSS` is called to finalize the staking request. After completing the request, core calls the `finishUpdateStakeHook` to notify DSS about it. It sends `msg.sender` in place of operator in the hook interface. Since anyone can invoke this, we can't gurantee if that's the correct operator or an attacker.
```solidity
    function validateAndUpdateVaultStakeInDSS(CoreLib.Storage storage self, QueuedStakeUpdate memory queuedStakeUpdate)
        external
    {
        ...
        HookLib.callHookIfInterfaceImplemented({
            dss: dss,
>           data: abi.encodeWithSelector(dss.finishUpdateStakeHook.selector, msg.sender),
            interfaceId: dss.finishUpdateStakeHook.selector,
            ignoreFailure: true,
            hookCallGasLimit: self.hookCallGasLimit,
            supportsInterfaceGasLimit: self.supportsInterfaceGasLimit,
            hookGasBuffer: self.hookGasBuffer
        });
    }
```

Note: This was previously reported, but seems not fixed yet.
https://code4rena.com/reports/2024-06-karak-pro-league#h-03-validateandupdatevaultstakeindss-forwards-incorrect-operator-address-to-finishupdatestakehook

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use the operator stored in `QueuedStakeUpdate memory queuedStakeUpdate`.
```solidity
    function validateAndUpdateVaultStakeInDSS(CoreLib.Storage storage self, QueuedStakeUpdate memory queuedStakeUpdate)
        external
    {
        ...
        HookLib.callHookIfInterfaceImplemented({
            dss: dss,
-           data: abi.encodeWithSelector(dss.finishUpdateStakeHook.selector, msg.sender),
+           data: abi.encodeWithSelector(dss.finishUpdateStakeHook.selector, queuedStakeUpdate.operator),
            interfaceId: dss.finishUpdateStakeHook.selector,
            ignoreFailure: true,
            hookCallGasLimit: self.hookCallGasLimit,
            supportsInterfaceGasLimit: self.supportsInterfaceGasLimit,
            hookGasBuffer: self.hookGasBuffer
        });
    }
```


## Assessed type

Error
A `DoS` on snapshots due to a rounding error in calculations.
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L425-L446
https://github.com/vectorized/solady/blob/main/src/tokens/ERC4626.sol#L192-L204


# Vulnerability details

## Impact

Creating a snapshot may be impossible due to a rounding error in the calculations.

## Proof of Concept

When initiating a snapshot, some assets are transferred to the slash store within the `_transferToSlashStore()` function. The transfer amount is calculated at `L430`. However, if `convertToAssets(balanceOf(nodeOwner))` exceeds `node.totalRestakedETH` (which could occur due to a rounding error in the calculations), the transaction will revert due to an integer underflow.

```solidity
    function _transferToSlashStore(address nodeOwner) internal {
        ...

430     uint256 slashedAssets = node.totalRestakedETH - convertToAssets(balanceOf(nodeOwner));
        ...
```

Consider the following scenario:

**Note:** The exchange rate formula is `(totalAssets + 1) / (totalSupply + 1)` as defined in [ERC4626](https://github.com/vectorized/solady/blob/main/src/tokens/ERC4626.sol#L201).

1. Current state of the `NativeVault`:
    - `totalAssets` = 3e18
    - `totalSupply` = 5e18
    - Bob's share = (5e18 - 4) / 2
    - Bob's assets = (5e18 - 4) / 2 * (3e18 + 1) / (5e18 + 1) = 1.5e18 - 2
    - `totalRestakedETH` for Bob's node = 1.5e18 - 2
2. An increase in totalAssets by 3:
    - Corresponding share = 3 * (5e18 + 1) / (3e18 + 1) = 4
    
    As a result:
    - `totalAssets` = 3e18 + 3
    - `totalSupply` = 5e18 + 4
    - Bob's assets = (5e18 - 4) / 2 * (3e18 + 4) / (5e18 + 5) = 1.5e18 - 1
    - `totalRestakedETH` for Bob's node = 1.5e18 - 2

In this situation, Bob's snapshot will revert because `convertToAssets(balanceOf(nodeOwner))` is greater than `node.totalRestakedETH`.

This issue arises from calculation rounding errors. As seen in the scenario above, the issue can arise even with a single increase in totalAssets, and the likelihood of this problem grows as the number of participants increases.

Additionally, attackers could exploit this vulnerability to execute a `DoS` attack on others' snapshots by transfering DUST ethers to the nodeAddress and performing the second step of the above scenario.

## Tools Used

Manual review

## Recommended Mitigation Steps

The `_transferToSlashStore()` function should be fixed as follows.

```diff
-       uint256 slashedAssets = node.totalRestakedETH - convertToAssets(balanceOf(nodeOwner));

+       uint256 slashedAssets;
+       if(node.totalRestakedETH > convertToAssets(balanceOf(nodeOwner))) {
+           slashedAssets = node.totalRestakedETH - convertToAssets(balanceOf(nodeOwner));
+       }
```


## Assessed type

DoS
Stakers unfairly forfeit their unattributed node balance included in the slashed assets.
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L425-L446
https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L482
https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L448-L474


# Vulnerability details

## Impact

Stakers may lose unattributed node balance included in the slashed assets.

## Proof of Concept

Assuming slashing occurs while a staker has an unattributed node balance (or some Ether is withdrawn to nodeAddress after slashing), the slashed assets will be transferred from `nodeAddress` to `slashStore` at [L436](https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L436). This may include some unattributed node balance, as slashedWithdrawable is only restricted to `node.nodeAddress.balance` at [L433](https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L433).

https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L425-L446
```solidity
425:     function _transferToSlashStore(address nodeOwner) internal {
426:         NativeVaultLib.Storage storage self = _state();
427:         NativeVaultLib.NativeNode storage node = self.ownerToNode[nodeOwner];
428: 
429:         // slashed ETH = total restaked ETH (node + beacon) - share price equivalent ETH
430:@>       uint256 slashedAssets = node.totalRestakedETH - convertToAssets(balanceOf(nodeOwner));
431: 
432:         // sweepable ETH = min(ETH available on node address, total slashed ETH)
433:@>       uint256 slashedWithdrawable = Math.min(node.nodeAddress.balance, slashedAssets);
434: 
435:         // withdraw sweepable ETH to slashStore
436:@>       INativeNode(node.nodeAddress).withdraw(self.slashStore, slashedWithdrawable);
437: 
438:         // update total restaked ETH available (node + beacon)
439:@>       node.totalRestakedETH -= slashedWithdrawable;
440: 
441:         // update withdrawable credited ETH available on the node only when node balance
442:         // decreases to less than that of the credited node ETH
443:         if (node.nodeAddress.balance < node.withdrawableCreditedNodeETH) {
444:             node.withdrawableCreditedNodeETH = node.nodeAddress.balance;
445:         }
446:     }
```

The unattributed node balance included in the slashed assets signifies an additional loss of Ether for the staker due to the following two factors:

Slashing has already reduced the total assets of the NativeVault and `node.totalRestakedETH` is also decreased in [L439](https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L439).

The unattributed node balance included in the slashed assets is not added to the `totalDeltaWei` at [L482](https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L482), because the `nodeBalanceWei` is calculated at [L459](https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L459) after the `nodeAddress.balance` is already decreased at [L456](https://github.co
A Malicious DSS can easily bypass the slashing limits and slash all the Assets in a Vault 
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Vault.sol#L198
https://github.com/code-423n4/2024-07-karak/blob/main/src/NativeVault.sol#L311-L313


# Vulnerability details

When a DSS requests slashing for a vault or operator, the [`SlasherLib.requestSlashing`](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/SlasherLib.sol#L94) function calculates the amount to be slashed and stores it in [`earmarkedStakes`](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/SlasherLib.sol#L107). This exact `earmarkedStakes` amount is later used in the [`finalizeSlashing`](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/SlasherLib.sol#L136) function to execute the `slashAssets()` operation.

The [`slashAssets`](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Vault.sol#L193) function ensures that the amount to be slashed `totalAssetsToSlash` does not exceed the total assets currently available in the vault. If the `totalAssetsToSlash` is greater than the `totalAssets()`, then the slashing amount will be `totalAssets()`.
```solidity
    function slashAssets(uint256 totalAssetsToSlash, address slashingHandler) external onlyCore returns (uint256 transferAmount){
@>      transferAmount = Math.min(totalAssets(), totalAssetsToSlash);
        SafeTransferLib.safeApproveWithRetry(asset(), slashingHandler, transferAmount);
        ISlashingHandler(slashingHandler).handleSlashing(IERC20(asset()), transferAmount);
        emit Slashed(transferAmount);
    }
```

### Issue
This opens the door for a malicious DSS to slash 100% of the assets available in the vault, regardless of whether the `maxSlashablePercentageWad` is set to 5%, 25%, or 50%. A DSS can `deposit()` a large amount into a vault and then call `requestSlashing()`, and then withdraw its funds before executing `finalizeSlashing()`.

## Proof of Concept
Let's consider a vault with `100` tokens/assets in it, registered with a DSS whose `maxSlashablePercentageWad` is 50%.

- The DSS finds the vault's operator unable to complete the task and eligible for slashing.
- The DSS deposits `105` tokens into that vault and quickly calls `startRedeem()`, so the total assets in that vault are now `205`.
- After 8 days, the DSS calls `requestSlashing()` for that vault, which will set the earmarked stakes/total assets to slash to `102.5` tokens.
- Now, in the `SLASHING_VETO_WINDOW`, the DSS will `finishRedeem()` for its 105 tokens and get back its funds. 
- Only 100 tokens are left in the vault.
- The `SLASHING_VETO_WINDOW` passes, and the DSS calls `finalizeSlashing()`, which executes `slashAssets()`.
- The `slashAssets` function finds that total assets to slash is 102.5, but the total assets available are only 100, so it transfers all `100` tokens to the `Slashin