From a7808c0bc94c3160b62d396c08216e1c60c8e293 Mon Sep 17 00:00:00 2001 From: faculerena Date: Mon, 29 Apr 2024 22:05:18 +0000 Subject: [PATCH] deploy: 082cc56b990d228ccc0556b05ed52eeb885f1ced --- 404.html | 4 ++-- about.html | 4 ++-- acknowledgements.html | 4 ++-- assets/js/{ca3c95aa.c1b92504.js => ca3c95aa.74dc6092.js} | 2 +- .../{runtime~main.66f3dcff.js => runtime~main.17c9b132.js} | 2 +- blog.html | 4 ++-- blog/archive.html | 4 ++-- blog/first-blog-post.html | 4 ++-- blog/long-blog-post.html | 4 ++-- blog/mdx-blog-post.html | 4 ++-- blog/tags.html | 4 ++-- blog/tags/docusaurus.html | 4 ++-- blog/tags/facebook.html | 4 ++-- blog/tags/hello.html | 4 ++-- blog/tags/hola.html | 4 ++-- blog/welcome.html | 4 ++-- docs/architecture.html | 4 ++-- docs/contribute.html | 4 ++-- docs/detectors.html | 4 ++-- docs/detectors/assert-violation.html | 4 ++-- docs/detectors/avoid-core-mem-forget.html | 4 ++-- docs/detectors/avoid-format-string.html | 4 ++-- docs/detectors/delegate-call.html | 4 ++-- docs/detectors/divide-before-multiply.html | 4 ++-- docs/detectors/dont-use-instantiate-contract-v1.html | 4 ++-- docs/detectors/dos-unbounded-operation.html | 4 ++-- docs/detectors/dos-unexpected-revert-with-vector.html | 4 ++-- docs/detectors/ink-version.html | 4 ++-- docs/detectors/insufficiently-random-values.html | 4 ++-- docs/detectors/integer-overflow-or-underflow.html | 4 ++-- docs/detectors/iterators-over-indexing.html | 4 ++-- docs/detectors/lazy-delegate.html | 4 ++-- docs/detectors/lazy-values-not-set.html | 4 ++-- docs/detectors/panic-error.html | 4 ++-- docs/detectors/reentrancy.html | 4 ++-- docs/detectors/set-contract-storage.html | 4 ++-- docs/detectors/unprotected-mapping-operation.html | 4 ++-- docs/detectors/unprotected-self-destruct.html | 4 ++-- docs/detectors/unprotected-set-code-hash.html | 4 ++-- docs/detectors/unrestricted-transfer-from.html | 4 ++-- docs/detectors/unsafe-expect.html | 4 ++-- docs/detectors/unsafe-unwrap.html | 4 ++-- docs/detectors/unused-return-enum.html | 4 ++-- docs/detectors/zero-or-test-address.html | 4 ++-- docs/intro.html | 4 ++-- docs/vulnerabilities.html | 4 ++-- docs/vulnerabilities/assert-violation.html | 4 ++-- docs/vulnerabilities/avoid-core-mem-forget.html | 4 ++-- docs/vulnerabilities/avoid-format-string.html | 4 ++-- docs/vulnerabilities/delegate-call.html | 4 ++-- docs/vulnerabilities/divide-before-multiply.html | 4 ++-- docs/vulnerabilities/dont-use-instantiate-contract-v1.html | 4 ++-- docs/vulnerabilities/dos-unbounded-operation.html | 4 ++-- docs/vulnerabilities/dos-unexpected-revert-with-vector.html | 4 ++-- docs/vulnerabilities/ink-version.html | 4 ++-- docs/vulnerabilities/insufficiently-random-values.html | 4 ++-- docs/vulnerabilities/integer-overflow-or-underflow.html | 4 ++-- docs/vulnerabilities/iterators-over-indexing.html | 4 ++-- docs/vulnerabilities/lazy-delegate.html | 4 ++-- docs/vulnerabilities/lazy-values-not-set.html | 6 +++--- docs/vulnerabilities/panic-error.html | 4 ++-- docs/vulnerabilities/reentrancy.html | 4 ++-- docs/vulnerabilities/set-contract-storage.html | 4 ++-- docs/vulnerabilities/unprotected-mapping-operation.html | 4 ++-- docs/vulnerabilities/unprotected-self-destruct.html | 4 ++-- docs/vulnerabilities/unprotected-set-code-hash.html | 4 ++-- docs/vulnerabilities/unrestricted-transfer-from.html | 4 ++-- docs/vulnerabilities/unsafe-expect.html | 4 ++-- docs/vulnerabilities/unsafe-unwrap.html | 4 ++-- docs/vulnerabilities/unused-return-enum.html | 4 ++-- docs/vulnerabilities/zero-or-test-address.html | 4 ++-- index.html | 4 ++-- markdown-page.html | 4 ++-- 73 files changed, 145 insertions(+), 145 deletions(-) rename assets/js/{ca3c95aa.c1b92504.js => ca3c95aa.74dc6092.js} (97%) rename assets/js/{runtime~main.66f3dcff.js => runtime~main.17c9b132.js} (99%) diff --git a/404.html b/404.html index ff44134d..e45419f6 100644 --- a/404.html +++ b/404.html @@ -5,13 +5,13 @@ Page Not Found | Scout - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/about.html b/about.html index 92c914bc..41d052fc 100644 --- a/about.html +++ b/about.html @@ -5,13 +5,13 @@ About | Scout - +
Skip to main content

About

We - CoinFabrik - are a research and development company specialized in Web3, with a strong background in cybersecurity. Founded in 2014, we have worked on over 180 blockchain-related projects, EVM based and also for Solana, Algorand, and Polkadot. Beyond development, we offer security audits through a dedicated in-house team of senior cybersecurity professionals, currently working on code in Substrate, Solidity, Clarity, Rust, and TEAL.

Our team has an academic background in computer science and mathematics, with work experience focused on cybersecurity and software development, including academic publications, patents turned into products, and conference presentations. Furthermore, we have an ongoing collaboration on knowledge transfer and open-source projects with the University of Buenos Aires.

- + \ No newline at end of file diff --git a/acknowledgements.html b/acknowledgements.html index 7f820afa..00ad0acb 100644 --- a/acknowledgements.html +++ b/acknowledgements.html @@ -5,13 +5,13 @@ Acknowledgements | Scout - +
Skip to main content

Acknowledgements

Scout is an open source vulnerability analyzer developed by CoinFabrik's Research and Development team.

Grants

We received support through grants from both the Web3 Foundation Grants Program and the Aleph Zero Ecosystem Funding Program.

Grant ProgramDescription
Web3 FoundationProof of Concept: We collaborated with the Laboratory on Foundations and Tools for Software Engineering (LaFHIS) at the University of Buenos Aires to establish analysis techniques and tools for our detectors, as well as to create an initial list of vulnerability classes and code examples. View PoC | View Application Form.

