Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Valid

Bug in configuring dependencies for `Laundrette` policy breaks upgradability of `MoneyShelf`

Summary

There's a bug in Laundrette::configureDependencies() which causes the MoneyShelf module not to be registered correctly as dependency of the Laundrette policy contract. This will prevent the godfather from upgrading the registered MoneyShelf module to the MoneyVault module in case of an emergency.

Vulnerability Details

As part of using the Default framework, "Policies" have to implement a configureDependencies() function that returns the keycodes of all the modules that the policy contract depends on. This is needed so the framework can keep track of which policies need to be reconfigured when modules are upgraded, or pruned when policies are deactivated.

The Laundrette contract is such a policy and implements a dedicated configureDependencies() function:

function configureDependencies() external override onlyKernel returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](2);
dependencies[0] = toKeycode("MONEY");
moneyShelf = MoneyShelf(getModuleAddress(toKeycode("MONEY")));
dependencies[0] = toKeycode("WEAPN");
weaponShelf = WeaponShelf(getModuleAddress(toKeycode("WEAPN")));
}

The keycodes used to identify modules in the system are just bytes5 values. So what happens here is that two references to MoneyShelf and WeaponShelf modules are created, and a list of corresponding dependencies is returned (MONEY and WEAPN keycodes).

However, looking closely, we can see that the entry for the MoneyShelf keycode is overridden by the WeaponShelf keycode. This results in Laundrette::configureDependencies() returning a list of two dependencies where the second entry is an empty bytes keycode.

This has some serious repercussion effects. While the MoneyShelf module is correctly registed with the system earlier via Actions.InstallModule and references to the individual modules work correctly within Laundrette, the Kernel is not aware that the Laundrette policy contract depends on a module in with the keycode MONEY. In practice, this means when the Laundrette policy has to be reconfigured due to a module upgrade, it will not be found for reconfiguration.

Impact

In case the godfather upgrades the MoneyShelf contract to the MoneyVault as described by the project, the protocol will fail to reconfigure the dependencies for the Laundrette contract as it can't find it in the dependency graph.

As a result of that, Laundrette::moneyShelf will keep a reference to the old MoneyShelf instance and the upgrade essentially failed.

Tools Used

  • Manual review

  • Foundry for testing

Recommended Mitigation

Ensure Laundrette::configureDependencies() returns the correct list of keycodes:

function configureDependencies() external override onlyKernel returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](2);
dependencies[0] = toKeycode("MONEY");
moneyShelf = MoneyShelf(getModuleAddress(toKeycode("MONEY")));
- dependencies[0] = toKeycode("WEAPN");
+ dependencies[1] = toKeycode("WEAPN");
weaponShelf = WeaponShelf(getModuleAddress(toKeycode("WEAPN")));
}

Proof of Concept

Below is a test that shows that after running the EmergencyMigration script, Laundrette::moneyShelf still points at the original MoneyShelf contract instance, instead of the new MoneyVault instance.

Here's what the storage layout of Laundrette looks like, we expect Laundrette::moneyShelf to live in storage slot 1:

| Name | Type | Slot | Offset | Bytes | Contract |
|-------------|----------------------|------|--------|-------|----------------------------------------|
| kernel | contract Kernel | 0 | 0 | 20 | src/policies/Laundrette.sol:Laundrette |
| isActive | bool | 0 | 20 | 1 | src/policies/Laundrette.sol:Laundrette |
| moneyShelf | contract MoneyShelf | 1 | 0 | 20 | src/policies/Laundrette.sol:Laundrette |
| weaponShelf | contract WeaponShelf | 2 | 0 | 20 | src/policies/Laundrette.sol:Laundrette |

This test can be dropped straight into EmergencyMigration.t.sol. Without the recommended fix, this will fail:

+ function test_ConfigureDependenciesBug() public {
+ // run the `EmergencyMigration` script which attempts to upgrade
+ // `MoneyShelf` to `MoneyVault`
+ EmergencyMigration migration = new EmergencyMigration();
+ MoneyVault moneyVault = migration.migrate(kernel, usdc, crimeMoney);
+
+ // load `Laundrette::moneyShelf` from storage slot 1, we need to do this because
+ // the field is private
+ bytes32 moneyVaultAddress = vm.load(address(laundrette), bytes32(uint256(1)));
+
+ // assert that the new `MoneyVault` address is the same as the one stored in `Laundrette`
+ assertEq(address(uint160(uint256(moneyVaultAddress))), address(moneyVault));
+ }
Updates

Lead Judging Commences

n0kto Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Laundrette incorrect dependencies

Support

FAQs

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