Platform: Code4rena
Start Date: 10/11/2023
Pot Size: $28,000 USDC
Total HM: 5
Participants: 185
Period: 5 days
Judge: 0xDjango
Id: 305
League: ETH
Rank: 175/185
Findings: 1
Award: $2.76
π Selected for report: 0
π Solo Findings: 0
π Selected for report: m_Rassska
Also found by: 0x1337, 0xAadi, 0xHelium, 0xLeveler, 0xblackskull, 0xbrett8571, 0xepley, 0xffchain, 0xluckhu, 0xmystery, 0xrugpull_detector, 0xvj, ABAIKUNANBAEV, Aamir, AerialRaider, Amithuddar, Bauchibred, Bauer, CatsSecurity, Cryptor, Daniel526, Draiakoo, Eigenvectors, ElCid, GREY-HAWK-REACH, Inspecktor, Juntao, King_, LinKenji, Madalad, MaslarovK, Matin, MatricksDeCoder, McToady, Noro, PENGUN, Pechenite, Phantasmagoria, RaoulSchaffranek, SBSecurity, SandNallani, Shaheen, Soul22, Stormreckson, T1MOH, Tadev, TeamSS, TheSchnilch, Topmark, Tumelo_Crypto, Udsen, Yanchuan, ZanyBonzy, _thanos1, adeolu, adriro, alexfilippov314, almurhasan, amaechieth, anarcheuz, ayden, baice, bareli, boredpukar, bronze_pickaxe, btk, cartlex_, catellatech, chaduke, cheatc0d3, circlelooper, codynhat, crack-the-kelp, critical-or-high, debo, deepkin, desaperh, dipp, eeshenggoh, evmboi32, ge6a, gesha17, glcanvas, gumgumzum, hals, hihen, hunter_w3b, jasonxiale, joaovwfreire, ke1caM, leegh, lsaudit, marchev, merlinboii, niser93, osmanozdemir1, paritomarrr, passion, pep7siup, phoenixV110, pipidu83, poneta, ro1sharkm, rouhsamad, rvierdiiev, sakshamguruji, seerether, shealtielanz, soliditytaker, spark, squeaky_cactus, stackachu, supersizer0x, tallo, taner2344, turvy_fuzz, twcctop, ubl4nk, wisdomn_, xAriextz, zach, zhaojie, zhaojohnson, ziyou-
2.7592 USDC - $2.76
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/RSETH.sol#L19 https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/RSETH.sol#L16 https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/RSETH.sol#L73
A critical issue in the RSETH contract arises from a conflict between two access control systems operating simultaneously, leading to confusing and possibly inconsistent privileges. The RSETH contract inherits from AccessControlUpgradeable and additionally holds a reference to an LRTConfig, which itself inherits from AccessControlUpgradeable. This dual-system approach (inheritance and composition) results in discrepancies in permissions across various functions.
The inherited access control in RSETH guards key functions like mint, burnFrom, unpause, and updateLRTConfig. Conversely, the pause function relies on the access control system of the LRTConfig. The core problem lies in these two systems not aligning on who holds the administrator role.
In other words, the two systems can assign roles to different addresses. For example, the inherited system grants Alice the DEFAULT_ADMIN_ROLE, while the composed system (from LRTConfig) assigns the DEFAULT_ADMIN_ROLE only to Bob. Depending on which system you asked, you will get different answers to "Is Alice an admin?".
Consider the following scenario:
There are two problematic consequences of this action:
Here are two test cases to trigger these scenarios respectively.
diff --git a/test/RSETHTest.t.sol b/test/RSETHTest.t.sol index a0047a8..96e31f8 100644 --- a/test/RSETHTest.t.sol +++ b/test/RSETHTest.t.sol @@ -260,3 +260,49 @@ contract RSETHUpdateLRTConfig is RSETHTest { assertEq(address(newLRTConfig), address(rseth.lrtConfig()), "LRT config address is not set"); } } + +contract RSETHAccessControlBug is RSETHTest { + + function test_access_control_violation1() external { + bytes32 DEFAULT_ADMIN_ROLE = 0x0; + address newAdmin = makeAddr("newAdmin"); + rseth.initialize(address(admin), address(lrtConfig)); + + // Step 1: Grant privilges to new admin + vm.prank(admin); + lrtConfig.grantRole(DEFAULT_ADMIN_ROLE, newAdmin); + + // Step 2: Revoke privilges from old admin + vm.prank(newAdmin); + lrtConfig.grantRole(DEFAULT_ADMIN_ROLE, admin); + + // Step 3: Use old admin to replace the configuration of RSETH + vm.prank(admin); + vm.expectRevert(); + rseth.updateLRTConfig(address(0x1)); + } + + function test_access_control_violation2() external { + bytes32 DEFAULT_ADMIN_ROLE = 0x0; + address newAdmin = makeAddr("newAdmin"); + rseth.initialize(address(admin), address(lrtConfig)); + vm.prank(admin); + lrtConfig.grantRole(LRTConstants.MANAGER, manager); + + // Step 1: Grant privilges to new admin + vm.prank(admin); + lrtConfig.grantRole(DEFAULT_ADMIN_ROLE, newAdmin); + + // Step 2: Revoke privilges from old admin + vm.prank(newAdmin); + lrtConfig.grantRole(DEFAULT_ADMIN_ROLE, admin); + + // Step 3: Manager pauses the contract + vm.prank(manager); + rseth.pause(); + + // Step 4: New admin tries to unpause the contract + vm.prank(newAdmin); + rseth.unpause(); + } +}
Use forge test --match-test test_access_control_violation
to reproduce. The first test fails because it's not reverting as expected in the last line. The second test fails because it is unexpectedly reverting in the last line.
forge test --match-test test_access_control_violation [17:57:43] [β ] Compiling... No files changed, compilation skipped Running 2 tests for test/RSETHTest.t.sol:RSETHAccessControlBug [FAIL. Reason: call did not revert as expected] test_access_control_violation1() (gas: 165722) [FAIL. Reason: revert: AccessControl: account 0x67ed0ac45b537a79406e01656d90659325455585 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000] test_access_control_violation2() (gas: 248761) Test result: FAILED. 0 passed; 2 failed; 0 skipped; finished in 2.32ms Ran 1 test suites: 0 tests passed, 2 failed, 0 skipped (2 total tests) Failing tests: Encountered 2 failing tests in test/RSETHTest.t.sol:RSETHAccessControlBug [FAIL. Reason: call did not revert as expected] test_access_control_violation1() (gas: 165722) [FAIL. Reason: revert: AccessControl: account 0x67ed0ac45b537a79406e01656d90659325455585 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000] test_access_control_violation2() (gas: 248761) Encountered a total of 2 failing tests, 0 tests succeeded FAIL
Eliminate the dual access control system in the RSETH contract. Ensure a consistent access control mechanism is used for all administrative functions to avoid conflicting permissions.
Foundry
Access Control
#0 - c4-pre-sort
2023-11-16T21:18:37Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-11-16T21:20:51Z
raymondfam marked the issue as sufficient quality report
#2 - c4-pre-sort
2023-11-16T21:21:04Z
raymondfam marked the issue as primary issue
#3 - c4-pre-sort
2023-11-17T06:55:46Z
raymondfam marked the issue as high quality report
#4 - c4-sponsor
2023-11-24T08:16:15Z
manoj9april marked the issue as disagree with severity
#5 - gus-stdr
2023-11-24T08:22:47Z
Thank you for submitting the review. We believe this issue is not of high severity given that we own the addresses that manages the access control of RSETH. It falls into a QA category of severity.
#6 - c4-sponsor
2023-11-24T10:19:32Z
manoj9april (sponsor) confirmed
#7 - fatherGoose1
2023-12-01T17:35:28Z
Agree that this constitutes QA.
#8 - c4-judge
2023-12-01T17:35:38Z
fatherGoose1 changed the severity to QA (Quality Assurance)
#9 - c4-judge
2023-12-01T17:35:44Z
fatherGoose1 marked the issue as grade-b