Prototype: We built a functioning prototype using linting detectors built with Dylint and expanded the list of vulnerability classes, detectors, and test cases. View Prototype | View Application Form.
Aleph Zero Grant ProgramWe improved the precision and number of detectors for the tool with a multi-phase approach. This included a manual vulnerability analysis of projects in the Aleph Zero ecosystem, extensive testing of the tool on top projects, and refining detection accuracy.
- + \ No newline at end of file diff --git a/assets/js/ca3c95aa.c1b92504.js b/assets/js/ca3c95aa.74dc6092.js similarity index 97% rename from assets/js/ca3c95aa.c1b92504.js rename to assets/js/ca3c95aa.74dc6092.js index ceb65c66..155ce935 100644 --- a/assets/js/ca3c95aa.c1b92504.js +++ b/assets/js/ca3c95aa.74dc6092.js @@ -1 +1 @@ -"use strict";(self.webpackChunkscout=self.webpackChunkscout||[]).push([[4915],{9613:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>v});var a=n(9496);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},p=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),c=u(n),d=r,v=c["".concat(s,".").concat(d)]||c[d]||m[d]||i;return n?a.createElement(v,l(l({ref:t},p),{},{components:n})):a.createElement(v,l({ref:t},p))}));function v(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=d;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[c]="string"==typeof e?e:r,l[1]=o;for(var u=2;u{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>u});var a=n(2564),r=(n(9496),n(9613));const i={},l="Lazy values not set",o={unversionedId:"vulnerabilities/lazy-values-not-set",id:"vulnerabilities/lazy-values-not-set",title:"Lazy values not set",description:"Description",source:"@site/docs/vulnerabilities/28-lazy-values-not-set.md",sourceDirName:"vulnerabilities",slug:"/vulnerabilities/lazy-values-not-set",permalink:"/scout/docs/vulnerabilities/lazy-values-not-set",draft:!1,editUrl:"https://github.com/CoinFabrik/scout/docs/vulnerabilities/28-lazy-values-not-set.md",tags:[],version:"current",sidebarPosition:28,frontMatter:{},sidebar:"docsSidebar",previous:{title:"Lazy storage on delegate",permalink:"/scout/docs/vulnerabilities/lazy-delegate"},next:{title:"Don't use instantiate_contract_v1",permalink:"/scout/docs/vulnerabilities/dont-use-instantiate-contract-v1"}},s={},u=[{value:"Description",id:"description",level:2},{value:"Exploit Scenario",id:"exploit-scenario",level:2},{value:"Remediation",id:"remediation",level:2},{value:"Known Issues",id:"known-issues",level:2}],p={toc:u},c="wrapper";function m(e){let{components:t,...n}=e;return(0,r.kt)(c,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"lazy-values-not-set"},"Lazy values not set"),(0,r.kt)("h2",{id:"description"},"Description"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Vulnerability Category: ",(0,r.kt)("inlineCode",{parentName:"li"},"Best Practices")),(0,r.kt)("li",{parentName:"ul"},"Vulnerability Severity: ",(0,r.kt)("inlineCode",{parentName:"li"},"Critical")),(0,r.kt)("li",{parentName:"ul"},"Detectors: ",(0,r.kt)("a",{parentName:"li",href:"https://github.com/CoinFabrik/scout/tree/main/lazy-values-not-set/"},(0,r.kt)("inlineCode",{parentName:"a"},"lazy-values-not-set"))),(0,r.kt)("li",{parentName:"ul"},"Test Cases: ",(0,r.kt)("a",{parentName:"li",href:"https://github.com/CoinFabrik/scout/tree/main/test-cases/lazy-values-not-set/lazy-values-not-set-1"},(0,r.kt)("inlineCode",{parentName:"a"},"lazy-values-not-set-1")))),(0,r.kt)("h2",{id:"exploit-scenario"},"Exploit Scenario"),(0,r.kt)("p",null,"Consider the following contract:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"}," #[ink(storage)]\n pub struct Contract {\n mapping: Mapping,\n }\n impl Contract {\n /* --- snip --- */\n #[ink(message)]\n pub fn sum(&mut self, value: u64) -> Result<(), Error> {\n let key = self.env().caller();\n let mut _val = self.mapping.get(key).unwrap_or_default();\n _val += value;\n Ok(())\n }\n /* --- snip --- */\n }\n")),(0,r.kt)("p",null,"In this case, when you ",(0,r.kt)("inlineCode",{parentName:"p"},".get(...)")," a value from a ",(0,r.kt)("inlineCode",{parentName:"p"},"Lazy")," storage field, you ",(0,r.kt)("em",{parentName:"p"},"probably")," want to mutate it. The values are not automatically flushed to storage, so you need to ",(0,r.kt)("inlineCode",{parentName:"p"},".set(...)")," it."),(0,r.kt)("h2",{id:"remediation"},"Remediation"),(0,r.kt)("p",null,"Use the ",(0,r.kt)("inlineCode",{parentName:"p"},".set(...)")," or ",(0,r.kt)("inlineCode",{parentName:"p"},".insert(...)")," method after using ",(0,r.kt)("inlineCode",{parentName:"p"},".get(...)")," to flush the new value to storage."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"}," #[ink(storage)]\n pub struct Contract {\n mapping: Mapping,\n }\n impl Contract {\n /* --- snip --- */\n #[ink(message)]\n pub fn sum(&mut self, value: u64) -> Result<(), Error> {\n let key = self.env().caller();\n let mut _val = self.mapping.get(key).unwrap_or_default();\n _val += value;\n self.mapping.insert(key, value);\n Ok(())\n }\n /* --- snip --- */\n }\n")),(0,r.kt)("h2",{id:"known-issues"},"Known Issues"),(0,r.kt)("p",null,"If you have a ",(0,r.kt)("inlineCode",{parentName:"p"},".get(...)")," function that you don't mutate (e.g., used as a const value), this detector triggers, if you want to ignore the lint you could add #","[allow(lazy_values_not_set)]"," immediately before the function definition."))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkscout=self.webpackChunkscout||[]).push([[4915],{9613:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>v});var a=n(9496);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function l(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},p=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,p=o(e,["components","mdxType","originalType","parentName"]),c=u(n),d=r,v=c["".concat(s,".").concat(d)]||c[d]||m[d]||i;return n?a.createElement(v,l(l({ref:t},p),{},{components:n})):a.createElement(v,l({ref:t},p))}));function v(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,l=new Array(i);l[0]=d;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[c]="string"==typeof e?e:r,l[1]=o;for(var u=2;u{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>m,frontMatter:()=>i,metadata:()=>o,toc:()=>u});var a=n(2564),r=(n(9496),n(9613));const i={},l="Lazy values not set",o={unversionedId:"vulnerabilities/lazy-values-not-set",id:"vulnerabilities/lazy-values-not-set",title:"Lazy values not set",description:"Description",source:"@site/docs/vulnerabilities/28-lazy-values-not-set.md",sourceDirName:"vulnerabilities",slug:"/vulnerabilities/lazy-values-not-set",permalink:"/scout/docs/vulnerabilities/lazy-values-not-set",draft:!1,editUrl:"https://github.com/CoinFabrik/scout/docs/vulnerabilities/28-lazy-values-not-set.md",tags:[],version:"current",sidebarPosition:28,frontMatter:{},sidebar:"docsSidebar",previous:{title:"Lazy storage on delegate",permalink:"/scout/docs/vulnerabilities/lazy-delegate"},next:{title:"Don't use instantiate_contract_v1",permalink:"/scout/docs/vulnerabilities/dont-use-instantiate-contract-v1"}},s={},u=[{value:"Description",id:"description",level:2},{value:"Exploit Scenario",id:"exploit-scenario",level:2},{value:"Remediation",id:"remediation",level:2},{value:"Known Issues",id:"known-issues",level:2}],p={toc:u},c="wrapper";function m(e){let{components:t,...n}=e;return(0,r.kt)(c,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"lazy-values-not-set"},"Lazy values not set"),(0,r.kt)("h2",{id:"description"},"Description"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Vulnerability Category: ",(0,r.kt)("inlineCode",{parentName:"li"},"Best Practices")),(0,r.kt)("li",{parentName:"ul"},"Vulnerability Severity: ",(0,r.kt)("inlineCode",{parentName:"li"},"Critical")),(0,r.kt)("li",{parentName:"ul"},"Detectors: ",(0,r.kt)("a",{parentName:"li",href:"https://github.com/CoinFabrik/scout/tree/main/lazy-values-not-set/"},(0,r.kt)("inlineCode",{parentName:"a"},"lazy-values-not-set"))),(0,r.kt)("li",{parentName:"ul"},"Test Cases: ",(0,r.kt)("a",{parentName:"li",href:"https://github.com/CoinFabrik/scout/tree/main/test-cases/lazy-values-not-set/lazy-values-not-set-1"},(0,r.kt)("inlineCode",{parentName:"a"},"lazy-values-not-set-1")))),(0,r.kt)("h2",{id:"exploit-scenario"},"Exploit Scenario"),(0,r.kt)("p",null,"Consider the following contract:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"}," #[ink(storage)]\n pub struct Contract {\n mapping: Mapping,\n }\n impl Contract {\n /* --- snip --- */\n #[ink(message)]\n pub fn sum(&mut self, value: u64) -> Result<(), Error> {\n let key = self.env().caller();\n let mut _val = self.mapping.get(key).unwrap_or_default();\n _val += value;\n Ok(())\n }\n /* --- snip --- */\n }\n")),(0,r.kt)("p",null,"In this case, when you ",(0,r.kt)("inlineCode",{parentName:"p"},".get(...)")," a value from a ",(0,r.kt)("inlineCode",{parentName:"p"},"Lazy")," storage field, you ",(0,r.kt)("em",{parentName:"p"},"probably")," want to mutate it. The values are not automatically flushed to storage, so you need to ",(0,r.kt)("inlineCode",{parentName:"p"},".set(...)")," it."),(0,r.kt)("h2",{id:"remediation"},"Remediation"),(0,r.kt)("p",null,"Use the ",(0,r.kt)("inlineCode",{parentName:"p"},".set(...)")," or ",(0,r.kt)("inlineCode",{parentName:"p"},".insert(...)")," method after using ",(0,r.kt)("inlineCode",{parentName:"p"},".get(...)")," to flush the new value to storage."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-rust"}," #[ink(storage)]\n pub struct Contract {\n mapping: Mapping,\n }\n impl Contract {\n /* --- snip --- */\n #[ink(message)]\n pub fn sum(&mut self, value: u64) -> Result<(), Error> {\n let key = self.env().caller();\n let mut _val = self.mapping.get(key).unwrap_or_default();\n _val += value;\n self.mapping.insert(key, value);\n Ok(())\n }\n /* --- snip --- */\n }\n")),(0,r.kt)("h2",{id:"known-issues"},"Known Issues"),(0,r.kt)("p",null,"If you have a ",(0,r.kt)("inlineCode",{parentName:"p"},".get(...)")," function that you don't mutate (e.g., used as a const value), this detector triggers, if you want to ignore the lint you could add ",(0,r.kt)("inlineCode",{parentName:"p"},"#[allow(lazy_values_not_set)]")," immediately before the function definition."))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.66f3dcff.js b/assets/js/runtime~main.17c9b132.js similarity index 99% rename from assets/js/runtime~main.66f3dcff.js rename to assets/js/runtime~main.17c9b132.js index b96dc66a..6e66692a 100644 --- a/assets/js/runtime~main.66f3dcff.js +++ b/assets/js/runtime~main.17c9b132.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,f,c,b,d={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var f=t[e]={exports:{}};return d[e].call(f.exports,f,f.exports,r),f.exports}r.m=d,e=[],r.O=(a,f,c,b)=>{if(!f){var d=1/0;for(i=0;i=b)&&Object.keys(r.O).every((e=>r.O[e](f[o])))?f.splice(o--,1):(t=!1,b0&&e[i-1][2]>b;i--)e[i]=e[i-1];e[i]=[f,c,b]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var b=Object.create(null);r.r(b);var d={};a=a||[null,f({}),f([]),f(f)];for(var t=2&c&&e;"object"==typeof t&&!~a.indexOf(t);t=f(t))Object.getOwnPropertyNames(t).forEach((a=>d[a]=()=>e[a]));return d.default=()=>e,r.d(b,d),b},r.d=(e,a)=>{for(var f in a)r.o(a,f)&&!r.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,f)=>(r.f[f](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",58:"e5c10141",153:"a64d7d4c",389:"558b4000",469:"c8d22d86",649:"99ca88ff",673:"f5f40401",907:"7a155d43",948:"8717b14a",1014:"e1597d36",1058:"69cd82a3",1270:"49e536ee",1329:"7cd38dd3",1612:"f9b37651",1634:"99eaca50",1699:"243618b7",1884:"c6cb2eb5",1914:"d9f32620",1926:"bbcc19ba",2148:"fe4b9b8a",2188:"1535e1e7",2247:"de850f57",2267:"59362658",2336:"0ffb054f",2362:"e273c56f",2535:"814f3328",2680:"acdb390b",2688:"3948feef",2696:"0b5b13ed",2891:"ad922fd8",2924:"69a002ee",3085:"1f391b9e",3089:"a6aa9e1f",3184:"64f73086",3237:"1df93b7f",3372:"6abe9605",3514:"73664a40",3529:"08b5abbb",3608:"9e4087bc",3687:"0e5fb7e3",3770:"f1679a91",3771:"5f7799ac",4013:"01a85c17",4162:"f27f720e",4168:"df3a40ef",4205:"124edd55",4209:"2cbb2c92",4298:"b4381ea5",4417:"9e2d31f4",4667:"428dabee",4915:"ca3c95aa",4944:"fc5a799d",5136:"acaf757c",5388:"665fb54a",5405:"4831a608",5458:"bf8500d4",5494:"858070c5",5628:"fd4b4da8",5784:"e5a1bf35",5927:"5281b7a2",6009:"607556d2",6103:"ccc49370",6325:"a47fe9b6",6516:"51bdbc41",6623:"ced73c00",6716:"7792a21f",6882:"639e0dfa",7059:"1cc114b2",7063:"3c4d22c1",7119:"3fc11f3e",7139:"10e1de95",7289:"fe202ad0",7414:"393be207",7584:"3ee3ec0f",7628:"551a9eac",7918:"17896441",8192:"c06dddcb",8280:"4c59ca6b",8610:"6875c492",8620:"8bee2a8a",8636:"f4f34a3a",9003:"925b3f96",9152:"be2f784f",9177:"e299c8cc",9302:"f577790e",9336:"6db42b5f",9363:"5182c60c",9514:"1be78505",9642:"7661071f",9671:"0e384e19",9735:"4ba7e5a3"}[e]||e)+"."+{53:"bb00ea07",58:"2596a2ab",153:"5aa5d334",389:"27262736",469:"d6cd3452",649:"eb465cce",673:"564099e1",907:"0b56c039",948:"d3524e6b",1014:"e22598eb",1058:"d8fc6e74",1270:"704a2bf0",1329:"607518fa",1612:"40110d2e",1634:"7361b849",1699:"0bf2deea",1884:"f41c0dda",1914:"f6e81993",1926:"5df476cb",2148:"c4b2d49d",2188:"ccb66765",2247:"9d2e7d7f",2267:"9f37cd21",2336:"d8488d13",2362:"06596e4f",2535:"36289364",2680:"207cec0e",2688:"85bf3489",2696:"419f8716",2891:"e6d6d79a",2924:"e471a582",3085:"524f0227",3089:"debdf23b",3184:"fe4fb7a1",3237:"a5a5b776",3372:"faa931e0",3514:"6fca9544",3529:"32eee644",3608:"1a40bcf2",3687:"0d10bdec",3770:"4ef0c75d",3771:"ec4d45de",4013:"57ffacc3",4162:"4cedf7ef",4168:"473b4140",4205:"409b8435",4209:"602c3908",4298:"67ef43bc",4417:"ede5726f",4667:"d4da5892",4915:"c1b92504",4944:"e07f39a5",5136:"a162ad4f",5388:"5a8f82e3",5405:"545711b2",5458:"0a7e61cd",5494:"eb9f3295",5628:"15a17c45",5784:"0efcdfb1",5927:"a1bde252",6009:"a38148f3",6103:"6095ed8c",6325:"3667ba51",6516:"f256d586",6623:"6251009d",6716:"451789a2",6882:"541c97cd",7059:"8e93450b",7063:"8b95d2b8",7119:"f4798a6f",7139:"1a797c66",7289:"320b0aed",7414:"ae76a8fa",7430:"5ca6a6fe",7520:"4e259ec9",7584:"76a6a570",7628:"f51ec47f",7918:"c3dab571",8192:"47db2b7d",8280:"934852a4",8610:"06e6f461",8620:"60f19fd7",8636:"573944b2",9003:"5c53f0e1",9135:"6fa44598",9152:"a9bc2431",9177:"478a95df",9302:"bb1bda18",9336:"8210965f",9363:"4a6268b5",9514:"6ee8e640",9642:"85377a5b",9671:"19cbe9d8",9735:"e696833f"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},b="scout:",r.l=(e,a,f,d)=>{if(c[e])c[e].push(a);else{var t,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var b=c[e];if(delete c[e],t.parentNode&&t.parentNode.removeChild(t),b&&b.forEach((e=>e(f))),a)return a(f)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/scout/",r.gca=function(e){return e={17896441:"7918",59362658:"2267","935f2afb":"53",e5c10141:"58",a64d7d4c:"153","558b4000":"389",c8d22d86:"469","99ca88ff":"649",f5f40401:"673","7a155d43":"907","8717b14a":"948",e1597d36:"1014","69cd82a3":"1058","49e536ee":"1270","7cd38dd3":"1329",f9b37651:"1612","99eaca50":"1634","243618b7":"1699",c6cb2eb5:"1884",d9f32620:"1914",bbcc19ba:"1926",fe4b9b8a:"2148","1535e1e7":"2188",de850f57:"2247","0ffb054f":"2336",e273c56f:"2362","814f3328":"2535",acdb390b:"2680","3948feef":"2688","0b5b13ed":"2696",ad922fd8:"2891","69a002ee":"2924","1f391b9e":"3085",a6aa9e1f:"3089","64f73086":"3184","1df93b7f":"3237","6abe9605":"3372","73664a40":"3514","08b5abbb":"3529","9e4087bc":"3608","0e5fb7e3":"3687",f1679a91:"3770","5f7799ac":"3771","01a85c17":"4013",f27f720e:"4162",df3a40ef:"4168","124edd55":"4205","2cbb2c92":"4209",b4381ea5:"4298","9e2d31f4":"4417","428dabee":"4667",ca3c95aa:"4915",fc5a799d:"4944",acaf757c:"5136","665fb54a":"5388","4831a608":"5405",bf8500d4:"5458","858070c5":"5494",fd4b4da8:"5628",e5a1bf35:"5784","5281b7a2":"5927","607556d2":"6009",ccc49370:"6103",a47fe9b6:"6325","51bdbc41":"6516",ced73c00:"6623","7792a21f":"6716","639e0dfa":"6882","1cc114b2":"7059","3c4d22c1":"7063","3fc11f3e":"7119","10e1de95":"7139",fe202ad0:"7289","393be207":"7414","3ee3ec0f":"7584","551a9eac":"7628",c06dddcb:"8192","4c59ca6b":"8280","6875c492":"8610","8bee2a8a":"8620",f4f34a3a:"8636","925b3f96":"9003",be2f784f:"9152",e299c8cc:"9177",f577790e:"9302","6db42b5f":"9336","5182c60c":"9363","1be78505":"9514","7661071f":"9642","0e384e19":"9671","4ba7e5a3":"9735"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,f)=>{var c=r.o(e,a)?e[a]:void 0;if(0!==c)if(c)f.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var b=new Promise(((f,b)=>c=e[a]=[f,b]));f.push(c[2]=b);var d=r.p+r.u(a),t=new Error;r.l(d,(f=>{if(r.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var b=f&&("load"===f.type?"missing":f.type),d=f&&f.target&&f.target.src;t.message="Loading chunk "+a+" failed.\n("+b+": "+d+")",t.name="ChunkLoadError",t.type=b,t.request=d,c[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,f)=>{var c,b,d=f[0],t=f[1],o=f[2],n=0;if(d.some((a=>0!==e[a]))){for(c in t)r.o(t,c)&&(r.m[c]=t[c]);if(o)var i=o(r)}for(a&&a(f);n{"use strict";var e,a,f,c,b,d={},t={};function r(e){var a=t[e];if(void 0!==a)return a.exports;var f=t[e]={exports:{}};return d[e].call(f.exports,f,f.exports,r),f.exports}r.m=d,e=[],r.O=(a,f,c,b)=>{if(!f){var d=1/0;for(i=0;i=b)&&Object.keys(r.O).every((e=>r.O[e](f[o])))?f.splice(o--,1):(t=!1,b0&&e[i-1][2]>b;i--)e[i]=e[i-1];e[i]=[f,c,b]},r.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return r.d(a,{a:a}),a},f=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,c){if(1&c&&(e=this(e)),8&c)return e;if("object"==typeof e&&e){if(4&c&&e.__esModule)return e;if(16&c&&"function"==typeof e.then)return e}var b=Object.create(null);r.r(b);var d={};a=a||[null,f({}),f([]),f(f)];for(var t=2&c&&e;"object"==typeof t&&!~a.indexOf(t);t=f(t))Object.getOwnPropertyNames(t).forEach((a=>d[a]=()=>e[a]));return d.default=()=>e,r.d(b,d),b},r.d=(e,a)=>{for(var f in a)r.o(a,f)&&!r.o(e,f)&&Object.defineProperty(e,f,{enumerable:!0,get:a[f]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((a,f)=>(r.f[f](e,a),a)),[])),r.u=e=>"assets/js/"+({53:"935f2afb",58:"e5c10141",153:"a64d7d4c",389:"558b4000",469:"c8d22d86",649:"99ca88ff",673:"f5f40401",907:"7a155d43",948:"8717b14a",1014:"e1597d36",1058:"69cd82a3",1270:"49e536ee",1329:"7cd38dd3",1612:"f9b37651",1634:"99eaca50",1699:"243618b7",1884:"c6cb2eb5",1914:"d9f32620",1926:"bbcc19ba",2148:"fe4b9b8a",2188:"1535e1e7",2247:"de850f57",2267:"59362658",2336:"0ffb054f",2362:"e273c56f",2535:"814f3328",2680:"acdb390b",2688:"3948feef",2696:"0b5b13ed",2891:"ad922fd8",2924:"69a002ee",3085:"1f391b9e",3089:"a6aa9e1f",3184:"64f73086",3237:"1df93b7f",3372:"6abe9605",3514:"73664a40",3529:"08b5abbb",3608:"9e4087bc",3687:"0e5fb7e3",3770:"f1679a91",3771:"5f7799ac",4013:"01a85c17",4162:"f27f720e",4168:"df3a40ef",4205:"124edd55",4209:"2cbb2c92",4298:"b4381ea5",4417:"9e2d31f4",4667:"428dabee",4915:"ca3c95aa",4944:"fc5a799d",5136:"acaf757c",5388:"665fb54a",5405:"4831a608",5458:"bf8500d4",5494:"858070c5",5628:"fd4b4da8",5784:"e5a1bf35",5927:"5281b7a2",6009:"607556d2",6103:"ccc49370",6325:"a47fe9b6",6516:"51bdbc41",6623:"ced73c00",6716:"7792a21f",6882:"639e0dfa",7059:"1cc114b2",7063:"3c4d22c1",7119:"3fc11f3e",7139:"10e1de95",7289:"fe202ad0",7414:"393be207",7584:"3ee3ec0f",7628:"551a9eac",7918:"17896441",8192:"c06dddcb",8280:"4c59ca6b",8610:"6875c492",8620:"8bee2a8a",8636:"f4f34a3a",9003:"925b3f96",9152:"be2f784f",9177:"e299c8cc",9302:"f577790e",9336:"6db42b5f",9363:"5182c60c",9514:"1be78505",9642:"7661071f",9671:"0e384e19",9735:"4ba7e5a3"}[e]||e)+"."+{53:"bb00ea07",58:"2596a2ab",153:"5aa5d334",389:"27262736",469:"d6cd3452",649:"eb465cce",673:"564099e1",907:"0b56c039",948:"d3524e6b",1014:"e22598eb",1058:"d8fc6e74",1270:"704a2bf0",1329:"607518fa",1612:"40110d2e",1634:"7361b849",1699:"0bf2deea",1884:"f41c0dda",1914:"f6e81993",1926:"5df476cb",2148:"c4b2d49d",2188:"ccb66765",2247:"9d2e7d7f",2267:"9f37cd21",2336:"d8488d13",2362:"06596e4f",2535:"36289364",2680:"207cec0e",2688:"85bf3489",2696:"419f8716",2891:"e6d6d79a",2924:"e471a582",3085:"524f0227",3089:"debdf23b",3184:"fe4fb7a1",3237:"a5a5b776",3372:"faa931e0",3514:"6fca9544",3529:"32eee644",3608:"1a40bcf2",3687:"0d10bdec",3770:"4ef0c75d",3771:"ec4d45de",4013:"57ffacc3",4162:"4cedf7ef",4168:"473b4140",4205:"409b8435",4209:"602c3908",4298:"67ef43bc",4417:"ede5726f",4667:"d4da5892",4915:"74dc6092",4944:"e07f39a5",5136:"a162ad4f",5388:"5a8f82e3",5405:"545711b2",5458:"0a7e61cd",5494:"eb9f3295",5628:"15a17c45",5784:"0efcdfb1",5927:"a1bde252",6009:"a38148f3",6103:"6095ed8c",6325:"3667ba51",6516:"f256d586",6623:"6251009d",6716:"451789a2",6882:"541c97cd",7059:"8e93450b",7063:"8b95d2b8",7119:"f4798a6f",7139:"1a797c66",7289:"320b0aed",7414:"ae76a8fa",7430:"5ca6a6fe",7520:"4e259ec9",7584:"76a6a570",7628:"f51ec47f",7918:"c3dab571",8192:"47db2b7d",8280:"934852a4",8610:"06e6f461",8620:"60f19fd7",8636:"573944b2",9003:"5c53f0e1",9135:"6fa44598",9152:"a9bc2431",9177:"478a95df",9302:"bb1bda18",9336:"8210965f",9363:"4a6268b5",9514:"6ee8e640",9642:"85377a5b",9671:"19cbe9d8",9735:"e696833f"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),c={},b="scout:",r.l=(e,a,f,d)=>{if(c[e])c[e].push(a);else{var t,o;if(void 0!==f)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var b=c[e];if(delete c[e],t.parentNode&&t.parentNode.removeChild(t),b&&b.forEach((e=>e(f))),a)return a(f)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=l.bind(null,t.onerror),t.onload=l.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/scout/",r.gca=function(e){return e={17896441:"7918",59362658:"2267","935f2afb":"53",e5c10141:"58",a64d7d4c:"153","558b4000":"389",c8d22d86:"469","99ca88ff":"649",f5f40401:"673","7a155d43":"907","8717b14a":"948",e1597d36:"1014","69cd82a3":"1058","49e536ee":"1270","7cd38dd3":"1329",f9b37651:"1612","99eaca50":"1634","243618b7":"1699",c6cb2eb5:"1884",d9f32620:"1914",bbcc19ba:"1926",fe4b9b8a:"2148","1535e1e7":"2188",de850f57:"2247","0ffb054f":"2336",e273c56f:"2362","814f3328":"2535",acdb390b:"2680","3948feef":"2688","0b5b13ed":"2696",ad922fd8:"2891","69a002ee":"2924","1f391b9e":"3085",a6aa9e1f:"3089","64f73086":"3184","1df93b7f":"3237","6abe9605":"3372","73664a40":"3514","08b5abbb":"3529","9e4087bc":"3608","0e5fb7e3":"3687",f1679a91:"3770","5f7799ac":"3771","01a85c17":"4013",f27f720e:"4162",df3a40ef:"4168","124edd55":"4205","2cbb2c92":"4209",b4381ea5:"4298","9e2d31f4":"4417","428dabee":"4667",ca3c95aa:"4915",fc5a799d:"4944",acaf757c:"5136","665fb54a":"5388","4831a608":"5405",bf8500d4:"5458","858070c5":"5494",fd4b4da8:"5628",e5a1bf35:"5784","5281b7a2":"5927","607556d2":"6009",ccc49370:"6103",a47fe9b6:"6325","51bdbc41":"6516",ced73c00:"6623","7792a21f":"6716","639e0dfa":"6882","1cc114b2":"7059","3c4d22c1":"7063","3fc11f3e":"7119","10e1de95":"7139",fe202ad0:"7289","393be207":"7414","3ee3ec0f":"7584","551a9eac":"7628",c06dddcb:"8192","4c59ca6b":"8280","6875c492":"8610","8bee2a8a":"8620",f4f34a3a:"8636","925b3f96":"9003",be2f784f:"9152",e299c8cc:"9177",f577790e:"9302","6db42b5f":"9336","5182c60c":"9363","1be78505":"9514","7661071f":"9642","0e384e19":"9671","4ba7e5a3":"9735"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(a,f)=>{var c=r.o(e,a)?e[a]:void 0;if(0!==c)if(c)f.push(c[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var b=new Promise(((f,b)=>c=e[a]=[f,b]));f.push(c[2]=b);var d=r.p+r.u(a),t=new Error;r.l(d,(f=>{if(r.o(e,a)&&(0!==(c=e[a])&&(e[a]=void 0),c)){var b=f&&("load"===f.type?"missing":f.type),d=f&&f.target&&f.target.src;t.message="Loading chunk "+a+" failed.\n("+b+": "+d+")",t.name="ChunkLoadError",t.type=b,t.request=d,c[1](t)}}),"chunk-"+a,a)}},r.O.j=a=>0===e[a];var a=(a,f)=>{var c,b,d=f[0],t=f[1],o=f[2],n=0;if(d.some((a=>0!==e[a]))){for(c in t)r.o(t,c)&&(r.m[c]=t[c]);if(o)var i=o(r)}for(a&&a(f);n Blog | Scout - +

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/archive.html b/blog/archive.html index 672cf360..ae392a97 100644 --- a/blog/archive.html +++ b/blog/archive.html @@ -5,13 +5,13 @@ Archive | Scout - + - + \ No newline at end of file diff --git a/blog/first-blog-post.html b/blog/first-blog-post.html index 218d1618..6458dad2 100644 --- a/blog/first-blog-post.html +++ b/blog/first-blog-post.html @@ -5,13 +5,13 @@ First Blog Post | Scout - +

First Blog Post

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/long-blog-post.html b/blog/long-blog-post.html index 3c0d9c44..7c740c2e 100644 --- a/blog/long-blog-post.html +++ b/blog/long-blog-post.html @@ -5,13 +5,13 @@ Long Blog Post | Scout - +

Long Blog Post

· 3 min read
Endilie Yacop Sucipto

This is the summary of a very long blog post,

Use a <!-- truncate --> comment to limit blog post size in the list view.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/mdx-blog-post.html b/blog/mdx-blog-post.html index 1f0ccc3a..c101b71d 100644 --- a/blog/mdx-blog-post.html +++ b/blog/mdx-blog-post.html @@ -5,13 +5,13 @@ MDX Blog Post | Scout - +
- + \ No newline at end of file diff --git a/blog/tags.html b/blog/tags.html index 9b73db12..b30c7398 100644 --- a/blog/tags.html +++ b/blog/tags.html @@ -5,13 +5,13 @@ Tags | Scout - + - + \ No newline at end of file diff --git a/blog/tags/docusaurus.html b/blog/tags/docusaurus.html index 8e48aebd..ce222d33 100644 --- a/blog/tags/docusaurus.html +++ b/blog/tags/docusaurus.html @@ -5,13 +5,13 @@ 4 posts tagged with "docusaurus" | Scout - +

4 posts tagged with "docusaurus"

View All Tags

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/tags/facebook.html b/blog/tags/facebook.html index 07c67023..5d7cb1cf 100644 --- a/blog/tags/facebook.html +++ b/blog/tags/facebook.html @@ -5,13 +5,13 @@ One post tagged with "facebook" | Scout - +

One post tagged with "facebook"

View All Tags

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

- + \ No newline at end of file diff --git a/blog/tags/hello.html b/blog/tags/hello.html index 66c86c59..b192339c 100644 --- a/blog/tags/hello.html +++ b/blog/tags/hello.html @@ -5,13 +5,13 @@ 2 posts tagged with "hello" | Scout - +

2 posts tagged with "hello"

View All Tags

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

- + \ No newline at end of file diff --git a/blog/tags/hola.html b/blog/tags/hola.html index 87a3d6c7..be0bbcb6 100644 --- a/blog/tags/hola.html +++ b/blog/tags/hola.html @@ -5,13 +5,13 @@ One post tagged with "hola" | Scout - +

One post tagged with "hola"

View All Tags

· One min read
Gao Wei

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

- + \ No newline at end of file diff --git a/blog/welcome.html b/blog/welcome.html index 4747645d..105c36b5 100644 --- a/blog/welcome.html +++ b/blog/welcome.html @@ -5,13 +5,13 @@ Welcome | Scout - +

Welcome

· One min read
Sébastien Lorber
Yangshun Tay

Docusaurus blogging features are powered by the blog plugin.

Simply add Markdown files (or folders) to the blog directory.

Regular blog authors can be added to authors.yml.

The blog post date can be extracted from filenames, such as:

  • 2019-05-30-welcome.md
  • 2019-05-30-welcome/index.md

A blog post folder can be convenient to co-locate blog post images:

Docusaurus Plushie

The blog supports tags as well!

And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.

- + \ No newline at end of file diff --git a/docs/architecture.html b/docs/architecture.html index 72111cc8..900f13ff 100644 --- a/docs/architecture.html +++ b/docs/architecture.html @@ -5,13 +5,13 @@ Architecture | Scout - +

Architecture

Scout Architectural Diagram

Scout is built on Trail of Bits’ Dylint, featuring a new set of lints. Dylint is a static analyzer that interfaces with the Rust compiler, providing access to the High-Level Intermediate Representation and the Mid-Level Intermediate Representation. These representations enable the accurate capture of many vulnerabilities. The lints are specifically designed to detect certain vulnerability classes. They are files integrated into the tool during compilation, and adding new lints, or detectors as we call them, is straightforward for any contributor. We have also contributed to the Dylint project, enhancing its capabilities to produce outputs in various formats, including PDF reports.

- + \ No newline at end of file diff --git a/docs/contribute.html b/docs/contribute.html index bce72811..2c0d7553 100644 --- a/docs/contribute.html +++ b/docs/contribute.html @@ -5,13 +5,13 @@ Contribute | Scout - +

Contribute

Thank you for your interest in contributing to the development of new detectors.

Getting Started

Create a new issue on our repository with the name of the new detector or test case you wish to contribute. Then, link a new branch to that issue.

If your detector or test case doesn't belong to an existing vulnerability class, please provide documentation for the new vulnerability class you're proposing.

Requirement: All detectors and test cases should follow the kebab-case naming convention, using lowercase and hyphens only.

Detectors

To contribute a new detector:

  1. Choose an appropriate template. Browse our templates at templates/detectors. Decide on the early-lint or late-lint template, based on whether you want to lint before or after macro expansion.

  2. Add your modified detector files to a new folder, naming it after your detector, inside the detectors directory.

Test Cases

To contribute a new test case:

  1. Determine the vulnerability class to which your test case belongs. Then, create a new sub-folder under that class in the test-cases directory. Remember to append the detector number at the end, separated by a hyphen.

  2. Within this sub-folder, create two directories: vulnerable-example and remediated-example. Fill each with the relevant files for their respective test cases. If possible, incorporate integration or e2e tests. For guidance, refer to the flipper template in templates/test-case.

- + \ No newline at end of file diff --git a/docs/detectors.html b/docs/detectors.html index 996ccd78..c784e4c5 100644 --- a/docs/detectors.html +++ b/docs/detectors.html @@ -5,13 +5,13 @@ Detectors | Scout - +

Detectors

In this section we introduce our set of detectors powered by Dylint - a Rust linting tool.

Similar to Clippy, Dylint can run lints to help identify potential issues in code. However, unlike Clippy, Dylint can run lints from user-specified dynamic libraries instead of just a statically predetermined set. This unique feature of Dylint makes it easier for developers to extend and customize their own personal lint collections, leading to reduced compile and run cycles.

Check our Proof of Concept Study for a more detailed analysis of different detection techniques and tools.

- + \ No newline at end of file diff --git a/docs/detectors/assert-violation.html b/docs/detectors/assert-violation.html index 9fe40811..f880ea9f 100644 --- a/docs/detectors/assert-violation.html +++ b/docs/detectors/assert-violation.html @@ -5,13 +5,13 @@ Assert violation | Scout - +

Assert violation

What it does

Checks for assert! macro usage.

Why is this bad?

The assert! macro can cause the contract to panic.

Example

    #[ink(message)]
pub fn assert_if_greater_than_10(&self, value: u128) -> bool {
assert!(value <= 10, "value should be less than 10");
true
}

Use instead:

    #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
GreaterThan10,
}

#[ink(message)]
pub fn revert_if_greater_than_10(&self, value: u128) -> Result<bool, Error> {
if value <= 10 {
Ok(true)
} else {
Err(Error::GreaterThan10)
}
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/avoid-core-mem-forget.html b/docs/detectors/avoid-core-mem-forget.html index 9ac97c40..cd0a1acb 100644 --- a/docs/detectors/avoid-core-mem-forget.html +++ b/docs/detectors/avoid-core-mem-forget.html @@ -5,13 +5,13 @@ Avoid core::mem::forget usage | Scout - +

Avoid core::mem::forget usage

What it does

Checks for core::mem::forget usage.

Why is this bad?

This is a bad practice because it can lead to memory leaks, resource leaks and logic errors.

Example

   #[ink(message)]
pub fn forget_value(&mut self) {
let forgotten_value = self.value;
self.value = false;
core::mem::forget(forgotten_value);
}

Use instead:

   #[ink(message)]
pub fn forget_value(&mut self) {
let forgotten_value = self.value;
self.value = false;
let _ = forgotten_value;
}

// or if droppable

#[ink(message)]
pub fn drop_value(&mut self) {
let forgotten_value = self.value;
self.value = false;
forget_value.drop();
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/avoid-format-string.html b/docs/detectors/avoid-format-string.html index ac20223e..084bb468 100644 --- a/docs/detectors/avoid-format-string.html +++ b/docs/detectors/avoid-format-string.html @@ -5,13 +5,13 @@ Avoid fromat! macro usage | Scout - +

Avoid fromat! macro usage

What it does

Checks for format! macro usage.

Why is this bad?

The usage of format! is not recommended.

Example

    #[ink(message)]
pub fn crash(&self) -> Result<(), Error> {
Err(Error::FormatError {
msg: (format!("{}", self.value)),
})
}

Use instead:

    pub enum Error {
FormatError { msg: String },
CrashError
}

#[ink(message)]
pub fn crash(&self) -> Result<(), Error> {
Err(Error::FormatError { msg: self.value.to_string() })
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/delegate-call.html b/docs/detectors/delegate-call.html index f414cef6..3354b160 100644 --- a/docs/detectors/delegate-call.html +++ b/docs/detectors/delegate-call.html @@ -5,13 +5,13 @@ Delegate call | Scout - +

Delegate call

What it does

Checks for delegated calls to contracts passed as arguments.

Why is this bad?

Delegated calls to contracts passed as arguments can be used to change the expected behavior of the contract. If you need to change the target of a delegated call, you should use a storage variable, and make a function with proper access control to change it.

Example

    #[ink(message)]
pub fn delegate_call(&mut self, target: Hash, argument: Balance) {
let selector_bytes = [0x0, 0x0, 0x0, 0x0];
let result: T = build_call::<DefaultEnvironment>()
.delegate(target)
.exec_input(
ExecutionInput::new(Selector::new(selector_bytes))
.push_arg(argument)
)
.returns::<T>()
.invoke();
}

Use instead:

    #[ink(message)]
pub fn delegate_call(&mut self, argument: Balance) {
let selector_bytes = [0x0, 0x0, 0x0, 0x0];
let result: T = build_call::<DefaultEnvironment>()
.delegate(self.target)
.exec_input(
ExecutionInput::new(Selector::new(selector_bytes))
.push_arg(argument)
)
.returns::<T>()
.invoke();
}

#[ink::message]
pub fn set_target(&mut self, new_target: Hash) -> Result<(), Error> {
if self.admin != self.env().caller() {
Err(Error::Unauthorized)
} else {
self.target = new_target;
Ok(())
}
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/divide-before-multiply.html b/docs/detectors/divide-before-multiply.html index f4ab9d98..3d8317fc 100644 --- a/docs/detectors/divide-before-multiply.html +++ b/docs/detectors/divide-before-multiply.html @@ -5,13 +5,13 @@ Divide before multiply | Scout - + - + \ No newline at end of file diff --git a/docs/detectors/dont-use-instantiate-contract-v1.html b/docs/detectors/dont-use-instantiate-contract-v1.html index 11acd0da..6623a6ba 100644 --- a/docs/detectors/dont-use-instantiate-contract-v1.html +++ b/docs/detectors/dont-use-instantiate-contract-v1.html @@ -5,13 +5,13 @@ Don't use instantiate_contract_v1 | Scout - + - + \ No newline at end of file diff --git a/docs/detectors/dos-unbounded-operation.html b/docs/detectors/dos-unbounded-operation.html index 4145e56c..6780f046 100644 --- a/docs/detectors/dos-unbounded-operation.html +++ b/docs/detectors/dos-unbounded-operation.html @@ -5,13 +5,13 @@ DoS unbounded operation | Scout - +

DoS unbounded operation

What it does

This detector checks that when using for or while loops, their conditions limit the execution to a constant number of iterations.

Why is this bad?

If the number of iterations is not limited to a specific range, it could potentially cause out of gas exceptions.

Known problems

False positives are to be expected when using variables that can only be set using controlled flows that limit the values within acceptable ranges.

Example

pub fn pay_out(&mut self) {
for i in 0..self.next_payee_ix {
let payee = self.payees.get(&i).unwrap();
self.env().transfer(payee.address, payee.value).unwrap();
}
}

Use instead:

pub fn pay_out(&mut self, payee: u128) {
let payee = self.payees.get(&payee).unwrap();
self.env().transfer(payee.address, payee.value).unwrap();
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/dos-unexpected-revert-with-vector.html b/docs/detectors/dos-unexpected-revert-with-vector.html index 546684e7..db7fd13a 100644 --- a/docs/detectors/dos-unexpected-revert-with-vector.html +++ b/docs/detectors/dos-unexpected-revert-with-vector.html @@ -5,13 +5,13 @@ DoS unexpected revert with vector | Scout - +

DoS unexpected revert with vector

What it does

Checks for array pushes without access control.

Why is this bad?

Arrays have a maximum size according to the storage cell. If the array is full, the push will revert. This can be used to prevent the execution of a function.

Known problems

If the owner validation is performed in an auxiliary function, the warning will be shown, resulting in a false positive.

Example

if self.votes.contains(candidate) {
Err(Errors::CandidateAlreadyAdded)
} else {
self.candidates.push(candidate); // Where candidates: Vec<AccountId>
self.votes.insert(candidate, &0);
Ok(())
}

Use instead:

if self.votes.contains(candidate) {
Err(Errors::CandidateAlreadyAdded)
} else {
self.candidates.insert(self.total_candidates, &candidate); // Where candidates: Mapping<u64, AccountId>
self.total_candidates += 1;
self.votes.insert(candidate, &0);
Ok(())
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/ink-version.html b/docs/detectors/ink-version.html index 93f5f571..b0844238 100644 --- a/docs/detectors/ink-version.html +++ b/docs/detectors/ink-version.html @@ -5,13 +5,13 @@ Ink! version | Scout - + - + \ No newline at end of file diff --git a/docs/detectors/insufficiently-random-values.html b/docs/detectors/insufficiently-random-values.html index 0cefd0c5..642b5b84 100644 --- a/docs/detectors/insufficiently-random-values.html +++ b/docs/detectors/insufficiently-random-values.html @@ -5,13 +5,13 @@ Insuficciently random values | Scout - +

Insuficciently random values

What it does

Checks the usage of block_timestamp or block_number for generation of random numbers.

Why is this bad?

Using block_timestamp is not recommended because it could be potentially manipulated by validator. On the other hand, block_number is publicly available, an attacker could predict the random number to be generated.

Example

#[ink(message, payable)]
pub fn bet_single(&mut self, number: u8) -> Result<bool> {
let inputs = self.check_inputs(36, 0, 36, number);
if inputs.is_err() {
return Err(inputs.unwrap_err());
}

let pseudo_random: u8 = (self.env().block_number() % 37).try_into().unwrap();
if pseudo_random == number {
return self
.env()
.transfer(self.env().caller(), self.env().transferred_value() * 36)
.map(|_| true)
.map_err(|_e| Error::TransferFailed);
}
return Ok(false);
}

Avoid using block attributes like block_timestamp or block_number for randomness generation, and consider using oracles instead.

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/integer-overflow-or-underflow.html b/docs/detectors/integer-overflow-or-underflow.html index e00c26c2..ac937ba6 100644 --- a/docs/detectors/integer-overflow-or-underflow.html +++ b/docs/detectors/integer-overflow-or-underflow.html @@ -5,7 +5,7 @@ Integer overflow or underflow | Scout - + @@ -18,7 +18,7 @@ attempted.

Why is this bad?

Integer overflow will trigger a panic in debug builds or will wrap in release mode. Division by zero will cause a panic in either mode. In some applications one wants explicitly checked, wrapping or saturating arithmetic.

Known problems

Example

let a = 0;
let b = a + 1;

Use instead:

let a = 0;
let b = a.checked_add(1).ok_or(Error::OverflowDetected)?;

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/iterators-over-indexing.html b/docs/detectors/iterators-over-indexing.html index c4840d30..5257c08f 100644 --- a/docs/detectors/iterators-over-indexing.html +++ b/docs/detectors/iterators-over-indexing.html @@ -5,13 +5,13 @@ Iterators over indexing | Scout - +

Iterators over indexing

What it does

It warns if for loop uses indexing instead of iterator. If the indexing goes to .len() it will not warn.

Why is this bad?

Accessing a vector by index is slower than using an iterator. Also, if the index is out of bounds, it will panic.

Example

    #[ink(message)]
pub fn bad_indexing(&self){
for i in 0..3 {
foo(self.value[i]);
}
}

Use instead:

   #[ink(message)]
pub fn iterator(&self) {
for item in self.value.iter() {
foo(self.value[i]);
}
}

// or if its not iterable (with `in`, `iter` or `to_iter()`)

#[ink(message)]
pub fn index_to_len(&self){
for i in 0..self.value.len() {
foo(self.value[i]);
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/lazy-delegate.html b/docs/detectors/lazy-delegate.html index 1823d999..81445b44 100644 --- a/docs/detectors/lazy-delegate.html +++ b/docs/detectors/lazy-delegate.html @@ -5,13 +5,13 @@ Lazy storage on delegate | Scout - +
- + \ No newline at end of file diff --git a/docs/detectors/lazy-values-not-set.html b/docs/detectors/lazy-values-not-set.html index 5b41954a..c6b26131 100644 --- a/docs/detectors/lazy-values-not-set.html +++ b/docs/detectors/lazy-values-not-set.html @@ -5,13 +5,13 @@ Lazy values get and not set | Scout - +

Lazy values get and not set

What it does

Check that if get is used on a Lazy<T> or Mapping<K,V> variable, set or insert is subsequently used to change its value.

Why is this bad?

The get function of a Mapping or a Lazy returns a local copy of the value, so changes made to that variable aren't automatically saved in the storage. To save those changes, you must call the insert function in the case of a Mapping, or set in the case of a Lazy.

As indicated here, it's a common pitfall that we decided to warn about, even though there are cases like getter functions where using "get" is necessary and not modifying the value. For cases like this, you can ignore the lint by adding #[allow(lazy_values_not_set)] immediately before the function definition.

More info

Example

    #[ink(message, payable)]
pub fn transfer(&mut self) {
let caller = self.env().caller();
let mut balance = self.balances.get(caller).unwrap_or(0);
let endowment = self.env().transferred_value();
balance += endowment;
}

Use instead:

    #[ink(message, payable)]
pub fn transfer(&mut self) {
let caller = self.env().caller();
let mut balance = self.balances.get(caller).unwrap_or(0);
let endowment = self.env().transferred_value();
balance += endowment;
self.balances.insert(caller, &balance);
}

If you want to ignore the lint in getter functions do:

    #[ink(message)]
#[allow(lazy_values_not_set)]
pub fn get_balance(&self) -> Option<Balance> {
let caller = self.env().caller();
self.balances.get(caller)
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/panic-error.html b/docs/detectors/panic-error.html index 786b556d..443a8f6c 100644 --- a/docs/detectors/panic-error.html +++ b/docs/detectors/panic-error.html @@ -5,14 +5,14 @@ Panic error | Scout - +

Panic error

What it does

The panic! macro is used to stop execution when a condition is not met. This is useful for testing and prototyping, but should be avoided in production code

Why is this bad?

The usage of panic! is not recommended because it will stop the execution of the caller contract.

Known problems

While this linter detects explicit calls to panic!, there are some ways to raise a panic such as unwrap() or expect().

Example

// example code where a warning is issued
pub fn add(&mut self, value: u32) {
match self.value.checked_add(value) {
Some(v) => self.value = v,
None => panic!("Overflow error"),
};
}

// example code that does not raise a warning

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
/// An overflow was produced while adding
OverflowError,
}

pub fn add(&mut self, value: u32) -> Result<(), Error> {
match self.value.checked_add(value) {
Some(v) => self.value = v,
None => return Err(Error::OverflowError),
};
Ok(())
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/reentrancy.html b/docs/detectors/reentrancy.html index 5d2c299a..5e5844a2 100644 --- a/docs/detectors/reentrancy.html +++ b/docs/detectors/reentrancy.html @@ -5,14 +5,14 @@ Reentrancy | Scout - +

Reentrancy

What it does

This linting rule checks whether the 'check-effect' interaction pattern has been properly followed by code that invokes a contract that may call back the original one.

Why is this bad?

If state modifications are made after a contract call, reentrant calls may not detect these modifications, potentially leading to unexpected behaviors such as double spending.

Known problems

If called method does not perform a malicious reentrancy (i.e. known method from known contract) false positives will arise. If the usage of set_allow_reentry(true) or later state changes are performed in an auxiliary function, this detector will not detect the reentrancy.

Example

let caller_addr = self.env().caller();
let caller_balance = self.balance(caller_addr);

if amount > caller_balance {
return Ok(caller_balance);
}

let call = build_call::<ink::env::DefaultEnvironment>()
.call(address)
.transferred_value(amount)
.exec_input(ink::env::call::ExecutionInput::new(Selector::new(
selector.to_be_bytes(),
)))
.call_flags(ink::env::CallFlags::default().set_allow_reentry(true))
.returns::<()>()
.params();
self.env()
.invoke_contract(&call)
.map_err(|_| Error::ContractInvokeFailed)?
.map_err(|_| Error::ContractInvokeFailed)?;

let new_balance = caller_balance.checked_sub(amount).ok_or(Error::Underflow)?;
self.balances.insert(caller_addr, &new_balance);

Use instead:

let caller_addr = self.env().caller();
let caller_balance = self.balances.get(caller_addr).unwrap_or(0);
if amount <= caller_balance {
//The balance is updated before the contract call
self.balances
.insert(caller_addr, &(caller_balance - amount));
let call = build_call::<ink::env::DefaultEnvironment>()
.call(address)
.transferred_value(amount)
.exec_input(ink::env::call::ExecutionInput::new(Selector::new(
selector.to_be_bytes(),
)))
.call_flags(ink::env::CallFlags::default().set_allow_reentry(true))
.returns::<()>()
.params();
self.env()
.invoke_contract(&call)
.unwrap_or_else(|err| panic!("Err {:?}", err))
.unwrap_or_else(|err| panic!("LangErr {:?}", err));

return caller_balance - amount;
} else {
return caller_balance;
}

Implementation

The detector's implementation can be found at these links link1, link2.

- + \ No newline at end of file diff --git a/docs/detectors/set-contract-storage.html b/docs/detectors/set-contract-storage.html index 39617e51..aa21fb45 100644 --- a/docs/detectors/set-contract-storage.html +++ b/docs/detectors/set-contract-storage.html @@ -5,13 +5,13 @@ Set contract storage | Scout - +

Set contract storage

What it does

Checks for calls to env::set_contract_storage.

Why is this bad?

Functions using keys as variables without proper access control or input sanitation can allow users to perform changes in arbitrary memory locations.

Known problems

Only check the function call, so false positives could result.

Example

fn set_contract_storage(
&mut self,
user_input_key: [u8; 68],
user_input_data: u128,
) -> Result<()> {
env::set_contract_storage(&user_input_key, &user_input_data);
Ok(())
}

Use instead:

fn set_contract_storage(
&mut self,
user_input_key: [u8; 68],
user_input_data: u128,
) -> Result<()> {
if self.env().caller() == self.owner {
env::set_contract_storage(&user_input_key, &user_input_data);
Ok(())
} else {
Err(Error::UserNotOwner)
}
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/unprotected-mapping-operation.html b/docs/detectors/unprotected-mapping-operation.html index 7b035185..c1ecb139 100644 --- a/docs/detectors/unprotected-mapping-operation.html +++ b/docs/detectors/unprotected-mapping-operation.html @@ -5,13 +5,13 @@ Unprotected Mapping Operation | Scout - +

Unprotected Mapping Operation

What it does

It warns you if a mapping operation (insert, take, remove) function is called with a user-given key field of the type AccountId.

Why is this bad?

Modifying mappings with an arbitrary key given by users can be a significant vulnerability for several reasons:

  • Unintended Modifications: Allowing users to provide arbitrary keys can lead to unintended modifications of critical data within the smart contract. If the input validation and sanitation are not done properly, users may be able to manipulate the data in ways that were not intended by the contract's author.

  • Data Corruption: Malicious users could intentionally provide keys that result in the corruption or manipulation of important data stored in the mapping. This could lead to incorrect calculations, unauthorized access, or other undesirable outcomes.

  • Denial-of-Service (DoS) Attacks: If users can set arbitrary keys, they may be able to create mappings with a large number of entries, potentially causing the contract to exceed its gas limit. This could lead to denial-of-service attacks, making the contract unusable for other users.

Known problems

Example

    #[ink(message)]
pub fn withdraw(&mut self, amount: Balance, from: AccountId) -> Result<(), Error> {
let current_bal = self.balances.take(from).unwrap_or(0);
if current_bal >= amount {
self.balances.insert(from, &(current_bal - amount));
self.env()
.transfer(from, current_bal)
.map_err(|_| Error::TransferError)
} else {
Err(Error::BalanceNotEnough)
}
}

Use instead:

    #[ink(message)]
pub fn withdraw(&mut self, amount: Balance) -> Result<(), Error> {
let caller = self.env().caller();
let current_bal = self.balances.take(caller).unwrap_or(0);
if current_bal >= amount {
self.balances.insert(caller, &(current_bal - amount));
self.env()
.transfer(caller, current_bal)
.map_err(|_| Error::TransferError)
} else {
Err(Error::BalanceNotEnough)
}
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/unprotected-self-destruct.html b/docs/detectors/unprotected-self-destruct.html index 4c696fc1..eb4c6b73 100644 --- a/docs/detectors/unprotected-self-destruct.html +++ b/docs/detectors/unprotected-self-destruct.html @@ -5,13 +5,13 @@ Unprotected self destruct | Scout - +

Unprotected self destruct

What it does

It warns you if terminate_contract function is called without a previous check of the address of the caller.

Why is this bad?

If users are allowed to call terminate_contract, they can intentionally or accidentally destroy the contract, leading to the loss of all associated data and functionalities given by this contract or by others that depend on it.

Known problems

None.

Example

    #[ink(message)]
pub fn delete_contract(&mut self, beneficiary: AccountId) {
self.env().terminate_contract(beneficiary)
}

Use instead:

pub fn delete_contract(&mut self, beneficiary: AccountId) {
if self.admin == self.env().caller() {
self.env().terminate_contract(beneficiary)
}
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/unprotected-set-code-hash.html b/docs/detectors/unprotected-set-code-hash.html index a0178c8e..d5a39eab 100644 --- a/docs/detectors/unprotected-set-code-hash.html +++ b/docs/detectors/unprotected-set-code-hash.html @@ -5,13 +5,13 @@ Unprotected set code hash | Scout - +

Unprotected set code hash

What it does

It warns you if set_code_hash function is called without a previous check of the address of the caller.

Why is this bad?

If users are allowed to call terminate_contract, they can intentionally modify the contract behaviour, leading to the loss of all associated data/tokens and functionalities given by this contract or by others that depend on it.

Known problems

None.

Example

    #[ink(message)]
pub fn update_code(&self, value: [u8; 32]) -> Result<(), Error> {
let res = set_code_hash(&value);

if res.is_err() {
return res.map_err(|_| Error::InvalidCodeHash);
}

Ok(())
}

Use instead:

    pub fn update_code(&self, value: [u8; 32]) -> Result<(), Error> {
if self.admin != Self::env().caller() {
return Err(Error::NotAnAdmin);
}

let res = set_code_hash(&value);

if res.is_err() {
return res.map_err(|_| Error::InvalidCodeHash);
}

Ok(())
}

Implementation

The detector's implementation can be found at this link

- + \ No newline at end of file diff --git a/docs/detectors/unrestricted-transfer-from.html b/docs/detectors/unrestricted-transfer-from.html index 327ae0fb..cb67f926 100644 --- a/docs/detectors/unrestricted-transfer-from.html +++ b/docs/detectors/unrestricted-transfer-from.html @@ -5,13 +5,13 @@ Unrestricted Transfer From | Scout - +

Unrestricted Transfer From

What it does

It warns you if a transfer_from function is called with a user-defined parameter in the from field.

Why is this bad?

An user Alice can approve a contract to spend their tokens. An user Bob can call that contract, use that allowance to send themselves Alice's tokens.

Known problems

None.

Example

// build_call example
#[ink(message)]
pub fn deposit(&mut self, from: AccountId) -> Result<(), Error> {
let call_params = build_call::<DefaultEnvironment>()
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!(
"PSP22::transfer_from"
)))
.push_arg(from)
.push_arg(self.env().account_id())
.push_arg(self.amount)
.push_arg([0u8]),
)
}
// ContractRef example
#[ink(message)]
pub fn deposit(&mut self, from: AccountId) -> Result<(), Error> {
let res = PSP22Ref::transfer_from(
&self.psp22_address,
from,
self.env().account_id(),
self.amount,
vec![],
);
}

Use instead:

// build_call example
pub fn deposit(&mut self) -> Result<(), Error> {
let call_params = build_call::<DefaultEnvironment>()
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!(
"PSP22::transfer_from"
)))
.push_arg(self.env().caller())
.push_arg(self.env().account_id())
.push_arg(self.amount)
.push_arg([0u8]),
)
}

// ContractRef example
pub fn deposit(&mut self) -> Result<(), Error> {
let res = PSP22Ref::transfer_from(
&self.psp22_address,
self.env().caller(),
self.env().account_id(),
self.amount,
vec![],
);
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/unsafe-expect.html b/docs/detectors/unsafe-expect.html index f931afc8..0cafdf92 100644 --- a/docs/detectors/unsafe-expect.html +++ b/docs/detectors/unsafe-expect.html @@ -5,13 +5,13 @@ Unsafe expect | Scout - +

Unsafe expect

What it does

Checks for usage of .expect()

Why is this bad?

.expect() might panic if the result value is an error or None.

Example

// example code where a warning is issued
fn main() {
let result = result_fn().expect("error");
}
fn result_fn() -> Result<u8, Error> {
Err(Error::new(ErrorKind::Other, "error"))
}

Use instead:

// example code that does not raise a warning
fn main() {
let result = if let Ok(result) = result_fn() {
result
}
}
fn result_fn() -> Result<u8, Error> {
Err(Error::new(ErrorKind::Other, "error"))
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/unsafe-unwrap.html b/docs/detectors/unsafe-unwrap.html index 69f9b85c..c2fc483e 100644 --- a/docs/detectors/unsafe-unwrap.html +++ b/docs/detectors/unsafe-unwrap.html @@ -5,13 +5,13 @@ Unsafe unwrap | Scout - +

Unsafe unwrap

What it does

Checks for usage of .unwrap()

Why is this bad?

.unwrap() might panic if the result value is an error or None.

Example

// example code where a warning is issued
fn main() {
let result = result_fn().unwrap("error");
}
fn result_fn() -> Result<u8, Error> {
Err(Error::new(ErrorKind::Other, "error"))
}

Use instead:

// example code that does not raise a warning
fn main() {
let result = if let Ok(result) = result_fn() {
result
}
}
fn result_fn() -> Result<u8, Error> {
Err(Error::new(ErrorKind::Other, "error"))
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/unused-return-enum.html b/docs/detectors/unused-return-enum.html index 3e55315f..3d1a77e3 100644 --- a/docs/detectors/unused-return-enum.html +++ b/docs/detectors/unused-return-enum.html @@ -5,13 +5,13 @@ Unused return enum | Scout - +

Unused return enum

What it does

It warns if a function that returns a Result type does not return the Result enum variant (Ok/Err).

Why is this bad?

If any of the variants (Ok/Err) is not used, the code could be simplified or it could imply a bug.

Known problems

If definitions of Err() and/or Ok() are in the code but do not flow to the return value due to the definition of a variable or because they are defined in a dead code block, the warning will not be shown. If the definitions are made in an auxiliary method, the warning will be shown, resulting in a false positive.

Example

Instead of using:

// example code that does not raise a warning
#![cfg_attr(not(feature = "std"), no_std)]
pub enum TradingPairErrors {
Overflow,
}

#[ink(message)]
pub fn get_percentage_difference(&mut self, value1: Balance, value2: Balance) -> Result<Balance, TradingPairErrors> {
let absolute_difference = value1.abs_diff(value2);
let sum = value1 + value2;
let percentage_difference =
match 100u128.checked_mul(absolute_difference / sum) {
Some(result) => Ok(result),
None => panic!("overflow!"),
};
return Err(TradingPairErrors::Overflow);
}

Use this:

    #![cfg_attr(not(feature = "std"), no_std)]
pub enum TradingPairErrors {
Overflow,
}

#[ink(message)]
pub fn get_percentage_difference(&mut self, value1: Balance, value2: Balance) -> Result<Balance, TradingPairErrors> {
let absolute_difference = value1.abs_diff(value2);
let sum = value1 + value2;
let percentage_difference =
match 100u128.checked_mul(absolute_difference / sum) {
Some(result) => result,
None => Err(TradingPairErrors::Overflow),
}
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/detectors/zero-or-test-address.html b/docs/detectors/zero-or-test-address.html index d971e593..5c913676 100644 --- a/docs/detectors/zero-or-test-address.html +++ b/docs/detectors/zero-or-test-address.html @@ -5,13 +5,13 @@ Zero or test address | Scout - +

Zero or test address

What it does

Checks whether the zero address is being inputed to a function without validation.

Why is this bad?

Because the private key for the zero address is known, anyone could take ownership of the contract.

Example

#[ink(message)]
pub fn modify_admin(&mut self, admin: AccountId) -> Result<AccountId, Error> {
if self.admin != self.env().caller() {
return Err(Error::NotAuthorized);
}

self.admin = admin;
Ok(self.admin)
}

Use instead:

#[ink(message)]
pub fn modify_admin(&mut self, admin: AccountId) -> Result<AccountId, Error> {
if self.admin != self.env().caller() {
return Err(Error::NotAuthorized);
}

if admin == AccountId::from([0x0; 32]) {
return Err(Error::InvalidAddress);
}

self.admin = admin;
Ok(self.admin)
}

Implementation

The detector's implementation can be found at this link.

- + \ No newline at end of file diff --git a/docs/intro.html b/docs/intro.html index 712e7aa0..b14c5740 100644 --- a/docs/intro.html +++ b/docs/intro.html @@ -5,14 +5,14 @@ Getting Started | Scout - +

Getting Started

Let's discover Scout in less than 5 minutes!.

About Scout

Scout is an extensible open-source tool intended to assist ink! smart contract developers and auditors detect common security issues and deviations from best practices. This tool helps developers write secure and more robust smart contracts.

Features

  • A list of vulnerabilities, best practices and enhancements, together with associated detectors to identify these issues in your code
  • Command Line Interface (CLI)
  • VSCode Extension

What you'll need

Make sure that Cargo is installed on your computer. For using the VSCode Extension you must be using VSCode.

You should be able to install and run Scout without issues on Mac, Linux or Windows.

Command Line Interface (CLI)

The command line interface is designed to allow you to run Scout on an entire project. It is especially useful for auditing or performing a final review of your code.

Installation

FIn order to install the Command Line Interface, first install Scout dependencies by running the following command:

cargo install cargo-dylint dylint-link

Afterwards, install Scout with the following command:

cargo install cargo-scout-audit

Usage

To run Scout on your project, navigate to its root directory and execute the following command:

cargo scout-audit

In the table below, we specify all the option available for the CLI.

Command/OptionExplanation
cargo scout-auditRuns the static analyzer on the current directory
cargo scout-audit --helpProvides a brief explanation of all the available commands and their usage.
cargo scout-audit --manifest-path <PATH_TO_CARGO_TOML>This option is used to specify the path to the Cargo.toml file that you want to analyze.
cargo scout-audit --profile <PROFILE_NAME>This option allows you to analyze code using specific group of detectors, configured previously on $HOME/.config/scout/(ink/soroban)-config.toml
cargo scout-audit --filter <DETECTOR_LIST_SEPARATED_BY_COMAS>This option allows you to analyze code using specific detectors. Provide a comma-separated list of detectors for this purpose.
cargo scout-audit --exclude <DETECTOR_LIST_SEPARATED_BY_COMAS>With this command, you can exclude specific detectors from the analysis. You need to give a comma-separated list of the detectors to be excluded.
cargo scout-audit --list-detectorsDisplay a list of all available detectors.
cargo scout-audit --versionDisplays the current version of the static analyzer.
cargo scout-audit --verbosePrint additional information on run
cargo scout-audit --local-detectors <PATH_TO_FOLDER>Uses the detectors of a local folder. This considers the sub-folders as detectors.
cargo scout-audit --output-format [text,json,html,sarif,pdf,md,markdown]Sets the output format. Selecting json, html, sarif, markdown, or pdf will create a file with the output
cargo scout-audit --output-path <PATH_TO_OUTPUT_FILE>Sets the output path. If a format was selected, this will replace the default file with the given one

Profile configuration

The profile configuration file is generated automatically in $HOME/.config/scout/(ink/soroban)-config.toml the first time scout-audit is run. The configuration has the following format

[<profile-name>.<detector-name>]
enabled = <true|false>

For example, if you want to define a profile named 'dev' in which the 'panic-error' detector is disabled and the 'ink-version' detector is enabled, you should do the following:

[dev.panic-error]
enabled = false
[dev.ink-version]
enabled = true

HTML Vulnerability Report

We've upgraded Scout's HTML output to introduce a comprehensive HTML Vulnerability Report, enhancing your ability to quickly assess and address the security status of your project. The new features included in the report are designed to provide a detailed yet concise overview of the findings.

html

Usage: cargo scout-audit --output-format html

VSCode Extension

We built the Scout VSCode Extension to help developers write secure and more robust smart contracts. Listing security issues, and highlighting issues with squiggles and hover-over descriptions, we hope our extension will help you catch vulnerabilities during development.

Installation

Install Scout from the Marketplace within the Extensions tab of Visual Studio Code. You can find the extension here.

You'll also need to have installed the CLI, as the extension uses the CLI to perform the analysis. You can find instructions for installing the CLI here.

Usage

After you've installed the extension, simply open a project workspace that contains any ink! (.rs) files. You will see potential issues and warnings via a wiggle underline of the relevant code.

- + \ No newline at end of file diff --git a/docs/vulnerabilities.html b/docs/vulnerabilities.html index a2f9029e..bcd2faa2 100644 --- a/docs/vulnerabilities.html +++ b/docs/vulnerabilities.html @@ -5,7 +5,7 @@ Vulnerabilities | Scout - + @@ -107,7 +107,7 @@ and has a Critical severity.

Check the following documentation for a more detailed explanation of this vulnerability class.

22 - Unprotected mapping operation

Modifying mappings with an arbitrary key given by the user could lead to unintented modifications of critical data, modifying data belonging to other users, causing denial of service, unathorized access, and other potential issues.

This vulnerability falls under the Validations and error handling category and has a Critical severity.

Check the following documentation for a more detailed explanation of this vulnerability class.

23 - Lazy storage on delegate

A bug in ink! causes delegated calls to not modify the caller's storage unless Lazy with ManualKey or Mapping is used.

This vulnerability falls under the Known Bugs category and has a Critical severity.

Check the following documentation for a more detailed explanation of this vulnerability class.

- + \ No newline at end of file diff --git a/docs/vulnerabilities/assert-violation.html b/docs/vulnerabilities/assert-violation.html index 850cd9f2..f9e2d1d8 100644 --- a/docs/vulnerabilities/assert-violation.html +++ b/docs/vulnerabilities/assert-violation.html @@ -5,13 +5,13 @@ Assert violation | Scout - +

Assert violation

Description

The assert! macro can cause the contract to panic. This is not a good practice.

Exploit Scenario

Consider the following ink! contract:

    #[ink(message)]
pub fn assert_if_greater_than_10(&self, value: u128) -> bool {
assert!(value <= 10, "value should be less than 10");
true
}

The problem arises from the use of the assert! macro, if the condition is not met, the contract panics.

The vulnerable code example can be found here.

Remediation

Avoid the use of assert! macro. Instead, use a proper error and return it.

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/avoid-core-mem-forget.html b/docs/vulnerabilities/avoid-core-mem-forget.html index 9019fc75..bf250bbc 100644 --- a/docs/vulnerabilities/avoid-core-mem-forget.html +++ b/docs/vulnerabilities/avoid-core-mem-forget.html @@ -5,13 +5,13 @@ Avoid core::mem::forget usage | Scout - +

Avoid core::mem::forget usage

Description

The core::mem::forget function usage is a bad practice.

Exploit Scenario

Consider the following ink! contract:

   #[ink(message)]
pub fn forget_value(&mut self) {
let forgotten_value = self.value;
self.value = false;
core::mem::forget(forgotten_value);
}

The problem arises from the use of the core::mem::forget function. This function is used to forget about a value without running its destructor. This is a bad practice because it can lead to memory leaks, resource leaks and logic errors.

The vulnerable code example can be found here.

Remediation

Use the pattern let _ = forgotten_value; or the .drop() method instead of core::mem::forget(forgotten_value);.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/avoid-format-string.html b/docs/vulnerabilities/avoid-format-string.html index 35cc6af6..c771c96c 100644 --- a/docs/vulnerabilities/avoid-format-string.html +++ b/docs/vulnerabilities/avoid-format-string.html @@ -5,13 +5,13 @@ Avoid fromat! macro usage | Scout - +

Avoid fromat! macro usage

Description

The format! macro is not recommended. A custom error is recommended instead.

Exploit Scenario

Consider the following ink! contract:

    #[ink(message)]
pub fn crash(&self) -> Result<(), Error> {
Err(Error::FormatError {
msg: (format!("{:?}", "false")),
})
}

The problem arises from the use of the format! macro. This is used to format a string with the given arguments. Returning a custom error is desirable.

The vulnerable code example can be found here.

Remediation

Create a custom error to avoid using the macro.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/delegate-call.html b/docs/vulnerabilities/delegate-call.html index c5fdbe3d..a0925a99 100644 --- a/docs/vulnerabilities/delegate-call.html +++ b/docs/vulnerabilities/delegate-call.html @@ -5,13 +5,13 @@ Delegate call | Scout - +

Delegate call

Description

Delegate calls can introduce security vulnerabilities if not handled carefully. The main idea is that delegate calls to contracts passed as arguments can be used to change the expected behavior of the contract, leading to potential attacks. It is important to validate and restrict delegate calls to trusted contracts, implement proper access control mechanisms, and carefully review external contracts to prevent unauthorized modifications, unexpected behavior, and potential exploits. By following these best practices, developers can enhance the security of their smart contracts and mitigate the risks associated with delegate calls.

Exploit Scenario

Consider the following ink! contract:

#[ink(message)]
pub fn delegate_call(&mut self, target: Hash, argument: Balance) {
let selector_bytes = [0x0, 0x0, 0x0, 0x0];
let result: T = build_call::<DefaultEnvironment>()
.delegate(target)
.exec_input(
ExecutionInput::new(Selector::new(selector_bytes))
.push_arg(argument)
)
.returns::<T>()
.invoke();
}

In this example, the delegate_call function allows for delegated calls to contracts passed as arguments without any validation or access control. This creates a vulnerability as it enables potential attackers to pass a malicious contract as the target, leading to unauthorized modifications or unexpected behavior in the smart contract.

The vulnerable code example can be found here.

Remediation

In the following remediated example, the vulnerability is addressed by removing the ability to pass the target contract as an argument in the delegate_call function. Instead, the target contract address is stored in a storage variable self.target, which can only be modified by calling the set_target function. The set_target function includes access control logic, allowing only the contract's administrator to update the target contract address. This remediation ensures that only trusted and authorized contracts can be delegated to, preventing the vulnerability associated with unvalidated and uncontrolled delegate calls.

    #[ink(message)]
pub fn delegate_call(&mut self, argument: Balance) {
let selector_bytes = [0x0, 0x0, 0x0, 0x0];
let result: T = build_call::<DefaultEnvironment>()
.delegate(self.target)
.exec_input(
ExecutionInput::new(Selector::new(selector_bytes))
.push_arg(argument)
)
.returns::<T>()
.invoke();
}

#[ink::message]
pub fn set_target(&mut self, new_target: Hash) -> Result<(), Error> {
if self.admin != self.env().caller() {
Err(Error::Unauthorized)
} else {
self.target = new_target;
Ok(())
}
}

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/divide-before-multiply.html b/docs/vulnerabilities/divide-before-multiply.html index 79d82f60..d1ae1d2b 100644 --- a/docs/vulnerabilities/divide-before-multiply.html +++ b/docs/vulnerabilities/divide-before-multiply.html @@ -5,13 +5,13 @@ Divide before multiply | Scout - +

Divide before multiply

Description

In Rust, the order of operations can influence the precision of the result, especially in integer arithmetic. Performing a division operation before a multiplication can lead to a loss of precision as division between integers might return zero. This issue can have serious consequences in programs such as smart contracts where numerical precision is critical.

Exploit Scenario

Consider the following ink! contract:

#[ink::contract]
mod divide_before_multiply {

#[ink(storage)]
pub struct FloatingPointAndNumericalPrecision {}

impl FloatingPointAndNumericalPrecision {
/// Creates a new FloatingPointAndNumericalPrecision contract.
#[ink(constructor)]
pub fn new() -> Self {
Self {}
}

/// Calculates the profit for a given percentage of the total profit.
#[ink(message)]
pub fn split_profit(&self, percentage: u64, total_profit: u64) -> u64 {
(percentage / 100) * total_profit
}
}
}

In this contract, the split_profit function divides the percentage by 100 before multiplying it with total_profit. This could lead to a loss of precision if percentage is less than 100 as the division would return 0. This could lead to incorrect calculations and potential financial loss in a real-world smart contract.

The vulnerable code example can be found here.

Remediation

Reverse the order of operations to ensure multiplication occurs before division.

#[ink::contract]
mod divide_before_multiply {

#[ink(storage)]
pub struct FloatingPointAndNumericalPrecision {}

impl FloatingPointAndNumericalPrecision {
#[ink(constructor)]
pub fn new() -> Self {
Self {}
}

#[ink(message)]
pub fn split_profit(&self, percentage: u64, total_profit: u64) -> u64 {
(percentage * total_profit) / 100
}
}
}

The remediated code example can be found here.

References

Rust documentation: Integer Division

- + \ No newline at end of file diff --git a/docs/vulnerabilities/dont-use-instantiate-contract-v1.html b/docs/vulnerabilities/dont-use-instantiate-contract-v1.html index 9a5bd09c..09db0f08 100644 --- a/docs/vulnerabilities/dont-use-instantiate-contract-v1.html +++ b/docs/vulnerabilities/dont-use-instantiate-contract-v1.html @@ -5,13 +5,13 @@ Don't use instantiate_contract_v1 | Scout - +

Don't use instantiate_contract_v1

Description

Avoid using instantiate_contract_v1 as it is a low level way to evaluate another smart contract. If needed, use instantiate_contract instead. Also, use methods on a ContractRef or the CreateBuilder through build_create if possible.

Exploit Scenario

Consider the following example

    impl MyContract {
#[ink(constructor)]
pub fn new() -> Self {
Self {}
}

#[ink(message)]
pub fn instantiate_contract(&self) -> MyContractRef {
let create_params = build_create::<OtherContractRef>()
.instantiate_v1()
.code_hash(Hash::from([0x42; 32]))
.gas_limit(500_000_000)
.endowment(25)
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!("new")))
.push_arg(42)
.push_arg(true)
.push_arg(&[0x10u8; 32]),
)
.salt_bytes(&[0xCA, 0xFE, 0xBA, 0xBE])
.returns::<OtherContractRef>()
.params();
self.env()
.instantiate_contract_v1(&create_params)
.unwrap_or_else(|error| {
panic!(
"Received an error from the Contracts pallet while instantiating: {:?}",
error
)
})
.unwrap_or_else(|error| {
panic!("Received a `LangError` while instatiating: {:?}", error)
})
}
}

Remediation

    impl MyContract {
#[ink(constructor)]
pub fn new() -> Self {
Self {}
}

#[ink(message)]
pub fn instantiate_contract(&self) -> MyContractRef {
let create_params = build_create::<OtherContractRef>()
.code_hash(Hash::from([0x42; 32]))
.ref_time_limit(500_000_000)
.proof_size_limit(100_000)
.storage_deposit_limit(500_000_000_000)
.endowment(25)
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!("new")))
.push_arg(42)
.push_arg(true)
.push_arg(&[0x10u8; 32]),
)
.salt_bytes(&[0xCA, 0xFE, 0xBA, 0xBE])
.returns::<OtherContractRef>()
.params();
self.env()
.instantiate_contract(&create_params)
.unwrap_or_else(|error| {
panic!(
"Received an error from the Contracts pallet while instantiating: {:?}",
error
)
})
.unwrap_or_else(|error| {
panic!("Received a `LangError` while instatiating: {:?}", error)
})
}
}

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/dos-unbounded-operation.html b/docs/vulnerabilities/dos-unbounded-operation.html index b87958e5..9e67612f 100644 --- a/docs/vulnerabilities/dos-unbounded-operation.html +++ b/docs/vulnerabilities/dos-unbounded-operation.html @@ -5,7 +5,7 @@ DoS unbounded operation | Scout - + @@ -30,7 +30,7 @@ instead call a function that will transfer the value to the payee's address.

If looping over an array of unknown size is absolutely necessary, then it should be planned to potentially take multiple blocks, and therefore require multiple transactions.

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/dos-unexpected-revert-with-vector.html b/docs/vulnerabilities/dos-unexpected-revert-with-vector.html index defa1ffd..0d50b5f6 100644 --- a/docs/vulnerabilities/dos-unexpected-revert-with-vector.html +++ b/docs/vulnerabilities/dos-unexpected-revert-with-vector.html @@ -5,7 +5,7 @@ DoS unexpected revert with vector | Scout - + @@ -50,7 +50,7 @@ propose the use of Mapping in order to avoid storage limits in the list of candidates.

#![cfg_attr(not(feature = "std"), no_std)]

#[ink::contract]
mod unexpected_revert {
use ink::storage::Mapping;
#[ink(storage)]
pub struct UnexpectedRevert {
/// Total votes performed.
total_votes: u64,
/// Total candidates.
total_candidates: u64,
/// List of candidates.
candidates: Mapping<u64, AccountId>,
/// Votes for each candidate.
votes: Mapping<AccountId, u64>,
/// Accounts that already voted.
already_voted: Mapping<AccountId, bool>,
/// Account id of the most voted candidate.
most_voted_candidate: AccountId,
/// Votes of the most voted candidate
candidate_votes: u64,
/// Timestamp when the vote ends.
vote_timestamp_end: u64,
}

#[derive(Debug, PartialEq, Eq, Clone, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))]
pub enum Errors {
/// Account already voted.
AccountAlreadyVoted,
/// Candidate already added.
CandidateAlreadyAdded,
/// Candidate doesn't exist.
CandidateDoesntExist,
/// Overflow was detected.
Overflow,
/// Timestamp before current block.
TimestampBeforeCurrentBlock,
/// Vote ended.
VoteEnded,
}

impl UnexpectedRevert {
/// Constructor that initializes the `bool` value to the given `init_value`.
#[ink(constructor)]
pub fn new(end_timestamp: u64) -> Result<Self, Errors> {
if end_timestamp <= Self::env().block_timestamp() {
return Err(Errors::TimestampBeforeCurrentBlock);
}
let zero_arr: [u8; 32] = [0; 32];
let zero_addr = AccountId::from(zero_arr);
Ok(Self {
total_votes: 0,
total_candidates: 0,
most_voted_candidate: zero_addr,
candidate_votes: 0,
candidates: Mapping::default(),
already_voted: Mapping::default(),
votes: Mapping::default(),
vote_timestamp_end: end_timestamp,
})
}

/// Add a candidate to this vote
#[ink(message)]
pub fn add_candidate(&mut self, candidate: AccountId) -> Result<(), Errors> {
if self.vote_ended() {
return Err(Errors::VoteEnded);
}
if self.votes.contains(candidate) {
Err(Errors::CandidateAlreadyAdded)
} else {
self.candidates.insert(self.total_candidates, &candidate);
self.total_candidates += 1;
self.votes.insert(candidate, &0);
Ok(())
}
}

/// Returns votes for a given candidate.
#[ink(message)]
pub fn get_votes_for_a_candidate(&self, candidate: AccountId) -> Result<u64, Errors> {
let votes_opt = self.votes.get(candidate);
if votes_opt.is_none() {
Err(Errors::CandidateDoesntExist)
} else {
Ok(votes_opt.unwrap())
}
}

/// Returns votes for the most voted candidate.
#[ink(message)]
pub fn most_voted_candidate_votes(&self) -> u64 {
self.candidate_votes
}

/// Returns account id for the most voted candidate.
#[ink(message)]
pub fn most_voted_candidate(&self) -> AccountId {
self.most_voted_candidate
}

/// Returns total votes performed.
#[ink(message)]
pub fn get_total_votes(&self) -> u64 {
self.total_votes
}

/// Returns total candidates.
#[ink(message)]
pub fn get_total_candidates(&self) -> u64 {
self.total_candidates
}

/// Returns candidate at index.
#[ink(message)]
pub fn get_candidate(&self, index: u64) -> Result<AccountId, Errors> {
match self.candidates.get(index) {
Some(candidate) => Ok(candidate),
None => Err(Errors::CandidateDoesntExist),
}
}

/// Returns true if the account has already voted.
#[ink(message)]
pub fn account_has_voted(&self, account: AccountId) -> bool {
self.already_voted.get(account).unwrap_or(false)
}

/// Vote for one of the candidates.
#[ink(message)]
pub fn vote(&mut self, candidate: AccountId) -> Result<(), Errors> {
if self.vote_ended() {
return Err(Errors::VoteEnded);
}
let caller = self.env().caller();
if self.already_voted.contains(caller) {
Err(Errors::AccountAlreadyVoted)
} else {
self.already_voted.insert(caller, &true);
let votes = self
.votes
.get(candidate)
.ok_or(Errors::CandidateDoesntExist)?
.checked_add(1)
.ok_or(Errors::Overflow)?;
self.votes.insert(candidate, &votes);
self.total_votes.checked_add(1).ok_or(Errors::Overflow)?;
if self.candidate_votes < votes {
self.candidate_votes = votes;
self.most_voted_candidate = candidate;
}
Ok(())
}
}

/// Returns true if the vote has ended.
#[ink(message)]
pub fn vote_ended(&self) -> bool {
self.vote_timestamp_end <= self.env().block_timestamp()
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::time::SystemTime;

#[ink::test]
fn insert_512_candidates() {
let now: u64 = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => (n.as_secs() + 10 * 60) * 1000,
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
};
let mut contract = UnexpectedRevert::new(now).unwrap();

let mut candidate: Result<(), Errors> = Err(Errors::VoteEnded);
for i in 0u32..512 {
let mut zero_vec = vec![0u8; 28];
zero_vec.extend(i.to_be_bytes().iter().cloned());
let arr: [u8; 32] = match zero_vec.as_slice().try_into() {
Ok(arr) => arr,
Err(_) => panic!(),
};
let addr = AccountId::from(arr);
candidate = contract.add_candidate(addr);
assert_eq!(contract.get_total_candidates(), (i + 1) as u64);
}

assert_eq!(contract.get_total_candidates(), 512u64);
assert_eq!(candidate.is_ok(), true);
}
}

#[cfg(all(test, feature = "e2e-tests"))]
mod e2e_tests {
use super::*;
use ink_e2e::build_message;
use std::time::SystemTime;

#[ink_e2e::test]
async fn insert_512_candidates(mut client: ink_e2e::Client<C, E>) {
let now: u64 = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => (n.as_secs() + 10 * 60) * 1000,
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
};
let constructor = UnexpectedRevertRef::new(now);
let contract_acc_id = client
.instantiate("unexpected-revert", &ink_e2e::alice(), constructor, 0, None)
.await
.expect("instantiate failed")
.account_id;

for i in 0u32..512 {
let mut zero_vec = vec![0u8; 28];
zero_vec.extend(i.to_be_bytes().iter().cloned());
let arr: [u8; 32] = match zero_vec.as_slice().try_into() {
Ok(arr) => arr,
Err(_) => panic!(),
};
let addr = AccountId::from(arr);

let add_candidate = build_message::<UnexpectedRevertRef>(contract_acc_id.clone())
.call(|contract| contract.add_candidate(addr));
client
.call(&ink_e2e::bob(), add_candidate.clone(), 0, None)
.await
.expect("add_candidate failed");
}
let get_total_candidates =
build_message::<UnexpectedRevertRef>(contract_acc_id.clone())
.call(|contract| contract.get_total_candidates());
let candidates_count = client
.call(&ink_e2e::bob(), get_total_candidates.clone(), 0, None)
.await
.expect("candidates_count failed");
assert_eq!(candidates_count.return_value(), 512);
}
}
}

The remediated code example can be found here.

Deployment (of the remediated contract)

In order to run the tests associated to this remediated contract in action:

  1. Save the remediated-example directory.
  2. Run a substrate node and save its FULL_PATH.
  3. Open a new terminal at the vulnerable-example directory and set the contract node environmental variable by running: export CONTRACTS_NODE=[FULL_PATH]
  4. Finally, run the test with: cargo test --features e2e-tests

You should see that the vulnerability is not realized for any of the tests.

$ cargo test --features e2e-tests

Updating crates.io index
Compiling unicode-ident v1.0.8

...

[5/5] Generating bundle
Finished test [unoptimized + debuginfo] target(s) in 14m 24s
Running unittests lib.rs (target/debug/deps/unexpected_revert-feae385052f36b92)

running 2 tests
test unexpected_revert::tests::insert_512_candidates ... ok
test unexpected_revert::e2e_tests::insert_512_candidates ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 46.06s

Doc-tests unexpected-revert

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/ink-version.html b/docs/vulnerabilities/ink-version.html index 85acaf51..9ef12a26 100644 --- a/docs/vulnerabilities/ink-version.html +++ b/docs/vulnerabilities/ink-version.html @@ -5,13 +5,13 @@ Ink! version | Scout - +

Ink! version

Description

Using an old version of ink! can be dangerous, as it may have bugs or security issues. Use the latest version available.

Exploit Scenario

Consider the following ink! contract:

[dependencies]
ink = { version = "5.0.0", default-features = false }

Problems can arise if the version is not updated to the latest available.

The vulnerable code example can be found here.

Remediation

Use the latest stable version available.

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/insufficiently-random-values.html b/docs/vulnerabilities/insufficiently-random-values.html index 9fb38424..5877a747 100644 --- a/docs/vulnerabilities/insufficiently-random-values.html +++ b/docs/vulnerabilities/insufficiently-random-values.html @@ -5,13 +5,13 @@ Insufficiently random values | Scout - +

Insufficiently random values

Description

Using block attributes like block_timestamp or block_number for random number generation in ink! Substrate smart contracts is not recommended due to the predictability of these values. Block attributes are publicly visible and deterministic, making it easy for malicious actors to anticipate their values and manipulate outcomes to their advantage. Furthermore, validators could potentially influence these attributes, further exacerbating the risk of manipulation. For truly random number generation, it's important to use a source that is both unpredictable and external to the blockchain environment, reducing the potential for malicious exploitation.

Exploit Scenario

Consider the following ink! contract:

#[ink(message, payable)]
pub fn bet_single(&mut self, number: u8) -> Result<bool> {
let inputs = self.check_inputs(36, 0, 36, number);
if inputs.is_err() {
return Err(inputs.unwrap_err());
}

let pseudo_random: u8 = (self.env().block_number() % 37).try_into().unwrap();
if pseudo_random == number {
return self
.env()
.transfer(self.env().caller(), self.env().transferred_value() * 36)
.map(|_| true)
.map_err(|_e| Error::TransferFailed);
}
return Ok(false);
}

The vulnerability in this bet_single function arises from the use of self.env().block_number() % 37 for pseudo-random number generation. Given the public visibility and predictability of block numbers, this method exposes the function to potential manipulation.

The vulnerable code example can be found here.

Remediation

Avoid using block attributes like block_timestamp or block_number for randomness generation, and consider using oracles instead.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/integer-overflow-or-underflow.html b/docs/vulnerabilities/integer-overflow-or-underflow.html index cf06721e..b8751c08 100644 --- a/docs/vulnerabilities/integer-overflow-or-underflow.html +++ b/docs/vulnerabilities/integer-overflow-or-underflow.html @@ -5,7 +5,7 @@ Integer overflow or underflow | Scout - + @@ -32,7 +32,7 @@ explorer.

The vulnerable code example can be found here.

Deployment

Before deployment, the contract must be built using the tool cargo-contract:

cargo contract build --release

Following that, the contract can be deployed either by using cargo-contract or a GUI tool (e.g., https://contracts-ui.substrate.io/):

cargo contract instantiate --constructor new --args 0 --suri //Alice

Remediation

Even though enabling the overflow/underflow checks in the Cargo.toml would eliminate the possibility of the vulnerability being realized, a panic error would still be raised.

[profile.release]
overflow-checks = true

All in all, considering that this check might be disabled and that raising a panic error is not the best way to handle this issue, it is recommended that the code be changed to explicitly use checked, overflowing, or saturating arithmetic. For example:

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
/// An overflow was produced while adding
OverflowError,
/// An underflow was produced while substracting
UnderflowError,
}

The problematic functions can be updated as follows:

#[ink(message)]
pub fn add(&mut self, value: u8) -> Result<(), Error> {
match self.value.checked_add(value) {
Some(v) => self.value = v,
None => return Err(Error::OverflowError),
};
Ok(())
}

#[ink(message)]
pub fn sub(&mut self, value: u8) -> Result<(), Error> {
match self.value.checked_sub(value) {
Some(v) => self.value = v,
None => return Err(Error::UnderflowError),
};
Ok(())
}

The remediated code example can be found here.

Other rules could be added to improve the checking. The set of rules can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/iterators-over-indexing.html b/docs/vulnerabilities/iterators-over-indexing.html index dc9fc3a6..ca46dcd0 100644 --- a/docs/vulnerabilities/iterators-over-indexing.html +++ b/docs/vulnerabilities/iterators-over-indexing.html @@ -5,13 +5,13 @@ Iterators over indexing | Scout - +

Iterators over indexing

Description

Iterating with hardcoded indexes is slower than using an iterator. Also, if the index is out of bounds, it will panic.

Exploit Scenario

Consider the following ink! contract:

    #[ink(message)]
pub fn bad_indexing(&self){
for i in 0..3 {
foo(self.value[i]);
}
}

The problem arises from the use of hardcoded indexes. If self.value has less than 4 elements, the contract will panic.

The vulnerable code example can be found here.

Remediation

Avoid the use of hardcoded indexes. Instead, use iter(), to_iter(), for ... in ... or range over 0..value.len()

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/lazy-delegate.html b/docs/vulnerabilities/lazy-delegate.html index a2aee0f3..d5f6a14b 100644 --- a/docs/vulnerabilities/lazy-delegate.html +++ b/docs/vulnerabilities/lazy-delegate.html @@ -5,13 +5,13 @@ Lazy storage on delegate | Scout - +

Lazy storage on delegate

Description

ink! has a bug that makes delegated calls not modify the storage of the caller, unless it's using Lazy with ManualKey or Mapping.

Exploit Scenario

Consider the following ink! contract:


// With this storage
#[ink(storage)]
pub struct DelegateCall {
admin: AccountId,
}


#[ink(message)]
pub fn change_admin(
&mut self,
target: Hash,
new_admin: AccountId,
) -> Result<AccountId, Error> {
if self.admin != self.env().caller() {
return Err(Error::NotAnAdmin);
}

let res = build_call::<DefaultEnvironment>()
.delegate(target)
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!("change_admin")))
.push_arg(new_admin),
)
.returns::<AccountId>()
.try_invoke()
.map_err(|_| Error::DelegateCallFailed)?
.map_err(|_| Error::DelegateCallFailed)?;

Ok(res)
}

In this example, the function change_admin takes new_admin and sets it as the new admin. If this function is called, self.admin will be the same as before, even if it's setted to a new AccountId.

The vulnerable code example can be found here.

Remediation

To remediate this, we can use Lazy to store things.

    #[ink(storage)]
#[derive(Default)]
pub struct DelegateCall {
admin: Lazy<AccountId, ManualKey<123456>>,
}

#[ink(message, payable)]
pub fn change_admin(
&mut self,
target: Hash,
new_admin: AccountId,
) -> Result<AccountId, Error> {
if self.admin.get().unwrap() != self.env().caller() {
return Err(Error::NotAnAdmin);
}

let res = build_call::<DefaultEnvironment>()
.delegate(target)
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!("change_admin")))
.push_arg(new_admin),
)
.returns::<AccountId>()
.try_invoke()
.map_err(|_| Error::DelegateCallFailed)?
.map_err(|_| Error::DelegateCallFailed)?;

