GivingThanks

First Flight #28
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: medium
Valid

`GivingThanks::constructor()` incorrectly uses `msg.sender` instead of constructor parameter `address _registry`

Relevant Links

Summary

Inside the GivingThanks::constructor, the contract's CharityRegistry registry state variable is incorrectly assigned to CharityRegistry(msg.sender), causing any updates to the original registry contract's state (such as newly verified charities), to be completely ignored.

Vulnerability Details

Inside the GivingThanks::constructor, the parameter address _registry is passed in as a way to assign the contract's CharityRegistry in order to keep track of what charity is verified and which ones aren't. However, instead of assigning this parameter to the contract's state variable CharityRegistry registry, CharityRegistry(msg.sender) is erroneously used as seen below:

constructor(address _registry) ERC721("DonationReceipt", "DRC") {
@> registry = CharityRegistry(msg.sender);
owner = msg.sender;
tokenCounter = 0;
}

Because of this incorrect assignment, any charity that was previously verified before contract initialization, will be ignored and therefore, are unable to recieve any donations provided by any donors.

Impact

Likelihood: High/Medium
State variables are a critical component of smart contracts, storing important data that affects the contract's behavior and security. Because of this erroneous assignment, any interaction with the contract's main function donate will revert, due to the given charity's verified data not being present.

Severity: High/Medium
Any donors who wish to donate any funds to a given charity will have their transactions revert, since the charity won't be present in the verifiedCharities mapping. This means any funds won't being able to reach the given charity.

POC

POC - 1: Original Test Failure

The first proof of code that shows this is an issue, comes from two of the three tests that are implemented inside /test/GivingThanks.t.sol:

  • testDonate()

  • testFuzzDonate()

Both of these tests fail when calling on GivingThanks::donate() due to the require as seen in the output of both tests.
To verify the output of testDonate, please run the command:

forge test --mt testDonate -vvv
Output
No files changed, compilation skipped
Ran 1 test for test/GivingThanks.t.sol:GivingThanksTest
[FAIL. Reason: EvmError: Revert] testDonate() (gas: 27707)
Traces:
[27707] GivingThanksTest::testDonate()
├─ [2383] GivingThanks::tokenCounter() [staticcall]
│ └─ ← [Return] 0
├─ [0] VM::deal(donor: [0xBd4D11D884c3c7bF373b013AC52fd99f9DD86D0A], 10000000000000000000 [1e19])
│ └─ ← [Return]
├─ [0] VM::prank(donor: [0xBd4D11D884c3c7bF373b013AC52fd99f9DD86D0A])
│ └─ ← [Return]
├─ [5256] GivingThanks::donate{value: 1000000000000000000}(charity: [0x59cf1Fbe6AD1EBf1a01e78EE808B7c889E6dd58A])
│ ├─ [0] admin::isVerified(charity: [0x59cf1Fbe6AD1EBf1a01e78EE808B7c889E6dd58A]) [staticcall]
│ │ └─ ← [Stop]
│ └─ ← [Revert] EvmError: Revert
└─ ← [Revert] EvmError: Revert
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 863.35µs (78.81µs CPU time)
Ran 1 test suite in 4.74ms (863.35µs CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/GivingThanks.t.sol:GivingThanksTest
[FAIL. Reason: EvmError: Revert] testDonate() (gas: 27707)
Encountered a total of 1 failing tests, 0 tests succeeded

And for testFuzzDonate(), please run the following command:

forge test --mt testFuzzDonate -vvv
Output
No files changed, compilation skipped
proptest: Aborting shrinking after the PROPTEST_MAX_SHRINK_ITERS environment variable or ProptestConfig.max_shrink_iters iterations (set 0 to a large(r) value to shrink more; current configuration: 0 iterations)
Ran 1 test for test/GivingThanks.t.sol:GivingThanksTest
[FAIL. Reason: EvmError: Revert; counterexample: calldata=0x06a6cd8900000000000000000000000000000000000000000000000000000226c25d4b2d args=[2365492906797 [2.365e12]]] testFuzzDonate(uint96) (runs: 0, μ: 0, ~: 0)
Logs:
Bound result 2365492906797
Traces:
[33991] GivingThanksTest::testFuzzDonate(2365492906797 [2.365e12])
├─ [0] console::log("Bound result", 2365492906797 [2.365e12]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::deal(donor: [0xBd4D11D884c3c7bF373b013AC52fd99f9DD86D0A], 20000000000000000000 [2e19])
│ └─ ← [Return]
├─ [2383] GivingThanks::tokenCounter() [staticcall]
│ └─ ← [Return] 0
├─ [0] VM::prank(donor: [0xBd4D11D884c3c7bF373b013AC52fd99f9DD86D0A])
│ └─ ← [Return]
├─ [5256] GivingThanks::donate{value: 2365492906797}(charity: [0x59cf1Fbe6AD1EBf1a01e78EE808B7c889E6dd58A])
│ ├─ [0] admin::isVerified(charity: [0x59cf1Fbe6AD1EBf1a01e78EE808B7c889E6dd58A]) [staticcall]
│ │ └─ ← [Stop]
│ └─ ← [Revert] EvmError: Revert
└─ ← [Revert] EvmError: Revert
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.72ms (797.81µs CPU time)
Ran 1 test suite in 4.49ms (1.72ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/GivingThanks.t.sol:GivingThanksTest
[FAIL. Reason: EvmError: Revert; counterexample: calldata=0x06a6cd8900000000000000000000000000000000000000000000000000000226c25d4b2d args=[2365492906797 [2.365e12]]] testFuzzDonate(uint96) (runs: 0, μ: 0, ~: 0)
Encountered a total of 1 failing tests, 0 tests succeeded

POC - 2: More Explicit

This second proof of code comes from a more explicit test, that specific tests what address is being used in the GivingThanks contract for the CharityRegistry data. Please add the following test inside /test/GivingThanks.t.sol:

function test_BrokenInitializedCharityRegistry() public {
CharityRegistry newRegistryContract; //define a new CharityRegistry
GivingThanks newCharityContract; //define a new GivingThanks
vm.prank(admin);
newRegistryContract = new CharityRegistry();
vm.prank(admin);
newCharityContract = new GivingThanks(address(newRegistryContract));
// Check stored registry address is NOT the same as the intended registry used in constructor
assertNotEq(address(newCharityContract.registry()), address(newRegistryContract));
}

Tools Used

  • Manual Review

Recommendations

To fix this vulnerability, replace msg.sender with the parameter _registry:

constructor(address _registry) ERC721("DonationReceipt", "DRC") {
- registry = CharityRegistry(msg.sender);
+ registry = CharityRegistry(_registry);
owner = msg.sender;
tokenCounter = 0;
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-bad-registry-set-at-construction

Likelyhood: High, the parameter is not well used and won't be set. Impact: Low, can be changed with the setter and no one will be able to donate to malicious charity.

Support

FAQs

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