DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: medium
Valid

DOS in init function in reseedField due to wrong index query

Summary

The Reseed Field is responsible for reinitializing the field on the L2 on which Beanstalk deploys. However, the init function uses the wrong index query resulting in a complete DOS for initializing on the L2.

Vulnerability Details

As you can see below, in the nested for loop, it loops through accountPlotsat index irather than index j.

This causes a complete a failure of reinitialization due to the wrong index query.

https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/4e0ad0b964f74a1b4880114f4dd5b339bc69cd3e/protocol/contracts/beanstalk/init/reseed/L2/ReseedField.sol#L49

* @notice Re-initializes the field.
* @param accountPlots the plots for each account
* @param totalPods The total number of pods on L1.
* @param harvestable The number of harvestable pods on L1.
* @param harvested The number of harvested pods on L1.
* @param initialTemperature the initial Temperature of the field.
*/
function init(
MigratedPlotData[] calldata accountPlots,
uint256 totalPods,
uint256 harvestable,
uint256 harvested,
uint256 fieldId,
uint8 initialTemperature
) external {
uint256 calculatedTotalPods;
for (uint i; i < accountPlots.length; i++) {
for (uint j; j < accountPlots[i].plots.length; i++) {//@audit
uint256 podIndex = accountPlots[i].plots[j].podIndex;
uint256 podAmount = accountPlots[i].plots[j].podAmounts;
s.accts[accountPlots[i].account].fields[fieldId].plots[podIndex] = podAmount;
s.accts[accountPlots[i].account].fields[fieldId].plotIndexes.push(podIndex);
emit MigratedPlot(accountPlots[i].account, podIndex, podAmount);
calculatedTotalPods += podAmount;
}
}
// perform verfication:
require(calculatedTotalPods == totalPods, "ReseedField: totalPods mismatch");
require(totalPods >= harvestable, "ReseedField: harvestable mismatch");
require(harvestable >= harvested, "ReseedField: harvested mismatch");
s.sys.field.pods = totalPods;
s.sys.field.harvestable = harvestable;
s.sys.field.harvested = harvested;
// soil demand initialization.
s.sys.weather.thisSowTime = type(uint32).max;
s.sys.weather.lastSowTime = type(uint32).max;
s.sys.weather.temp = initialTemperature;
}

POC

When running these tests, ADD the below getter function into reseedField for testing purposes

// Getter function for testing purposes
function getPlot(address account, uint256 fieldId, uint256 podIndex) external view returns (uint256) {
return s.accts\[account].fields\[fieldId].plots\[podIndex];
}

pragma solidity >=0.6.0 <0.9.0;
pragma abicoder v2;
import {TestHelper} from "test/foundry/utils/TestHelper.sol";
import {ReseedField} from "../../../contracts/beanstalk/init/reseed/L2/ReseedField.sol";
import {AppStorage} from "contracts/beanstalk/storage/AppStorage.sol";
contract ReseedFieldTest is TestHelper {
ReseedField internal reseedField;
function setUp() public {
reseedField = new ReseedField();
}
function testPartialInitialization() public {
ReseedField.MigratedPlotData\[] memory accountPlots = new ReseedField.MigratedPlotData\[]\(2);
// Setup account 1
accountPlots\[0].account = address(0x1);
accountPlots\[0].plots = new ReseedField.Plot\[]\(2);
accountPlots\[0].plots\[0] = ReseedField.Plot({podIndex: 1, podAmounts: 100});
accountPlots\[0].plots\[1] = ReseedField.Plot({podIndex: 2, podAmounts: 200});
// Setup account 2
accountPlots\[1].account = address(0x2);
accountPlots\[1].plots = new ReseedField.Plot\[]\(2);
accountPlots\[1].plots\[0] = ReseedField.Plot({podIndex: 3, podAmounts: 300});
accountPlots\[1].plots\[1] = ReseedField.Plot({podIndex: 4, podAmounts: 400});
uint256 totalPods = 1000;
uint256 harvestable = 500;
uint256 harvested = 100;
uint256 fieldId = 0;
uint8 initialTemperature = 10;
// Call the init function
reseedField.init(accountPlots, totalPods, harvestable, harvested, fieldId, initialTemperature);
// Verify the initialization using the getter
assertEq(reseedField.getPlot(address(0x1), fieldId, 1), 100, "Plot 1 should be 100");
assertEq(reseedField.getPlot(address(0x1), fieldId, 2), 200, "Plot 2 should be 200");
assertEq(reseedField.getPlot(address(0x2), fieldId, 3), 300, "Plot 3 should be 300");
assertEq(reseedField.getPlot(address(0x2), fieldId, 4), 400, "Plot 4 should be 400");
}
}

OUTPUT