Ok(res)
}

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/lazy-values-not-set.html b/docs/vulnerabilities/lazy-values-not-set.html index 9341cf7c..64098734 100644 --- a/docs/vulnerabilities/lazy-values-not-set.html +++ b/docs/vulnerabilities/lazy-values-not-set.html @@ -5,13 +5,13 @@ Lazy values not set | Scout - +
-

Lazy values not set

Description

Exploit Scenario

Consider the following contract:

    #[ink(storage)]
pub struct Contract {
mapping: Mapping<AccountId, u64>,
}
impl Contract {
/* --- snip --- */
#[ink(message)]
pub fn sum(&mut self, value: u64) -> Result<(), Error> {
let key = self.env().caller();
let mut _val = self.mapping.get(key).unwrap_or_default();
_val += value;
Ok(())
}
/* --- snip --- */
}

In this case, when you .get(...) a value from a Lazy storage field, you probably want to mutate it. The values are not automatically flushed to storage, so you need to .set(...) it.

Remediation

Use the .set(...) or .insert(...) method after using .get(...) to flush the new value to storage.

    #[ink(storage)]
pub struct Contract {
mapping: Mapping<AccountId, u64>,
}
impl Contract {
/* --- snip --- */
#[ink(message)]
pub fn sum(&mut self, value: u64) -> Result<(), Error> {
let key = self.env().caller();
let mut _val = self.mapping.get(key).unwrap_or_default();
_val += value;
self.mapping.insert(key, value);
Ok(())
}
/* --- snip --- */
}

