As I discussed in my previous articles, lightweight contracts provide a very secure alternative to existing “smart” contracts frameworks. Our Ardor based contracts are executed only by specific contract runner nodes and do not store any state, making them secure against bugs which might allow hackers to steal or freeze funds. As long as the contract runner node trusts the contract developer and/or took reasonable measures to review the contract code, the node can run contracts under much better security guarantees compared to other frameworks.
However, to reach the full potential of the technology, we would like contract runners to run contracts developed by 3rd parties they do not fully trust. Supporting this would provide an “App Store” like marketplace experience for contracts, where any node runner can choose to run any contract without having to trust the developer of the contract much the same way apps run on smartphones. We achieve this by granting permissions per contract and sand boxing of contract execution.
Let’s discuss the potential risks of running untrusted contract code downloaded from the blockchain. Doing so opens up several attack vectors that we need to protect against.
Note that any node by default does not run contracts so is not exposed to the risks listed below. Even a node acting as contract runner does not run any contract by default. It first needs to set an explicit contract reference from the contract runner account to the contract it wants to run.
Let’s assume you already setup your node as a contract runner and choose to run a contract by setting a contract reference. What risks is your node exposed to?
Let’s list the risks and then see how Ardor lightweight contracts protect against them.
- Operating System Access — contracts downloaded from the blockchain can in theory read any file on your local drive or delete any file. It can access environment variables, run programs on the local workstation, kill your node etc. Clearly we don’t want to allow contracts to perform these operations without our permission.
- Query node information — even if we block direct access to the local workstation resources, we still don’t want a contract to be able to access it indirectly using the node itself, for example, the contract runner’s properties file, may store a passphrase or admin password. We also don’t want a contract to be able to tell the node process id or other environment information about the contract runner since this might help attacking the node using a side channel.
- Misuse the node — a malicious contract can try to use the node’s peer connections to DDOS other nodes, or otherwise attempt to misuse the node resources and reputation to attack other nodes or end users.
- Corrupt the node internal state — a malicious contract can change the state of the contract runner node, for example, by altering the internal account balances table in a way that messes up the internal state of the node. This is more of a nuisance than a real problem since this node will be blacklisted by other nodes at some point, but it can be used to attempt to somehow mislead the node operator or other contracts running on the same node.
To mitigate these risks and protect the contract runner node against attacks issued by untrusted contracts it chooses to run, a sophisticated sandbox is used to run contracts in their own reduced permissions environment. Our solution is based on the Java Authentication and Authorization Service (JAAS), which is arguably one of the most robust security frameworks available.
Based on this solution. contracts are loaded using their own class loader. Contract permissions are assigned to contracts by the contract runner using the standard Java policy tool and policies can be assigned per contract developer account or per specific contract. Security is enforced by the Java class libraries themselves.
We did not invent this permissioned sandbox solution. Instead, we choose to integrate the Java permission system into the internals of our code. We trust the Java engineers and the 20-year legacy of mission critical Java applications to provide the best possible security framework for our contracts.
To summarize, lightweight contracts are stored on the blockchain but their execution is not part of the blockchain consensus. Their output is normal blockchain transactions. Therefore, they cannot systematically harm the blockchain itself. Since they are stateless, the risk of a contract losing or locking user funds is much smaller compared to other solutions.
Contract runner nodes run their contracts in a protected sandbox, which protects the local node from any malicious activity attempted by the contract. This is backed by the built-in Java security framework. We did not role our own security system, instead we are leveraging one of the best security frameworks available.