solidity.security.superfluid-ctx-injection.superfluid-ctx-injection
semgrep
Author
unknown
Download Count*
License
A specially crafted calldata may be used to impersonate other accounts
Run Locally
Run in CI
Defintion
rules:
- id: superfluid-ctx-injection
message: A specially crafted calldata may be used to impersonate other accounts
metadata:
category: security
technology:
- solidity
cwe: "CWE-20: Improper Input Validation"
confidence: HIGH
likelihood: HIGH
impact: HIGH
subcategory:
- vuln
references:
- https://rekt.news/superfluid-rekt/
- https://medium.com/superfluid-blog/08-02-22-exploit-post-mortem-15ff9c97cdd
- https://polygonscan.com/address/0x07711bb6dfbc99a1df1f2d7f57545a67519941e7
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
vulnerability_class:
- Improper Validation
patterns:
- pattern: $T.decodeCtx(ctx);
- pattern-not-inside: |
require($T.isCtxValid(...), "...");
...
languages:
- solidity
severity: ERROR
Examples
superfluid-ctx-injection.sol
// SPDX-License-Identifier: AGPLv3
pragma solidity 0.7.6;
pragma experimental ABIEncoderV2;
import {
ISuperfluidGovernance,
ISuperfluid,
ISuperfluidToken,
ISuperApp,
SuperAppDefinitions,
ContextDefinitions
} from "../interfaces/superfluid/ISuperfluid.sol";
import { ISuperfluidToken } from "../interfaces/superfluid/ISuperfluidToken.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
/**
* @dev Helper library for building super agreement
*/
library AgreementLibrary {
using SignedSafeMath for int256;
using SafeCast for uint256;
using SafeCast for int256;
/**************************************************************************
* Context helpers
*************************************************************************/
/**
* @dev Authorize the msg.sender to access token agreement storage
*
* NOTE:
* - msg.sender must be the expected host contract.
* - it should revert on unauthorized access.
*/
function authorizeTokenAccess(ISuperfluidToken token, bytes memory ctx)
internal view
returns (ISuperfluid.Context memory)
{
require(token.getHost() == msg.sender, "AgreementLibrary: unauthroized host");
// ruleid: superfluid-ctx-injection
return ISuperfluid(msg.sender).decodeCtx(ctx);
}
/**************************************************************************
* Agreement callback helpers
*************************************************************************/
struct CallbackInputs {
ISuperfluidToken token;
address account;
bytes32 agreementId;
bytes agreementData;
uint256 appAllowanceGranted;
int256 appAllowanceUsed;
uint256 noopBit;
}
function createCallbackInputs(
ISuperfluidToken token,
address account,
bytes32 agreementId,
bytes memory agreementData
)
internal pure
returns (CallbackInputs memory inputs)
{
inputs.token = token;
inputs.account = account;
inputs.agreementId = agreementId;
inputs.agreementData = agreementData;
}
function callAppBeforeCallback(
CallbackInputs memory inputs,
bytes memory ctx
)
internal
returns(bytes memory cbdata)
{
bool isSuperApp;
bool isJailed;
uint256 noopMask;
(isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account));
if (isSuperApp && !isJailed) {
bytes memory appCtx = _pushCallbackStack(ctx, inputs);
if ((noopMask & inputs.noopBit) == 0) {
bytes memory callData = abi.encodeWithSelector(
_selectorFromNoopBit(inputs.noopBit),
inputs.token,
address(this) /* agreementClass */,
inputs.agreementId,
inputs.agreementData,
new bytes(0) // placeholder ctx
);
cbdata = ISuperfluid(msg.sender).callAppBeforeCallback(
ISuperApp(inputs.account),
callData,
inputs.noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP,
appCtx);
}
_popCallbackStack(ctx, 0);
}
}
function callAppAfterCallback(
CallbackInputs memory inputs,
bytes memory cbdata,
bytes memory ctx
)
internal
returns (ISuperfluid.Context memory appContext, bytes memory newCtx)
{
bool isSuperApp;
bool isJailed;
uint256 noopMask;
(isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account));
if (isSuperApp && !isJailed) {
newCtx = _pushCallbackStack(ctx, inputs);
if ((noopMask & inputs.noopBit) == 0) {
bytes memory callData = abi.encodeWithSelector(
_selectorFromNoopBit(inputs.noopBit),
inputs.token,
address(this) /* agreementClass */,
inputs.agreementId,
inputs.agreementData,
cbdata,
new bytes(0) // placeholder ctx
);
newCtx = ISuperfluid(msg.sender).callAppAfterCallback(
ISuperApp(inputs.account),
callData,
inputs.noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP,
newCtx);
appContext = ISuperfluid(msg.sender).decodeCtx(newCtx);
// adjust allowance used to the range [appAllowanceWanted..appAllowanceGranted]
appContext.appAllowanceUsed = max(0, min(
inputs.appAllowanceGranted.toInt256(),
max(appContext.appAllowanceWanted.toInt256(), appContext.appAllowanceUsed)));
}
newCtx = _popCallbackStack(ctx, appContext.appAllowanceUsed);
}
}
function _selectorFromNoopBit(uint256 noopBit)
private pure
returns (bytes4 selector)
{
if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP) {
return ISuperApp.beforeAgreementCreated.selector;
} else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP) {
return ISuperApp.beforeAgreementUpdated.selector;
} else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP) {
return ISuperApp.beforeAgreementTerminated.selector;
} else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP) {
return ISuperApp.afterAgreementCreated.selector;
} else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP) {
return ISuperApp.afterAgreementUpdated.selector;
} else /* if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP) */ {
return ISuperApp.afterAgreementTerminated.selector;
}
}
function _pushCallbackStack(
bytes memory ctx,
CallbackInputs memory inputs
)
private
returns (bytes memory appCtx)
{
// app allowance params stack PUSH
// pass app allowance and current allowance used to the app,
appCtx = ISuperfluid(msg.sender).appCallbackPush(
ctx,
ISuperApp(inputs.account),
inputs.appAllowanceGranted,
inputs.appAllowanceUsed,
inputs.token);
}
function _popCallbackStack(
bytes memory ctx,
int256 appAllowanceUsedDelta
)
private
returns (bytes memory newCtx)
{
// app allowance params stack POP
return ISuperfluid(msg.sender).appCallbackPop(ctx, appAllowanceUsedDelta);
}
/**************************************************************************
* Misc
*************************************************************************/
function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; }
function min(int256 a, int256 b) internal pure returns (int256) { return a > b ? b : a; }
}
Short Link: https://sg.run/9KNy