Known Issues

If you have a .get(...) function that you don't mutate (e.g., used as a const value), this detector triggers, if you want to ignore the lint you could add #[allow(lazy_values_not_set)] immediately before the function definition.

- +

Lazy values not set

Description

Exploit Scenario

Consider the following contract:

    #[ink(storage)]
pub struct Contract {
mapping: Mapping<AccountId, u64>,
}
impl Contract {
/* --- snip --- */
#[ink(message)]
pub fn sum(&mut self, value: u64) -> Result<(), Error> {
let key = self.env().caller();
let mut _val = self.mapping.get(key).unwrap_or_default();
_val += value;
Ok(())
}
/* --- snip --- */
}

In this case, when you .get(...) a value from a Lazy storage field, you probably want to mutate it. The values are not automatically flushed to storage, so you need to .set(...) it.

Remediation

Use the .set(...) or .insert(...) method after using .get(...) to flush the new value to storage.

    #[ink(storage)]
pub struct Contract {
mapping: Mapping<AccountId, u64>,
}
impl Contract {
/* --- snip --- */
#[ink(message)]
pub fn sum(&mut self, value: u64) -> Result<(), Error> {
let key = self.env().caller();
let mut _val = self.mapping.get(key).unwrap_or_default();
_val += value;
self.mapping.insert(key, value);
Ok(())
}
/* --- snip --- */
}

