Farseen0/scar-pairs huggingface dataset:
LOW severity: [MAOR-1] Oracle DoS by depositing into a withdrawn validator. **Severity:** High

**Path:** services/oracle/reporter/reporter.go:computeNumValidatorsFullyWithdrawn#L201-L232

**Description:**

The oracle implementation uses a validator’s balance to compute the number of validators that have withdrawn in a report period in...

Vulnerable code:
if (
    uint256(newRecord.currentNumValidatorsWithBalance)
        + uint256(newRecord.cumulativeNumValidatorsFullyWithdrawn) > staking.numInitiatedValidators()
) {
    revert InvalidUpdateMoreValidatorsThanInitiated(
        newRecord.currentNumValidatorsWithBalance + newRecord.cumulativeNumValidatorsFullyWithdrawn,
        staking.numInitiatedValidators()
    );
}

func (r *Reporter) computeNumValidatorsFullyWithdrawn(ctx context.Context, beginVals, endVals []*validator.Validator) (uint64, error) {
	pubKeyToBal := make(map[string]*big.Int)
	for _, v := range beginVals {
		pubKeyToBal[v.PubKey] = v.BalanceWei
	}

	count := uint64(0)
	for _, v := range endVals {
		// If the validator still has a balance, skip it.
		if v.BalanceWei.Cmp(zero) != 0 {
			continue
		}
		// If the validator is not in the begin set and has no balance, it is added and fully withdrawn within the current period.
		// This is very unlikely to happen as there is a 256 epoch delay for exiting after creating a validator, but we include it
		// in case either; we have a giant report window, or the ethereum rules change.
		_, ok := pubKeyToBal[v.PubKey]
		if !ok {
			logger.Infow("Found withdrawn validator", "pubKey", v.PubKey)
			count++
			continue
		}

		prevBalance := pubKeyToBal[v.PubKey]
		// If the validator is in the begin set and has some balance, and it is fully withdrawn (i.e. no balance) within the current period, then increase the counter.
		if prevBalance.Cmp(zero) != 0 {
			logger.Infow("Found withdrawn validator", "pubKey", v.PubKey)
			count++
		}
	}

	return count, nil
}
LOW severity: [MAOR-9] Validator IsFullyWithdrawn predicate is incorrect and can cause double counting. **Severity:** High

**Path:** lib/validator/validator.go:IsFullyWithdrawn#L44-L46

**Description:**

The newly implemented logic after validation of "MAOR-1: Oracle DoS by depositing into a withdrawn validator" is still vulnerable.

The reporter uses the validator predicates as filters on the...

Vulnerable code:
func (v *Validator) IsFullyWithdrawn() bool {
	return (v.Status == v1.ValidatorStateWithdrawalPossible && v.BalanceWei.Cmp(big.NewInt(0)) == 0) || v.Status == v1.ValidatorStateWithdrawalDone
}

if val.WithdrawableEpoch() <= epoch {
	if val.EffectiveBalance() != 0 {
		return validator.WithdrawalPossible, nil
	} else {
		return validator.WithdrawalDone, nil
	}
}

if (
    uint256(newRecord.currentNumValidatorsNotFullyWithdrawn)
        + uint256(newRecord.cumulativeNumValidatorsFullyWithdrawn) > staking.numInitiatedValidators()
) {
    revert InvalidUpdateMoreValidatorsThanInitiated(
        newRecord.currentNumValidatorsNotFullyWithdrawn + newRecord.cumulativeNumValidatorsFullyWithdrawn,
        staking.numInitiatedValidators()
    );
}
LOW severity: [MAOR-10] Validator's withdrawn principal could be counted as rewards. **Severity:** High

**Path:** lib/withdrawals/withdrawals.go:ComputeWithdrawals#L57-69

**Description:**

In order to correctly count withdrawals and separate principal from reward amounts, the oracle uses the `ComputeWithdrawals` function. It does so by looking at all withdrawals and filtering on.....