Ran 1 test for test/foundry/reseed/ReseedFieldTest.t.sol:ReseedFieldTest
&#x20;
\[FAIL. Reason: panic: array out-of-bounds access (0x32)] testPartialInitialization() (gas: 150947)
Traces:
\[383903] ReseedFieldTest::setUp()
├─ \[328966] → new ReseedField\@0xc83a02f4761098514aE50013Ba713a79E39699F3
│ └─ ← \[Return] 1643 bytes of code
└─ ← \[Stop]
\[150947] ReseedFieldTest::testPartialInitialization()
├─ \[142590] ReseedField::init(\[MigratedPlotData({ account: 0x0000000000000000000000000000000000000001, plots: \[Plot({ podIndex: 1, podAmounts: 100 }), Plot({ podIndex: 2, podAmounts: 200 })] }), MigratedPlotData({ account: 0x0000000000000000000000000000000000000002, plots: \[Plot({ podIndex: 3, podAmounts: 300 }), Plot({ podIndex: 4, podAmounts: 400 })] })], 1000, 500, 100, 0, 10)
│ ├─ emit MigratedPlot(account: 0x0000000000000000000000000000000000000001, plotIndex: 1, pods: 100)
│ ├─ emit MigratedPlot(account: 0x0000000000000000000000000000000000000002, plotIndex: 3, pods: 300)
│ └─ ← \[Revert] panic: array out-of-bounds access (0x32)
└─ ← \[Revert] panic: array out-of-bounds access (0x32)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 2.11ms (71.91µs CPU time)
Ran 1 test suite in 686.00ms (2.11ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/foundry/reseed/ReseedFieldTest.t.sol:ReseedFieldTest
\[FAIL. Reason: panic: array out-of-bounds access (0x32)] testPartialInitialization() (gas: 150947)
Encountered a total of 1 failing tests, 0 tests succeeded

Impact

Initialization of field will DOS resulting in farmers not being issued their plots on L2.

Tools Used

Foundry/Forge

Recommendations

Change the index query fro ito jand re run the test.

Ran 1 test for test/foundry/reseed/ReseedFieldTest.t.sol:ReseedFieldTest
[PASS] testPartialInitialization() (gas: 348997)
Traces:
[373273] ReseedFieldTest::setUp()
├─ [318353] → new ReseedField@0xc83a02f4761098514aE50013Ba713a79E39699F3
│ └─ ← [Return] 1590 bytes of code
└─ ← [Stop]
[348997] ReseedFieldTest::testPartialInitialization()
├─ [329820] ReseedField::init([MigratedPlotData({ account: 0x0000000000000000000000000000000000000001, plots: [Plot({ podIndex: 1, podAmounts: 100 }), Plot({ podIndex: 2, podAmounts: 200 })] }), MigratedPlotData({ account: 0x0000000000000000000000000000000000000002, plots: [Plot({ podIndex: 3, podAmounts: 300 }), Plot({ podIndex: 4, podAmounts: 400 })] })], 1000, 500, 100, 0, 10)
│ ├─ emit MigratedPlot(account: 0x0000000000000000000000000000000000000001, plotIndex: 1, pods: 100)
│ ├─ emit MigratedPlot(account: 0x0000000000000000000000000000000000000001, plotIndex: 2, pods: 200)
│ ├─ emit MigratedPlot(account: 0x0000000000000000000000000000000000000002, plotIndex: 3, pods: 300)
│ ├─ emit MigratedPlot(account: 0x0000000000000000000000000000000000000002, plotIndex: 4, pods: 400)
│ └─ ← [Stop]
├─ [712] ReseedField::getPlot(0x0000000000000000000000000000000000000001, 0, 1) [staticcall]
│ └─ ← [Return] 100
├─ [0] VM::assertEq(100, 100, "Plot 1 should be 100") [staticcall]
│ └─ ← [Return]
├─ [712] ReseedField::getPlot(0x0000000000000000000000000000000000000001, 0, 2) [staticcall]
│ └─ ← [Return] 200
├─ [0] VM::assertEq(200, 200, "Plot 2 should be 200") [staticcall]
│ └─ ← [Return]
├─ [712] ReseedField::getPlot(0x0000000000000000000000000000000000000002, 0, 3) [staticcall]
│ └─ ← [Return] 300
├─ [0] VM::assertEq(300, 300, "Plot 3 should be 300") [staticcall]
│ └─ ← [Return]
├─ [712] ReseedField::getPlot(0x0000000000000000000000000000000000000002, 0, 4) [staticcall]
│ └─ ← [Return] 400
├─ [0] VM::assertEq(400, 400, "Plot 4 should be 400") [staticcall]
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.92ms (248.74µs CPU time)
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Very broken loop in ReseedField::init

Appeal created

golanger85 Submitter
12 months ago
holydevoti0n Judge
12 months ago
inallhonesty Lead Judge
12 months ago
inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Very broken loop in ReseedField::init

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Very broken loop in ReseedField::init

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Very broken loop in ReseedField::init

Support

FAQs

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