<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://austinhartzheim.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://austinhartzheim.me/" rel="alternate" type="text/html" /><updated>2024-04-18T23:54:16-05:00</updated><id>https://austinhartzheim.me/feed.xml</id><title type="html">Austin Hartzheim</title><subtitle>Writing about software engineering, security, and privacy.</subtitle><entry><title type="html">Technical Analysis of DuckDuckGo Privacy Essentials (part 2)</title><link href="https://austinhartzheim.me/blog/2021/07/20/ddg-technical-analysis-part-2.html" rel="alternate" type="text/html" title="Technical Analysis of DuckDuckGo Privacy Essentials (part 2)" /><published>2021-07-20T23:37:17-05:00</published><updated>2021-07-20T23:37:17-05:00</updated><id>https://austinhartzheim.me/blog/2021/07/20/ddg-technical-analysis-part-2</id><content type="html" xml:base="https://austinhartzheim.me/blog/2021/07/20/ddg-technical-analysis-part-2.html"><![CDATA[<p><em>Analysis of the DuckDuckGo extension’s protection of the Audio API. A broader look at the extension’s privacy protections is in <a href="/blog/2021/06/27/ddg-technical-analysis-part-1.html">part 1</a>.</em></p>

<p>The following writeup is adapted from my bug report to DuckDuckGo in <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/issues/736">issue #736</a> and partial patch proposed in <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/pull/737">PR #737</a>.</p>

<h2 id="intended-protection">Intended Protection</h2>
<p>One technique for device fingerprinting involves observation of minor variations in mathematical operations. For example, the result of floating point operations may differ depending on the underlying implementation. These differences may be introduced by software or hardware and will often affect the least significant bits of the floating point representation.</p>

<p>To expose these implementation differences, fingerprinting software executes floating point operations that are likely produce disparate results when executed on different hardware or software stacks. In particular, the <a href="https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer">Web Audio API</a> defines an <code class="language-plaintext highlighter-rouge">AudioBuffer</code> object consisting of 32-bit floats. The API also provides numerous operations for modifying the audio data - each of which is a potential source for implementation differences.</p>

<p>To prevent fingerprinting software from using these differences to fingerprint a browser, the DuckDuckGo Privacy Essentials (DPE) browser extension adds random noise when reading the contents of an <code class="language-plaintext highlighter-rouge">AudioBuffer</code>. The extension generates this random noise by using a pseudo-random number generator (PRNG) to modify calls to the <code class="language-plaintext highlighter-rouge">AudioBuffer</code> API.</p>

<h2 id="prng-analysis">PRNG Analysis</h2>
<p>Analysis of the PRNG used in DPE yields two weaknesses which lessen the effectiveness of the protection. First, the PRNG is seeded with low-entropy input. Second, due to an implementation flaw, the chosen PRNG results in low quality randomness.</p>

<h3 id="low-entropy-seed">Low-Entropy Seed</h3>
<p>The following code snippet demonstrates how the seed is selected for the PRNG. Notice that <code class="language-plaintext highlighter-rouge">key.charCodeAt(0)</code> selects the first character from <code class="language-plaintext highlighter-rouge">key</code>, where <code class="language-plaintext highlighter-rouge">key</code> is a hexadecimal string. Consequently, <code class="language-plaintext highlighter-rouge">item</code> takes a value in the range <code class="language-plaintext highlighter-rouge">[0-9a-f]</code>, which has only four bits of entropy.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Iterate through the key, passing an item index and a byte to be modified </span>
<span class="k">export</span> <span class="kd">function</span> <span class="nx">iterateDataKey</span> <span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span> 
    <span class="kd">let</span> <span class="nx">item</span> <span class="o">=</span> <span class="nx">key</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> 
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">i</span> <span class="k">in</span> <span class="nx">key</span><span class="p">)</span> <span class="p">{</span> 
        <span class="kd">let</span> <span class="nx">byte</span> <span class="o">=</span> <span class="nx">key</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span> 
        <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span> <span class="nx">j</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">j</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span> 
            <span class="nx">callback</span><span class="p">(</span><span class="nx">item</span><span class="p">,</span> <span class="nx">byte</span><span class="p">)</span> 
 
            <span class="c1">// find next item to perturb </span>
            <span class="nx">item</span> <span class="o">=</span> <span class="nx">nextRandom</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> 
 
            <span class="c1">// Right shift as we use the least significant bit of it </span>
            <span class="nx">byte</span> <span class="o">=</span> <span class="nx">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">1</span> 
        <span class="p">}</span> 
    <span class="p">}</span> 
<span class="p">}</span> 
</code></pre></div></div>
<p><a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/918c48c6db17f09135d375c5d2fa91280f019777/shared/js/content-scope/utils.js#L62-L77">Source</a></p>

<p><strong>Recommendation 1:</strong> Seed the PRNG with sufficient entropy. For example, a 64-bit LFSR should be seeded with 64-bits of entropy.</p>

<h3 id="choice-of-prng">Choice of PRNG</h3>
<p>DPE uses a Linear Feedback Shift Register (LFSR) PRNG. LFSRs have the benefit of being quite fast, which might be beneficial for low-latency audio applications. However, the chosen LFSR implementation operates on a 64-bit register. Normally, this would be fine, however JavaScript uses IEEE 754 format to represent numbers internally. This format only allows lossless representation of up to <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER">53-bit</a> integers. The result is that the PRNG output (ignoring the low-entropy seed discussed above) quickly decays into a predictable sequence of outputs.</p>

<p>The PRNG implementation is as follows:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">nextRandom</span> <span class="p">(</span><span class="nx">v</span><span class="p">)</span> <span class="p">{</span> 
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">((</span><span class="nx">v</span> <span class="o">&gt;&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">|</span> <span class="p">(((</span><span class="nx">v</span> <span class="o">&lt;&lt;</span> <span class="mi">62</span><span class="p">)</span> <span class="o">^</span> <span class="p">(</span><span class="nx">v</span> <span class="o">&lt;&lt;</span> <span class="mi">61</span><span class="p">))</span> <span class="o">&amp;</span> <span class="p">(</span><span class="o">~</span><span class="p">(</span><span class="o">~</span><span class="mi">0</span> <span class="o">&lt;&lt;</span> <span class="mi">63</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="mi">62</span><span class="p">)))</span> 
<span class="p">}</span> 
</code></pre></div></div>
<p><a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/918c48c6db17f09135d375c5d2fa91280f019777/shared/js/content-scope/utils.js#L11-L13">Source</a></p>

<p>The PRNG selects which indexes in the <code class="language-plaintext highlighter-rouge">AudioBuffer</code> output are modified. Because the PRNG decays into a predictable state, the result is that far fewer than the desired numbered of outputs are modified. The following displays a simulation of the PRNG output for several seeds, demonstrating the high number of repeated indexes. Note that the output values of the PRNG are taken modulo the length of the <code class="language-plaintext highlighter-rouge">AudioBuffer</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>length=64  seed=97  sequence=[33, 48, 24, 12, 58, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31, 15, 7, 3, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 31, 47, 55, 59, 3, 63, 31]
length=128  seed=57  sequence=[57, 28, 114, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15, 7, 3, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 63, 95, 111, 119, 123, 67, 31, 15]
length=200  seed=98  sequence=[98, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87, 43, 127, 163, 167, 183, 191, 95, 47, 23, 111, 55, 127, 63, 31, 15, 7, 103, 151, 175, 87, 143, 71, 135, 167, 183, 191, 195, 151, 175, 87]
length=200  seed=99  sequence=[99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199, 99, 199, 99, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 99, 199, 199]
</code></pre></div></div>

<p><strong>Recommendation 2:</strong> Change the PRNG implementation. For example, a 32-bit LFSR would have similar performance characteristics but, better randomness given the limitation on JavaScript’s integers.</p>

