Static Analysis: improvements suggested by chriseth

pull/1/head
soad003 8 years ago committed by chriseth
parent 85ae6115e5
commit 7ef1afc331
  1. 46
      src/app/staticanalysis/modules/abstractAstView.js
  2. 28
      src/app/staticanalysis/modules/checksEffectsInteraction.js
  3. 29
      src/app/staticanalysis/modules/constantFunctions.js
  4. 1
      src/app/staticanalysis/modules/list.js
  5. 3
      src/app/staticanalysis/modules/staticAnalysisCommon.js

@ -2,11 +2,20 @@ var common = require('./staticAnalysisCommon')
var AstWalker = require('ethereum-remix').util.AstWalker var AstWalker = require('ethereum-remix').util.AstWalker
function abstractAstView () { function abstractAstView () {
this.contracts = null this.contracts = []
this.currentContractIndex = null this.currentContractIndex = null
this.currentFunctionIndex = null this.currentFunctionIndex = null
this.currentModifierIndex = null this.currentModifierIndex = null
this.isFunctionNotModifier = false this.isFunctionNotModifier = false
/*
file1: contract c{}
file2: import "file1" as x; contract c{}
therefore we have two contracts with the same name c. At the moment this is not handled because alias name "x" is not
available in the current AST implementation thus can not be resolved.
Additionally the fullQuallified function names e.g. [contractName].[functionName](param1Type, param2Type, ... ) must be prefixed to
fully support this and when inheritance is resolved it must include alias resolving e.g x.c = file1.c
*/
this.multipleContractsWithSameName = false
} }
/** /**
@ -33,8 +42,7 @@ function abstractAstView () {
* @contractsOut {list} return list for high level AST view * @contractsOut {list} return list for high level AST view
* @return {ASTNode -> void} returns a function that can be used as visit function for static analysis modules, to build up a higher level AST view for further analysis. * @return {ASTNode -> void} returns a function that can be used as visit function for static analysis modules, to build up a higher level AST view for further analysis.
*/ */
abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut) { abstractAstView.prototype.build_visit = function (relevantNodeFilter) {
this.contracts = contractsOut
var that = this var that = this
return function (node) { return function (node) {
if (common.isContractDefinition(node)) { if (common.isContractDefinition(node)) {
@ -49,9 +57,6 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut)
var currentContract = getCurrentContract(that) var currentContract = getCurrentContract(that)
var inheritsFromName = common.getInheritsFromName(node) var inheritsFromName = common.getInheritsFromName(node)
currentContract.inheritsFrom.push(inheritsFromName) currentContract.inheritsFrom.push(inheritsFromName)
// add variables from inherited contracts
var inheritsFrom = that.contracts.find((contract) => common.getContractName(contract.node) === inheritsFromName)
currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables)
} else if (common.isFunctionDefinition(node)) { } else if (common.isFunctionDefinition(node)) {
setCurrentFunction(that, { setCurrentFunction(that, {
node: node, node: node,
@ -76,7 +81,36 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut)
} }
} }
abstractAstView.prototype.build_report = function (wrap) {
var that = this
return function (compilationResult) {
resolveStateVariablesInHierarchy(that.contracts)
return wrap(that.contracts, that.multipleContractsWithSameName)
}
}
function resolveStateVariablesInHierarchy (contracts) {
contracts.map((c) => {
resolveStateVariablesInHierarchyForContract(c, contracts)
})
}
function resolveStateVariablesInHierarchyForContract (currentContract, contracts) {
currentContract.inheritsFrom.map((inheritsFromName) => {
// add variables from inherited contracts
var inheritsFrom = contracts.find((contract) => common.getContractName(contract.node) === inheritsFromName)
if (inheritsFrom) {
currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables)
} else {
console.log('abstractAstView.js: could not find contract defintion inherited from ' + inheritsFromName)
}
})
}
function setCurrentContract (that, contract) { function setCurrentContract (that, contract) {
var name = common.getContractName(contract.node)
if (that.contracts.map((c) => common.getContractName(c.node)).filter((n) => n === name).length > 0) {
console.log('abstractAstView.js: two or more contracts with the same name dectected, import aliases not supported at the moment')
that.multipleContractsWithSameName = true
}
that.currentContractIndex = (that.contracts.push(contract) - 1) that.currentContractIndex = (that.contracts.push(contract) - 1)
} }

@ -6,22 +6,25 @@ var fcallGraph = require('./functionCallGraph')
var AbstractAst = require('./abstractAstView') var AbstractAst = require('./abstractAstView')
function checksEffectsInteraction () { function checksEffectsInteraction () {
this.contracts = [] this.abstractAst = new AbstractAst()
var that = this this.visit = this.abstractAst.build_visit(
(node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node)
checksEffectsInteraction.prototype.visit = new AbstractAst().builder(
(node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node),
that.contracts
) )
this.report = this.abstractAst.build_report(report)
} }
checksEffectsInteraction.prototype.report = function (compilationResults) { checksEffectsInteraction.prototype.visit = function () { throw new Error('checksEffectsInteraction.js no visit function set upon construction') }
checksEffectsInteraction.prototype.report = function () { throw new Error('checksEffectsInteraction.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
var warnings = [] var warnings = []
var hasModifiers = this.contracts.some((item) => item.modifiers.length > 0) var hasModifiers = contracts.some((item) => item.modifiers.length > 0)
var callGraph = fcallGraph.buildGlobalFuncCallGraph(this.contracts) var callGraph = fcallGraph.buildGlobalFuncCallGraph(contracts)
this.contracts.forEach((contract) => { contracts.forEach((contract) => {
contract.functions.forEach((func) => { contract.functions.forEach((func) => {
func.changesState = checkIfChangesState(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters), func.changesState = checkIfChangesState(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters),
getContext(callGraph, contract, func)) getContext(callGraph, contract, func))
@ -30,9 +33,10 @@ checksEffectsInteraction.prototype.report = function (compilationResults) {
contract.functions.forEach((func) => { contract.functions.forEach((func) => {
if (isPotentialVulnerableFunction(func, getContext(callGraph, contract, func))) { if (isPotentialVulnerableFunction(func, getContext(callGraph, contract, func))) {
var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
var comments = (hasModifiers) ? '<br/><i>Note:</i>Modifiers are currently not considered by the this static analysis.' : '' var comments = (hasModifiers) ? '<br/><i>Note:</i> Modifiers are currently not considered by this static analysis.' : ''
comments += (multipleContractsWithSameName) ? '<br/><i>Note:</i> Import aliases are currently not supported by this static analysis.' : ''
warnings.push({ warnings.push({
warning: `Potential Violation of Checks-Effects-Interaction pattern in <i>${funcName}</i>: Could potentially lead to re-entrancy vulnerability.${comments}`, warning: `Potential Violation of Checks-Effects-Interaction pattern in <i>${funcName}</i>: Could potentially lead to re-entrancy vulnerability. ${comments}`,
location: func.src, location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/security-considerations.html#re-entrancy' more: 'http://solidity.readthedocs.io/en/develop/security-considerations.html#re-entrancy'
}) })

@ -6,22 +6,26 @@ var fcallGraph = require('./functionCallGraph')
var AbstractAst = require('./abstractAstView') var AbstractAst = require('./abstractAstView')
function constantFunctions () { function constantFunctions () {
this.contracts = [] this.abstractAst = new AbstractAst()
var that = this
constantFunctions.prototype.visit = new AbstractAst().builder( this.visit = this.abstractAst.build_visit(
(node) => common.isLowLevelCall(node) || common.isTransfer(node) || common.isExternalDirectCall(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node) || common.isInlineAssembly(node) || common.isNewExpression(node), (node) => common.isLowLevelCall(node) || common.isTransfer(node) || common.isExternalDirectCall(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node) || common.isInlineAssembly(node) || common.isNewExpression(node)
that.contracts
) )
this.report = this.abstractAst.build_report(report)
} }
constantFunctions.prototype.report = function (compilationResults) { constantFunctions.prototype.visit = function () { throw new Error('constantFunctions.js no visit function set upon construction') }
constantFunctions.prototype.report = function () { throw new Error('constantFunctions.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
var warnings = [] var warnings = []
var hasModifiers = this.contracts.some((item) => item.modifiers.length > 0) var hasModifiers = contracts.some((item) => item.modifiers.length > 0)
var callGraph = fcallGraph.buildGlobalFuncCallGraph(this.contracts) var callGraph = fcallGraph.buildGlobalFuncCallGraph(contracts)
this.contracts.forEach((contract) => { contracts.forEach((contract) => {
contract.functions.forEach((func) => { contract.functions.forEach((func) => {
func.potentiallyshouldBeConst = checkIfShouldBeConstant(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters), func.potentiallyshouldBeConst = checkIfShouldBeConstant(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters),
getContext(callGraph, contract, func)) getContext(callGraph, contract, func))
@ -30,16 +34,17 @@ constantFunctions.prototype.report = function (compilationResults) {
contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => { contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => {
if (common.isConstantFunction(func.node) !== func.potentiallyshouldBeConst) { if (common.isConstantFunction(func.node) !== func.potentiallyshouldBeConst) {
var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
var comments = (hasModifiers) ? '<br/><i>Note:</i>Modifiers are currently not considered by the this static analysis.' : '' var comments = (hasModifiers) ? '<br/><i>Note:</i> Modifiers are currently not considered by this static analysis.' : ''
comments += (multipleContractsWithSameName) ? '<br/><i>Note:</i> Import aliases are currently not supported by this static analysis.' : ''
if (func.potentiallyshouldBeConst) { if (func.potentiallyshouldBeConst) {
warnings.push({ warnings.push({
warning: `<i>${funcName}</i>: Potentially should be constant but is not.${comments}`, warning: `<i>${funcName}</i>: Potentially should be constant but is not. ${comments}`,
location: func.src, location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions' more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions'
}) })
} else { } else {
warnings.push({ warnings.push({
warning: `<i>${funcName}</i>: Is constant but potentially should not be.${comments}`, warning: `<i>${funcName}</i>: Is constant but potentially should not be. ${comments}`,
location: func.src, location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions' more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions'
}) })

@ -4,6 +4,7 @@ module.exports = [
require('./thisLocal'), require('./thisLocal'),
require('./checksEffectsInteraction'), require('./checksEffectsInteraction'),
require('./constantFunctions') require('./constantFunctions')
// require('./similarVariableNames.js'),
// require('./inlineAssembly'), // require('./inlineAssembly'),
// require('./blockTimestamp'), // require('./blockTimestamp'),
// require('./lowLevelCalls'), // require('./lowLevelCalls'),

@ -55,7 +55,8 @@ var builtinFunctions = {
'mulmod(uint256,uint256,uint256)': true, 'mulmod(uint256,uint256,uint256)': true,
'selfdestruct(address)': true, 'selfdestruct(address)': true,
'revert()': true, 'revert()': true,
'assert()': true 'assert(bool)': true,
'require(bool)': true
} }
var lowLevelCallTypes = { var lowLevelCallTypes = {

Loading…
Cancel
Save