Reentrancy: Beyond the Basics
DeFi Security Summit 2023 · 15 Jul 2023 · 16:35
Anton Permenev of ChainSecurity argues that reentrancy is easier to reason about when smart contracts are treated as concurrent objects. He starts from the classic vulnerable withdraw function and the checks-effects-interactions fix, then frames an external call to untrusted code as a yield point where any other function touching the same storage can run. Reentrancy guards map to mutexes, and he shows how to check read and write patterns across the whole contract, not only the function under review.
Speakers
Topics
From DeFi Security Summit 2023, hosted by DeFi Security Summit.
Transcript
okay uh hello everyone my name is Anton and I'm like a engineer from chain security this is already installed uh who we are uh we are like in security of securing the smart contracts in 2017 it's a list of our awesome clients I'm gonna like Rush a bit in the beginning just because uh a lot of material to go through and yeah here today we want to give you like kind of our view on a smart contracts and that helps us to reason about really complex systems and to start like going beyond the basics let's go first into the most basic Korean currency example I mean
this is something you'll find if you uh look on the internet for re-entrancy right and people most I hope already know that's here for example visual function is vulnerable but it's vulnerable because if you have like a high level specifications that we want our balances some of our balances to be balanced uh either balance of the smart contract by calling visual function inside this code message sender like message sender can be potentially a contract via the fallback function right you can potentially violate this specification right
and like by looking at kind of patterns in this code we can see that like we're reading some variables we are having external call right this is a call to untrusted parties so to say like this is address that can be controlled by uh not the developers of the smart contract but it's sort of controlled by message sender in this case right and afterwards we have right and the quite standard method how people are dealing with with fixing this problem is this check effect interaction pattern right this is already fixed code you see the last thing here withdrawal function does this
call so it's basically performs the same thing but it behaves correctly with respect to like this external calls that might occur right so it's like pattern wise regarding like read write operations we basically have call as a last item on our execution list and as a result any anything message to Sender will do in their fallback function it shouldn't affect our code right so this is where Basics but uh like what we would here I'll try to uh kind of introduce you is mindset how you can think about contracts as a
concurrent objects right uh concurrency is like quite well established field right nobody has like a single processor on their PC now right everybody has multiple and there is uh like created for done on like crypto on on sorry concurrency scientific field to make sure that operations are like behaving correctly when they need to right and the good way to think about this as this is still broken example you see call in the middle is a good way to think about code in this case is that the there is some execution thread right there is some threads that needs to do a
work in this case call withdraw function and the external call call to uh somebody who didn't write the code not the non-developer controlled code is basically like a yield operation right and regular concurrency systems like usually any operation can be yielded to some other thread right and you need to be sure that you are like establishing motexes which is like exclusive execution uh contexts where only one thread can operate at the time technically it's super real the same thing is like you have the re-entrant cigars right and you basically forbid any other contract any other
execution threat entering your contract again right by using these modifiers right and like to find uh [Music] box using this concurrent smart conferences concurrent object mindset we need to think about what can be done uh during our yield operation right if we yield there is a potential uh thread one let's go uh message sender control thread that can ex can be executing anything in our smart contract right and as you see like I here have a kind of read write patterns for not only like
his draw function but for any other function in our smart contract right so there is a deposit function by the way I'm here dropping some values there's only basically storage values if I have time I will go take a look at the Special Value that is balance of account because this is a really special variable and but for now like let's assume we only care about the state of our balances mapping and as you see like deposit withdrawal balances all these three functions they are kind of actually reading or writing uh our uh writing to the same balances mapping as
our trade zero right and uh like why what we can kind of uh get from this concurrency field it's going to be next slide back to this kind of my I guess interpretation of this theoretical uh Works done before I'm gonna have a kind of list of references in the variant of my presentation but so in most cases we can from our experience as Auditors we looked a lot of at a lot of clients code we can say that [Music] um worst case scenarios for re-entrances happening when developers are not
assuming that their code can be executed in parallels of the same when if their code can be executed with re-entracy because in most cases developers are thinking about so to say sequential way of things they're not thinking about [Music] like a call that can re-enter and this is this usually leads to the worst kind of bugs of course there are some like protocols it had a look at and developers who plan their smart contract around the range we see right for example if you have a router units of operators that like it should render or like flash
flash loan smart contracts right there is a built-in function that calls the kind of hook to send fonts for this flash loan and this basically contract is intended to be re-entered right it expects to yield execution to third party but there is like post execution threads uh post execution checks that prevent uh basically This Thread one from doing something that you don't want to happen on your to your protocol right okay so the next point is that if we want like to detect that our smart contract is
um oh yeah sorry it's it will be certain point but the second point is that uh if our concurrent object which is smart contract is linearizable this is the special concurrency property of this smart of this objects is that the concurrent execution is equivalent to a legal sequential execution with respect to some sequential specification right so what does it mean it means like if developers are syncing only in a sequential ways they only have a sequential specification right this if their contracts comply with this linearization property then most likely their code is executions like reentrance is safe right
and thus to avoid unexpected bugs we need to have our smart contracts a linearizable of course assuming developers writing their code assuming only sequential specification right and the there is a really nice check from the kind of concurrency domain that if it's not really analyzable then you need to find two operations belonging to different threads so to say that I performed on the uh shared variables concurrently and at least one of the separations is right what means let's get back to our example I kind of summarized if you see on top of this conflicting operations what it
really means right so there is two operations that can be conflicting if they belong to different threads they operate on the same data item and at least one of those operations is the right operation right so by looking at this picture we have three youth operations right and technically previewed operations already happened they we cannot affect them in most cases trade one cannot affect them we'll see later like examples where it's actually important to also consider free youth but in most cases what we need to be sure is that positive is Operation the thread zero going to perform not gonna collide with anything trade one can perform and
if you like follow these rules you can see that our post-used operations are basically conflicting with deposits with withdrawal and balances and yeah worst case scenario we know is like uh you're calling visual again right and it's this basically means that there is inconsistent conflicting right operations of Israel but let's like think for a moment also I don't know if people did this mental uh effort before just to think about what what re-entrants you can actually mean for even such functions it's like getter balances right so if you see here the balances of the public mapping right and you are reading potentially a field
that's going to be immediately overridden before overridden after the execution right so you technically thread one will be already sort of reading stale data right while still because in positive execution of trade zero is going to be it's going to be overridden right so and and the same for actually like a deposit in deposit it's attack kind of doesn't make economic reason for message center but if you decode deposit in during the trade one execution uh you're basically donating money to the smart contract but nevertheless you will break this invariant we have in the very bottom the
the sum of balances should equal to this balance assuming there is no of course this uh forced ether into a smart contract right in this basically means that uh all this conflicts that our like heuristics from concurrency concurrency the security helped us to basically find all kind of things that potentially can break our balances right our environment sorry so I want to know really pay attention to that we had the properties this uh the sum of balances
equals balance of a smart contract that we wrote our contract with this with respect to this property which wrote Our smart contract and it's gone broken right but developers can potentially uh like write a smart contract that completely has completely different kind of specification right so in this case uh for example I wrote a smart contract and it's kind of just snapshots the message sender right you see like in the beginning we are like reading old one we're just calling him to notify for some reason and in the very end we are writing we are writing the message standard right and developer can have uh
like uh invariant in mind that's basically lost color should be message sender right uh after my execution of right and if you see how this function works it actually has a correct execution uh it no matter what the old color will be doing in his callback he cannot affect the kind of assignment of the last cover right and if you look on the right side this is basically the broken example of the East Vault right this is classical reentracy and has the same exact order of operations and it's just the fact that we had we had different
invariant helped us to sort of uh say that the contract on the left is safe and contract has the right is broken right so like going back to this really really packed with data points uh first of all there should be a sequential specification specification without re-entracy and only then by using this method you can uh you can reason if you have something unexpected happening or not right because if developer had in mind uh specification like on the left side it's actually reentracy safe so to say the rendency can happen but it won't be breaking in violating the invariance of the smart contract right
yeah I also have an example similar where basically on the right side you have already fixed code right this is checked effects interactions pattern right and on the left side this is the last color but we're doing call as the last execution like and as a result you can violate this invariant depending how you define the end of function call right assuming this function call ends with the end of the call frame so to say is the old color can call again the right function and snapshot himself again right and not the
new color right so it's again reentrancy is really hard problem just because there is this check effect interaction pattern like you see here it won't even be helping you because if you have a specification in mind that doesn't comply with it it's not going to work so summary the arrangement simulability cannot be detected accurately without knowledge for smart contract specification yeah read only re-empton Cy is uh yeah we had suicide but uh heuristics there is that you can basically check what you are accessing in pre-post uh pre-healed and positive of your execution and if there exist any way to
combine your uh pre-post data that you have modified then there is potential way to basically have a return rear entrancy I think certori are going to present like a way nicer method for for this is their method they're going to have like I think kind of symbolic execution building a model but I think it's quite good yep I guess I have amount of time so I'm out of time right cool