Vulnerable code:
if val.WithdrawableEpoch() <= epoch {
	if val.EffectiveBalance() != 0 {
		return validator.WithdrawalPossible, nil
	} else {
		return validator.WithdrawalDone, nil
	}
}

def process_effective_balance_updates(state: BeaconState) -> None:
    # Update effective balances with hysteresis
    for index, validator in enumerate(state.validators):
        balance = state.balances[index]
        [..]
        validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE)
            
def process_epoch(state: BeaconState) -> None:
    [..]
    process_effective_balance_updates(state)
    [..]

def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
    [..]

def process_block(state: BeaconState, block: BeaconBlock) -> None:
    [..]
    process_withdrawals(state, block.body.execution_payload)  # [New in Capella]
    [..]
HIGH severity: [MAOR-7] Node API dependency for verification of VersionedSignedBeaconBlock. **Severity:** Low

**Description:**

The oracle service retrieves a signed beacon block without verifying the data or signatures. The node API it currently uses is the QuickNode API. The issue exists in the following functions: `ComputeWithdrawals()` called from `OracleReporter` class (line 162),...

Vulnerable code:
func (a *Analyzer) findWithdrawalsAtSlot(ctx context.Context, slotNumber uint64) ([]*Withdrawal, error) {
	block, err := a.consensusClient.SignedBeaconBlock(ctx, strconv.FormatUint(slotNumber, 10))
	[..]
}

func (s *Scheduler) alignBlockWindow(ctx context.Context, lastRecord oracle.OracleRecord) (uint64, uint64, error) {
	// Find the latest finalized block
	block, err := s.consensusClient.SignedBeaconBlock(ctx, consensus.BlockIdentifierFinalized)
}
LOW severity: [MAOR-12] BlockStamp library uses parent hash instead of block hash. **Severity:** Low

**Path:** lib/consensus/blockstamp.go#L64, L72

**Description:**

The library for creating a BlockStamp from a beacon block, exposes the function `NewFromSignedBeaconBlock` to facilitate this.

However, for both Capella and Deneb versions, it will place the beacon block’s...

Vulnerable code:
return &BlockStamp{
			StateRoot:      root.String(),
			SlotNumber:     uint64(slot),
			BlockNumber:    block.Capella.Message.Body.ExecutionPayload.BlockNumber,
>>>>		BlockHash:      block.Capella.Message.Body.ExecutionPayload.ParentHash.String(),
			BlockTimestamp: block.Capella.Message.Body.ExecutionPayload.Timestamp,
		}, nil

func NewFromSignedBeaconBlock(block *spec.VersionedSignedBeaconBlock) (*BlockStamp, error) {
	root, err := block.StateRoot()
	if err != nil {
		return nil, err
	}
	slot, err := block.Slot()
	if err != nil {
		return nil, err
	}
	switch block.Version {
	case spec.DataVersionBellatrix:
		return &BlockStamp{
			StateRoot:      root.String(),
			SlotNumber:     uint64(slot),
			BlockNumber:    block.Bellatrix.Message.Body.ExecutionPayload.BlockNumber,
			BlockHash:      block.Bellatrix.Message.Body.ExecutionPayload.BlockHash.String(),
			BlockTimestamp: block.Bellatrix.Message.Body.ExecutionPayload.Timestamp,
		}, nil
	case spec.DataVersionCapella:
		return &BlockStamp{
			StateRoot:      root.String(),
			SlotNumber:     uint64(slot),
			BlockNumber:    block.Capella.Message.Body.ExecutionPayload.BlockNumber,
			BlockHash:      block.Capella.Message.Body.ExecutionPayload.ParentHash.String(),
			BlockTimestamp: block.Capella.Message.Body.ExecutionPayload.Timestamp,
		}, nil
	case spec.DataVersionDeneb:
		return &BlockStamp{
			StateRoot:      root.String(),
			SlotNumber:     uint64(slot),
			BlockNumber:    block.Deneb.Message.Body.ExecutionPayload.BlockNumber,
			BlockHash:      block.Deneb.Message.Body.ExecutionPayload.ParentHash.String(),
			BlockTimestamp: block.Deneb.Message.Body.ExecutionPayload.Timestamp,
		}, nil
	}
	return nil, fmt.Errorf("unknown block version %d", block.Version)
}
LOW severity: [EIG-10] Withdrawal proofs can be forged due to missing index bit size check. **Severity:** Critical