Known Issues

If you have a .get(...) function that you don't mutate (e.g., used as a const value), this detector triggers, if you want to ignore the lint you could add #[allow(lazy_values_not_set)] immediately before the function definition.

+ \ No newline at end of file diff --git a/docs/vulnerabilities/panic-error.html b/docs/vulnerabilities/panic-error.html index 5d5672d9..ba241866 100644 --- a/docs/vulnerabilities/panic-error.html +++ b/docs/vulnerabilities/panic-error.html @@ -5,7 +5,7 @@ Panic Error | Scout - + @@ -28,7 +28,7 @@ then he will receive ContractTrapped as the only error message.

The vulnerable code example can be found here.

Remediation

A possible remediation goes as follows:

#[ink(message)]
pub fn add(&mut self, value: u32) -> Result<(), Error> {
match self.value.checked_add(value) {
Some(v) => self.value = v,
None => return Err(Error::OverflowError),
};
Ok(())
}

And adding the following Error enum:

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
/// An overflow was produced while adding
OverflowError,
}

By first defining the Error enum and then returning a Result<(), Error>, more information is added to the caller and, e.g. the caller contract could decide to revert the transaction or to continue execution.

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/reentrancy.html b/docs/vulnerabilities/reentrancy.html index ae082a88..893a3f16 100644 --- a/docs/vulnerabilities/reentrancy.html +++ b/docs/vulnerabilities/reentrancy.html @@ -5,7 +5,7 @@ Reentrancy | Scout - + @@ -44,7 +44,7 @@ remediated-example-2). This is equivalent in Substrate to using a reentrancy guard like the one offered by OpenBrush.

