Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit fix/liquidity allocation #307

Merged
merged 22 commits into from
Apr 13, 2023

Conversation

clemlak
Copy link
Contributor

@clemlak clemlak commented Mar 31, 2023

I squeezed 2 fixes in this PR as I felt like they were closely related (see https://github.com/spearbit-audits/review-primitive/issues/10 and https://github.com/spearbit-audits/review-primitive/issues/4):

  1. Liquidity can be (de-)allocated at a bad price: I added 2 extra parameters when allocating / deallocating liquidity, so users can now specify the expected amount to pay in asset / quote tokens when providing liquidity and also the expected amount of asset / quote tokens to receive when removing liquidity.
  2. Unsafe type-casting: packed amounts in the decodeAllocateOrDeallocate function were vulnerable to overflow. I added an extra check to patch this. And since decodeSwap and decodeCreatePool were sharing the same issue and fixed them too.

@clemlak
Copy link
Contributor Author

clemlak commented Mar 31, 2023

Should we merge this now or try to reduce the bytecode size first?

@@ -806,13 +811,13 @@ abstract contract PortfolioVirtual is Objective {
(, bytes1 instruction) = AssemblyLib.separate(data[0]); // Upper byte is useMax, lower byte is instruction.

if (instruction == FVM.ALLOCATE) {
(uint8 useMax, uint64 poolId, uint128 deltaLiquidity) =
(uint8 useMax, uint64 poolId, uint128 deltaLiquidity, uint128 deltaAsset, uint128 deltaQuote) =
Copy link
Collaborator

@MrToph MrToph Apr 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally, I find the deltaAsset and deltaQuote names misleading in this context. It sounds like these are the ones that the protocol will use, but these are the min/max amounts. Could consider something like desiredDeltaAsset, expectedDeltaAsset or just maxDeltaAsset instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's reasonable, I'll change these names.

Comment on lines +638 to +639
gt(input, 0xffffffffffffffffffffffffffffffff),
gt(output, 0xffffffffffffffffffffffffffffffff)
Copy link
Collaborator

@MrToph MrToph Apr 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the overflow checks don't work like this because the overflow can already have happened in output := mul(base1, exp(10, power1)). base1 = 1, power1 = 78 => mul(1, 1e78) overflows 256 bits and would result in the value mod 2**256. Maybe you should just do the decoding in assembly and the final multiplication for input and output in Solidity as uint128 which defaults to multiplication with overflow checks.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue at the other decoding functions

Copy link
Collaborator

@MrToph MrToph Apr 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or if you want to stay in assembly you could do the reverse check in addition to checking if it's < 2**128:

p1 := exp(10, power1) // assuming power1 < 78. this can also overflow and should be checked
output := mul(base1, p1)

// something like this: should work iff output did not overflow
eq(base1, div(output, p1))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out, I missed that part. I just pushed a fix for the decodeSwap function, if it's correct I'll update the rest of the decoding functions.

Copy link
Collaborator

@kmbarry1 kmbarry1 Apr 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated version looks correct to me. I'll note that the operations to determine length0 and length1 could have underflow as well, but I think the most likely consequence of that is both swap values being zero (base0 and base1 will get right shifted out of existence) so shouldn't be a big risk to a caller that messes up their calldata.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, could add a quick check that length0/length1 <= 256/8=32 so the sub(256, mul(8, length0)) doesn't underflow. this restricts base0/1 to 32 bytes.

@clemlak clemlak merged commit d62af6a into spearbit/post-audit Apr 13, 2023
} else if (instruction == FVM.DEALLOCATE) {
(uint8 useMax, uint64 poolId, uint128 deltaLiquidity) =
(uint8 useMax, uint64 poolId, uint128 deltaLiquidity, uint128 minDeltaAsset, uint128 minDeltaQuote) =
FVM.decodeAllocateOrDeallocate(data);
_deallocate(useMax == 1, poolId, deltaLiquidity);
Copy link
Collaborator

@MrToph MrToph Apr 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a GitHub display bug? For me, it looks like you're calling _deallocate(useMax == 1, poolId, deltaLiquidity); without the min amounts which should be impossible because you changed _deallocate to take the two additional parameters.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, looks like it was not a display bug; fixed now: cbce5a7

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that yesterday while merging but then fixed it on the spearbit/post-audit branch, I just don't know what happened, it looks like resolving the conflicts resulted in a weird merge...

}
let decimals := exp(10, power)

let length := sub(pointer1, 12)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can underflow as well if someone creates a bad encoding that points into the first 11 bytes (useMax, poolid, pointer1, pointer2). could check for underflow here too

@Alexangelj Alexangelj deleted the audit-fix/liquidity-allocation branch April 17, 2023 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants