New Ethereum talks, every Monday. The week's conference uploads by event, in your inbox.

Loading player…

Bug Patterns in Solidity and Smart Contract Auditing by Yannis Smaragdakis

Ethereum DenverMon, Oct 9, 2023, 12:00 AM

Speaker

Yannis Smaragdakis

In the past year, we (Dedaub) have disclosed numerous vulnerabilities in deployed smart contracts and received 10 significant bug bounties totaling over $3M. The talk derives from this experience: I'll talk about the Dedaub tooling (including the contract-library.com code explorer) and a couple of Solidity patterns that have been behind high-value vulnerabilities we've identified. The main body of the talk, however, will focus on a simple question: "what can I do as a developer to make my smart contract auditor more efficient and effective?" This is not much different from asking "how can I write better code?" but I intend to give actionable advice, based on numerous audits (and instances of hair pulling). In a way, this is an update of previous talks of the past 10 months or so. E.g., talks given at the Solidity Summit: https://summit.soliditylang.org/agenda (https://www.youtube.com/watch?v=hcRY0oMscoM) and at Chainlink SmartCon 2022: https://smartcon.chain.link/agenda The current proposal updates these talks with slightly different technical content: both because of new items, and because I'm keeping the elements that work best. (E.g., I had a talk at TrustX that turned out to be a bit "too technical". The above seem to be "just right".) Yannis Smaragdakis

Transcript

Hey, hello everyone. Hey, thank you. So, I'm Janice. I'm from Dub. Uh, we are a security company in the blockchain space.

And, uh, if you are doing blockchain security, you've probably heard of us already. If you're not doing blockchain security, you probably have not heard of us. Uh so we've been around for for a little while. So if you have heard of us, it's probably because of this little site uh contract library.com which is a something that's been continuously running since uh mid 2018 or so.

So contract library or library.dub.com is this service that has the best decompiler out there. So it uh reverse engineers contracts as they are being deployed on Ethereum and other EVM based chains and it also lets you inspect contract state transactions etc. Let me blow this up a little bit.

Um well okay network is slow so maybe not the best for a live demo right now but within a couple of seconds it's going to show us a contract and we can have a look there. But that's not the topic of today's talk. Although I will very briefly go through contract library. You can uh browse on your own uh at library.dub.

com or contract-l.com. So since the network is slow, let me get back to my talk. Um and go through the slides again. Well, network being slow doesn't help for anything.

Uh, one sec. Let's not have network. One second. Okay, sorry about that. It is wired by the way.

It's not the wireless network that's acting up right now. Okay, so we have contract library. Our core technology is static analysis, meaning trying to produce models of programs of smart contracts in this case for all possible program executions. And we write static analysis by writing lots and lots like thousands and thousands of little rules that look like logic. So we build up concepts such as a loop is bound by this variable.

Uh a variable flows to another. We have exit conditions, induction variables, etc. We have logic that analyzes smart contracts. That's the core of our technology including the decompiler. Now do you care about that?

No. But that's what's behind the hood. that's under the hood. So, all of this technology has led to many many different vulnerability disclosures. We've found many visible bugs out there.

And in the past couple of years, uh we've disclosed about 10 different vulnerabilities. Well, we've disclosed many more, but 10 vulnerabilities in deployed uh projects out there. We've gotten bug bounties of over 3 million total uh by some fairly visible uh DeFi services. Uh so these kind of showcase our main analysis technology that's actually also used in our flagship tool the DOB watchdog which continuously analyzes and monitors the blockchain. Now what I'm going to do in this talk is uh first of all just give a couple of examples so you can take something home and have kind of a couple of fun examples to remember of code.

So those are going to be our solidity/evm traps. Uh, and I have a lot more. So, if you want to talk to me afterwards, find me over here uh in our impromptu Q&A location and I can discuss more. And then I'm going to talk a bit about uh smart contract auditing and how to best prepare your code so it's actually very well auditable. So, EVM traps, here's a very, very simple one.

That's about the simplest you can get in most object-oriented languages. you write this fun and it's exactly the same thing as writing fun. Well, that's not the case in Solidity. The difference, as many of you know if you've been developing Solidity, is that the first one is an external call, the second one is an internal call. External calls change the message sender.

So since the message center is our main authentication mechanism to make sure that we're being called only by approved uh accounts, this has significant security implications. So this is something to be afraid of in solidity. Whenever you say this fun, it's not something innocent as if you're just calling a local function. So that's something that you know you've internalized. It's in the back of your mind.

So it's kind of a standard caveat of solidity development, but it is not what beginners will do for instance. So that's that's a trap in solidity. Here's a second one. And in fact, the first one was behind the vulnerability that we've disclosed, but the second one is behind the vulnerability we disclosed, a vulnerability that could possibly have been the largest hack ever of more than a billion dollars in value. So this is what we call phantom functions.

That's a term we came up with. And now this will require your attention a bit unless you've seen phantom functions before and you know what they are. So here is the vulnerable code. We have two functions. Deposit and deposit uses transfer from.

And as you may know if you've done smart contract development or even used smart contracts with transfer from you need to have given approval to the contract before for a certain amount or infinity that so that it can spend your coin in this case the underlying coin. So we have a legitimate function deposit that says transfer from whoever called the function. You use that person's that party's uh money and you transfer it to this. And there's nothing wrong with deposit. I can tell you that already.

You have to give it approval before and then you do a transfer from just as the ERC20 standard intends. The second function is where the bug exists. We have deposit with permit which says even if you haven't give me given me approval before you can give me approval with an off-chain signature. That's the signature that you can sign without actually spending gas. You sign offchain and then you submit that signature together with your transfer from and that permit gives approval so you can spend the money and you give approval to this contract to do whatever it wants with your money and it moves the money from the target to itself.

And the bug here, the phantom functions bug is the following. What's the permit function for W 9, the wrapped ether contract? Well, you're looking at it and it doesn't have the word permit anywhere. So, WE 9 is a contract that supports a permit just because it has a catchall function, a nameless function to receive ether originally and when it receives ether, it just deposits it locally. So, it this contract can accept a call to permit without ever defining a permit.

That's what we call a phantom function. So if we go back to our example, the main idea is that the main element of the attack is that this function is not reverting. It accepts the contract accepts a call to permit doesn't do anything. But let's look at our code again. The first line here is a noop when we're talking about the web contract when we're talking about wrapped ether.

So calls permit nothing happens and then you can transfer from anyone's money to this. Well, but they haven't given approval, have they? Well, they have if they've ever used the first function. So, all the many thousands of accounts that have given approval because they wanted to use deposit, the non-buggy function are now exploitable using deposit with permit, the buggy function that has the permit be a noop. That's the phantom function vulnerability.

A phantom function is generally a function that even though it doesn't exist, the contract accepts a call to it without reverting. So now that we saw a couple of caveats, a couple of buggy patterns in solidity or big threats in solidity, let's go to the main body of the talk and give an idea of advice on writing well auditable code. And I could also say correct code or secure code, but maybe that would be too much of a claim. So I'll just say well auditable code. So I first should say that you've probably have not heard of our company DDOP but uh if you've used the blockchain at all you've definitely used almost certainly I should say in the past 6 months you've almost certainly used code by some company we've audited we've audited a lot of topname uh D5 protocols.

So we've done a lot of smart contract audits and this kind of comes as the experience from all this all these audits. Now first before I give advice, let's let's start with some unpleasant news. The number one unpleasant truth is that auditing will not catch all bugs in your code. And in fact, auditing is not even the best way to find some kinds of bugs. What kinds of bugs?

Anything that has to do with things that humans are bad at but machines are really good at. Boundary conditions, treating a difference like an absolute number. You know, auditors may notice those things but they may also not notice those things at all. Whereas very simple tests will catch those things. Generally in correctness of programs, there's usually a distinction between security and functional correctness.

For functional correctness, meaning for correctness under normal use, the best way to find bugs is testing. Test under many, many different scenarios. For security, tests will not help you too much because hackers or malicious hackers, black hat hackers will try to think of ways to abuse your code. And that's what auditing is best at, to try to think like a hacker and try to come up with ways that a given correct piece of code within the envelope of correctness can be abused to produce weird results. So auditing is best for malicious use.

Testing is best for normal use. Now having said this and let's also get to the second unpleasant truth and there will be a third one at the very end. Not all audits are equal. You do a smart contract audit with even the best firms out there, including ourselves. There's a wide range in the auditor's confidence.

An audit doesn't stop when there's 100% confidence because there's never going to be 100% confidence. An audit stops when there's reasonable confidence within a reasonable amount of time. That's close to the originally allocated time. So given that the question is how to best prepare your code so you get the most you can out of someone who is your strongest ally in the security business that is your smart contract auditor and there won't be too many surprises here. First of all, we want to cover all the good bases, documentation, comments, good variable names, well- tested code, good communication channel, and let's say you've covered all those.

What's the number one thing that you can do to help your auditor and then of course this way help yourself. For me, the biggest difference maker is invariance. That's kind of a standard thing in all of computing. Our minds are weak. Our minds cannot process instructions at billions per second.

And the only way we have to control this complexity is to come up with rules that are never broken. And that's what an invariant is. And there are many many kinds of invariants. So in all these slides, perhaps more than what I say, more important is the example that I put underneath. So in terms of temporal or state invariance, here are a few examples.

I would love when I audit code to see something like this. We only get here with lock called before. This function is only reachable from methods called by the hub. Whatever the hub is, that's very informative. That's a property that I can now question but also it gives me some confidence that oh they've thought of this these are the scenarios that I care about I'll check if there are other scenarios of course but now I can form a model much more easily of what the program is trying to do and of course something standard like every time you have contracts and they're being audited the auditor will say oh is the balance is there supposed to be a balance on this contract either ETH or token balance balance.

That's a big thing. Or they will say if there is a balance, it's stealable. So don't do that. That's a standard invariant property. And invariants don't only pertain to temporal or state properties.

Obviously, we can have functional invariance like the logic of a computation. So here are a couple of examples. Example functional invariance. Let's start from the bottom one. That's the easiest.

If you have a quantity and you know that it's in 10 decimal 10 decimal scale tell me put it in a comment there this is an E5 scale I want to know that I don't want to have to reverse engineer that and similarly many others if you do smart encoding absolutely tell your auditor also in this way you're telling yourself and you're telling everyone else who will ever read this code and you're actually making your auditor try to prove you a liar. Try to go there and say, "Hey, this is not true. You violated that over there." For instance, if this quantity is positive, it means it's an input amount. If it's negative, it means an output amount.

Okay, clever. Okay, let's see if it really holds. Uh, if I have an array, it may say some quantity that goes together with an array when I pass it around functions is denominated in the last of the tokens in the array. That's very useful. That's the token out.

I want to know that that I have an array and then some amount. The amount is in token out. Good. I want to know this property. Other properties there could be structural invariance like how smart contracts in a complex protocol interact.

For instance, strategies have to override functions, reinvest and report profit. That's very useful to know. That's something you should write. You should document. It will help everyone including yourselves and it will help establish what's correct behavior in the protocol.

If a strategy issues rewards, it should override both the reward token function and the reward rate function. The moment you write it, you will find violations, especially say in a yield farming protocol that will have 50 different strategies. So, and then there would be there could be a lot of other things and I'm not going to go into great detail on this slide because I don't have too much time but for instance imagine something as simple as this sub until 0xy and the behavior is actually either x - y or y - x and one of the two is zero depending on which one is larger. Well, tell me that. You know, that's that's a great property to know.

I have it there and I know it every time I look at sub until zero. And same with liquidity. And this is more complex. That's why I don't want to go into it. But if you have some AMM for instance, unis swap style, you may have some complex property like this.

And it makes sense if you know where the current liquidity uh tick is. Okay, so to wrap up, we saw a few different invariants that are interesting properties, but what's the number one take-home message from all of this? Number one take-home message is that you are the owner of your project as a developer. Your auditor is not an owner. Your auditor is on your side.

They will spend significant time. They will become you. They will try to get into your brain for a couple of weeks and think exactly like you do based on your code, they will be your ally, but they're not an owner. So, if an auditor tells you, well, here's a potential vulnerability. Well, the the first wrong reaction would be, "Ah, no, no, that's that's not true or that's not important or this and that."

The second wrong reaction is, "Oh, you're right. That's a vulnerability, and here's the easiest way to fix it. We'll put an if there, and it will exclude just these exact behaviors." What's the right reaction? The right reaction is, "Wow, so someone who didn't even write this freaking code thought about something that I hadn't thought of.

Where's my mental model wrong? What else did I not consider? How can I generalize from this one vulnerability and find three more that the auditor never even considered? That's how to get the most out of an audit. If you treat every report of an audit as something that will provoke more thought and will make you think a lot more to find very very similar issues.

So, I think that was kind of a quick rundown of my advice pre- audit. Uh, thank you again for all the bugs in code that result in bounties, result in interesting intellectual challenges for us every time we audit code or we find it in the wild. And please check out contract library and ddop. Thank you very much. We only have 17 seconds.

cannot take questions but like I said the Q&A informal Q&A session will be here and then back there. So come catch me.

Automatic transcript — names and jargon may be misspelled.