#[ink(message)]
pub fn call_with_value(&mut self, address: AccountId, amount: Balance, selector: u32) -> Balance {
ink::env::debug_println!("call_with_value function called from {:?}",self.env().caller());
let caller_addr = self.env().caller();
let caller_balance = self.balances.get(caller_addr).unwrap_or(0);
if amount <= caller_balance {
let call = build_call::<ink::env::DefaultEnvironment>()
.call(address)
.transferred_value(amount)
.exec_input(
ink::env::call::ExecutionInput::new(Selector::new(selector.to_be_bytes()))
)
.returns::<()>()
.params();
self.env().invoke_contract(&call)
.unwrap_or_else(|err| panic!("Err {:?}",err))
.unwrap_or_else(|err| panic!("LangErr {:?}",err));
self.balances.insert(caller_addr, &(caller_balance - amount));

return caller_balance - amount;
} else {
return caller_balance
}
}

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/set-contract-storage.html b/docs/vulnerabilities/set-contract-storage.html index 61556b2f..781da889 100644 --- a/docs/vulnerabilities/set-contract-storage.html +++ b/docs/vulnerabilities/set-contract-storage.html @@ -5,7 +5,7 @@ Set contract storage | Scout - + @@ -45,7 +45,7 @@ Set access control and proper authorization validation for the set_contract_storage() function.