<h2 id="implementation-choices">Implementation Choices</h2>
<p>Beyond the PRNG, an additional design choice limits DPE’s ability to limit fingerprinting via the <code class="language-plaintext highlighter-rouge">AudioBuffer</code> API due to the amount of unprotected data.</p>

<h3 id="perturbation-ratio">Perturbation ratio</h3>
<p>As an implementation choice, the <code class="language-plaintext highlighter-rouge">iterateDataKey</code> function (linked above) does not increase the number of perturbed elements with the length of the <code class="language-plaintext highlighter-rouge">AudioBuffer</code>. Instead, at most <code class="language-plaintext highlighter-rouge">8 * key.length</code> elements are perturbed. In practice, the actual number is lower due to 1) duplicate elements chosen by the PRNG (discussed above), and 2) <code class="language-plaintext highlighter-rouge">byte</code> having at most seven ASCII bits<sup id="fnref:ascii-7b" role="doc-noteref"><a href="#fn:ascii-7b" class="footnote" rel="footnote">1</a></sup>.</p>

<p>As the length of the audio buffer grows, the ratio of perturbed to unperturbed elements falls. This makes it easier to extract unprotected fingerprint data from longer audio buffers.</p>

<p><strong>Recommendation 3:</strong> Scale perturbation with the length of the buffer so that longer buffers do not yield a greater chance of selecting unperturbed items. If performance constraints allow, perturbing all elements in the buffer is preferable.</p>

<h3 id="boolean-logic">Boolean Logic</h3>
<p>In the following code, <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/918c48c6db17f09135d375c5d2fa91280f019777/shared/js/content-scope/fingerprintingAudio-protection.js#L26"><code class="language-plaintext highlighter-rouge">byte ^ 0x1</code></a> is always <code class="language-plaintext highlighter-rouge">true</code> except when <code class="language-plaintext highlighter-rouge">byte</code> is zero, in which case, the index is perturbed by adding zero (i.e., not perturbed). Perhaps <code class="language-plaintext highlighter-rouge">byte &amp; 0x1</code> was intended instead. Currently, this has the effect that all perturbations are subtractions which further limits the randomness of perturbations applied.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">iterateDataKey</span><span class="p">(</span><span class="nx">audioKey</span><span class="p">,</span> <span class="p">(</span><span class="nx">item</span><span class="p">,</span> <span class="nx">byte</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">itemAudioIndex</span> <span class="o">=</span> <span class="nx">item</span> <span class="o">%</span> <span class="nx">channelData</span><span class="p">.</span><span class="nx">length</span>

    <span class="kd">let</span> <span class="nx">factor</span> <span class="o">=</span> <span class="nx">byte</span> <span class="o">*</span> <span class="mf">0.0000001</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">^</span> <span class="mh">0x1</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">factor</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">-</span> <span class="nx">factor</span>
    <span class="p">}</span>
    <span class="nx">channelData</span><span class="p">[</span><span class="nx">itemAudioIndex</span><span class="p">]</span> <span class="o">=</span> <span class="nx">channelData</span><span class="p">[</span><span class="nx">itemAudioIndex</span><span class="p">]</span> <span class="o">+</span> <span class="nx">factor</span>
<span class="p">})</span>
</code></pre></div></div>

<h2 id="practical-attacks">Practical Attacks</h2>
<p>Due to the issues outlined above, the several practical attacks become possible. Fixing these issues should significantly mitigate the following attacks.</p>

<p>As a general theme, the following attacks allow reversing or bypassing the anti-fingerprinting techniques applied by the extension. At least some fingerprinting tools have expressed interest in reversing anti-fingerprinting protections (see section <a href="https://fingerprintjs.com/blog/audio-fingerprinting/">“Reverting Brave standard farbling”</a>).</p>

<h3 id="pre-computed-perturbation-set">Pre-computed perturbation set</h3>
<p>If the fingerprinting software relies on an <code class="language-plaintext highlighter-rouge">AudioBuffer</code> of known length, it is possible to pre-compute the set of indexes which may be perturbed over all PRNG seeds. (In fact, the computation is fast enough to be computed dynamically if variable buffer lengths are used.) Once the set is computed, the fingerprinting software may choose to ignore those indexes when generating the fingerprint, regardless of the extension’s presence. This makes the fingerprints comparable across all browsers.</p>

<p>The limited entropy in the seed means there are only sixteen possible sequences of indexes to ignore. And due to the poor performance of the PRNG, those sequences share significant overlap.</p>

<p>The following table shows the percent of items that <em>could</em> be perturbed without <em>a priori</em> knowledge of the PRNG seed. The listed percentage represents the amount of data in the <code class="language-plaintext highlighter-rouge">AudioBuffer</code> that is perturbed by at least one of the 16 possible seed values. Even with a short buffer length (such as <code class="language-plaintext highlighter-rouge">n=64</code>), approximately 50% of the buffer’s indexes are unperturbed under all seed values, giving plenty of unprotected values from which a fingerprint might be extracted. Modestly longer buffer lengths have even less protection.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>length=64  percent=0.515625
length=128  percent=0.335938
length=192  percent=0.333333
length=256  percent=0.218750
length=320  percent=0.246875
length=384  percent=0.205729
length=448  percent=0.194196
length=512  percent=0.132812
length=576  percent=0.166667
length=640  percent=0.151562
length=704  percent=0.144886
length=768  percent=0.119792
length=832  percent=0.123798
length=896  percent=0.116071
length=960  percent=0.111458
length=1024  percent=0.076172
length=1088  percent=0.103860
length=1152  percent=0.096354
length=1216  percent=0.092928
length=1280  percent=0.089063
length=1344  percent=0.087798
length=1408  percent=0.081676
length=1472  percent=0.081522
length=1536  percent=0.066406
length=1600  percent=0.076875
length=1664  percent=0.069712
length=1728  percent=0.076968
length=1792  percent=0.066964
length=1856  percent=0.068427
length=1920  percent=0.063542
length=1984  percent=0.064516
length=2048  percent=0.042480
</code></pre></div></div>

<p>To give a specific example, a buffer of length <code class="language-plaintext highlighter-rouge">n=64</code> will have the following indexes perturbed by the extension depending on the input key:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="p">{</span><span class="err">seed:</span><span class="w"> </span><span class="p">[</span><span class="err">indexes</span><span class="p">]}</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"48"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">48</span><span class="p">,</span><span class="w"> </span><span class="mi">58</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"49"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">49</span><span class="p">,</span><span class="w"> </span><span class="mi">55</span><span class="p">,</span><span class="w"> </span><span class="mi">58</span><span class="p">,</span><span class="w"> </span><span class="mi">59</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"50"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">35</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"51"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"52"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">38</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span><span class="w"> </span><span class="mi">55</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"53"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="mi">23</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">38</span><span class="p">,</span><span class="w"> </span><span class="mi">43</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">53</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"54"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="mi">23</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">43</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">54</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"55"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">55</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"56"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">28</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">56</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"57"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">28</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">55</span><span class="p">,</span><span class="w"> </span><span class="mi">57</span><span class="p">,</span><span class="w"> </span><span class="mi">59</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"97"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">33</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">48</span><span class="p">,</span><span class="w"> </span><span class="mi">55</span><span class="p">,</span><span class="w"> </span><span class="mi">58</span><span class="p">,</span><span class="w"> </span><span class="mi">59</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"98"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">34</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"99"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">35</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"100"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">14</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">36</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"101"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="mi">14</span><span class="p">,</span><span class="w"> </span><span class="mi">23</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">37</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">],</span><span class="w">
  </span><span class="nl">"102"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="mi">23</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">38</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Taking the union of indexes for each key, the following list of indexes could be ignored when generating a fingerprint to avoid accessing data modified by DPE:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="p">[</span><span class="err">indexes</span><span class="p">]</span><span class="w">
</span><span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="mi">14</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="mi">23</span><span class="p">,</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="mi">28</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="mi">33</span><span class="p">,</span><span class="w"> </span><span class="mi">34</span><span class="p">,</span><span class="w"> </span><span class="mi">35</span><span class="p">,</span><span class="w"> </span><span class="mi">36</span><span class="p">,</span><span class="w"> </span><span class="mi">37</span><span class="p">,</span><span class="w"> </span><span class="mi">38</span><span class="p">,</span><span class="w"> </span><span class="mi">39</span><span class="p">,</span><span class="w"> </span><span class="mi">43</span><span class="p">,</span><span class="w"> </span><span class="mi">47</span><span class="p">,</span><span class="w"> </span><span class="mi">48</span><span class="p">,</span><span class="w"> </span><span class="mi">49</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">51</span><span class="p">,</span><span class="w"> </span><span class="mi">52</span><span class="p">,</span><span class="w"> </span><span class="mi">53</span><span class="p">,</span><span class="w"> </span><span class="mi">54</span><span class="p">,</span><span class="w"> </span><span class="mi">55</span><span class="p">,</span><span class="w"> </span><span class="mi">56</span><span class="p">,</span><span class="w"> </span><span class="mi">57</span><span class="p">,</span><span class="w"> </span><span class="mi">58</span><span class="p">,</span><span class="w"> </span><span class="mi">59</span><span class="p">,</span><span class="w"> </span><span class="mi">63</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<h2 id="recovery-of-prng-state">Recovery of PRNG state</h2>
<p>Using the “Sample pre-computed perturbation set” table above, it is possible to reverse the set of perturbed elements to the seed used by the PRNG. The following code snippet recovers the seed by observing the perturbations in the audio buffer. (Note, while this example initializes the buffer to a known state, this attack can be performed on any buffer where the sum of all elements is <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/918c48c6db17f09135d375c5d2fa91280f019777/shared/js/content-scope/fingerprintingAudio-protection.js#L16">not zero</a>).</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">extractAudioKey</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// Reverse table for length=64</span>
  <span class="kd">const</span> <span class="nx">revtable</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">3,7,12,15,24,31,48,58,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">48</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,12,15,24,31,47,49,55,58,59,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">49</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,15,31,35,39,50,51,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">50</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,15,31,39,51,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">51</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,27,31,38,47,51,52,55,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">52</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,11,23,27,31,38,43,47,51,53,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">53</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,11,23,27,31,43,47,51,54,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">54</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,27,31,47,51,55,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">55</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,15,28,31,50,56,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">56</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,15,28,31,47,50,55,57,59,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">57</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,12,15,24,31,33,47,48,55,58,59,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">97</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,15,31,34,39,51,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">98</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,15,31,35,39,51,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">99</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,14,15,31,36,39,51,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,11,14,23,27,31,37,39,47,51,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">101</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">3,7,11,23,27,31,38,39,47,51,63</span><span class="dl">"</span><span class="p">:</span> <span class="mi">102</span>
  <span class="p">};</span>

  <span class="kd">var</span> <span class="nx">ab</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AudioBuffer</span><span class="p">({</span><span class="na">length</span><span class="p">:</span> <span class="mi">64</span><span class="p">,</span> <span class="na">numberOfChannels</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">sampleRate</span><span class="p">:</span> <span class="mi">48000</span><span class="p">});</span>
  <span class="kd">var</span> <span class="nx">abb</span> <span class="o">=</span> <span class="nx">ab</span><span class="p">.</span><span class="nx">getChannelData</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

  <span class="c1">// Initialize the buffer to a known value</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">abb</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">abb</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// Read back the data, which will be perturbed by DDG</span>
  <span class="kd">var</span> <span class="nx">abb</span> <span class="o">=</span> <span class="nx">ab</span><span class="p">.</span><span class="nx">getChannelData</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

  <span class="c1">// Find perturbed elements.</span>
  <span class="kd">var</span> <span class="nx">perturbed</span> <span class="o">=</span> <span class="p">[];</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span><span class="nx">In</span> <span class="nx">pra</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">abb</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">abb</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">abb</span><span class="p">[</span><span class="nx">i</span><span class="p">],</span> <span class="nx">abb</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span>
      <span class="nx">perturbed</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="c1">// Lookup the perturbation set in `revtable`</span>
  <span class="kd">var</span> <span class="nx">lookup</span> <span class="o">=</span> <span class="nx">perturbed</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="p">);</span>
  <span class="k">return</span> <span class="nx">revtable</span><span class="p">[</span><span class="nx">lookup</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Due to how the cache is linked with the <code class="language-plaintext highlighter-rouge">AudioBuffer</code> instance, it is possible to discover the seed <em>after</em> having read the perturbed data.</p>

<p><a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/918c48c6db17f09135d375c5d2fa91280f019777/shared/js/content-scope/fingerprintingAudio-protection.js#L59-L70">Source</a></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">getCachedResponse</span> <span class="p">(</span><span class="nx">thisArg</span><span class="p">,</span> <span class="nx">args</span><span class="p">)</span> <span class="p">{</span> 
    <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">cacheData</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">thisArg</span><span class="p">)</span> 
    <span class="kd">const</span> <span class="nx">timeNow</span> <span class="o">=</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> 
    <span class="k">if</span> <span class="p">(</span><span class="nx">data</span> <span class="o">&amp;&amp;</span> 
        <span class="nx">data</span><span class="p">.</span><span class="nx">args</span> <span class="o">===</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="o">&amp;&amp;</span> 
        <span class="nx">data</span><span class="p">.</span><span class="nx">expires</span> <span class="o">&gt;</span> <span class="nx">timeNow</span><span class="p">)</span> <span class="p">{</span> 
        <span class="nx">data</span><span class="p">.</span><span class="nx">expires</span> <span class="o">=</span> <span class="nx">timeNow</span> <span class="o">+</span> <span class="nx">cacheExpiry</span> 
        <span class="nx">cacheData</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">thisArg</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span> 
        <span class="k">return</span> <span class="nx">data</span> 
    <span class="p">}</span> 
    <span class="k">return</span> <span class="p">{</span> <span class="na">audioKey</span><span class="p">:</span> <span class="kc">null</span> <span class="p">}</span> 
<span class="p">}</span> 
</code></pre></div></div>

<p>Once the seed is known, there are a few possible uses:</p>
<ul>
  <li>The seed is four bits of entropy which can be used to fingerprint a DuckDuckGo extension user’s session. This value is derived from the user’s session key and therefore cannot be used to track a user across sessions, but could track the user within a given session even if other data (such as cookies) have been cleared.</li>
  <li>The seed determines which indexes are perturbed and can be used to target specific indexes for exclusion from a fingerprint. This is more targeted than the “pre-computed perturbation set” described above, allowing for recovery of additional unperturbed data from a smaller buffer.</li>
  <li>The seed (which is also the first four bits of the <code class="language-plaintext highlighter-rouge">audioKey</code>) fully determines the perturbation applied to several indexes in the buffer. Knowing the seed may allow the perturbation to be reversed. The remainder of this section is dedicated to this case.</li>
</ul>

<p>Knowing the seed allows determination of the perturbation applied to several indexes within the buffer. The amount of perturbation is determined by a byte (hex digit) of the key and the iteration of the perturbation process. Because the first byte of the key is used eight times (bit-shifted once each time), it is possible to compute the perturbation that was applied. In this instance, the non-random PRNG output provides a degree of protection by perturbing most values many, many times with a different portion of the key. However, the first few PRNG outputs are sufficiently unique to only appear once for several seeds.</p>

<p>This green cells in this table show the indexes perturbed only once, depending on the seed. All such indexes are perturbed using the first byte of the key - which is known during this attack. Therefore, the perturbations are reversible (to the degree allowed by floating point precision).</p>

<p><img src="/assets/blog/prng-analysis.png" alt="PRNG state recovery allows determination of value shift. Diagram displays a table of AudioBuffer offsets by PRNG seeds. It indicates which values are perturbed only once for a given seed." /></p>

<p>In practice, I believe there are simpler methods to approximate the original values. However, they are not related to the PRNG and are therefore not discussed here.</p>

<h2 id="footnotes">Footnotes</h2>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:ascii-7b" role="doc-endnote">
      <p>It may be preferable to access raw bytes from the hash or to parse hex digits into longer integers. Direct operation on the hex ASCII codes provides at most 4-bits of entropy encoded in 7-bit ASCII. This results in potential weakness <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/918c48c6db17f09135d375c5d2fa91280f019777/shared/js/content-scope/utils.js#L74">here</a>. <a href="#fnref:ascii-7b" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><category term="audit" /><category term="privacy" /><summary type="html"><![CDATA[Analysis of the DuckDuckGo extension’s protection of the Audio API. A broader look at the extension’s privacy protections is in part 1.]]></summary></entry><entry><title type="html">Technical Analysis of DuckDuckGo Privacy Essentials (part 1)</title><link href="https://austinhartzheim.me/blog/2021/06/27/ddg-technical-analysis-part-1.html" rel="alternate" type="text/html" title="Technical Analysis of DuckDuckGo Privacy Essentials (part 1)" /><published>2021-06-27T23:37:17-05:00</published><updated>2021-07-07T13:30:24-05:00</updated><id>https://austinhartzheim.me/blog/2021/06/27/ddg-technical-analysis-part-1</id><content type="html" xml:base="https://austinhartzheim.me/blog/2021/06/27/ddg-technical-analysis-part-1.html"><![CDATA[<p><em>A technical audit of the DuckDuckGo Privacy Essentials addon.</em></p>

<p>The DuckDuckGo Privacy Essentials addon is a browser addon available for the Firefox and Chrome browsers. The addon lists <a href="https://addons.mozilla.org/en-US/firefox/addon/duckduckgo-for-firefox/">several features</a> including blocking trackers, private search, encryption, privacy grades, and support for Global Privacy Control. It is the 7th most popular addon for Firefox<sup id="fnref:firefox-data-dashboard-usage" role="doc-noteref"><a href="#fn:firefox-data-dashboard-usage" class="footnote" rel="footnote">1</a></sup> with approximately 1.5 million users.</p>

<p>While the addon’s name clearly implies privacy-related functionality, its technical measures remain undocumented. The objective of this post is to analyze the anti-fingerprinting functionality of the addon for ease of comparison against alternative privacy solutions.</p>

<h2 id="scope">Scope</h2>
<p>At the time of writing, DuckDuckGo Privacy Essentials (DPE) has 25 released versions on <a href="https://addons.mozilla.org/en-US/firefox/addon/duckduckgo-for-firefox/versions/">addons.mozilla.org</a>, with the first version released August 21, 2019.</p>

<p>This post will only discuss version <code class="language-plaintext highlighter-rouge">2021.6.2</code> of the addon. The addon’s XPI file was <a href="https://addons.mozilla.org/firefox/downloads/file/3788189/duckduckgo_privacy_essentials-2021.6.2-an+fx.xpi">downloaded</a> from the Mozilla addons site, and was found to have a SHA256 hash of <code class="language-plaintext highlighter-rouge">510c4e7b7e720ccb5ee0eec78c498dee5c012a5939b8b0706b7a195377876fa2</code>. Based on the tags from the addon’s Git repository, the commit hash corresponding to this version is <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/tree/bcef1583085ecbfdb5a3a52c12538ea949e8a65e"><code class="language-plaintext highlighter-rouge">bcef1583085ecbfdb5a3a52c12538ea949e8a65e</code></a>.</p>

<p>All analysis and testing in this report is focused on Firefox for Desktop. Analysis of other browsers - including the Firefox mobile browsers - is out of scope for this post. For “part one” of this series, we limit our analysis to the anti-fingerprinting functionality found in <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/tree/bcef1583085ecbfdb5a3a52c12538ea949e8a65e/shared/js/content-scope">./shared/js/content-scope/</a>.</p>

<h2 id="anti-fingerprinting">Anti-fingerprinting</h2>

<h3 id="audio-api">Audio API</h3>
<p>DPE intercepts calls to several methods in the audio API. Specifically:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">AudioBuffer.getChannelData</code></li>
  <li><code class="language-plaintext highlighter-rouge">AnalyserNode.getByteTimeDomainData</code></li>
  <li><code class="language-plaintext highlighter-rouge">AnalyserNode.getByteTimeDomainData</code></li>
  <li><code class="language-plaintext highlighter-rouge">AnalyserNode.getFloatTimeDomainData</code></li>
  <li><code class="language-plaintext highlighter-rouge">AnalyserNode.getByteFrequencyData</code></li>
  <li><code class="language-plaintext highlighter-rouge">AnalyserNode.getFloatFrequencyData</code></li>
</ul>

<p>The addon modifies the return values of these methods by adding deterministic noise to the <code class="language-plaintext highlighter-rouge">channelData</code> buffer based on the value of <code class="language-plaintext highlighter-rouge">audioKey</code>:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">iterateDataKey</span><span class="p">(</span><span class="nx">audioKey</span><span class="p">,</span> <span class="p">(</span><span class="nx">item</span><span class="p">,</span> <span class="nx">byte</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">itemAudioIndex</span> <span class="o">=</span> <span class="nx">item</span> <span class="o">%</span> <span class="nx">channelData</span><span class="p">.</span><span class="nx">length</span>
    <span class="kd">let</span> <span class="nx">factor</span> <span class="o">=</span> <span class="nx">byte</span> <span class="o">*</span> <span class="mf">0.0000001</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">^</span> <span class="mh">0x1</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">factor</span> <span class="o">=</span> <span class="mi">0</span> <span class="o">-</span> <span class="nx">factor</span>
    <span class="p">}</span>
    <span class="nx">channelData</span><span class="p">[</span><span class="nx">itemAudioIndex</span><span class="p">]</span> <span class="o">=</span> <span class="nx">channelData</span><span class="p">[</span><span class="nx">itemAudioIndex</span><span class="p">]</span> <span class="o">+</span> <span class="nx">factor</span>
<span class="p">})</span>
</code></pre></div></div>

