- 1 - Table of Contents
- 2 - Introduction
- 2.1 - Authenticity
- 2.2 - Audit Goals and Focus
- 2.2.1 - Sound Architecture
- 2.2.2 - Smart Contract Best Practices
- 2.2.3 - Code Correctness
- 2.2.4 - Code Quality
- 2.2.5 - Security
- 2.3 - About ENS
- 2.4 - Terminology
- 3 - Overview
- 3.1 - Source Code
- 3.2 - Contracts
- 4 - General Findings
- 4.1 - General Thoughts
- 4.2 - Minor Issues
- 4.3 - Medium Issues
- 4.3.1 - Deed Factory
- 4.4 - Major Issues
- 4.5 - Critical Issues
- 4.6 - Test Coverage Analysis
- 5 - Detailed Findings
- 5.1 - Minor Issues
- 5.2 - Medium Issues
- 5.3 - Major Issues
- 5.4 - Critical Issues
From December 2nd through December 9th of 2016, Piper Merriam conducted an audit of the smart contracts that make up the ENS service. The findings of the audit are presented in this document.
This audit was performed under a contracted hourly rate with no other compensation.
Disclaimer that Piper Merriam is also one of the keyholders for the ENS multisignature contract.
This document should have an attached cryptographic signature to ensure it has not been tampered with. The signature can be verified using the public key from Piper Merriam's the keybase.io.
This audit includes both objective findings from the contract code as well as subjective assessments of the overall architecture and design choices. Given the subjective nature of certain findings it will be up to the ENS development team to determine the appropriate response to each issue.
This audit will evaluate whether the codebase follows the current established best practices for smart contract development.
This audit will evaluate whether the code does what it is intended to do.
This audit will evaluate whether the code has been written in a way that ensures readability and maintainability.
This audit will look for any exploitable security vulnerabilities, or other potential threats to the integrity of the ENS system as well as user funds held by ENS contracts.
The primary ENS website found at ens.domains says the following about ENS.
ENS offers a secure and decentralised way to address resources both on and off the blockchain using simple, human-readable names.
More information about ENS can be found at the following resources.
- Project Documentation: http://ens.readthedocs.org/
- EIP137: ethereum/EIPs#137
- EIP162: ethereum/EIPs#162
- EIP181: ethereum/EIPs#181
This audit uses the following terminology.
Measurement of the testing code coverage. This measurement was done via inspection of the code.
No tests.
The tests do not cover some set of non-trivial functionality.
The tests cover all major functionality.
The tests cover all code paths.
Measurement of magnitude of an issue.
Minor issues are generally subjective in nature, or potentially deal with topics like "best practices" or "readability". Minor issues in general will not indicate an actual problem or bug in code.
The maintainers should use their own judgement as to whether addressing these issues improves the codebase.
Medium issues are generally objective in nature. Most medium level issues will not represent an actively exploitable bugs or security problem, but rather an issue that is likely to lead to a future error or security issue.
In most cases a medium issue should be addressed unless there is a clear reason not to.
Major issues will be things like bugs or security vulnerabilities. These issues may not be directly exploitable such as requiring a specific condition to arise in order to be exploited.
Left unaddressed these issues are highly likely to cause problems with the operation of the contract or lead to a situation which allows the system to be exploited in some way.
Critical issues are directly exploitable bugs or security vulnerabilities.
Left unaddressed these issues are highly likely or guaranteed to cause major problems or potentially a full failure in the operations of the contract.
The ENS source code is publicly available in the ethereum/ens
github
repository.
https://github.com/ethereum/ens
The code being evaluated can be found under the commit hash
TODO
This audit covers the following Solidity source files
./ENS.lll
and./ENS.sol
(see below)./HashRegistrarSimplified.sol
./ReverseRegistrar.sol
The sha256 of that contract source is as follows.
$ shasum -a 256 ./ENS.lll ./ENS.sol ./HashRegistrarSimplified.sol ./ReverseRegistrar.sol
TODO
TODO
This section contains higher level issues and analysis.
The contracts that make up the ENS system appear to be generally well written. Most functions contain both inlined comments as well as higher level comments for each function explaining the intended functionality.
Currently, the Registrar
and Deed
contracts are tightly coupled. There is
no mechanism that would allow replacing one without also replacing the other.
If a bug were to be found in the Deed contract it would be unfortunate to have
to redeploy the entire ENS system.
The common "Factory" pattern could assist in decoupling these two contracts.
The Registrar contract would likely need to implement a mechanism to update the
address of the DeedFactory
as well as some mechanism for allowing users to
upgrade their deeds.
One argument that may be made against this idea is that the Deed
contract is
quite simple and that it is unlikely to contain such a bug. I would point to
the recent Solidity Security Issue
as a type of bug that will not surface through security audits but could still
result in an exploitable bug.
None
None
TODO
The following is an exhaustive list of all issues found during in this audit.
The Registrar.returnDeed
function contains the following conditional.
if(deed.registrar() != address(this) || deed.creationDate() > registryStarted)
throw;
The second statement in this conditional checks whether the deed was created
after this registrar began. For the initial registrar this statement will
always evaluate to true
resulting in this function always throwing an
exceptions.
Suggest removing this function.
The Deed
contract uses the address 0xdead
as the recipient of burned funds.
Recommend changing this to use 0x0
to conform with the more commonly used
burn address.
5.2.1 Registrar
contract uses the entry.highestBid
and entry.deed
variables to derive secondary information about the state of the entry.
The Registrar
contract implements a modifier named state
which can be used
to restrict execution of a function unless the record it operates on is in the
specified state.
This modifier contains the following conditionals.
if(entry.highestBid == 0) {
return Mode.Open;
} else if(entry.deed == Deed(0)) {
return Mode.Forbidden;
}
...
Determining whether a record is in either the Open
or Forbidden
state
hinges on the value of the highestBid
and the deed
properties of the
record.
The result of this is that the information stored in these fields is used to convey two separate pieces of information.
Consider the situation where a modification to the logic of the contract allows
for the highestBid
for an owned record to be set to 0
. This would result
in the state of that record evaluating to Open
when it should in fact be
Owned
.
Recommend implementing additional fields on the record
struct to represent
these two pieces of information rather than overloading the existing variables.
The Deed.destroyDeed
function contains the following code.
function destroyDeed() {
if (active) throw;
if(owner.send(this.balance))
selfdestruct(burn);
}
The second if
conditional in this function is split across multiple lines but
does not make use of braces to logically contain the logic within the if
statement. This style of code can result in a higher probability of bugs being
introduced at a future date.
Consider the following refactor which adds one additional line of logic to be run prior to destruction of a deed..
function destroyDeed() {
if (active) throw;
if(owner.send(this.balance))
runPreDestroy();
selfdestruct(burn);
}
Since the runPreDestroy
and selfdestruct
instructions are both at the same
indentation level under the if
statement, a cursory inspection of this code
may miss the fact that the selfdestruct
instruction is in fact not contained
within the conditional. This function will now always execute the
selfdestruct
call regardless of whether the second conditional returns true.
This rould result in burning the full balance of the deed.
Recommend either adding braces or to put both instructions on the same line.