**Path:** BeaconChainProofs.sol:verifyWithdrawal#L269-L399

**Description:**  

In order for the EigenPod to verify and consequently process a withdrawal from the beacon chain, it uses the BeaconChainsProofs' `verifyWithdrawal` function. This function takes various...

Vulnerable code:
require(
    withdrawalProof.blockRootIndex < 2 ** BLOCK_ROOTS_TREE_HEIGHT,
    "BeaconChainProofs.verifyWithdrawal: blockRootIndex is too large"
);

uint256 historicalBlockHeaderIndex = (HISTORICAL_SUMMARIES_INDEX <<
    ((HISTORICAL_SUMMARIES_TREE_HEIGHT + 1) + 1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
    (uint256(withdrawalProof.historicalSummaryIndex) << (1 + (BLOCK_ROOTS_TREE_HEIGHT))) |
    (BLOCK_SUMMARY_ROOT_INDEX << (BLOCK_ROOTS_TREE_HEIGHT)) |
    uint256(withdrawalProof.blockRootIndex);

**Remediation:**  Check the length of `historicalSummaryIndex`, for example:
MEDIUM severity: [EIG-14] EigenPods can restake and withdraw without proving and burning shares. **Severity:** Critical

**Path:** EigenPod.sol:withdrawNonBeaconChainETHBalanceWei#L393-L404

**Description:**  

The EigenPod offers 2 functions to withdraw ETH directly without proving a withdrawal. The first one is `withdrawBeforeRestaking`, which requires `hasRestaked` to be `false`. The...

Vulnerable code:
receive() external payable {
    nonBeaconChainETHBalanceWei += msg.value;
    emit NonBeaconChainETHReceived(msg.value);
}

function withdrawNonBeaconChainETHBalanceWei(
    address recipient,
    uint256 amountToWithdraw
) external onlyEigenPodOwner {
    require(
        amountToWithdraw <= nonBeaconChainETHBalanceWei,
        "EigenPod.withdrawnonBeaconChainETHBalanceWei: amountToWithdraw is greater than nonBeaconChainETHBalanceWei"
    );
    nonBeaconChainETHBalanceWei -= amountToWithdraw;
    emit NonBeaconChainETHWithdrawn(recipient, amountToWithdraw);
    _sendETH_AsDelayedWithdrawal(recipient, amountToWithdraw);
}

function withdrawBeforeRestaking() external onlyEigenPodOwner hasNeverRestaked {
    _processWithdrawalBeforeRestaking(podOwner);
}

function _processWithdrawalBeforeRestaking(address _podOwner) internal {
    mostRecentWithdrawalTimestamp = uint32(block.timestamp);
    _sendETH_AsDelayedWithdrawal(_podOwner, address(this).balance);
}
LOW severity: [EIG-17] Operator or delegation approver have the power to censor delegated stakers. **Severity:** Medium

**Path:** DelegationManager.sol:undelegate#L213-L257

**Description:**  

The operator or delegation approver that stakers have delegated to, have the power to selectively censor those stakers.

As soon as anyone can register an operator on EigenLayer, an attacker can create...

Vulnerable code:
The staker is able to delegate his shares to another operator only after `withdrawalDelayBlocks` period. Which is 1 week according to the documentation https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/docs/core/StrategyManager.md#strategymanager.
LOW severity: [EIG-19] The EigenPod balance update function has to be permissioned. **Severity:** Medium

**Path:** EigenPod.sol:verifyBalanceUpdate#L193-L274

**Description:**  

The `verifyBalanceUpdate()` function is permissionless and, therefore, could be called by anyone with valid proof of a validator's current balance on the beacon chain. Resulting in the adjustment of a...

Vulnerable code:
function verifyBalanceUpdate(
        uint64 oracleTimestamp,
        uint40 validatorIndex,
        BeaconChainProofs.StateRootProof calldata stateRootProof,
        BeaconChainProofs.BalanceUpdateProof calldata balanceUpdateProof,
        bytes32[] calldata validatorFields
    ) external onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_BALANCE_UPDATE) {  // @audit permissionless
    ...
    }
LOW severity: [EIG-1] Custom errors. **Severity:** Low

**Description:**  

In each contract the validation checks are performed using the `require` function with a reason string.

**Remediation:**  We would recommend to replace these with custom errors. This should be done by flipping the check.

For example:

becomes

The usage of...

Vulnerable code:
**Remediation:**  We would recommend to replace these with custom errors. This should be done by flipping the check.

For example:
LOW severity: [EIG-13] Creation of pods can suffer denial of service. **Severity:** Low

**Path:** EigenPodManager.sol:_deployPod#L378-L396

**Description:**  

The function to deploy a pod uses a counter `numPods` to check the total amount of pods against the `maxPods` configuration variable.

An attacker can create as many pods as needed in order to reach...

Vulnerable code:
function _deployPod() internal onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (IEigenPod) {
        // check that the limit of EigenPods has not been hit, and increment the EigenPod count
        require(numPods + 1 <= maxPods, "EigenPodManager._deployPod: pod limit reached");
        ++numPods;
        // create the pod
        IEigenPod pod = IEigenPod(
            Create2.deploy( 
                0,
                bytes32(uint256(uint160(msg.sender))),
                // set the beacon address to the eigenPodBeacon and initialize it
                abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
            )
        );
        pod.initialize(msg.sender);
        // store the pod in the mapping
        ownerToPod[msg.sender] = pod;
        emit PodDeployed(address(pod), msg.sender);
        return pod;
    }
HIGH severity: [RSCO-3] DoS with SHA ECALL using big block compression count. **Severity:** High

**Path:** zkvm/src/exec/executor.rs

**Description:**

In the executor.rs the function ecall_sha() implements the SYSCALL(3), which is meant to be the SHA256 hash function's implementation. It takes as arguments the out-state, in-state, blocks' pointers, and the count of the...

Vulnerable code:
// Limit syscall buffers so that the Executor doesn't get into an infinite
// split situation.
pub const MAX_BUF_BYTES: usize = 4 * 1024;
pub const MAX_BUF_WORDS: usize = MAX_BUF_BYTES / WORD_SIZE;
pub const MAX_SHA_COMPRESS_BLOCKS: usize = 1000;

...

pub unsafe extern "C" fn sys_sha_buffer(
    out_state: *mut [u32; DIGEST_WORDS],
    in_state: *const [u32; DIGEST_WORDS],
    buf: *const u8,
    count: u32,
) {
...
    while count_remain > 0 {
        let count = min(count_remain, MAX_SHA_COMPRESS_BLOCKS as u32);
        ecall_4(
            ecall::SHA,
            out_state as u32,
            in_state as u32,
            ptr as u32,
            ptr.add(DIGEST_BYTES) as u32,
            count,
        );
...
    }
}

fn ecall_sha(&mut self) -> Result<OpCodeResult> {
        let out_state_ptr = self.monitor.load_register(REG_A0);
        let in_state_ptr = self.monitor.load_register(REG_A1);
        let mut block1_ptr = self.monitor.load_register(REG_A2);
        let mut block2_ptr = self.monitor.load_register(REG_A3);
        let count = self.monitor.load_register(REG_A4);

        let in_state: [u8; DIGEST_BYTES] = self.monitor.load_array(in_state_ptr)?;
        let mut state: [u32; DIGEST_WORDS] = bytemuck::cast_slice(&in_state).try_into().unwrap();
        for word in &mut state {
            *word = word.to_be();
        }

        log::debug!("Initial sha state: {state:08x?}");
        for _ in 0..count {
          ...
        }
HIGH severity: [RSCO-6] ELF segment virtual address can be set to an arbitrary address. **Severity:** Medium

**Path:** binfmt/src/elf.rs

**Description:**

The function load_elf() loads and checks the ELF sections and checks that the file size and each segment’s size are not bigger than MAX_MEM (2^28 by default). However, it fails to check that the virtual address (vaddr), which...

Vulnerable code:
This situation can lead to system memory overwriting, where a malicious ELF can subtly change the guest program's initial state, which otherwise would have been impossible due to memory read/write security checks. This means the code disassembly or decompilation will not show malicious instructions. However, the other segments' layouts may collide with the system memory region and undermine the expected program execution flow.
MEDIUM severity: [RSCO-5] Ecall parameters missing address range check. **Severity:** Medium

**Path:** zkvm/src/exec/executor.rs

**Description:**

The executor’s ecall functions that correspond to their syscalls (`ecall_sha`, `ecall_halt`, `ecall_input`, `ecall_software`, `ecall_bigint`) are missing the security check to ensure that the memory pointers provided by...

Vulnerable code:
fn read_mem(&mut self, addr: u32, size: MemAccessSize) -> Option<u32> {
    if addr < TEXT_START || addr as usize >= SYSTEM.start() {
        return None;
    }
    ...
}

fn write_mem(&mut self, addr: u32, size: MemAccessSize, store_data: u32) -> bool {
    if addr < TEXT_START || addr as usize >= SYSTEM.start() {
        return false;    
    }
    ...
}
HIGH severity: [RSCO-11] CUDA memory can leak private inputs from data trace. **Severity:** Low

**Path:**  risc0/zkvm/src/host/server/prove/prover_impl.rs

**Description:**

Risc0 implements GPU acceleration for proving calculations, such as field operations, NTT/INTT, and Poseidon/SHA hashes functions. The prover supports CUDA and Metal architectures and has built kernels.....

Vulnerable code:
pub unsafe fn uninitialized(size: usize) -> CudaResult<Self> {
        let ptr = if size > 0 && size_of::<T>() > 0 {
            cuda_malloc(size)?
        } 
        ...
    }

prover.commit_group(
            REGISTER_GROUP_DATA,
            hal.copy_from_elem("data", &adapter.get_data().as_slice()),
        );

// buf ownership is transferred to make_coeffs and returned back to coeffs
        let coeffs = make_coeffs(self.hal, buf, group_size);
        // PolyGroup takes the ownership and gets inserted into self.groups
        let group_ref = self.groups[tap_group_index].insert(PolyGroup::new(
            self.hal,
            coeffs,
            group_size,
            self.cycles,
            "data",
        ));
LOW severity: [RSCO-8] Executor ELF loader virtual address word alignment check missing. **Severity:** Low

**Path:** binfmt/src/elf.rs

**Description:**

The function load_elf() parses the ELF binary, loads the memory segments, and does sanity checks on the ELF structure, such as checking that the binary is 32-bit, RISC-V machine type, max memory checks, etc. Nonetheless, it fails to.....

Vulnerable code:
let vaddr: u32 = segment.p_vaddr.try_into()?;   
            let offset: u32 = segment.p_offset.try_into()?;
            for i in (0..mem_size).step_by(4) {
                let addr = vaddr.checked_add(i).context("Invalid segment vaddr")?;
                if i >= file_size {
                    // Past the file size, all zeros.
                    image.insert(addr, 0);
                } else {
                    let mut word = 0;
                    // Don't read past the end of the file.
                    let len = std::cmp::min(file_size - i, 4);
                    for j in 0..len {
                        let offset = (offset + i + j) as usize;
                        let byte = input.get(offset).context("Invalid segment offset")?;
                        word |= (*byte as u32) << (j * 8);
                    }
                    image.insert(addr, word);
                }
            }
HIGH severity: [RSCO-10] Syscall pointer type parameters missing address range check. **Severity:** Low

**Path:** zkvm/src/host/server/exec/syscall.rs

**Description:**

Risc0’s syscalls (`SysGetenv`, `SysLog`, `SysPanic`, `SysSliceIo`, `PosixIo’s` `sys_write`) read a buffer from the memory via `buf_ptr` and `buf_len` parameters. The `buf_ptr` parameter is missing the security...

Vulnerable code:
impl Syscall for SysGetenv {
    fn syscall(
        &mut self,
        _syscall: &str,
        ctx: &mut dyn SyscallContext,
        to_guest: &mut [u32],
    ) -> Result<(u32, u32)> {
        let buf_ptr = ctx.load_register(REG_A3);
        let buf_len = ctx.load_register(REG_A4);
        let from_guest = ctx.load_region(buf_ptr, buf_len)?;
        ...
}
HIGH severity: [FNG-11] All interest tokens can be stolen from the interest market. **Severity:** Critical

**Path:** CErc721.sol:supplyInterestStoredInternal#L752-L763

**Description:** The NFT underlying CToken, CErc721, uses a CErc20InterestMarket as tokens for both interest rewards for suppliers and interest payments for borrowers. In order to calculate the rewards for those...

Vulnerable code:
function supplyInterestStoredInternal(address account) internal view returns (uint) {
        Exp memory exchangeRate = Exp({mantissa: exchangeRateStored()});
        uint supplyBalance = mul_ScalarTruncate(exchangeRate, accountTokens[account]);

        if (supplyBalance == 0) {
            return 0;
        }

        SupplyInterestSnapshot storage supplyInterestSnapshot = supplyInterest[account];
        uint newInterestAccrued = (supplyBalance * supplyIndex / supplyInterestSnapshot.interestIndex) - supplyBalance;
        return supplyInterestSnapshot.interestAccrued + newInterestAccrued;
    }
HIGH severity: [FNG-17] CErInterestMarket collateral can be directly used to pay interest and bypass health check. **Severity:** Critical

**Path:** CErc20InterestMarket.sol:payInterestInternal#L67-L101

**Description:** A CErc20InterestMarket is a CERC20 token that can be used to pay interest for a CERC721 loan. These tokens have USD stable coins as underlying, such as USDC. These tokens are also normal...

Vulnerable code:
function payInterestInternal(address borrowMarket, address payer, uint interestAmount) internal {
    if (interestAmount == 0) {
        return;
    }

    /* Fail if pay interest not allowed */
    uint allowed = comptroller.payInterestAllowed(address(this), borrowMarket, payer, interestAmount);
    require(allowed == 0, "pay not allowed");

    Exp memory exchangeRate = Exp({mantissa: exchangeRateStoredInternal()});
    uint interestTokens = div_(interestAmount, exchangeRate);

    // payer interest market balance is reduced to cover interest being paid
    uint balancePayer = accountTokens[payer];
    require(balancePayer >= interestTokens, "insufficient payer reserve");
    accountTokens[payer] = balancePayer - interestTokens;
    emit Transfer(payer, address(0), interestTokens);

    uint totalVirtual_ = totalVirtual;
    uint heldBalance;
    if (interestTokens > totalVirtual_) {
        heldBalance = interestTokens - totalVirtual_;
        totalSupply = totalSupply - totalVirtual_;
        totalVirtual = 0;
    } else {
        totalSupply = totalSupply - interestTokens;
        totalVirtual = totalVirtual_ - interestTokens;
    }

    if (heldBalance != 0) {
        // keep a reserve of cToken
        accountTokens[address(this)] = accountTokens[address(this)] + heldBalance;
        emit Transfer(address(0), address(this), heldBalance);
    }
}
HIGH severity: [FNG-15] CErliquidation will always revert if interest state is up-to-date. **Severity:** High

**Path:** CErc721.sol:_liquidateBorrow#L428-L464

**Description:** The function `_liquidateBorrow` of the CErc721 contract fetches the current exchange rate using `accrueInterest()` on line 431:

Afterwards, this value is used in a check for freshness on lines...

Vulnerable code:
uint assetsExchangeRate = accrueInterest();

if (accrualBlockNumber != getBlockNumber() || assetsExchangeRate == 0) {
    revert LiquidateFreshnessCheck();
}

if (accrualBlockNumberPrior == currentBlockNumber) {
    return NO_ERROR;
}