For example, the code below, ensures only the owner can call misused_set_contract_storage().

#[ink(message)]
fn misused_set_contract_storage(&mut self, user_input_key: [u8; 68], user_input_data: u128) -> Result<()> {
if self.env().caller() == self.owner {
env::set_contract_storage(&user_input_key, &user_input_data);
Ok(())
} else {
Err(Error::UserNotOwner)
}
}

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/unprotected-mapping-operation.html b/docs/vulnerabilities/unprotected-mapping-operation.html index fe799b4d..0e2c6c41 100644 --- a/docs/vulnerabilities/unprotected-mapping-operation.html +++ b/docs/vulnerabilities/unprotected-mapping-operation.html @@ -5,13 +5,13 @@ Unprotected mapping operation | Scout - +

Unprotected mapping operation

Description

Modifying mappings with an arbitrary key given by users can be a significant vulnerability for several reasons:

  • Unintended Modifications: Allowing users to provide arbitrary keys can lead to unintended modifications of critical data within the smart contract. If the input validation and sanitation are not done properly, users may be able to manipulate the data in ways that were not intended by the contract's author.

  • Data Corruption: Malicious users could intentionally provide keys that result in the corruption or manipulation of important data stored in the mapping. This could lead to incorrect calculations, unauthorized access, or other undesirable outcomes.

  • Denial-of-Service (DoS) Attacks: If users can set arbitrary keys, they may be able to create mappings with a large number of entries, potentially causing the contract to exceed its gas limit. This could lead to denial-of-service attacks, making the contract unusable for other users.

