Auditing Substrate Based Systems in Rust
As a systems programming language, Rust is fast and memory-efficient, thread-safe and memory-safe, with predictable performance, expansive documentation, an amazing tool stack and an extremely welcoming community.
With all of the features that Rust and its ecosystem can provide, it’s not difficult to see why there has been an exponential increase in adoption in the tech industry and has been considered StackOverflow’s most-loved programming language for the last half-decade.
The inherent security features that Rust provides have been propelling the language among blockchain ventures during the past few years, which sparked our interest during Q4 2019 to expand our expertise in the field in order to prepare for the approaching generation of projects that would be built using the popular programming language and its respective frameworks.
The most notable Rust project in the DLT sector is Substrate from Parity Technologies, due to its modular design, expansive documentation, top-tier development team and community.
Substrate is a Rust framework that enables you to create purpose-built blockchains by composing custom or pre-built components, with a modular design that allows you to reuse battle-tested libraries while focusing more on building the components that matter most. Substrate is powered by best-in-class cryptographic research and comes with peer-to-peer networking, consensus mechanisms, and much more.
Similar to most programming languages, Rust can be considered as safe as its code. The fact that Rust is designed to be a secure language doesn’t subtract the possibility of undefined or unintentional behavior. This implies that logic bugs and memory access errors can still arise, even when the code passes the compilation stage and appears nominal.
Thorough auditing of a codebase is highly vital for blockchain technology in order to ensure its adherence to all specifications, especially when a large amount of funds is involved or sensitive information is at risk. To demonstrate the way we’re tackling Rust and Substrate code at CertiK, we’ve put together the following workflow model:
First things first, we naturally start by reviewing the documentation, the architectural diagrams, and research papers, among other information related to the project in question, to cultivate a proper understanding of it.
Secondly, we draft the security assessment plan, which holistically scans the project, mapping an overall risk profile, indicating areas of high concern, while suggesting operational approaches that should be considered for each case.
Furthermore, we double-check the code, do excessive testing, and observe the total code coverage to extract intel relevant to the correctness of the implementation, while assessing the test cases considered by the developers.
This analysis empowers the design of our native test cases, absent from the project’s toolkit, to study a plethora of simple and sophisticated potential attack vectors in a simulative manner.
The diagram below summarizes the steps:
Execution pt. 1 — Autonomous Scan
The audit starts with the deployment of automated tooling, strict linters, and dependency checkers to quote for unsafe code and known issues.
Considering that sometimes Rust code labeled as ‘unsafe’ could be wrapped around safe functions, most tests tend to overlook what’s generally considered as a safe dependency.
However, Rust offers a variety of tools that helps us check dependencies and enable an in-depth analysis of the tree of each project to ensure there is nothing that could affect the system’s performance.
While the use of unsafe code in Rust isn’t always pegged to performance issues, it surely makes it more possible, considering advanced developers are empowered with the ability to bypass compiler-based security checks when working with unsafe code, resulting in panic or various memory issues.
That’s why it is crucial to check both unsafe and safe sections of the code for potential flaws that may arise without any signals by the borrow checker and/or native compiler.
At this stage, the automated tools would have provided us with a first glance at the ethical aspect of the project as well as its overall health status.
Execution pt. 2 — Manual Assessment
Once the first part of the operation is complete, CertiK engineers manually review the code in a line by line focus, in parallel to the findings of the automated scan.
When it comes to Rust, you want to start the manual review by looking for panicking issues, while each bit of code will be eventually assessed individually through the entire process.
At the same time, macro usage simulations help us determine the system’s ability to deal with unintended and panicking macros.
Generally, it is wise to avoid panicking at all costs during production as a graceful exit allows for better handling of the exit processes, errors, results, and options.
When it comes to arithmetic problems, examining functionality, values, and boundaries subject to such operations is a must, especially when dealing with the kind of problems that can occur in the form of an integer overflow and/or underflow.
In complement, we ensure stack overflows, resource exhaustion, and other memory issues are not affecting the performance of the system. Commonly overlooked issues in the blockchain space are usually relevant to time and space complexity.
Suspicious code areas are examined to ensure proper allocations are present, and memory usage is optimal at all times. After evaluating the above, an optimization strategy is conducted and proposed to mitigate the respective issues.
Before wrapping the manual assessment procedure up, we highlight parsing functionality, making sure we avoid any low-level bugs and issues using fuzzing techniques.
In essence, fuzzers relate to a plethora of brute force processes that help us spot bugs unable to be tracked otherwise. Although fuzzing can be extremely time-consuming, it earned a place in our toolkit due to its ability to provide game-changing insights.
At CertiK, core engineers and developers are undergoing extensive fuzzing training, among other Rust security classes to ensure our overall skillset level is always following industry standards.
- Investigate for unsafe code blocks, risk evaluation
- Investigate for improper use of unwrap, resulting in panicking
- Investigate for arithmetic issues, brute force in debug mode with silenced release
- Evaluate functions visibility
- Investigate for recursive function calls, assess the risk of stack overflow
- Investigate APIs that could benefit from being fuzzed
- Ensure sensitive values are dropped immediately after functionality
- Arithmetic errors
- Integer overflow / underflow
- String utf8 handling
- Index out of bounds
- Stack overflows
- Resource exhaustion / out of memory errors (OOM)
Moving to the assessment of the Substrate framework, we follow an analogous operational strategy starting with reviewing the coherence between specification and implemented functionality.
As previously mentioned, Substrate is a high-level modular system, where developers are eligible to implement high-quality building blocks/modules from Parity. That is easily achieved by simply dropping the module directly in runtime, and providing its respective parameters.
Although the framework itself is tailored for security, the implementation of external modules should be considered as a catalyst for unsafe functionality. The integrity of the assessment in such cases is related to the knowledge-base of the auditing team.
Ideally, a good understanding of the external frameworks’ building blocks, in combination with a clear vision of how the modules in question could be implemented to Substrate, is essential to tackle the issue with confidence.
The manual examination of the system is revolving around intentionally or unintentionally suspicious modules generated using Substrate primitives to express their intended functionality.
Pay attention to any improper utility of framework specifics related to accounting and management, balances, storage, and event handling. Once again, keep in mind Substrate can be considered secure as long as imported modules are tailored to respect the framework’s rules and terms of functionality.
Some other noticeable concepts in Substrate would be privileged functionality, analogizing the usage of origin and sudo. In a nutshell, while origin represents the source of the function, sudo represents the superuser/administrator of the system.
Such logical functions are examined to ensure a secured environment where each account can only access the functionalities it is eligible to call, eliminating the possibility of functions being summoned without proper verification of the caller.
A final observation of the overall data flow, as well as the outcome of the runtime in areas of high interest, ensures the soundness of the implementation.
- Compare specs to code
- Parameterization and construction of the runtime
- Custom and/or imported modules assessment
- Ensure proper framework usage in all circumstances
- Investigate privileged functionality
Assessment & Reporting
CertiK’s initial report showcases a list of sorted issues found during the auditing process.
Recommended actions prioritizing the security, as well as the long-term maintainability of the project are provided in parallel to the above, while severity-based scores accompanied by rationales and/or proof of concepts wrap up the document.
Each case is analyzed on an individual depth to help us co-conclude to the validity of the claims as a team. Findings lacking in-depth evaluation, are subject to test-net experimentation until a proper and coherent evaluation is present.
CertiK’s final report is what comes next, and only if we’re confident about the integrity of each case included in the document, we shall proceed in its draft.
Although not as critical as the initial draft, the process of conducting the final report is diversified between small steps focusing on synchronization with the respective client in terms of acknowledgment of the situation and issues found, as well as in the approval of suggested implementations.
Last, but certainly not least, we reserve post-audit time to validate the outcome of implementations suggested by the report, while making sure the existing code is maintainable and conveys the functionality of the system at all times.
We at CertiK strive to secure the cyberworld regardless of the nature of each project that might be entrusting the integrity of its operations on DLTs and smart contracts and our numbers are loud about it.
Over the past years, we’ve audited and secured more than 150 smart contracts, and over 25 whole chains, while our security experts have performed more than 20 VAPTs for top-shelf industry pioneers including but not limited to Binance, Tera, Kava, e-Money, Fetch.ai, Akropolis, Bancor, Shapeshift, and Blockstack.
To learn more about smart contracts, and find out the most optimal way to secure your next venture, don’t hesitate to connect with one of our engineers and get a free consultation today.
Yvan Nasr, Global Head Of Professional Services
email@example.com | firstname.lastname@example.org