Merge pull request #4927 from ethereum/struct_event_jsonpath_0425

[backport] Buglist check script supports json paths
This commit is contained in:
chriseth 2018-09-11 12:06:31 +02:00 committed by GitHub
commit e913b753b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 228 additions and 18 deletions

View File

@ -155,6 +155,23 @@ jobs:
- store_artifacts: *solc_artifact - store_artifacts: *solc_artifact
- persist_to_workspace: *all_artifacts - persist_to_workspace: *all_artifacts
test_buglist:
docker:
- image: circleci/node
environment:
TERM: xterm
steps:
- checkout
- run:
name: JS deps
command: |
npm install download
npm install JSONPath
npm install mktemp
- run:
name: Test buglist
command: ./test/buglistTests.js
test_x86_linux: test_x86_linux:
docker: docker:
- image: buildpack-deps:artful - image: buildpack-deps:artful

View File

@ -1,12 +1,4 @@
[ [
{
"name": "EventStructWrongData",
"summary": "Using structs in events logged wrong data.",
"description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.",
"introduced": "0.4.17",
"fixed": "0.4.25",
"severity": "very low"
},
{ {
"name": "ExpExponentCleanup", "name": "ExpExponentCleanup",
"summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.", "summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.",
@ -15,6 +7,24 @@
"severity": "medium/high", "severity": "medium/high",
"check": {"regex-source": "[^/]\\*\\* *[^/0-9 ]"} "check": {"regex-source": "[^/]\\*\\* *[^/0-9 ]"}
}, },
{
"name": "EventStructWrongData",
"summary": "Using structs in events logged wrong data.",
"description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.",
"introduced": "0.4.17",
"fixed": "0.4.25",
"severity": "very low",
"check": {"ast-compact-json-path": "$..[?(@.nodeType === 'EventDefinition')]..[?(@.nodeType === 'UserDefinedTypeName' && @.typeDescriptions.typeString.startsWith('struct'))]"}
},
{
"name": "NestedArrayFunctionCallDecoder",
"summary": "Calling functions that return multi-dimensional fixed-size arrays can result in memory corruption.",
"description": "If Solidity code calls a function that returns a multi-dimensional fixed-size array, array elements are incorrectly interpreted as memory pointers and thus can cause memory corruption if the return values are accessed. Calling functions with multi-dimensional fixed-size arrays is unaffected as is returning fixed-size arrays from function calls. The regular expression only checks if such functions are present, not if they are called, which is required for the contract to be affected.",
"introduced": "0.1.4",
"fixed": "0.4.22",
"severity": "medium",
"check": {"regex-source": "returns[^;{]*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\]\\s*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\][^{;]*[;{]"}
},
{ {
"name": "OneOfTwoConstructorsSkipped", "name": "OneOfTwoConstructorsSkipped",
"summary": "If a contract has both a new-style constructor (using the constructor keyword) and an old-style constructor (a function with the same name as the contract) at the same time, one of them will be ignored.", "summary": "If a contract has both a new-style constructor (using the constructor keyword) and an old-style constructor (a function with the same name as the contract) at the same time, one of them will be ignored.",

View File

@ -56,6 +56,19 @@ conditions
is an object that can contain a boolean value ``optimizer``, which is an object that can contain a boolean value ``optimizer``, which
means that the optimizer has to be switched on to enable the bug. means that the optimizer has to be switched on to enable the bug.
If no conditions are given, assume that the bug is present. If no conditions are given, assume that the bug is present.
check
This field contains different checks that report whether the smart contract
contains the bug or not. The first type of check are Javascript regular
expressions that are to be matched against the source code ("source-regex")
if the bug is present. If there is no match, then the bug is very likely
not present. If there is a match, the bug might be present. For improved
accuracy, the checks should be applied to the source code after stripping
comments.
The second type of check are patterns to be checked on the compact AST of
the Solidity program ("ast-compact-json-path"). The specified search query
is a `JsonPath <https://github.com/json-path/JsonPath>`_ expression.
If at least one path of the Solidity AST matches the query, the bug is
likely present.
.. literalinclude:: bugs.json .. literalinclude:: bugs.json
:language: js :language: js

View File

@ -74,6 +74,7 @@
"0.1.4": { "0.1.4": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
"SkipEmptyStringLiteral", "SkipEmptyStringLiteral",
@ -92,6 +93,7 @@
"0.1.5": { "0.1.5": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
"SkipEmptyStringLiteral", "SkipEmptyStringLiteral",
@ -110,6 +112,7 @@
"0.1.6": { "0.1.6": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
"SkipEmptyStringLiteral", "SkipEmptyStringLiteral",
@ -129,6 +132,7 @@
"0.1.7": { "0.1.7": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
"SkipEmptyStringLiteral", "SkipEmptyStringLiteral",
@ -148,6 +152,7 @@
"0.2.0": { "0.2.0": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
"SkipEmptyStringLiteral", "SkipEmptyStringLiteral",
@ -167,6 +172,7 @@
"0.2.1": { "0.2.1": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
"SkipEmptyStringLiteral", "SkipEmptyStringLiteral",
@ -186,6 +192,7 @@
"0.2.2": { "0.2.2": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
"SkipEmptyStringLiteral", "SkipEmptyStringLiteral",
@ -205,6 +212,7 @@
"0.3.0": { "0.3.0": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -224,6 +232,7 @@
"0.3.1": { "0.3.1": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -242,6 +251,7 @@
"0.3.2": { "0.3.2": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -260,6 +270,7 @@
"0.3.3": { "0.3.3": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -277,6 +288,7 @@
"0.3.4": { "0.3.4": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -294,6 +306,7 @@
"0.3.5": { "0.3.5": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -311,6 +324,7 @@
"0.3.6": { "0.3.6": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -326,6 +340,7 @@
"0.4.0": { "0.4.0": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -341,6 +356,7 @@
"0.4.1": { "0.4.1": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -356,6 +372,7 @@
"0.4.10": { "0.4.10": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -367,6 +384,7 @@
"0.4.11": { "0.4.11": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -377,6 +395,7 @@
"0.4.12": { "0.4.12": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput" "ECRecoverMalformedInput"
@ -386,6 +405,7 @@
"0.4.13": { "0.4.13": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput" "ECRecoverMalformedInput"
@ -395,6 +415,7 @@
"0.4.14": { "0.4.14": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue" "DelegateCallReturnValue"
], ],
@ -403,6 +424,7 @@
"0.4.15": { "0.4.15": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector" "ZeroFunctionSelector"
], ],
"released": "2017-08-08" "released": "2017-08-08"
@ -410,35 +432,40 @@
"0.4.16": { "0.4.16": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector" "ZeroFunctionSelector"
], ],
"released": "2017-08-24" "released": "2017-08-24"
}, },
"0.4.17": { "0.4.17": {
"bugs": [ "bugs": [
"EventStructWrongData",
"ExpExponentCleanup", "ExpExponentCleanup",
"EventStructWrongData",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector" "ZeroFunctionSelector"
], ],
"released": "2017-09-21" "released": "2017-09-21"
}, },
"0.4.18": { "0.4.18": {
"bugs": [ "bugs": [
"ExpExponentCleanup",
"EventStructWrongData", "EventStructWrongData",
"ExpExponentCleanup" "NestedArrayFunctionCallDecoder"
], ],
"released": "2017-10-18" "released": "2017-10-18"
}, },
"0.4.19": { "0.4.19": {
"bugs": [ "bugs": [
"ExpExponentCleanup",
"EventStructWrongData", "EventStructWrongData",
"ExpExponentCleanup" "NestedArrayFunctionCallDecoder"
], ],
"released": "2017-11-30" "released": "2017-11-30"
}, },
"0.4.2": { "0.4.2": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -452,43 +479,46 @@
}, },
"0.4.20": { "0.4.20": {
"bugs": [ "bugs": [
"ExpExponentCleanup",
"EventStructWrongData", "EventStructWrongData",
"ExpExponentCleanup" "NestedArrayFunctionCallDecoder"
], ],
"released": "2018-02-14" "released": "2018-02-14"
}, },
"0.4.21": { "0.4.21": {
"bugs": [ "bugs": [
"ExpExponentCleanup",
"EventStructWrongData", "EventStructWrongData",
"ExpExponentCleanup" "NestedArrayFunctionCallDecoder"
], ],
"released": "2018-03-07" "released": "2018-03-07"
}, },
"0.4.22": { "0.4.22": {
"bugs": [ "bugs": [
"EventStructWrongData",
"ExpExponentCleanup", "ExpExponentCleanup",
"EventStructWrongData",
"OneOfTwoConstructorsSkipped" "OneOfTwoConstructorsSkipped"
], ],
"released": "2018-04-16" "released": "2018-04-16"
}, },
"0.4.23": { "0.4.23": {
"bugs": [ "bugs": [
"EventStructWrongData", "ExpExponentCleanup",
"ExpExponentCleanup" "EventStructWrongData"
], ],
"released": "2018-04-19" "released": "2018-04-19"
}, },
"0.4.24": { "0.4.24": {
"bugs": [ "bugs": [
"EventStructWrongData", "ExpExponentCleanup",
"ExpExponentCleanup" "EventStructWrongData"
], ],
"released": "2018-05-16" "released": "2018-05-16"
}, },
"0.4.3": { "0.4.3": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -502,6 +532,7 @@
"0.4.4": { "0.4.4": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -514,6 +545,7 @@
"0.4.5": { "0.4.5": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -527,6 +559,7 @@
"0.4.6": { "0.4.6": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -539,6 +572,7 @@
"0.4.7": { "0.4.7": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -550,6 +584,7 @@
"0.4.8": { "0.4.8": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",
@ -561,6 +596,7 @@
"0.4.9": { "0.4.9": {
"bugs": [ "bugs": [
"ExpExponentCleanup", "ExpExponentCleanup",
"NestedArrayFunctionCallDecoder",
"ZeroFunctionSelector", "ZeroFunctionSelector",
"DelegateCallReturnValue", "DelegateCallReturnValue",
"ECRecoverMalformedInput", "ECRecoverMalformedInput",

134
test/buglistTests.js Executable file
View File

@ -0,0 +1,134 @@
#!/usr/bin/env node
"use strict";
var util = require('util')
var exec = util.promisify(require('child_process').exec)
var mktemp = require('mktemp');
var download = require('download')
var JSONPath = require('JSONPath')
var fs = require('fs')
var bugs = JSON.parse(fs.readFileSync(__dirname + '/../docs/bugs.json', 'utf8'))
var bugsByName = {}
for (var i in bugs)
{
if (bugs[i].name in bugsByName)
{
throw "Duplicate bug name: " + bugs[i].name
}
bugsByName[bugs[i].name] = bugs[i]
}
var tests = fs.readFileSync(__dirname + '/buglist_test_vectors.md', 'utf8')
var testVectorParser = /\s*#\s+(\S+)\s+## buggy\n([^#]*)## fine\n([^#]*)/g
runTests()
async function runTests()
{
var result;
while ((result = testVectorParser.exec(tests)) !== null)
{
var name = result[1]
var buggy = result[2].split('\n--\n')
var fine = result[3].split('\n--\n')
console.log("Testing " + name + " with " + buggy.length + " buggy and " + fine.length + " fine instances")
try {
await checkRegex(name, buggy, fine)
await checkJSONPath(name, buggy, fine)
} catch (err) {
console.error("Error: " + err)
}
}
}
function checkRegex(name, buggy, fine)
{
return new Promise(function(resolve, reject) {
var regexStr = bugsByName[name].check['regex-source']
if (regexStr !== undefined)
{
var regex = RegExp(regexStr)
for (var i in buggy)
{
if (!regex.exec(buggy[i]))
{
reject("Bug " + name + ": Buggy source does not match: " + buggy[i])
}
}
for (var i in fine)
{
if (regex.exec(fine[i]))
{
reject("Bug " + name + ": Non-buggy source matches: " + fine[i])
}
}
}
resolve()
})
}
async function checkJSONPath(name, buggy, fine)
{
var jsonPath = bugsByName[name].check['ast-compact-json-path']
if (jsonPath !== undefined)
{
var url = "http://github.com/ethereum/solidity/releases/download/v" + bugsByName[name].introduced + "/solc-static-linux"
try {
var tmpdir = await mktemp.createDir('XXXXX')
var binary = tmpdir + "/solc-static-linux"
await download(url, tmpdir)
exec("chmod +x " + binary)
for (var i in buggy)
{
var result = await checkJsonPathTest(buggy[i], tmpdir, binary, jsonPath, i)
if (!result)
throw "Bug " + name + ": Buggy source does not contain path: " + buggy[i]
}
for (var i in fine)
{
var result = await checkJsonPathTest(fine[i], tmpdir, binary, jsonPath, i + buggy.length)
if (result)
throw "Bug " + name + ": Non-buggy source contains path: " + fine[i]
}
exec("rm -r " + tmpdir)
} catch (err) {
throw err
}
}
}
function checkJsonPathTest(code, tmpdir, binary, query, idx) {
return new Promise(function(resolve, reject) {
var solFile = tmpdir + "/jsonPath" + idx + ".sol"
var astFile = tmpdir + "/ast" + idx + ".json"
writeFilePromise(solFile, code)
.then(() => {
return exec(binary + " --ast-compact-json " + solFile + " > " + astFile)
})
.then(() => {
var jsonRE = /(\{[\s\S]*\})/
var ast = JSON.parse(jsonRE.exec(fs.readFileSync(astFile, 'utf8'))[0])
var result = JSONPath({json: ast, path: query})
if (result.length > 0)
resolve(true)
else
resolve(false)
})
.catch((err) => {
reject(err)
})
})
}
function writeFilePromise(filename, data) {
return new Promise(function(resolve, reject) {
fs.writeFile(filename, data, 'utf8', function(err) {
if (err) reject(err)
else resolve(data)
})
})
}