<p>In an attempt to limit tracking across sites, <code class="language-plaintext highlighter-rouge">audioKey</code> is a hash including the domain of the site. This should imply that a user’s fingerprint will not be shared across domains and therefore cannot be used for cross-site tracking. Further analysis is required to determine if this noise is sufficient to mask the underlying fingerprint. The key is generated as follows:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">getDataKeySync</span> <span class="p">(</span><span class="nx">sessionKey</span><span class="p">,</span> <span class="nx">domainKey</span><span class="p">,</span> <span class="nx">inputData</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">hmac</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">sjcl</span><span class="p">.</span><span class="nx">misc</span><span class="p">.</span><span class="nx">hmac</span><span class="p">(</span><span class="nx">sjcl</span><span class="p">.</span><span class="nx">codec</span><span class="p">.</span><span class="nx">utf8String</span><span class="p">.</span><span class="nx">toBits</span><span class="p">(</span><span class="nx">sessionKey</span> <span class="o">+</span> <span class="nx">domainKey</span><span class="p">),</span> <span class="nx">sjcl</span><span class="p">.</span><span class="nx">hash</span><span class="p">.</span><span class="nx">sha256</span><span class="p">)</span>
    <span class="k">return</span> <span class="nx">sjcl</span><span class="p">.</span><span class="nx">codec</span><span class="p">.</span><span class="nx">hex</span><span class="p">.</span><span class="nx">fromBits</span><span class="p">(</span><span class="nx">hmac</span><span class="p">.</span><span class="nx">encrypt</span><span class="p">(</span><span class="nx">inputData</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="canvas-api">Canvas API</h3>
<p>DPE intercepts calls to several methods in the canvas API:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">CanvasRenderingContext2D.getImageData</code></li>
  <li><code class="language-plaintext highlighter-rouge">HTMLCanvasElement.toDataURL</code></li>
  <li><code class="language-plaintext highlighter-rouge">HTMLCanvasElement.toBlob</code></li>
</ul>

<p>Similar to the audio API protection, canvas data is also modified based on a domain-specific key to limit the ability to track users across sites:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">iterateDataKey</span><span class="p">(</span><span class="nx">canvasKey</span><span class="p">,</span> <span class="p">(</span><span class="nx">item</span><span class="p">,</span> <span class="nx">byte</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">channel</span> <span class="o">=</span> <span class="nx">byte</span> <span class="o">%</span> <span class="mi">3</span>
    <span class="kd">const</span> <span class="nx">lookupId</span> <span class="o">=</span> <span class="nx">item</span> <span class="o">%</span> <span class="nx">length</span>
    <span class="kd">const</span> <span class="nx">pixelCanvasIndex</span> <span class="o">=</span> <span class="nx">arr</span><span class="p">[</span><span class="nx">lookupId</span><span class="p">]</span> <span class="o">+</span> <span class="nx">channel</span>

    <span class="nx">imageData</span><span class="p">.</span><span class="nx">data</span><span class="p">[</span><span class="nx">pixelCanvasIndex</span><span class="p">]</span> <span class="o">=</span> <span class="nx">imageData</span><span class="p">.</span><span class="nx">data</span><span class="p">[</span><span class="nx">pixelCanvasIndex</span><span class="p">]</span> <span class="o">^</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">&amp;</span> <span class="mh">0x1</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>

<h3 id="do-not-track-api">Do Not Track API</h3>

<table>
  <thead>
    <tr>
      <th>JS Variable</th>
      <th>Default Value</th>
      <th>Addon Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigator.doNotTrack</code><sup id="fnref:dnt-navigator" role="doc-noteref"><a href="#fn:dnt-navigator" class="footnote" rel="footnote">2</a></sup></td>
      <td><code class="language-plaintext highlighter-rouge">"1"</code> if DNT enabled, <code class="language-plaintext highlighter-rouge">"unspecified"</code> otherwise.</td>
      <td><code class="language-plaintext highlighter-rouge">"unspecified"</code></td>
    </tr>
  </tbody>
</table>

<p>DPE unsets <code class="language-plaintext highlighter-rouge">navigator.doNotTrack</code>, but does not also unset the <code class="language-plaintext highlighter-rouge">DNT</code> header. If a fingerprint includes both the header and DOM values, this behavior may make a user of the addon more unique compared to the global population.</p>

<p class="notice--success"><strong>NOTE:</strong> In response to this post, a future release is expected to stop removing <code class="language-plaintext highlighter-rouge">navigator.doNotTrack</code>.<sup id="fnref:update-dnt" role="doc-noteref"><a href="#fn:update-dnt" class="footnote" rel="footnote">3</a></sup></p>

<h3 id="global-privacy-control">Global Privacy Control</h3>
<p>The DuckDuckGo Privacy Essentials addon has a feature titled Global Privacy Control (GPC). On a technical level, GPC is a <a href="https://globalprivacycontrol.github.io/gpc-spec/">draft specification</a> for communicating privacy preferences via HTTP headers and JavaScript properties.</p>

<table>
  <thead>
    <tr>
      <th>Header Name</th>
      <th>Default Value</th>
      <th>Addon Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Sec-GPC</code> <sup id="fnref:gpc-header" role="doc-noteref"><a href="#fn:gpc-header" class="footnote" rel="footnote">4</a></sup></td>
      <td>not present</td>
      <td><code class="language-plaintext highlighter-rouge">1</code> when GPC is enabled, not present otherwise</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>JS Variable</th>
      <th>Default Value</th>
      <th>Addon Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigator.globalPrivacyControl</code> <sup id="fnref:gpc-navigator" role="doc-noteref"><a href="#fn:gpc-navigator" class="footnote" rel="footnote">5</a></sup></td>
      <td><code class="language-plaintext highlighter-rouge">undefined</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code> when GPC is enabled, <code class="language-plaintext highlighter-rouge">false</code> otherwise</td>
    </tr>
  </tbody>
</table>

<p>It is interesting that DPE disables <code class="language-plaintext highlighter-rouge">navigator.doNotTrack</code> but specifically adds the <code class="language-plaintext highlighter-rouge">navigator.globalPrivacyControl</code> field and <code class="language-plaintext highlighter-rouge">Sec-GPC</code> header. Do Not Track and Global Privacy Control are competing specifications for communicating tracking preferences; DuckDuckGo was involved in creating the draft specification for GPC, which may indicate the reason for this preference. Still, it seems like this behavior should be documented as it may interfere with the user’s expectations when enabling Do Not Track.</p>

<p>DPE allows the presence of the GPC header to be toggled by the user via the addon’s settings. However, <code class="language-plaintext highlighter-rouge">navigator.globalPrivacyControl</code> is always present and indicates the user’s configuration of the addon. Beyond allowing fingerprinting of the addon’s presence, this allows fingerprinting based on the addon’s configuration.</p>

<p>The value of <code class="language-plaintext highlighter-rouge">navigator.globalPrivacyControl</code> is computed as follows:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// If GPC on, set DOM property to true if not already true</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">args</span><span class="p">.</span><span class="nx">globalPrivacyControlValue</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nb">navigator</span><span class="p">.</span><span class="nx">globalPrivacyControl</span><span class="p">)</span> <span class="k">return</span>
    <span class="nx">defineProperty</span><span class="p">(</span><span class="nb">navigator</span><span class="p">,</span> <span class="dl">'</span><span class="s1">globalPrivacyControl</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">value</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
        <span class="na">enumerable</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">})</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="c1">// If GPC off, set DOM property prototype to false so it may be overwritten</span>
    <span class="c1">// with a true value by user agent or other extensions</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nb">navigator</span><span class="p">.</span><span class="nx">globalPrivacyControl</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">undefined</span><span class="dl">'</span><span class="p">)</span> <span class="k">return</span>
    <span class="nx">defineProperty</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">getPrototypeOf</span><span class="p">(</span><span class="nb">navigator</span><span class="p">),</span> <span class="dl">'</span><span class="s1">globalPrivacyControl</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">value</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
        <span class="na">enumerable</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">})</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="referer">Referer</h3>

<table>
  <thead>
    <tr>
      <th>JS Variable</th>
      <th>Default Value</th>
      <th>Addon Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">document.referrer</code> (external link)</td>
      <td><code class="language-plaintext highlighter-rouge">https://example.com/index.html</code> depending on the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy"><code class="language-plaintext highlighter-rouge">Referrer-Policy</code></a></td>
      <td><code class="language-plaintext highlighter-rouge">https://example.com/</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">document.referrer</code> (internal link)</td>
      <td><code class="language-plaintext highlighter-rouge">https://example.com/index.html</code> depending on the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy"><code class="language-plaintext highlighter-rouge">Referrer-Policy</code></a></td>
      <td><code class="language-plaintext highlighter-rouge">https://example.com/index.html</code> depending on the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy"><code class="language-plaintext highlighter-rouge">Referrer-Policy</code></a></td>
    </tr>
  </tbody>
</table>

<p>In the absence of a <code class="language-plaintext highlighter-rouge">Referrer-Policy</code> header, Firefox defaults to stripping path information from the <code class="language-plaintext highlighter-rouge">Referer</code> header for external links. However, certain <code class="language-plaintext highlighter-rouge">Referrer-Policy</code> configurations, such as <code class="language-plaintext highlighter-rouge">unsafe-url</code>, will cause the full path to be sent.</p>

<p>DPE specifically avoids modification of <code class="language-plaintext highlighter-rouge">document.referrer</code> on internal links (i.e., links between pages on the same host) using the following condition:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">URL</span><span class="p">).</span><span class="nx">hostname</span> <span class="o">!==</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">referrer</span><span class="p">).</span><span class="nx">hostname</span>
</code></pre></div></div>

<h3 id="hardware-apis">Hardware APIs</h3>

<table>
  <thead>
    <tr>
      <th>JS Variable</th>
      <th>Default Value</th>
      <th>Addon Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigator.keyboard</code></td>
      <td><code class="language-plaintext highlighter-rouge">undefined</code></td>
      <td><code class="language-plaintext highlighter-rouge">undefined</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigator.hardwareConcurrency</code></td>
      <td><code class="language-plaintext highlighter-rouge">2</code> if <code class="language-plaintext highlighter-rouge">privacy.resistFingerprinting</code> is enabled, or the accurate number of cores up to <code class="language-plaintext highlighter-rouge">dom.maxHardwareConcurrency</code> otherwise.<sup id="fnref:ff-max-concurrency" role="doc-noteref"><a href="#fn:ff-max-concurrency" class="footnote" rel="footnote">6</a></sup></td>
      <td><code class="language-plaintext highlighter-rouge">8</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigator.deviceMemory</code></td>
      <td><code class="language-plaintext highlighter-rouge">undefined</code></td>
      <td><code class="language-plaintext highlighter-rouge">undefined</code></td>
    </tr>
  </tbody>
</table>

<p>The choice of <code class="language-plaintext highlighter-rouge">8</code> for <code class="language-plaintext highlighter-rouge">navigator.hardwareConcurrency</code> may make a user more unique. According to the <a href="https://data.firefox.com/dashboard/hardware">Firefox Public Data Report</a> for June 14th, 2021, 52% of Firefox users have two physical cores, followed by 34% with four cores. Only 3% reported having eight physical cores. Firefox’s own “resist fingerprinting” functionality spoofs <code class="language-plaintext highlighter-rouge">navigator.hardwareConcurrency</code> to <code class="language-plaintext highlighter-rouge">2</code> to make users appear less unique compared to the global population.</p>

<p>It is possible that the addon creators are attempting to mitigate potential performance impacts. If a webpage uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">workers</a> for compute-intensive tasks, the webpage may throttle performance by letting CPU capacity go unused. However, spoofing additional cores may also cause a webpage to spawn more than there are available cores, which may also have deleterious performance impact.</p>

<p class="notice--success"><strong>NOTE:</strong> In response to this post, a future release of DPE is expected to set <code class="language-plaintext highlighter-rouge">navigator.hardwareConcurrency</code> to <code class="language-plaintext highlighter-rouge">2</code>.<sup id="fnref:update-hardware-concurrency" role="doc-noteref"><a href="#fn:update-hardware-concurrency" class="footnote" rel="footnote">7</a></sup></p>

<h3 id="screen-size">Screen Size</h3>

<table>
  <thead>
    <tr>
      <th>JS Variable</th>
      <th>Default Behavior</th>
      <th>Addon Behavior</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">screen.availWidth</code>, <code class="language-plaintext highlighter-rouge">screen.availHeight</code></td>
      <td>Displays the amount of available space on the screen, minus any OS UI elements.</td>
      <td>Displays the total height and width of the screen.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">screen.availTop</code>, <code class="language-plaintext highlighter-rouge">screen.availLeft</code></td>
      <td>Displays the amount of space dedicated to OS UI elements in pixels.</td>
      <td>Always <code class="language-plaintext highlighter-rouge">0</code>.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">screen.colorDepth</code>, <code class="language-plaintext highlighter-rouge">screen.pixelDepth</code></td>
      <td>Displays the actual color/pixel depth.</td>
      <td>Always <code class="language-plaintext highlighter-rouge">24</code>.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">window.screenX</code>, <code class="language-plaintext highlighter-rouge">window.screenLeft</code>, <code class="language-plaintext highlighter-rouge">window.screenY</code>, <code class="language-plaintext highlighter-rouge">window.screenTop</code></td>
      <td>Displays the position of the window across all monitors. Values update when the window is moved.</td>
      <td>Maps the window position onto a single monitor to prevent calculation of monitor count and positioning. The values are updated when the window is resized, but not when the window is moved.</td>
    </tr>
  </tbody>
</table>

<p>Overall, DPE prevents observation of window movements and total monitor count. The position of the window on the screen remains static whereas the default behavior is to update those parameters in realtime. Additionally, since certain OS-level UI elements are only present on some monitors, movement of the window across monitors cannot be detected in this way. DPE does not hide monitor resolution, which may be somewhat unique, especially on mobile browsers and monitors with special form factors (ultra-wide, HDPI, etc.). The <a href="https://data.firefox.com/dashboard/hardware">Firefox Public Data Report</a> indicates that the two most popular display resolutions are <code class="language-plaintext highlighter-rouge">1920x1080</code> (38%) and <code class="language-plaintext highlighter-rouge">1366x768</code> (26%).</p>

<h3 id="non-firefox-apis">Non-Firefox APIs</h3>
<p>Browsers often offer experimental or non-standard APIs. Additionally, not all browsers conform with all released standards. The addon has anti-fingerprinting protection for the following APIs which are not supported in Firefox and are therefore not analyzed here:</p>

<ul>
  <li>Floc API (<code class="language-plaintext highlighter-rouge">Document.prototype.interestCohort</code>)</li>
  <li>Battery API (<code class="language-plaintext highlighter-rouge">navigator.getBattery()</code>)</li>
  <li>Temporary Storage API (<code class="language-plaintext highlighter-rouge">navigator.webkitTemporaryStorage</code>)</li>
</ul>

<h2 id="future-work">Future Work</h2>
<ul>
  <li>Evaluate data shared with DuckDuckGo servers during/after the installation of the addon.</li>
  <li>Evaluate the efficacy of the above anti-fingerprinting techniques. For example, whether it is possible to amplify the signal of the audio API fingerprinting techniques to overcome the injected noise.</li>
  <li>Analyze how the DPE Privacy Grade is computed and whether external servers are accessed in computing the grade.</li>
  <li>Assess the reproducibility of the published XPI file from the addon’s source code.</li>
  <li>Analyze the tracker blocking functionality, including cookie handling for known tracker domains.</li>
</ul>

<h2 id="updates">Updates</h2>
<ul>
  <li>2020-07-07:
    <ul>
      <li>Added notes indicating that a future release of DPE will stop removing <code class="language-plaintext highlighter-rouge">navigator.doNotTrack</code> and will set <code class="language-plaintext highlighter-rouge">navigator.hardwareConcurrency</code> to <code class="language-plaintext highlighter-rouge">2</code>. Thanks to the DuckDuckGo team for their communication and quick response!</li>
      <li>Corrected a spelling error.</li>
    </ul>
  </li>
</ul>

<h2 id="footnotes">Footnotes</h2>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:firefox-data-dashboard-usage" role="doc-endnote">
      <p><a href="https://data.firefox.com/dashboard/usage-behavior">https://data.firefox.com/dashboard/usage-behavior</a> <a href="#fnref:firefox-data-dashboard-usage" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:dnt-navigator" role="doc-endnote">
      <p><code class="language-plaintext highlighter-rouge">navigator.doNotTrack</code> referenecs: <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/bcef1583085ecbfdb5a3a52c12538ea949e8a65e/shared/js/content-scope/do-not-track-protection.js#L4-L8">source</a> <a href="#fnref:dnt-navigator" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:update-dnt" role="doc-endnote">
      <p><a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/pull/720">https://github.com/duckduckgo/duckduckgo-privacy-extension/pull/720</a> <a href="#fnref:update-dnt" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:gpc-header" role="doc-endnote">
      <p><code class="language-plaintext highlighter-rouge">Sec-GPC</code> references: <a href="https://globalprivacycontrol.github.io/gpc-spec/#the-sec-gpc-header-field-for-http-requests">header specification</a>, <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/bcef1583085ecbfdb5a3a52c12538ea949e8a65e/shared/js/background/GPC.es6.js#L11-L16">source</a> <a href="#fnref:gpc-header" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:gpc-navigator" role="doc-endnote">
      <p><code class="language-plaintext highlighter-rouge">navigator.globalPrivacyControl</code> references: <a href="https://globalprivacycontrol.github.io/gpc-spec/#javascript-property-to-detect-preference">property specification</a>, <a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/blob/bcef1583085ecbfdb5a3a52c12538ea949e8a65e/shared/js/content-scope/gpc-protection.js#L6-L21">source</a> <a href="#fnref:gpc-navigator" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ff-max-concurrency" role="doc-endnote">
      <p><a href="https://hg.mozilla.org/mozilla-central/file/41d54fa2f39ceb56d13baec5398f4a97e8626101/dom/workers/RuntimeService.cpp#l2065"><code class="language-plaintext highlighter-rouge">RuntimeService::ClampedHardwareConcurrency</code></a>, <a href="https://hg.mozilla.org/mozilla-central/file/41d54fa2f39ceb56d13baec5398f4a97e8626101/dom/workers/RuntimeService.cpp#l1543"><code class="language-plaintext highlighter-rouge">gMaxHardwareConcurrency</code></a>, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1360039">Bugzilla: spoof navigator.hardwareConcurrency</a> <a href="#fnref:ff-max-concurrency" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:update-hardware-concurrency" role="doc-endnote">
      <p><a href="https://github.com/duckduckgo/duckduckgo-privacy-extension/pull/722">https://github.com/duckduckgo/duckduckgo-privacy-extension/pull/722</a> <a href="#fnref:update-hardware-concurrency" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><category term="audit" /><category term="privacy" /><summary type="html"><![CDATA[A technical audit of the DuckDuckGo Privacy Essentials addon.]]></summary></entry><entry><title type="html">Git Bisect and Security Advisories</title><link href="https://austinhartzheim.me/blog/2021/06/06/git-bisect-security-advisories.html" rel="alternate" type="text/html" title="Git Bisect and Security Advisories" /><published>2021-06-06T11:54:42-05:00</published><updated>2021-06-06T11:54:42-05:00</updated><id>https://austinhartzheim.me/blog/2021/06/06/git-bisect-security-advisories</id><content type="html" xml:base="https://austinhartzheim.me/blog/2021/06/06/git-bisect-security-advisories.html"><![CDATA[<p><em>How to compute a vulnerability exposure window with <code class="language-plaintext highlighter-rouge">git bisect</code>.</em></p>

<p>When a security vulnerability is found in software, it is desirable to know how long the software was vulnerable to that particular vulnerability and which versions were impacted. This information allows communications to be sent to consumers of that software so they know to upgrade, and it places bounds on the known window of vulnerability (which is relevant to to investigations that might look for indicators of abuse). Additionally, accurate information about the range of affected versions reduces false positives in security scanning tools.</p>

<p>Recently, I <a href="https://github.com/RustSec/advisory-db/pull/931">submitted</a> a security advisory for Rust’s <code class="language-plaintext highlighter-rouge">nalgebra</code> crate. While <a href="https://github.com/RustSec/advisory-db/issues/880">discussion</a> provided a version number that fixed the vulnerability, I still needed to find the version that introduced the vulnerability to populate the <code class="language-plaintext highlighter-rouge">unaffected</code> field in the <a href="https://github.com/RustSec/advisory-db#advisory-format">advisory format</a>. The following post describes my methodology for finding when the vulnerability was introduced, without any prior knowledge into the <code class="language-plaintext highlighter-rouge">nalgebra</code> crate.</p>

<h2 id="understanding-the-vulnerability">Understanding the vulnerability</h2>

<p>First, we need to understand the vulnerability well enough to evaluate whether a given version of the code contains that vulnerability. The <a href="https://rustsec.org/advisories/RUSTSEC-2021-0070.html">vulnerability</a> in question results from a failure to validate input in a deserialize implementation for the <code class="language-plaintext highlighter-rouge">VecStorage</code> struct. In this case, the deserialize function was automatically generated by a macro.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[cfg_attr(feature</span> <span class="nd">=</span> <span class="s">"serde-serialize"</span><span class="nd">,</span> <span class="nd">derive(Serialize,</span> <span class="nd">Deserialize))]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">VecStorage</span><span class="o">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">R</span><span class="p">:</span> <span class="n">Dim</span><span class="p">,</span> <span class="n">C</span><span class="p">:</span> <span class="n">Dim</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="n">data</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="n">nrows</span><span class="p">:</span> <span class="n">R</span><span class="p">,</span>
    <span class="n">ncols</span><span class="p">:</span> <span class="n">C</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The first line of the code snippet is the macro that generates the serialization and deserialization logic (<code class="language-plaintext highlighter-rouge">derive(Serialize, Deserialize)</code>). This logic is only enabled if the <code class="language-plaintext highlighter-rouge">serde-serialize</code> feature flag is enabled (this allows users to opt-in to the serialization functionality if it is required).</p>

<p>In the following lines, we see the struct members. <code class="language-plaintext highlighter-rouge">data</code> is a vector which is expected to have length <code class="language-plaintext highlighter-rouge">nrows * ncols</code>. However, the automatically generated deserializer does not guarantee that invariant. This allows specially crafted inputs to violate the length invariant, which allows memory access past the end of <code class="language-plaintext highlighter-rouge">data</code>’s allocated buffer.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>

<p>We now know what to look for: if <code class="language-plaintext highlighter-rouge">VecStorage</code> has an automatically derived <code class="language-plaintext highlighter-rouge">Deserialize</code> implementation, then then the version is vulnerable.</p>

<h2 id="finding-the-range-of-affected-commits">Finding the range of affected commits</h2>

<p>Now that we understand how to check a version of the software for the vulnerability, we need an efficient strategy to scan the history of the project to find when the vulnerability was introduced. The <code class="language-plaintext highlighter-rouge">nalgebra</code> Git history has nearly two thousand commits, so evaluating each commit by hand is unreasonable.</p>

<p>The process can be accelerated using a binary search to determine which commits to evaluate. Git implements this binary search functionality with a sub-command called <code class="language-plaintext highlighter-rouge">bisect</code>. With the bisect tool, you tell Git whether a given commit should be labeled as “good” or “bad.” With those labels, Git will attempt to narrow in on the exact commit that caused the change from “good” to “bad.” In our case, we define “good” to mean “not vulnerable” and “bad” to mean “vulnerable.”</p>

<p><img src="/assets/blog/git-bisect.webp" alt="Git bisect binary search animation" /></p>

<p>The following steps demonstrate how to run <code class="language-plaintext highlighter-rouge">git bisect</code> on the <code class="language-plaintext highlighter-rouge">nalgebra</code> repository:</p>

<ol>
  <li><strong>Find the most recent vulnerable commit.</strong> If a fix is available, the commit immediately preceding it is likely to be the most recent vulnerable commit. If a fix is not available, the latest commit on the main branch may suffice. In this case, the <code class="language-plaintext highlighter-rouge">nalgebra</code> project fixed the issue in <a href="https://github.com/dimforge/nalgebra/commit/5bff5368bf38ddfa31416e4ae9897b163031a513"><code class="language-plaintext highlighter-rouge">5bff536</code></a>. So, we run <code class="language-plaintext highlighter-rouge">git checkout 5bff536^</code>, where the <code class="language-plaintext highlighter-rouge">^</code> is used to checkout the preceding commit.
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git clone https://github.com/dimforge/nalgebra.git
 <span class="nb">cd </span>nalgebra/
 git checkout 5bff536^
</code></pre></div>    </div>
  </li>
  <li><strong>Start the bisect process.</strong> Because we know the current commit is vulnerable, we mark it as “bad” with <code class="language-plaintext highlighter-rouge">git bisect bad</code>.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git bisect start
 git bisect bad
</code></pre></div>    </div>
  </li>
  <li><strong>Choose a known-good commit.</strong> If additional information is available to indicate a previously “good” commit, you can provide that information to reduce the search space. In this case, we do not have such information and must assume the vulnerability could have been introduced at any earlier point.
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # Without knowledge of a previous good commit, this will tell
 # `git bisect` to search the entire commit history.
 git bisect next

 # With knowledge of a good commit
 git checkout 123ABCD
 git bisect good
</code></pre></div>    </div>
  </li>
  <li>
    <p><strong>Step through commits:</strong> At this point, Git will begin automatically stepping through commits. When you mark a commit as good or bad, it will automatically advance to the next commit. As you continue marking commits, Git will narrow the search space to find which commit introduced the vulnerability.</p>

    <p>At each step, we run a recursive search with <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> to check if the derived <code class="language-plaintext highlighter-rouge">Deserialize</code> implementation is present:</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> rg 'struct VecStorage' -B 1
</code></pre></div>    </div>
    <p>When we see output like the following, we know the commit is vulnerable.</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 27-#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
 28:pub struct VecStorage&lt;T, R: Dim, C: Dim&gt; {
</code></pre></div>    </div>
    <p>And we mark those commits as bad:</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git bisect bad
</code></pre></div>    </div>
    <p>When there is no output from <code class="language-plaintext highlighter-rouge">ripgrep</code>, we know that <code class="language-plaintext highlighter-rouge">VecStorage</code> is not present in that commit, so we mark it as good:</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git bisect good
</code></pre></div>    </div>
  </li>
  <li><strong>Continue stepping through commits:</strong> Repeat the instructions from step #4 until you see output like the following:
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Bisecting: 7 revisions left to test after this (roughly 3 steps)
 [0f66403cbbe9eeac15cedd8a906c0d6a3d8841f2] Rename `MatrixVec` to `VecStorage`.
</code></pre></div>    </div>
    <p>If at any point you make a mistake, you can start over by running:</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> git bisect reset
 git checkout 5bff536^
</code></pre></div>    </div>
  </li>
</ol>

<h3 id="relocated-code">Relocated code</h3>
<p>We were able to identify commit <a href="https://github.com/dimforge/nalgebra/commit/0f66403cbbe9eeac15cedd8a906c0d6a3d8841f2"><code class="language-plaintext highlighter-rouge">0f66403</code></a> after only several iterations. However, this is not quite the commit that introduced the vulnerability. From the commit message, we see that <code class="language-plaintext highlighter-rouge">VecStorage</code> was renamed from <code class="language-plaintext highlighter-rouge">MatrixVec</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Bisecting: 7 revisions left to test after this (roughly 3 steps)
[0f66403cbbe9eeac15cedd8a906c0d6a3d8841f2] Rename `MatrixVec` to `VecStorage`.
</code></pre></div></div>

<p>Running <code class="language-plaintext highlighter-rouge">git show 0f66403cbbe9eeac15cedd8a906c0d6a3d8841f2</code> displays the diff for this commit. We can see that the struct was only renamed - the derive code is still present, which means the code is still vulnerable:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
<span class="gd">-pub struct MatrixVec&lt;N, R: Dim, C: Dim&gt; {
</span><span class="gi">+pub struct VecStorage&lt;N, R: Dim, C: Dim&gt; {
</span>     data: Vec&lt;N&gt;,
     nrows: R,
     ncols: C,
 }
</code></pre></div></div>

<p>To continue searching backwards from this point, we need to restart the bisect operation at this new commit:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git bisect reset
git checkout 0f66403cbbe9eeac15cedd8a906c0d6a3d8841f2
git bisect bad
git bisect next
</code></pre></div></div>

<p>And modify our checks with the new struct name:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rg 'struct MatrixVec' -B 1
</code></pre></div></div>

<h3 id="modified-code">Modified code</h3>
<p>We are quickly closing in on the point where the vulnerability was introduced. Along the way, you might notice that the feature flag logic was removed. However, <code class="language-plaintext highlighter-rouge">Deserialize</code> is still present inside the <code class="language-plaintext highlighter-rouge">#[derive(...)]</code> macro, which means we can mark this commit as “bad” and continue:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">#[derive(Eq,</span> <span class="nd">Debug,</span> <span class="nd">Clone,</span> <span class="nd">PartialEq,</span> <span class="nd">Serialize,</span> <span class="nd">Deserialize)]</span>
<span class="k">pub</span> <span class="k">struct</span> <span class="n">MatrixVec</span><span class="o">&lt;</span><span class="n">N</span><span class="p">,</span> <span class="n">R</span><span class="p">:</span> <span class="n">Dim</span><span class="p">,</span> <span class="n">C</span><span class="p">:</span> <span class="n">Dim</span><span class="o">&gt;</span> <span class="p">{</span>
</code></pre></div></div>

<h3 id="found-at-last">Found, at last</h3>
<p>Finally, we have reached the end and have identified commit <a href="https://github.com/dimforge/nalgebra/commit/086e6e719f53fecba6dadad2e953a487976387f5"><code class="language-plaintext highlighter-rouge">086e6e7</code></a>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>086e6e719f53fecba6dadad2e953a487976387f5 is the first bad commit
commit 086e6e719f53fecba6dadad2e953a487976387f5
Date:   Sun Feb 12 18:17:09 2017 +0100

    Doc + slerp + conversions.

:100644 100644 37a437170a0652cdea2b1fa9acc432b0c36d0238 398f0db9bd785f614dce61b12189118ee94f0243 M      Cargo.toml
:100644 100644 f9586c02d4bb6566c368f4af1355ce0ddeeb4468 00457c113ed7b92e490db1c0fc40369ce9bee791 M      Makefile
:100644 100644 c860659cb7e48ed3fe1b02f10eb477e6dde753f7 7d6a99fa156eac406835d7ed97573ed5f5266061 M      README.md
:000000 040000 0000000000000000000000000000000000000000 1ec3a39daebd14edf61757bf8bd97def798d24b4 A      examples
:040000 040000 75d90764b1b2611d068ac8924988cefb425c05f3 2f2d71b9b97c4b8c339ae3397e67d550afa4a027 M      src
:040000 040000 6daa48d65a69f7450442bf4dd3f35fa2549d8f5e 3224ca40928811a275336a1d7f47784b3962d876 M      tests
</code></pre></div></div>

<p>Running <code class="language-plaintext highlighter-rouge">git show 086e6e719f53fecba6dadad2e953a487976387f5</code>, we scan the diff and find that this is where the <code class="language-plaintext highlighter-rouge">Deserialize</code> implementation was added. We found the commit where this vulnerability was introduced!</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">-#[derive(Eq, Debug, Clone, PartialEq)]
</span><span class="gi">+#[derive(Eq, Debug, Clone, PartialEq, Serialize, Deserialize)]
</span> pub struct MatrixVec&lt;N, R: Dim, C: Dim&gt; {
</code></pre></div></div>

<h2 id="converting-to-versions">Converting to versions</h2>
<p>There is one last step to our process. We need to find the first version of this crate that was released with vulnerable code. We know that <code class="language-plaintext highlighter-rouge">086e6e719f53fecba6dadad2e953a487976387f5</code> first introduced the vulnerability, so we can run the following command to see which tags contain that commit in their history:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git tag <span class="nt">--contains</span> 086e6e719f53fecba6dadad2e953a487976387f5
</code></pre></div></div>

<p>This outputs a list of tag names. <code class="language-plaintext highlighter-rouge">v0.11.0</code> is the earliest version containing the vulnerable <code class="language-plaintext highlighter-rouge">Deserialize</code> implementation. We now have all the information we need to complete the advisory report.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>This is known as a <a href="https://en.wikipedia.org/wiki/Buffer_over-read">buffer over-read</a> or a <a href="https://en.wikipedia.org/wiki/Buffer_overflow">buffer overflow</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><category term="git" /><category term="security" /><summary type="html"><![CDATA[How to compute a vulnerability exposure window with git bisect.]]></summary></entry></feed>