Exploit Scenario

Consider the following ink! contract:

    #[ink(message)]
pub fn withdraw(&mut self, amount: Balance, from: AccountId) -> Result<(), Error> {
let current_bal = self.balances.take(from).unwrap_or(0);
if current_bal >= amount {
self.balances.insert(from, &(current_bal - amount));
self.env()
.transfer(from, current_bal)
.map_err(|_| Error::TransferError)
} else {
Err(Error::BalanceNotEnough)
}
}

The vulnerability in this withdraw function arises from the use of from, an user-defined parameter used as key in the mapping without prior sanitizing. Alice can withdraw tokens from any user to the user balance.

The vulnerable code example can be found here.

Remediation

Avoid using user-given arguments as key parameter in mapping. Instead, use self.env().caller() or sanitize the values.

    #[ink(message)]
pub fn withdraw(&mut self, amount: Balance) -> Result<(), Error> {
let caller = self.env().caller();
let current_bal = self.balances.take(caller).unwrap_or(0);
if current_bal >= amount {
self.balances.insert(caller, &(current_bal - amount));
self.env()
.transfer(caller, current_bal)
.map_err(|_| Error::TransferError)
} else {
Err(Error::BalanceNotEnough)
}
}

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/unprotected-self-destruct.html b/docs/vulnerabilities/unprotected-self-destruct.html index 7cb9cd2c..16e43213 100644 --- a/docs/vulnerabilities/unprotected-self-destruct.html +++ b/docs/vulnerabilities/unprotected-self-destruct.html @@ -5,13 +5,13 @@ Unprotected Self Destruct | Scout - +

Unprotected Self Destruct

Description

Allowing users to call terminate_contract can be a significant vulnerability due to the following reasons:

  • Permanent Deletion of Contract: The terminate_contract function in a smart contract is intended to allow the contract itself to be destroyed and remove it permanently from the blockchain. If users are allowed to call this function, they can intentionally or accidentally destroy the contract, leading to the loss of all associated data and functionalities.

  • Loss of Funds: If the contract holds any funds or tokens, invoking terminate_contract would transfer the contract's remaining balance to the specified target address. If users can call this function, they may attempt to drain the contract's funds, leading to a loss of funds for the contract owner or other users interacting with the contract.

  • Contract Dependency Issues: If other contracts or systems depend on the functionality provided by the contract being self-destructed, those dependent contracts or systems may become dysfunctional or throw errors, potentially causing further disruptions in the blockchain ecosystem.

Exploit Scenario

Consider the following ink! contract:

Example

    #[ink(message)]
pub fn delete_contract(&mut self, beneficiary: AccountId) {
self.env().terminate_contract(beneficiary)
}

The vulnerable code example can be found here.

Remediation

To prevent this, the function should be restricted to administrators or authorized users only.

pub fn delete_contract(&mut self, beneficiary: AccountId) {
if self.admin == self.env().caller() {
self.env().terminate_contract(beneficiary)
}
}

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/unprotected-set-code-hash.html b/docs/vulnerabilities/unprotected-set-code-hash.html index c2f44747..944c25fc 100644 --- a/docs/vulnerabilities/unprotected-set-code-hash.html +++ b/docs/vulnerabilities/unprotected-set-code-hash.html @@ -5,13 +5,13 @@ Unprotected Set Code Hash | Scout - +

Unprotected Set Code Hash

Description

Allowing users to call set_code_hash can be a significant vulnerability due to the following reasons:

  • Unintended Modifications: set_code_hash allow for changes to the contract's logic or behavior after deployment. Without proper access restrictions, unauthorized users or malicious actors could upgrade functionality and modify the contract in unintended ways. This could lead to the introduction of bugs, security vulnerabilities, or undesirable changes to the contract's behavior.

  • Unauthorized Upgrades: If access controls are not properly implemented, malicious users could upgrade the contract without authorization. Unauthorized upgrades can lead to the introduction of malicious code, exploitation of contract vulnerabilities, or even complete compromise of the contract, resulting in loss of funds or data.

  • Dependency Risks: Upgrading a contract may introduce changes that affect other dependent contracts or systems. Without proper access restrictions, unauthorized upgrades may cause disruptions or compatibility issues with the rest of the blockchain ecosystem.

Exploit Scenario

Consider the following ink! contract:

Example

    #[ink(message)]
pub fn update_code(&self, value: [u8; 32]) -> Result<(), Error> {
let res = set_code_hash(&value);

if res.is_err() {
return res.map_err(|_| Error::InvalidCodeHash);
}

Ok(())
}

The vulnerable code example can be found here.

Remediation

To prevent this, the function should be restricted to administrators or authorized users only.

    pub fn update_code(&self, value: [u8; 32]) -> Result<(), Error> {
if self.admin != Self::env().caller() {
return Err(Error::NotAnAdmin);
}

let res = set_code_hash(&value);

if res.is_err() {
return res.map_err(|_| Error::InvalidCodeHash);
}

Ok(())
}

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/unrestricted-transfer-from.html b/docs/vulnerabilities/unrestricted-transfer-from.html index c256f72c..88ae965e 100644 --- a/docs/vulnerabilities/unrestricted-transfer-from.html +++ b/docs/vulnerabilities/unrestricted-transfer-from.html @@ -5,13 +5,13 @@ Unrestricted Transfer From | Scout - +

Unrestricted Transfer From

Description

Using an user-defined argument as a transfer_from's from parameter could lead to transfer funds from a third party account without proper authorization.

Exploit Scenario

Consider the following ink! contract:

// build_call example
#[ink(message)]
pub fn deposit(&mut self, from: AccountId) -> Result<(), Error> {
let call_params = build_call::<DefaultEnvironment>()
.exec_input(
ExecutionInput::new(Selector::new(ink::selector_bytes!(
"PSP22::transfer_from"
)))
.push_arg(from)
.push_arg(self.env().account_id())
.push_arg(self.amount)
.push_arg([0u8]),
)
}

// ContractRef example
#[ink(message)]
pub fn deposit(&mut self, from: AccountId) -> Result<(), Error> {
let res = PSP22Ref::transfer_from(
&self.psp22_address,
from,
self.env().account_id(),
self.amount,
vec![],
);
}

The vulnerability in this deposit function arises from the use of from, an user-defined parameter as an argument in the from field of the transfer_from function. Alice can approve a contract to spend their tokens, then Bob can call that contract, use that allowance to send as themselves Alice's tokens.

The vulnerable code example can be found here.

Remediation

Avoid using user-defined arguments as from parameter in transfer_from. Instead, use self.env().caller().

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/unsafe-expect.html b/docs/vulnerabilities/unsafe-expect.html index f41a0bea..1aabe30d 100644 --- a/docs/vulnerabilities/unsafe-expect.html +++ b/docs/vulnerabilities/unsafe-expect.html @@ -5,13 +5,13 @@ Unsafe expect | Scout - +

Unsafe expect

Description

In Rust, the expect method is often used for error handling. It returns the contained Ok value for a Result or Some value for an Option. If an error occurs, it calls panic! with a provided error message.

The usage of expect can lead to a panic and crash the program, which is not desired behavior in most cases, especially for a smart contract.

Exploit Scenario

Consider the following ink! contract:

#[ink::contract]
mod unsafe_expect {
use ink::storage::Mapping;

#[ink(storage)]
pub struct UnsafeExpect {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}

impl UnsafeExpect {

// ...

/// Returns the balance of a given account.
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balances.get(owner).expect("could not get balance")
}
}
}

In this contract, the balance_of function uses the expect method to retrieve the balance of an account. If there is no entry for this account in the balances mapping, the contract will panic and halt execution, which could be exploited maliciously to disrupt the contract's operation.

The vulnerable code example can be found here.

Remediation

Instead of using expect, use a safer method for error handling. In this case, if there is no entry for an account in the balances mapping, return a default value (like 0).

#[ink::contract]
mod unsafe_expect {
use ink::storage::Mapping;

#[ink(storage)]
pub struct UnsafeExpect {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}

impl UnsafeExpect {

// ...

/// Returns the balance of a given account.
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
if let Some(balance) = self.balances.get(owner) {
balance
} else {
0
}
}
}
}

The remediated code example can be found here.

References

Rust documentation: expect

- + \ No newline at end of file diff --git a/docs/vulnerabilities/unsafe-unwrap.html b/docs/vulnerabilities/unsafe-unwrap.html index b847618d..7281089c 100644 --- a/docs/vulnerabilities/unsafe-unwrap.html +++ b/docs/vulnerabilities/unsafe-unwrap.html @@ -5,13 +5,13 @@ Unsafe unwrap | Scout - +

Unsafe unwrap

Description

In Rust, the unwrap method is commonly used for error handling. It retrieves the inner value of an Option or Result. If an error or None occurs, it calls panic! without a custom error message.

The usage of unwrap can lead to a panic and crash the program, which is not desired behavior in most cases, particularly in smart contracts.

Exploit Scenario

Consider the following ink! contract:

#[ink::contract]
mod unsafe_unwrap {
use ink::storage::Mapping;

#[ink(storage)]
pub struct UnsafeUnwrap {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}

// ...

impl UnsafeUnwrap {
/// Returns the balance of a given account.
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balances.get(owner).unwrap()
}

// ...
}
}

In this contract, the balance_of function uses the unwrap method to retrieve the balance of an account. If there is no entry for this account in the balances mapping, the contract will panic and halt execution, potentially leading to malicious exploitation to disrupt the contract's operation.

The vulnerable code example can be found here.

Remediation

Instead of using unwrap, use a safer method for error handling. In this case, if there is no entry for an account in the balances mapping, return a default value (like 0).

#[ink::contract]
mod unsafe_unwrap {
use ink::storage::Mapping;

#[ink(storage)]
pub struct UnsafeUnwrap {
total_supply: Balance,
balances: Mapping<AccountId, Balance>,
}

// ...

impl UnsafeUnwrap {
/// Returns the balance of a given account.
#[ink(message)]
pub fn balance_of(&self, owner: AccountId) -> Balance {
self.balances.get(owner).unwrap_or(0)
}

// ...
}
}

The remediated code example can be found here.

References

Rust documentation: unwrap

- + \ No newline at end of file diff --git a/docs/vulnerabilities/unused-return-enum.html b/docs/vulnerabilities/unused-return-enum.html index 089d982e..617fcb24 100644 --- a/docs/vulnerabilities/unused-return-enum.html +++ b/docs/vulnerabilities/unused-return-enum.html @@ -5,7 +5,7 @@ Unused return enum | Scout - + @@ -19,7 +19,7 @@ when the percentage difference is calculated successfully. By providing a check in the linter that ensures that all the variants of the Result enum are used, this bug could have been avoided. This is shown in the example below:

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum TradingPairErrors {
Overflow,
}

#[ink(message)]
pub fn get_percentage_difference(
&mut self,
value1: Balance,
value2: Balance
) -> Result<Balance, TradingPairErrors> {
let absolute_difference = value1.abs_diff(value2);
let sum = value1 + value2;
match 100u128.checked_mul(absolute_difference / sum) {
Some(result) => Ok(result),
None => Err(TradingPairErrors::Overflow)
}
}

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/docs/vulnerabilities/zero-or-test-address.html b/docs/vulnerabilities/zero-or-test-address.html index 3c5adc03..0d46a922 100644 --- a/docs/vulnerabilities/zero-or-test-address.html +++ b/docs/vulnerabilities/zero-or-test-address.html @@ -5,13 +5,13 @@ Zero or test address | Scout - +

Zero or test address

Description

Verifying that the zero address is not assigned in a smart contract, including those built with ink! on the Substrate platform, is essential to avoid a potential vulnerability. The zero address has known private keys, and this poses a significant risk. If ownership is mistakenly transferred to the zero address, the contract becomes unmanageable as malicious actors can access and control it using the known private keys associated with the zero address. This would render any funds or functionality within the contract vulnerable and easily exploitable. Hence, always ensure the zero address is not set as the owner while coding or operating your ink! smart contracts to safeguard against this vulnerability.

Assigning a test address can also have similar implications, including the loss of access or granting access to a malicious actor if its private keys are not handled with care.

Exploit Scenario

Consider the following ink! contract:

#[ink(message)]
pub fn modify_admin(&mut self, admin: AccountId) -> Result<AccountId, Error> {
if self.admin != self.env().caller() {
return Err(Error::NotAuthorized);
}

self.admin = admin;
Ok(self.admin)
}

The modify_admin function in this specific smart contract could be vulnerable due to an absence of validation for the incoming admin address. The function is intended to allow the existing admin to change the admin of the contract, but if the zero address is provided, it gets assigned as the admin. The private key for the zero address is known, which means anyone can claim ownership. Therefore, a validation check that rejects the zero address during the admin reassignment is crucial for the contract's security.

The vulnerable code example can be found here.

Remediation

To remediate this problem, verify in your code whether the admin provided is the zero address and return an Error if this is the case.

#[ink(message)]
pub fn modify_admin(&mut self, admin: AccountId) -> Result<AccountId, Error> {
if self.admin != self.env().caller() {
return Err(Error::NotAuthorized);
}

if admin == AccountId::from([0x0; 32]) {
return Err(Error::InvalidAddress);
}

self.admin = admin;
Ok(self.admin)
}

The remediated code example can be found here.

References

- + \ No newline at end of file diff --git a/index.html b/index.html index 9e239872..ab8492c0 100644 --- a/index.html +++ b/index.html @@ -5,13 +5,13 @@ Scout - +

Scout

Security Analysis Tool

The Tool

Scout is an extensible open-source tool intended to assist ink! smart contract developers and auditors detect common security issues and deviations from best practices.

Security

This tool will help developers write secure and more robust smart contracts. Our interest in this project comes from our experience in manual auditing and our usage of comparable tools in other blockchains.

Research

To improve coverage and precision, we persist in research efforts on static and dynamic analysis techniques. Find more about our ongoing research at our associated repository.

- + \ No newline at end of file diff --git a/markdown-page.html b/markdown-page.html index ede11b97..a81ee468 100644 --- a/markdown-page.html +++ b/markdown-page.html @@ -5,13 +5,13 @@ Markdown page example | Scout - +

Markdown page example

You don't need React to write simple standalone pages.

- + \ No newline at end of file