<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Sergey &quot;Shnatsel&quot; Davidoff on Medium]]></title>
        <description><![CDATA[Stories by Sergey &quot;Shnatsel&quot; Davidoff on Medium]]></description>
        <link>https://medium.com/@shnatsel?source=rss-fb9e4cd5e1ee------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*hhZ8-oatvBSbLtns74DrTw.png</url>
            <title>Stories by Sergey &amp;quot;Shnatsel&amp;quot; Davidoff on Medium</title>
            <link>https://medium.com/@shnatsel?source=rss-fb9e4cd5e1ee------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 09 Apr 2026 05:14:52 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@shnatsel/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[The state of SIMD in Rust in 2025]]></title>
            <link>https://shnatsel.medium.com/the-state-of-simd-in-rust-in-2025-32c263e5f53d?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/32c263e5f53d</guid>
            <category><![CDATA[simd]]></category>
            <category><![CDATA[rust-programming-language]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[rust]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Wed, 05 Nov 2025 15:14:40 GMT</pubDate>
            <atom:updated>2025-11-08T09:44:45.578Z</atom:updated>
            <content:encoded><![CDATA[<p>If you’re already familiar with SIMD, the table below is all you need.</p><p>And if you’re not, you will understand the table by the end of this article!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ERUaDSMPbto4ifv6EpaJuQ.png" /></figure><h3>What’s SIMD? Why SIMD?</h3><p>Hardware that does arithmetic is cheap, so any CPU made this century has plenty of it. But you still only have one instruction decoding block and it is hard to get it to go fast, so the arithmetic hardware is vastly underutilized.</p><p>To get around the instruction decoding bottleneck, you can feed the CPU a batch of numbers all at once for a single arithmetic operation like addition. Hence the name: “single instruction, multiple data,” or SIMD for short.</p><p>Instead of adding two numbers together, you can add two batches or “vectors” of numbers and it takes about the same amount of time.</p><p>On recent x86 chips these batches can be up to 512 bits in size, so in theory you can get an 8x speedup for math on u64 or a 64x speedup on u8!</p><h3>Instruction sets</h3><p>Historically, SIMD instructions were added after the CPU architecture was already designed, so SIMD is an extension with its own marketing name on each architecture.</p><p>ARM calls theirs “NEON”, and all 64-bit ARM CPUs have it.</p><p>WebAssembly doesn’t have a marketing department, so they just call theirs “WebAssembly 128-bit packed SIMD extension”.</p><p>64-bit x86 shipped with one called “SSE2” which has basic instructions for 128-bit vectors, but <em>later</em> they added a whole menagerie of extensions on top of that, with SSE 4.2 adding more operations, AVX and AVX2 adding 256-bit vectors and AVX-512 adding 512-bit vectors.</p><p>The word “later” in the above paragraph creates a problem.</p><h4>Does this CPU have that instruction?</h4><p>If you’re running a program on an x86 CPU, it’s not a given that the CPU has any particular SIMD extension. So by default the compiler isn’t allowed to use instructions beyond SSE2 because that won’t work on all x86 CPUs.</p><p>There are two ways around this problem.</p><p>If you work for a company that only ever runs their binaries on their own servers or on a public cloud, you can just assert that they’re all recent enough to at least have AVX2 that was introduced over 10 years ago, and have the program crash or misbehave if it ever runs on anything without AVX2:</p><pre>RUSTFLAGS=&#39;-C target-cpu=x86–64-v3&#39; cargo build --release</pre><p>However, if you are distributing the binaries for other people to run, that’s not really an option.</p><p>Instead you can do something called <strong>function multiversioning: </strong>compile the same function multiple times for different SIMD extensions, and when the program actually runs, check what features the CPU supports and select the appropriate version based on that.</p><p>Fortunately, this problem only exists on x86.</p><p>ARM made its NEON mandatory in all 64-bit CPUs and then didn’t bother expanding the width beyond 128 bits. (Technically <a href="https://en.wikipedia.org/wiki/AArch64#Scalable_Vector_Extension_(SVE)">SVE</a> exists, but in 2025 it is still mostly on paper, and Rust support for it is <a href="https://github.com/rust-lang/rust-project-goals/issues/270">still in progress</a>).</p><p>WebAssembly makes you compile two different binaries, one with SIMD and one without, and use JavaScript to check if the browser supports SIMD.</p><h3>Solution space</h3><p>There are four approaches to SIMD in Rust, in ascending order of effort:</p><ol><li>Automatic vectorization</li><li>Fancy iterators</li><li>Portable SIMD abstractions</li><li>Raw intrinsics</li></ol><h3>Automatic vectorization</h3><p>The easiest approach to SIMD is letting the compiler do it for you.</p><p>It works surprisingly well, as long as you structure your code in a way that is amenable to vectorization. This article covers it:</p><p><a href="https://matklad.github.io/2023/04/09/can-you-trust-a-compiler-to-optimize-your-code.html">Can You Trust a Compiler to Optimize Your Code?</a></p><p>You can check if it’s working with <a href="https://crates.io/crates/cargo-show-asm">cargo-show-asm</a> or <a href="https://rust.godbolt.org/">godbolt.org</a>, but your benchmarks are the ultimate judge of the results.</p><p>Sadly there is a limit on the complexity of the code that the compiler will vectorize, and it may change between compiler versions. If something vectorizes today that doesn’t necessarily mean it still will in a year from now.</p><p>The other drawback of this method is that the optimizer won’t even touch anything involving floats (f32 and f64 types). It’s not permitted to change any observable outputs of the program, and reordering float operations may alter the result due to precision loss. (There is a way to tell the compiler not to worry about precision loss, but it’s currently <a href="https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.algebraic_add">nightly-only</a>).</p><p>So right now,<strong> if you need to process floats, autovectorization is a no-go </strong>unless you can use nightly builds of the Rust compiler.</p><p>(Floats are cursed even without SIMD. Something as simple as summing an array of them in a usable way turns out to be <a href="https://orlp.net/blog/taming-float-sums/">really hard</a>).</p><p>There is no built-in way to multiversion functions, but the <a href="https://crates.io/crates/multiversion">multiversion</a> crate works great with autovectorization.</p><h3>Fancy iterators</h3><p>Just like <a href="https://crates.io/crates/rayon">rayon</a> lets you run your iterators in parallel by swapping .iter() with .par_iter(), there have been attempts to do the same for SIMD. After all, what is SIMD but another kind of parallelism?</p><p>This is the approach that the <a href="https://github.com/AdamNiederer/faster">faster</a> crate takes. That crate has been abandoned for years, and it doesn’t look like this approach has panned out.</p><h3>Portable SIMD abstractions</h3><p>The idea is to let you write your algorithm by explicitly operating on chunks of data, something like [f32; 8] but wrapped in a custom type, and then provide custom implementations of operations like + that compile down into SIMD instructions.</p><p><a href="https://doc.rust-lang.org/stable/std/simd/index.html">std::simd</a> is exactly that. It supports all instruction sets LLVM supports, so its platform support is unparalleled. It pairs well with the <a href="https://crates.io/crates/multiversion">multiversion</a> crate. Sadly it’s <strong>nightly-only</strong> and will remain such for the foreseeable future, so it’s unusable in most situations.</p><p>The <a href="https://crates.io/crates/wide">wide</a> crate is a mature, established option. It supports NEON, WASM and all the x86 instruction sets. But it <strong>doesn’t support multiversioning</strong> at all, save for very exotic and limited approaches like <a href="https://github.com/ronnychevalier/cargo-multivers">cargo-multivers</a>.</p><p>The <a href="https://github.com/sarah-quinones/pulp">pulp</a> crate has built-in multiversioning, and is reasonably mature and complete, if not as much as wide. It powers <a href="https://crates.io/crates/faer">faer</a>, so its performance is clearly proven. A major limitation is that it <strong>only operates on the native SIMD width,</strong> so you need your code to be able to handle variable width chunks as opposed to expressing everything in terms of something like [f32; 8] and letting the library lower it into the appropriate instructions like std::simd and wide do. And it’s difficult to write code that’s generic over type, so if you want both f32 and f64 there will be some code duplication. The architecture support is also limited — only NEON, AVX2 and AVX-512. AVX2 was introduced in 2012, but in the <a href="https://firefoxgraphics.github.io/telemetry/#view=system">Firefox hardware survey</a> only 75% of systems have it.</p><p>The <a href="https://crates.io/crates/macerator">macerator</a> crate is a fork of pulp with better support for generic programming and vastly expanded instruction set support. It supports all x86 extensions, WASM, NEON, and even the LoongArch SIMD extensions. It also improves on pulp for generic programming. It’s used only by <a href="https://crates.io/crates/burn-ndarray">burn-ndarray</a>, and even there it’s an optional dependency. It sounds great on paper, but it’s oddly obscure and therefore unproven.</p><p>The <a href="https://crates.io/crates/fearless_simd">fearless_simd</a> crate is inspired by pulp’s design, but also supports fixed-size chunks just like std::simd and wide. It’s far less mature than pulp, but it’s under active development. As of this writing it supports NEON, WASM and SSE4.2, but not the newer x86 extensions. Seems too immature just yet, but something to keep an eye on.</p><p><a href="https://crates.io/crates/simdeez">simdeez</a> is a rather old crate that supports all instruction sets except AVX-512 and comes with built-in multiversioning. What gives me pause is that despite existing for many years, it’s still barely used. Everyone else who needed SIMD built their own instead of using it. And its README says:</p><blockquote>Currently things are well fleshed out for i32, i64, f32, and f64 types.</blockquote><p>So I guess the other types aren’t complete?</p><p><strong>TL;DR:</strong> use std::simd if you don’t mind nightly, wide if you don’t need multiversioning, and otherwise pulp or macerator.</p><p>If it’s not 2025 when you’re reading this, check out fearless_simd, because std::simd is <em>still</em> in nightly in your glorious future, isn’t it?</p><h3>Raw intrinsics</h3><p>If you want to get really close to the metal, there are always <a href="https://doc.rust-lang.org/stable/std/arch/index.html">the raw intrinsics</a>, just one step removed from the processor instructions.</p><p>The problem looming over any use of raw intrinsics is that you have to manually write them for every platform and instruction set you’re targeting. Whereas std::simd or wide let you write your logic once and compile it down to the assembly automatically, with intrinsics you have to write a separate implementation for every single platform and instruction set (SSE, AVX, NEON…) you care to support. That’s a lot of code!</p><p>It’s really not helped by the fact that they are all named something like _mm256_srli_epi32 and your code ends up as a long list of calls to these arcanely named functions. And wrappers that help readability introduce their own problems, such as <a href="https://crates.io/crates/safe_arch">clashes with multiversioning</a> or <a href="https://github.com/etemesi254/zune-image/blob/c77248891a4209d72f5d268b7208780692168606/crates/zune-jpeg/src/unsafe_utils_avx2.rs#L50-L60">unsafe code</a> or <a href="https://github.com/libjxl/jxl-rs/blob/46decb1f84f9bfb95e6661682f09270dade161d1/jxl_simd/src/x86_64/avx.rs#L225-L241">arcane macros</a>.</p><p>You also have to build your own multiversioning. Or rather, you have to manually dispatch to the dedicated implementation you have manually written for each instruction set. <a href="https://doc.rust-lang.org/stable/std/macro.is_x86_feature_detected.html">std::is_x86_feature_detected!</a> macro takes care of the feature detection, but it is somewhat slow. In some cases it is beneficial to detect available features exactly once and then cache the results, but you have to implement that manually too.</p><p>On the bright side, this year writing intrinsics got markedly less awful. Most of them are no longer unsafe to call in Rust 1.87 and later, and the <a href="https://crates.io/crates/safe_unaligned_simd">safe_unaligned_simd</a> crate provides safe wrappers for the rest.</p><p>So at least this approach is no longer unsafe on top of all the other problems it has!</p><h3>Which one is right for you?</h3><p>The right tool for the job ultimately depends on the use case.</p><p>Want zero dependencies and little up-front hassle? Autovectorization. Porting existing C code or targeting very specific hardware? Intrinsics. Anything else? Portable SIMD abstraction.</p><p>And now that you made it this far, you can understand the table at the top of the article, which will help guide your decision!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=32c263e5f53d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I am stepping back from maintaining ‘cargo audit’]]></title>
            <link>https://shnatsel.medium.com/i-am-stepping-back-from-maintaining-cargo-audit-35bb5f832d43?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/35bb5f832d43</guid>
            <category><![CDATA[cargo]]></category>
            <category><![CDATA[maintenance]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[rust]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Wed, 05 Mar 2025 14:34:50 GMT</pubDate>
            <atom:updated>2025-03-05T14:34:50.608Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>TL;DR:</strong> I will no longer be actively working on <a href="https://crates.io/crates/cargo-audit">cargo audit</a> and the <a href="https://rustsec.org/">RustSec security advisory database</a>. I will continue working on my other Rust projects and contributing to the Rust ecosystem. Maintenance of cargo audit and its database will be carried on by <a href="https://www.rust-lang.org/governance/wgs/wg-secure-code">the other maintainers</a>.</p><h3>The catalyst</h3><p>Any command that goes through Rust’s build system, Cargo, <a href="https://shnatsel.medium.com/do-not-run-any-cargo-commands-on-untrusted-projects-4c31c89a78d6">can execute arbitrary code</a>. This applies to <em>any</em> command starting with cargo.</p><p>This is a pretty reasonable thing for a build system. After all, you’d expect the same from calling make or gradle or pretty much anything else.</p><p>It is not so reasonable when you consider external plugins for Cargo such as <a href="https://crates.io/crates/cargo-audit">cargo audit</a>. Similar programs like <a href="https://github.com/google/osv-scanner/">osv-scanner</a> or <a href="https://github.com/aquasecurity/trivy">trivy</a> do not execute arbitrary code from the repository, so why should cargo audit?</p><h3>So what do we do?</h3><p>The exact details of the issue are not really relevant here, but you can read my article about it if you’re curious:</p><p><a href="https://shnatsel.medium.com/do-not-run-any-cargo-commands-on-untrusted-projects-4c31c89a78d6">Do not run any Cargo commands on untrusted projects</a></p><p>The important bit is that there are three ways to resolve this issue:</p><h4>Change the behavior of Cargo</h4><p>While the Cargo team agrees this is desirable in the long run, this is also a breaking change for existing users that may be using these features for legitimate purposes.</p><p>And since this is a breaking change, it will need to go through a lengthy deprecation period first. So any solution here is not going to arrive soon enough, even if implementation on it started today. Plus there are always people out there running years-old versions of Cargo.</p><h4>Document the issue and pretend that this solves it</h4><p>So should we just accept this and document that cargo audit cannot be run on untrusted code?</p><p>Most people are running cargo audit either on CI, which is well equipped for running untrusted code, or on their own projects they trust. And yet, I’m certain that someone, somewhere, will quite reasonably assume that cargo audit isn’t different from other vulnerability scanners, and run it on untrusted code without sandboxing, and get pwned.</p><p>I even wrote <a href="https://crates.io/crates/binfarce">a custom binary format parser</a> to make its binary scanning 100% memory safe and robust, so it feels weird to get an arbitrary code execution vulnerability from a far simpler source.</p><h4>Rename ‘cargo audit’ and migrate all users</h4><p>So should we rename it to something like rust-audit and tell all users to migrate?</p><p>But if we’re migrating users anyway, does it have to be a migration to the new name of the same tool? Maybe it’s time to just port the remaining “special sauce” like binary scanning and automatic fixes to something else, like <a href="https://github.com/google/osv-scanner/">osv-scanner</a>, and just migrate people to that?</p><h3>Why do we even have ‘cargo audit’?</h3><p>When Tony started cargo audit eight years ago, the very concept of an actually machine-readable vulnerability database and a tool that could automatically check your dependencies against it was novel. There was <em>literally nothing else</em> that could do the job of cargo audit.</p><p>But in the last few years the rest of the industry has finally caught up. There is now a single machine-readable format and aggregator (<a href="https://osv.dev/">OSV</a>), a <a href="https://github.com/google/osv-scanner/">bunch</a> of <a href="https://github.com/aquasecurity/trivy">tools</a> that pull data from it, and even a <a href="https://github.com/advisories?query=ecosystem%3Arust">competing vulnerability database</a> under a permissive license that imports our data and exports all its data to OSV (<a href="https://en.wikipedia.org/wiki/Enshittification">at least for now</a>).</p><p>cargo audit still has special sauce, to be sure:</p><ol><li>It generates the Cargo.lock file if it’s not present or out of date. But it calls into Cargo to do it, so that’s <a href="https://shnatsel.medium.com/do-not-run-any-cargo-commands-on-untrusted-projects-4c31c89a78d6">unsafe on an untrusted project</a>.</li><li>It can scan even binaries built without <a href="https://github.com/rust-secure-code/cargo-auditable">cargo auditable</a> and recover an (incomplete) list of dependencies <a href="https://docs.rs/quitters/latest/quitters/">by parsing panic messages</a>.</li><li>You can run cargo audit fix to try and automatically upgrade your vulnerable dependencies to versions with the fixes.</li><li>It will notify you about unmaintained dependencies that do not receive security updates.</li></ol><p>But 1 through 3 are features that I’ve implemented (at least in their current form), and the code is quite straightforward. There is no reason why other tools, even ones not specifically built for Rust, could not adopt them.</p><p>And 4 has been a controversial feature. Some people love it, some people <a href="https://lucumr.pocoo.org/2024/3/26/rust-cdo/">hate</a> it. cargo audit is configured to only show a warning by default and not cause CI to fail, but lots of people use a Github action that treats them as an error, and get upset when unmaintained crates break CI. This cannot be easily fixed because, ironically, that Github action is unmaintained. Even if this doesn’t fail CI, it can still <a href="https://web.archive.org/web/20250302001313/https://old.reddit.com/r/rust/comments/1bo5dle/we_lost_serdeyaml_whats_the_next_one/kwml1ti/">put unwanted pressure</a> on people.</p><p>Given that this feature is not unambiguously and universally great, I wouldn’t be too sad to let it go.</p><h3>The costs of maintenance</h3><p>Unlike some of my other Rust projects, cargo audit requires ongoing maintenance to keep up with the changes to Cargo and the ecosystem.</p><p>And that’s not mentioning the work and stress of maintaining <a href="https://rustsec.org/">a database of security advisories</a>. Every single one should be reviewed and published promptly, any day of the week, any day of the year, and contain information that is accurate and actionable.</p><p>Oh, and did I mention all of this has been done by only a small team of volunteers for years now?</p><p>cargo audit never had any funding (aside from that one time when Google paid me to bridge the advisory database to OSV. Thanks, Google!), and the volunteer maintainers are spread thin as it is. I maintain <a href="https://crates.io/crates/cargo-auditable">way</a> <a href="https://crates.io/crates/cargo-supply-chain">too</a> <a href="https://crates.io/crates/cargo-cyclonedx">many</a> Cargo plugins and <a href="https://github.com/image-rs/image-png/pull/512">make</a> <a href="https://github.com/image-rs/image-png/pull/539">other</a> <a href="https://github.com/image-rs/image-png/pull/503">contributions</a> <a href="https://github.com/image-rs/image-webp/pull/123">across</a> <a href="https://github.com/image-rs/image/pull/2299">the</a> <a href="https://github.com/rust-fuzz/cargo-fuzz/pull/376">ecosystem</a>. Tony maintains <a href="https://crates.io/users/tarcieri?sort=recent-downloads">so many crates</a> that just listing them takes over 10 pages. Alex started <a href="https://rust-for-linux.com/">Rust for Linux</a> and maintains Python’s <a href="https://github.com/pyca/cryptography">cryptography</a> package, among other things. And Alexis has multiple important projects of his own, and also seems to be the only one of us who actually has a life.</p><h3>Is it worth it?</h3><p>cargo audit is not as crucial and irreplaceable as it used to be. It still has features that are completely unique, but those are relatively straightforward to implement and require barely any maintenance.</p><p>The real maintenance hog is the boring parts, which are now implemented perfectly fine by multiple other tools. And sinking a lot of time and effort into maintaining yet another implementation that isn’t meaningfully better than others just so that we could add a thin layer of unique features on top doesn’t seem like a good use of anyone’s resources.</p><p>And it’s difficult to keep up with the times when everyone is spread so thin.</p><p>For example, right now the database of cargo audit is not as complete as that of some of the other tools. The <a href="https://github.com/advisories?query=ecosystem%3Arust">Github database</a> pulls data from us and exports to OSV, but we didn’t yet wire up import of data from the Github database. Alexis already did most of the work on that and I meant to get it over the finish line, but never did because most of my time goes into urgent maintenance.</p><p>Now that cargo audit is not vastly ahead of the industry anymore, I think it’s time to share the special sauce around, take a step back, and focus on things that <a href="https://github.com/rust-secure-code/cargo-auditable">remain genuinely novel</a> or that <a href="https://github.com/CycloneDX/cyclonedx-rust-cargo/">the industry is willing to fund</a>. So that’s exactly what I will be doing.</p><p>And if you disagree with my assessment, the remaining maintainers could definitely use some help. You can get in touch with them <a href="https://rust-lang.zulipchat.com/#narrow/channel/146229-wg-secure-code">on Zulip</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=35bb5f832d43" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Do not run any Cargo commands on untrusted projects]]></title>
            <link>https://shnatsel.medium.com/do-not-run-any-cargo-commands-on-untrusted-projects-4c31c89a78d6?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/4c31c89a78d6</guid>
            <category><![CDATA[rust-programming-language]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[cargo]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Mon, 03 Mar 2025 12:57:47 GMT</pubDate>
            <atom:updated>2025-03-03T17:29:37.258Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>TL;DR:</strong> Treat <em>anything</em> starting with cargo as if it is cargo run.</p><h3>Background</h3><p><a href="https://doc.rust-lang.org/cargo/">Cargo</a> is the build system for the Rust programming language.</p><p>It is common knowledge that you should not invoke cargo run on random code from the internet, because that will run the code.</p><p>It is reasonably common knowledge that cargo build will also run arbitrary code. It is a build system, after all! They run code to build code.</p><p>I’m here to tell you that <em>any</em> command starting with cargo can run arbitrary code when operating on an untrusted repository, and should be treated the same as cargo run.</p><p>This shouldn’t be surprising to people coming from other languages. For example, anything starting with make also runs arbitrary code. Cargo just takes care of things by default so you hardly ever have to inject your own code - but the fact that you don’t <em>have</em> to doesn’t mean you <em>can’t.</em></p><h3>Attack vector #1</h3><p>Cargo reads its configuration from a <a href="https://doc.rust-lang.org/cargo/reference/config.html">number of files</a>, some of which can be defined right in the repository, e.g. in .cargo/config.toml.</p><p>Among other things, you can redefine the path to the Rust compiler:</p><pre>[build]<br>rustc = &quot;/usr/bin/rustc&quot; # path to the rust compiler</pre><p>So you can also put a malicious script in the repository and do this:</p><pre>[build]<br>rustc = &quot;.cargo/fake_malicious_rustc&quot;</pre><p>which will be called in more situations than you might think: not only on cargo build, but also cargo clean, cargo update, cargo metadata. In fact, I’m not aware of a single Cargo subcommand that doesn’t call rustc.</p><p>And rustc is not the only problematic field. There’s also rustc-wrapper, rustc-workspace-wrapper, the linker configuration, and the entire [env] section that can be used to override environment variables for processes that Cargo launches, so you could override $PATH and get arbitrary code execution that way.</p><p>“But wait,” you might think, “I’m using this cool third-party plugin for Cargo, and it’s not reading the Cargo configuration. Surely running <em>that</em> is fine?”</p><h3>Attack vector #2</h3><p>There are many Cargo plugins meant to help with supply chain security. There’s <a href="https://crates.io/crates/cargo-audit">cargo audit</a>, <a href="https://crates.io/crates/cargo-cyclonedx">cargo cyclonedx</a>, <a href="https://crates.io/crates/cargo-vet">cargo vet</a>, <a href="https://crates.io/crates/cargo-crev">cargo crev</a>, and so on. They aren’t even part of Cargo, and they shouldn’t read the config files.</p><p>This doesn’t mean that they are safe to run. When you run cargo audit, what gets called is Cargo which then invokes the actual cargo-audit.</p><p>That is, unless when you put this in .cargo/config.toml in the repository:</p><pre>[alias]<br>audit = &quot;run&quot;</pre><p>which makes it so that Cargo turns the cargo audit you typed into a cargo run behind the scenes and it’s arbitrary code execution all over again.</p><h3>Disclosure</h3><p>I reached out to the Rust Security Response WG and a member of the Cargo team. The consensus was that this behavior is not an entirely new discovery and <strong>this behavior is not going to change in the near future,</strong> so it should just be better documented.</p><p>There is also a desire to eventually remove attack vector #2, but it will require a deprecation period since it’s a breaking change for people who override Cargo plugins for a legitimate reason, and there is no clear timeline for it as yet.</p><p>I also looked up what Cargo plugins had bug bounty programs. I reported attack vector #1 to Mozilla who maintain <a href="https://github.com/mozilla/cargo-vet">cargo vet</a>, but they <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1951018">concluded</a> it’s not really a problem for their intended use cases.</p><p>There’s also a bug bounty for <a href="https://crates.io/crates/cargo-cyclonedx">cargo cyclonedx</a>, but for obvious reasons it excludes maintainers, and I am a maintainer of <a href="https://crates.io/crates/cargo-cyclonedx">cargo cyclonedx</a> (along with <a href="https://crates.io/crates/cargo-audit">cargo audit</a>, which is why I’ve been using it as an example above. I maintain <a href="https://crates.io/crates/cargo-auditable">way</a> <a href="https://crates.io/crates/cargo-supply-chain">too</a> <a href="https://crates.io/crates/cargo-loc">many</a> Cargo plugins). So no bug bounty money for me.</p><h3>So is this a problem?</h3><p>This behavior is kind of by design, and isn’t really different from how build systems work in other programming languages.</p><p>It’s the existence of Cargo plugins that makes the situation murky. And whether it’s a problem or not should be decided on a case-by-case basis.</p><p>It is definitely a problem for <a href="https://crates.io/crates/cargo-crev"><strong>cargo crev</strong></a>, which explicitly treats the code it’s running on as untrusted and lets the user review it before running. The code under review being able to misrepresent itself is a problem. They will have to become simply crev or rust-crev or something else that doesn’t involve getting called through cargo.</p><p>It’s not really a problem for <a href="https://github.com/rust-lang/cargo/pull/13709"><strong>cargo cyclonedx</strong></a> because in the long term it should <a href="https://github.com/rust-lang/cargo/pull/13709">become a wrapper</a> for cargo build anyway. Although we’ll probably have to shutter the bug bounty program: if we assume the code you’re running it on is trusted, then there aren’t any attack vectors at all.</p><p>And this puts <a href="https://crates.io/crates/cargo-audit"><strong>cargo audit</strong></a> in a really awkward spot. Most people just run it on their own projects, which is entirely safe. On the other hand, similar tools like <a href="https://github.com/google/osv-scanner/">osv-scanner</a> or <a href="https://github.com/aquasecurity/trivy">trivy</a> do not execute arbitrary code from the repository, so why should cargo audit?</p><p>I even wrote <a href="https://crates.io/crates/binfarce">a custom binary format parser</a> to make its binary scanning 100% memory safe and robust, so it feels weird to get an arbitrary code execution vulnerability from a far simpler source.</p><p>I don’t really know what the right answer is for cargo audit.</p><h3>Workarounds</h3><p>The <strong>good news</strong> is that attack vector #2 can be avoided right now by calling a Cargo subcommand directly. For example, instead of cargo crev you would type cargo-crev crev (yes, ‘crev’ appears twice) and that fixes it.</p><p>It’s still a terrible default and completely untenable except as a quick and dirty patch, but it’s something.</p><p>The <strong>bad news</strong> is that there is no robust workaround for attack vector #1, and it affects most third-party subcommands too because they still call Cargo behind the scenes.</p><p>You can <a href="https://doc.rust-lang.org/cargo/reference/config.html#environment-variables">override individual configuration keys</a> in a way that takes precedence over config.toml files, but this will never be airtight. There are too many individual configuration keys that could cause problems (looking at you, [env] section), and more of them can be added over time.</p><p>You could try deleting all the configuration files in a given project. But it’s not safe to put them back in once you’re done, otherwise two instances of a tool that does that running in parallel would still be vulnerable: one instance can delete the files and then put them back in before the other instance is finished running, leading to arbitrary code execution. And you also would need to ensure only you have permissions to write to these files, otherwise it’s a local privilege escalation vulnerability. This approach is too brittle to be practical.</p><p>So the best advice I can give is in the title of the article: <strong>Do not run <em>any</em> Cargo commands on untrusted projects.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4c31c89a78d6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to avoid bounds checks in Rust (without unsafe!)]]></title>
            <link>https://shnatsel.medium.com/how-to-avoid-bounds-checks-in-rust-without-unsafe-f65e618b4c1e?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/f65e618b4c1e</guid>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[optimization]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Tue, 17 Jan 2023 14:29:38 GMT</pubDate>
            <atom:updated>2025-05-20T05:50:50.083Z</atom:updated>
            <content:encoded><![CDATA[<p>You can often hear online that indexing into a slice, such as my_slice[i] is slow in Rust and you should do something else instead for performance.</p><p>The details, however, are murky. There’s little in the way of benchmarks, and hardly any documentation on removing this overhead without resorting to unsafe code.</p><p>So after optimizing a bunch of high-profile crates by removing bounds checks (and also removing unsafe code from some of them), I figured I should write down the results, and the techniques I discovered.</p><p><strong>In this article I’m going to cover:</strong></p><ol><li>What is the typical runtime cost of bounds checks on indexing</li><li>How to avoid bounds checks without unsafe code</li><li>How to verify that bounds checks have been eliminated</li><li>How to benchmark and profile Rust code</li><li>When a bounds check is absolutely necessary, how to construct the cheapest possible bounds check</li></ol><h3>What are bounds checks?</h3><p>Consider the following code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6768b245de1fa7ea4d7ae766ff092218/href">https://medium.com/media/6768b245de1fa7ea4d7ae766ff092218/href</a></iframe><p>Accessing the 7th element of the array is incorrect, because the array is only 4 elements long, so there is no 7th element.</p><p>In C this would lead to accessing some random memory outside the intended range, which is called a <strong>buffer overflow</strong>.</p><p>The notorious <a href="https://en.wikipedia.org/wiki/Heartbleed">Heartbleed</a> is a buffer overflow, but most vulnerabilities of this type don’t get flashy names because there’s <a href="https://www.cvedetails.com/vulnerability-list/cweid-787/vulnerabilities.html">just so many of them</a>. Yet writing outside the intended range is a very common way for an attacker to execute their code on your machine and do literally anything they want — steal credit card info, mine cryptocurrency, spy on you, etc. This is why buffer overflows are considered <a href="https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html">the most dangerous software vulnerability</a>.</p><p>To avoid that, Rust inserts so called <strong>bounds checks</strong> on every access that ensure that a buffer overflow never happens — something like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/def2081c05a86a225231110846d2ad90/href">https://medium.com/media/def2081c05a86a225231110846d2ad90/href</a></iframe><p>If you try accessing an invalid index, a Rust program will <a href="https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html">panic</a> instead of creating a security vulnerability.</p><p>While this is great for security, this has a small cost in runtime performance, because now there is more code for the CPU to execute.</p><h3>Do bounds checks actually slow you down?</h3><p>The real-world performance impact of bounds checks is surprisingly low.</p><p>The greatest impact I’ve ever seen on real-world code from removing bounds checks alone was<strong> 15%,</strong> but the typical gains are in <strong>1% to 3% range, </strong>and even that only happens in code that does a lot of number crunching.</p><p>You can occasionally see greater impact (as we’ll see soon!) if removing bounds checks allows the compiler to perform other optimizations.</p><p>Still, performance of code that’s not doing large amounts of number crunching will probably <a href="https://blog.readyset.io/bounds-checks/">not be impacted by bounds checks</a> at all.</p><h3>Try it yourself!</h3><p>While I will be posting the results I got, there’s nothing quite like trying things for yourself. So I’ve prepared a repository with all the code and will be providing all the necessary commands so you can follow along.</p><p>If you have already <a href="https://rustup.rs/">installed Rust</a>, run this to get the code and all the tools:</p><pre>cargo install cargo-show-asm hyperfine<br>git clone https://github.com/Shnatsel/bounds-check-cookbook<br>cd bounds-check-cookbook<br>cargo build --release</pre><h3>Let’s see some bounds checks</h3><p>To have a simple example to experiment with, let’s write a function that calculates the <a href="https://en.wikipedia.org/wiki/Fibonacci_number">Fibonacci numbers</a> and writes them to a <a href="https://doc.rust-lang.org/stable/std/vec/struct.Vec.html">Vec</a>:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/35213c54ff786e01dd4b94f4caa52545/href">https://medium.com/media/35213c54ff786e01dd4b94f4caa52545/href</a></iframe><p>The compiler is really good at removing any code that’s not being called, and at precomputing everything it can in advance. I had to add a main with a lot of tricks in it to make sure it doesn’t happen, and so that we get to see the bounds checks at runtime.</p><p>Let’s look at the assembly and see what the bounds checks look like:</p><pre>cargo asm --rust --bin fibvec_naive_indexing fibonacci_vec</pre><p>This will print the optimized assembly of the fibonacci_vec function, i.e. the instructions the CPU will actually execute, along with the Rust code that produced them.</p><blockquote>You can do this even if you know nothing about assembly! Just eyeballing the amount of assembly produced and looking for function names is sufficient.</blockquote><p>Let’s look at the hot inner loop first, the fib[i] = fib[i-1] + fib[i-2]; part simply by searching for it in the output of cargo asm:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4bf457f67e13dd6e921b8efd515a91a1/href">https://medium.com/media/4bf457f67e13dd6e921b8efd515a91a1/href</a></iframe><p>That’s it? That’s just two instructions!</p><figure><img alt="That can’t be it! Where’s the rest of it?" src="https://cdn-images-1.medium.com/max/1024/1*HEad6jq7-WU6092NGHukng.jpeg" /></figure><p>And indeed, if we scroll down a bit, we’ll see more code attributed to this line - it’s not all in one place:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0431830cf7afa55b133baf66070923d6/href">https://medium.com/media/0431830cf7afa55b133baf66070923d6/href</a></iframe><p>What happened here is the compiler <strong>outlining</strong> the code that’s taken when the bounds check fails. That code path leads to a panic, and panics are rare. So the compiler shuffled the code in such a way that we <strong>don’t even load</strong> the code that is only executed when leading up to a panic until we we actually need it. Clever!</p><p>Anyway, back to the assembly! core::panicking::panic_bounds_check appears to be the panic on bounds check failure, happening in assembly attributed to our line of code. <strong>So this is what they look like!</strong></p><p>Let’s see if the if length &gt; 1 { fib[1] = 1; } bit outside the hot loop also has a bounds check on it…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ced5d303487125638afc40e56528bbfc/href">https://medium.com/media/ced5d303487125638afc40e56528bbfc/href</a></iframe><p>No bounds checks here! The compiler was smart enough to realize that when length is strictly greater than 1, it’s impossible for the bounds check to fail. Our Vec called fib also has length strictly greater than 1, and so fib[1] is always in bounds.</p><p>However, it didn’t seem to realize that the same holds for the loop, specifically the fib[i] = fib[i-1] + fib[i-2]; line.</p><p>Perhaps we can help it?</p><h3>Help the optimizer</h3><p>We’re going to make two changes to the code to make it easier for the optimizer to prove that the bounds checks never fail:</p><ol><li>Instead of indexing up to length, which is just some integer, we’ll index up to fib.len(), to make it obvious that the index is always in bounds.</li><li>Instead of using a Vec, we’ll make a slice of it once and index into the slice. This makes it more clear that the length doesn’t change.</li></ol><p>This gets us the following code:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d7f8005e9819baa121f2c8c6f5e07b0/href">https://medium.com/media/6d7f8005e9819baa121f2c8c6f5e07b0/href</a></iframe><p>And let’s verify it with cargo asm — the command is in the code above:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c5de518211e9eac12c3aa42a91c5348d/href">https://medium.com/media/c5de518211e9eac12c3aa42a91c5348d/href</a></iframe><p>It’s again split in two parts for some reason, but the<strong> bounds check is gone!</strong></p><p><strong>But is it any faster?</strong> Let’s find out!</p><pre>$ hyperfine &#39;target/release/fibvec_naive_indexing 1000000000&#39; &#39;target/release/fibvec_clever_indexing 1000000000&#39;<br><br>Benchmark 1: target/release/fibvec_naive_indexing 1000000000<br>  Time (mean ± σ):      3.612 s ±  0.040 s    [User: 1.435 s, System: 2.132 s]<br>  Range (min … max):    3.546 s …  3.693 s    10 runs<br> <br>Benchmark 2: target/release/fibvec_clever_indexing 1000000000<br>  Time (mean ± σ):      3.133 s ±  0.019 s    [User: 0.995 s, System: 2.103 s]<br>  Range (min … max):    3.106 s …  3.163 s    10 runs<br> <br>Summary<br>  &#39;target/release/fibvec_clever_indexing 1000000000&#39; ran<br>    1.15 ± 0.01 times faster than &#39;target/release/fibvec_naive_indexing 1000000000&#39;</pre><p><em>If you’re on Windows, you may have to add .exe to those paths.</em></p><p>It is faster, by a whopping 15%! That much is often no cause for celebration, but that’s the greatest boost from eliminating bounds checks that I’ve ever seen, so that’s just about <strong>the best we could have hoped for!</strong></p><p>And while this example was somewhat contrived, I used these techniques to <a href="https://github.com/fschutt/fastblur/pull/3">speed up the fastblur crate by 15%</a>. (Although I’ve shaved off 6x as much execution time <a href="https://github.com/fschutt/fastblur/pull/2">through other means</a> first).</p><blockquote><strong>Update:</strong> the fastblur example was also helped by indexing into a slice instead of a &amp;mut Vec, more info <a href="https://github.com/rust-lang/rust-clippy/issues/10269">here</a>. So the gain from removing bounds checks is actually less than 15%.</blockquote><h4>Aside: Compiler Optimizations</h4><p>Now let’s also try this on a 64-bit ARM CPU, just to confirm…</p><pre>$ hyperfine &#39;target/release/fibvec_naive_indexing 1000000000&#39; &#39;target/release/fibvec_clever_indexing 1000000000&#39; <br>Benchmark 1: target/release/fibvec_naive_indexing 1000000000<br>  Time (mean ± σ):      3.320 s ±  0.024 s    [User: 1.131 s, System: 2.179 s]<br>  Range (min … max):    3.263 s …  3.346 s    10 runs<br> <br>Benchmark 2: target/release/fibvec_clever_indexing 1000000000<br>  Time (mean ± σ):      3.226 s ±  0.019 s    [User: 1.092 s, System: 2.127 s]<br>  Range (min … max):    3.209 s …  3.263 s    10 runs<br> <br>Summary<br>  &#39;target/release/fibvec_clever_indexing 1000000000&#39; ran<br>    1.03 ± 0.01 times faster than &#39;target/release/fibvec_naive_indexing 1000000000&#39;</pre><p>Aaand it’s back in the expected 3% range. No huge 15% uplift here.</p><p>But the assembly on ARM is really short:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ddda537209cebe68825cca6c063d24af/href">https://medium.com/media/ddda537209cebe68825cca6c063d24af/href</a></iframe><p>No bounds checks in sight! And that’s just 3 instructions, which means very little work to do, so it should be very fast!</p><p><strong>What’s going on?</strong></p><p>Recall that removal of bounds checks by themselves doesn’t matter much. You can only see a big uplift if removing bounds checks allowed the compiler to perform <strong>other optimizations.</strong></p><p>If you go back and squint at the x86 assembly of fibonacci_vec without bounds checks, it’s almost the same lines repeated over and over, which looks suspiciously like <a href="https://en.wikipedia.org/wiki/Loop_unrolling">loop unrolling</a>.</p><p><strong>Why is it performed on x86 and not on ARM?</strong> I have no idea! It should be — this is a basic optimization that should not be related to the CPU in any way.</p><p>For comparison I tried this on a <a href="https://en.wikipedia.org/wiki/POWER9">POWER9</a> CPU, and the compiler seems to <a href="https://gist.github.com/Shnatsel/862cbed11b3d88fcea31db1633433b05">unroll the loop even more</a> and hyperfine reports a massive speedup of 1.78 ± 0.04 times, so I’m just going to <a href="https://github.com/rust-lang/rust/issues/105857">file a bug for rustc</a> and let people who know how compilers work deal with it.</p><p><strong>The important takeaway for us is this:</strong> optimizing compilers are solving an <a href="https://en.wikipedia.org/wiki/NP-hardness">NP-hard</a> problem in a really short time, and there are always some cases they don’t handle exactly right.</p><p>Worse, the exact details <strong>change between versions.</strong> Even if the compiler improves on average, it may regress your specific code! <a href="https://en.wikipedia.org/wiki/Automatic_vectorization">Automatic vectorization</a> for example is notoriously fickle, which is frustrating because it can get you much better performance when it works.</p><p>I’ve found the optimizations that remove bounds checks to be <strong>very reliable</strong> — once you get them working, they tend to keep working. So you can use these techniques and generally expect them not to break in the future. But that’s only the part responsible for the 3% uplift!</p><p>Since the loop unrolling responsible for the 15% uplift works on x86 but not on ARM, I wouldn’t bet on it working reliably in the future. Such is the sad reality of having something solve an NP-hard problem in a very short time.</p><p>Fortunately, in real programs that don’t spend all of the execution time in a single hot loop the differences are nowhere near this pronounced — regressions in one place are counterbalanced by improvements in another.</p><h4>Aside: Benchmarking</h4><p>So you may be wondering, why am I using hyperfine and going through all this trouble of writing a non-trivial main()?</p><p>Why don’t I just use cargo bench or <a href="https://github.com/bheisler/criterion.rs">criterion</a> or something else specifically designed for benchmarking?</p><p>That once again has to do with the compiler’s tendency to precompute everything it can, and eliminate all code that doesn’t result in any changes to the output.</p><blockquote>If the return value of a function is never used, and the function doesn’t panic, the compiler will simply remove it!</blockquote><p>This is great for production code and terrible for benchmarks.</p><p>You can try to combat this by wrapping inputs and outputs in <a href="https://doc.rust-lang.org/stable/std/hint/fn.black_box.html">std::hint::black_box()</a>, but <a href="https://gendignoux.com/blog/2022/01/31/rust-benchmarks.html">it’s difficult to wrap all the things correctly, and it’s not clear which optimizations it inhibits, exactly</a>.</p><p>I am sidestepping all this by making a real binary that reads the inputs, and <strong>the inputs are supplied only when the program is actually run,</strong> so there’s no way for the compiler to precompute anything. It also <strong>prints the result,</strong> so the compiler cannot remove the fibonacci_vec function as dead code.</p><p>And having standalone binaries also makes inspecting the assembly and profiling easier, as you will see shortly!</p><p>On the flip side, I have to crank the Vec lengths way up to get easily measurable execution times, and this may not be representative of how these functions perform in on small Vecs due to <a href="http://igoro.com/archive/gallery-of-processor-cache-effects/">CPU cache effects</a>.</p><p>Now, back to experimenting with bounds checks…</p><h3>Just Use Iterators</h3><p>Experienced Rust users have probably been screaming at me about that the entire time they’ve been reading this article - up to this point, anyway!</p><p>Rust provides convenient <a href="https://doc.rust-lang.org/stable/book/ch13-02-iterators.html">iterators</a> that let you work with collections without worrying about bounds checks, off-by-one errors and the like.</p><p>They also let you express what you want to accomplish more clearly — as long as you can remember the names of all the iterators or always use an IDE that shows documentation, which is a pretty big catch if you ask me!</p><p>So let’s go ahead and rewrite our code using iterators. We need sliding windows over our Vec, and there’s a handy iterator for that called <a href="https://doc.rust-lang.org/stable/std/primitive.slice.html#method.windows">windows</a>, so let’s give it a shot:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b94efbc4155f11f447b047948785e9c9/href">https://medium.com/media/b94efbc4155f11f447b047948785e9c9/href</a></iframe><p>Uh oh! That doesn’t compile! The issue is that <a href="https://doc.rust-lang.org/stable/std/primitive.slice.html#method.windows">windows()</a> only gives us read-only slices, we cannot write through them. For most other iterators there is a corresponding _mut() version that gives mutable slices, but there is no windows_mut()! What gives?</p><p>Turns out windows_mut() cannot be implemented as a regular iterator in Rust, because if you were to .collect() it into say a Vec, you would end up with many <em>overlapping </em>and <em>mutable</em> slices — but Rust requires every part of memory to be mutable from only one place at a time!</p><p>What we’re looking for is called a so-called <em>streaming iterator</em> that cannot be .collect()ed, but there doesn’t seem to be such a thing in the standard library yet. So we’ll have to change our code a bit:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4433ce48a3b4fd23c0aa9a42072912ff/href">https://medium.com/media/4433ce48a3b4fd23c0aa9a42072912ff/href</a></iframe><p>This works, and you can use cargo asm to confirm that there are no bounds checks going on here. On Rust 1.65 it benchmarks slightly faster than our earlier attempt on my machine, by about 2%. On 1.66 it’s another big boost:</p><pre>$ hyperfine &#39;target/release/fibvec_naive_indexing 1000000000&#39; &#39;target/release/fibvec_clever_indexing 1000000000&#39; &#39;target/release/fibvec_iterator 1000000000&#39;<br>Benchmark 1: target/release/fibvec_naive_indexing 1000000000<br>  Time (mean ± σ):      3.530 s ±  0.056 s    [User: 1.452 s, System: 2.027 s]<br>  Range (min … max):    3.450 s …  3.616 s    10 runs<br> <br>Benchmark 2: target/release/fibvec_clever_indexing 1000000000<br>  Time (mean ± σ):      3.111 s ±  0.058 s    [User: 1.037 s, System: 2.038 s]<br>  Range (min … max):    3.039 s …  3.207 s    10 runs<br> <br>Benchmark 3: target/release/fibvec_iterator 1000000000<br>  Time (mean ± σ):      2.763 s ±  0.057 s    [User: 0.666 s, System: 2.078 s]<br>  Range (min … max):    2.686 s …  2.847 s    10 runs<br> <br>Summary<br>  &#39;target/release/fibvec_iterator 1000000000&#39; ran<br>    1.13 ± 0.03 times faster than &#39;target/release/fibvec_clever_indexing 1000000000&#39;<br>    1.28 ± 0.03 times faster than &#39;target/release/fibvec_naive_indexing 1000000000&#39;</pre><p>And it does provide a nice uplift on ARM as well:</p><pre>$ hyperfine &#39;target/release/fibvec_naive_indexing 1000000000&#39; &#39;target/release/fibvec_clever_indexing 1000000000&#39; &#39;target/release/fibvec_iterator 1000000000&#39;<br>Benchmark 1: target/release/fibvec_naive_indexing 1000000000<br>  Time (mean ± σ):      3.324 s ±  0.024 s    [User: 1.160 s, System: 2.154 s]<br>  Range (min … max):    3.285 s …  3.354 s    10 runs<br> <br>Benchmark 2: target/release/fibvec_clever_indexing 1000000000<br>  Time (mean ± σ):      3.257 s ±  0.022 s    [User: 1.112 s, System: 2.136 s]<br>  Range (min … max):    3.232 s …  3.297 s    10 runs<br> <br>Benchmark 3: target/release/fibvec_iterator 1000000000<br>  Time (mean ± σ):      2.968 s ±  0.025 s    [User: 0.782 s, System: 2.175 s]<br>  Range (min … max):    2.929 s …  3.011 s    10 runs<br> <br>Summary<br>  &#39;target/release/fibvec_iterator 1000000000&#39; ran<br>    1.10 ± 0.01 times faster than &#39;target/release/fibvec_clever_indexing 1000000000&#39;<br>    1.12 ± 0.01 times faster than &#39;target/release/fibvec_naive_indexing 1000000000&#39;</pre><p>Fortunately this isn’t some sort of iterator secret sauce — a program with <a href="https://github.com/Shnatsel/bounds-check-cookbook/blob/main/src/bin/fibvec_clever_indexing_alt.rs">the same structure but using indexing with optimizer hints</a> is just as fast.</p><p>But notice that we had to <strong>significantly change</strong> how we implement the computation! Iterators are very handy if you are writing code from scratch, and <strong>you totally should use them</strong> — but they can be a pain to retrofit into an existing code. And some patterns cannot be expressed with iterators at all!</p><blockquote><strong>Update:</strong> after the release of this article the standard library documentation has been <a href="https://github.com/rust-lang/rust/pull/106889/files">updated</a> with the instructions for emulating windows_mut(). An example calculating Fibonacci numbers can be found <a href="https://old.reddit.com/r/rust/comments/10edmjf/how_to_avoid_bounds_checks_in_rust_without_unsafe/j4u91of/?context=3">here</a>.</blockquote><p>And finally, I’ve used the for loop with an iterator here, but in this case the compiler <strong>can miss some “obvious” optimizations</strong>. If you instead use .foreach on the iterator, the compiler <a href="https://github.com/rust-lang/rust/issues/101814#issuecomment-1247184222">should optimize the code better</a>. This is especially relevant if you have a chain of iterator adapters, something like .skip().filter().take() or even longer.</p><p>So if you find yourself writing long iterator chains, it might be worth benchmarking it against an index-based implementation with optimizer hints, like the one I’ve described earlier. Or like the following…</p><h3>Put an assert in front of the hot loop</h3><p>Let’s make use of this function we’ve written.</p><p>Now that we have a Vec full of Fibonacci numbers, we can write a function that checks if another Vec also has Fibonacci numbers simply by comparing the two. If our Vec is small enough to fit into the <a href="https://en.wikipedia.org/wiki/CPU_cache">CPU cache</a>, this could be faster than doing the math over and over!</p><p>A naive implementation could look like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b16277c161cf59251d5a5e567b569fe4/href">https://medium.com/media/b16277c161cf59251d5a5e567b569fe4/href</a></iframe><p>Let’s check the assembly:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/dd65f78d2b53904d41e812829f5afed4/href">https://medium.com/media/dd65f78d2b53904d41e812829f5afed4/href</a></iframe><p>Oh no — the bounds checks are back! The is_fibonacci() function has them in the hot loop!</p><p>We <em>have</em> to<strong> </strong>check bounds here<strong>,</strong> because we don’t know the lengths of either of these slices in advance. It’s required for correctness! But what we <em>can</em> do is perform the bounds check<strong> only once </strong>outside the loop, instead of for every element, which will make the cost negligible.</p><p>Let’s make sure the sizes are the same <strong>before we enter the loop</strong>, and use the trick of iterating only up to .len() from earlier:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2d69bc4df8ddbf4acc30fbe2fba41334/href">https://medium.com/media/2d69bc4df8ddbf4acc30fbe2fba41334/href</a></iframe><p>Et voila, no more bounds checks inside the hot loop:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d55f3c69320ef3b0c0444cf8794b2a7c/href">https://medium.com/media/d55f3c69320ef3b0c0444cf8794b2a7c/href</a></iframe><p>That’s it, that’s all the assembly attributed to the indexing line now!</p><p>This can <a href="https://github.com/Shnatsel/bounds-check-cookbook/blob/main/src/bin/comparison_iterator.rs">also be achieved with an iterator</a>, but realistically you’ll j<a href="https://github.com/Shnatsel/bounds-check-cookbook/blob/main/src/bin/comparison_realistic.rs">ust use the </a><a href="https://github.com/Shnatsel/bounds-check-cookbook/blob/main/src/bin/comparison_realistic.rs">== operator to compare the slices</a>. The code <em>is</em> contrived - showing off the optimization technique is what’s important here.</p><p>I’ve <a href="https://github.com/image-rs/jpeg-decoder/pull/167">sped up the jpeg-decoder crate</a> using this approach, so check that out if you want to see applications to real-world code. There I used assert!s instead of slicing, but the principle is the same.</p><p>The great thing about this approach is that you <strong>cannot have too many</strong> assert!s —<strong> the redundant ones will be removed</strong> by the compiler, just like the bounds checks!</p><h3>Inlining propagates constraints across functions</h3><p>So you’ve used your shiny new is_fibonacci function for a while, and decided to split comparing elements into its own function for reuse:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/60fa936bec800bdda3ddbecde3a76175/href">https://medium.com/media/60fa936bec800bdda3ddbecde3a76175/href</a></iframe><p>And now the bounds checks are back! The elements_are_equal function is a separate entity and cannot make any assumptions about the way it is called (or at least when it has #[inline(never)] on it).</p><p><a href="https://matklad.github.io/2021/07/09/inline-in-rust.html">Inlining</a> is when the compiler copies the contents of the function to the place where it’s being called, instead of just putting a function call there. (Inlining is <a href="https://matklad.github.io/2021/07/09/inline-in-rust.html">its own rabbit hole</a> that goes pretty deep, just like <a href="http://igoro.com/archive/gallery-of-processor-cache-effects/">CPU cache</a>.)</p><p>We use #[inline(never)] on functions the assembly of which we want to view so that the function does not get inlined and become part of another function in the generated code. (While it is in theoretically possible to attribute the code to inlined functions, cargo asm doesn’t do that yet).</p><p>Instead we’re going to use #[inline(always)] for elements_are_equal() to make sure its contents are copied into is_fibonacci() and they are optimized together — getting us the benefits if them being separate in the source code, but a single entity for the optimizer, so that the knowledge of index constraints would be propagated across functions.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/55f2b08e8c9a1d493f595c11e3f73f6e/href">https://medium.com/media/55f2b08e8c9a1d493f595c11e3f73f6e/href</a></iframe><p>We’ve swapped #[inline(never)] for #[inline(always)] and the bounds checks should be gone! Let’s verify:</p><pre>$ cargo asm --rust --bin comparison_split_inline elements_are_equal</pre><pre>Error: No matching functions, try relaxing your search request</pre><p>Right, we can’t view the assembly of elements_are_equal() because it no longer exists in the assembly as a separate function.</p><p>But we can still check the assembly of is_fibonacci and verify that it worked! The bounds checks are gone again!</p><p>Out in the real world I’ve <a href="https://github.com/rust-random/rand/pull/960">sped up the </a><a href="https://github.com/rust-random/rand/pull/960">rand crate by 7%</a> with a few assert!s and an #[inline(always)] — the same techniques as shown here.</p><p>Let’s see how much of a difference this optimization actually made here:</p><pre>$ hyperfine --warmup 3 --min-runs 20 &#39;target/release/comparison_realistic 100000000 100000000&#39; &#39;target/release/comparison_naive 100000000 100000000&#39; &#39;target/release/comparison_clever 100000000 100000000&#39; &#39;target/release/comparison_iterator 100000000 100000000&#39;<br>Benchmark 1: target/release/comparison_realistic 100000000 100000000<br>  Time (mean ± σ):     729.8 ms ±  13.5 ms    [User: 193.8 ms, System: 532.5 ms]<br>  Range (min … max):   711.9 ms … 748.4 ms    20 runs<br> <br>Benchmark 2: target/release/comparison_naive 100000000 100000000<br>  Time (mean ± σ):     739.8 ms ±  12.8 ms    [User: 206.5 ms, System: 529.1 ms]<br>  Range (min … max):   725.9 ms … 761.7 ms    20 runs<br> <br>Benchmark 3: target/release/comparison_clever 100000000 100000000<br>  Time (mean ± σ):     736.2 ms ±  13.0 ms    [User: 210.0 ms, System: 521.7 ms]<br>  Range (min … max):   719.1 ms … 761.6 ms    20 runs<br> <br>Benchmark 4: target/release/comparison_iterator 100000000 100000000<br>  Time (mean ± σ):     734.6 ms ±  10.9 ms    [User: 201.8 ms, System: 528.3 ms]<br>  Range (min … max):   724.3 ms … 760.7 ms    20 runs<br> <br>Summary<br>  &#39;target/release/comparison_realistic 100000000 100000000&#39; ran<br>    1.01 ± 0.02 times faster than &#39;target/release/comparison_iterator 100000000 100000000&#39;<br>    1.01 ± 0.03 times faster than &#39;target/release/comparison_clever 100000000 100000000&#39;<br>    1.01 ± 0.03 times faster than &#39;target/release/comparison_naive 100000000 100000000&#39;</pre><p>Hm. Hardly any difference, this is below 1% and might as well be noise. I had to add warmup and crank up the number of runs to get past the noise.</p><p>Okay, we probably should have answered an important question about this function before we started optimizing it:</p><blockquote>Does this function even account for a large enough portion of the execution time to be worth optimizing?</blockquote><h4>Aside: Profiling</h4><p>If we make a function twice faster, but it only accounted for 2% of the execution time of the program, we’ve only sped up the program by 1%!</p><p>We can use a <strong>profiler</strong> to find where time is spent in the program, and so which function would be a good target for optimization.</p><p>Profiling languages that compile to native code, such as Rust, is remarkably poorly documented. There’s a multitude of tools in various states of disrepair, most of which only work on a single OS, so the landscape can be difficult to navigate and is filled with gotchas. So here’s a guide for doing this with modern tools!</p><p>As a teaser, here’s the result we’re going to get:</p><figure><a href="https://share.firefox.dev/3G3nCiJ"><img alt="Profiler UI" src="https://cdn-images-1.medium.com/max/992/1*dMmeVx3mK8QWoZ834-6gag.png" /></a></figure><p>Yes, I know it’s not very readable. <strong>Click it!</strong></p><p>See? It’s a whole interactive UI for viewing profiles, right in the browser! What you’re looking at is <a href="https://profiler.firefox.com/">Firefox Profiler</a> — which actually works in <em>any</em> browser, and it’s one the best profiler UIs I’ve ever used.</p><blockquote>The killer feature is sharing the results <strong>in two clicks.</strong></blockquote><p>I cannot overstate how awesome the sharing feature is, and how much easier it makes communicating performance results. If you include a profile in a bug report about performance, it saves <em>so much time</em> for both you and whoever is going to end up working on fixing it!</p><h4>Interpreting profiles</h4><p>What we’re looking at is a <a href="https://www.datadoghq.com/knowledge-center/distributed-tracing/flame-graph/"><strong>flame graph</strong></a>. Each horizontal bar represents a function, and it’s as wide as the function’s share of execution time.</p><p>The bars are stacked on top of each other; the bar on top is called by the function represented by a bar directly below it.</p><p>The yellow bars are in userspace, and the orange bars are in the kernel.</p><p>For example, I can tell that main calls fibonacci_vec which in turn calls into the kernel, which does something with “pages” — that must be referring to <a href="https://en.wikipedia.org/wiki/Page_(computer_memory)">memory pages</a>. We’re creating a Vec there, so this must be memory allocation.</p><p>So apparently 51% of the time of this program is spent allocating memory, and another 9% is spent deallocating it! Actually hyperfine was telling us about it earler that we spend a lot of time in the kernel — it reports user and sys times, with user being our program and sys being the kernel.</p><p>Note that the order of the bars in a flame graph is meaningless, it only shows the <strong>aggregate</strong> time spent in a given function. If you want to see how the program execution actually went instead of viewing aggregates, switch to the “Stack Chart” tab:</p><figure><a href="https://profiler.firefox.com/public/j4ses9f8ca5qb1774ph4fztcpc0kkrm55te2s7g/stack-chart/?globalTrackOrder=0&amp;thread=0&amp;timelineType=category&amp;v=8"><img alt="" src="https://cdn-images-1.medium.com/max/1003/1*8ruJjXaTMoYs3KznM3CvPg.png" /></a></figure><p>This shows that the calls into the kernel from fibonacci_vec are spread evenly across the execution time. Apparently the kernel gradually provisions memory instead of serving us a big allocation up front when we request it. The deallocation at the end, however, happens in a single chunk.</p><p>Modern operating systems provision memory only when it’s actually being written to, which is what we’re seeing here. That’s also why you can try allocate 100 TB of RAM and the initial allocation call will succeed — but your process will get killed if you try to actually write to <em>all</em> of that.</p><h4>Capturing execution profiles</h4><p>Here’s how to create one of those beautiful graphs for your own code.</p><p>First off, the profiler needs debug symbols to attribute chunks of machine code to functions. To enable debug symbols, add this to your Cargo.toml:</p><pre>[profile.release]<br>debug = true</pre><p>If you don’t do this, the compiler will generate very limited debug symbols, which will in turn provide very little visibility — although you’ll still get <em>some</em> info even if you haven’t done this.</p><p>The other steps are unfortunately platform-specific:</p><h4>Profiling on macOS &amp; Linux</h4><p>There’s a convenient profiler that shows results in <a href="https://profiler.firefox.com/">Firefox Profiler</a>:</p><pre>cargo install samply<br>samply record target/release/comparison_naive 100000000 100000000</pre><p>This will record the profile and open the web browser with the results.</p><h4><strong>Seeing into the OS kernel (on Linux)</strong></h4><p>There is one thing Samply cannot do yet, and that’s reporting on where the time is spent inside the kernel. This is only needed if Samply is showing wide orange bars on the flame graph, and you want to understand what they stand for.</p><p>In this case use perf script to visualize the profile. It is slower and less accurate, but lets you<strong> see into the kernel</strong> as well:</p><pre>sudo perf script -F +pid &gt; profile.perf</pre><p>Now go to <a href="https://profiler.firefox.com">profiler.firefox.com</a> and upload the resulting profile.perf file, and you should see the same profiling UI showing the results.</p><p>The profiles I showed earlier were captured using this method.</p><h4>Profiling on Windows</h4><p>Here’s the list of good free profiling tools available on Windows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1/1*jjG4tHxhjtc-WzEBHR3gNw.png" /></figure><p><em>(This section was intentionally left blank by Microsoft).</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1/1*jjG4tHxhjtc-WzEBHR3gNw.png" /></figure><p><a href="https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html">Intel VTune</a> and <a href="https://www.amd.com/en/developer/uprof.html">AMD uProf</a> are free of charge, but are not particularly great — the UIs are clunky, and it may be difficult to get them to work in the first place (e.g. you may have to change some settings in the BIOS).</p><p>Fortunately, you can just use the Linux instructions in <a href="https://learn.microsoft.com/en-us/windows/wsl/install">WSL2</a> or any other VM with Linux.</p><blockquote><strong>Update:</strong> after valiant reverse-engineering of barely-documented Windows APIs, samply now also works on Windows!</blockquote><h4>The results</h4><p>So the question we wanted to answer was:</p><blockquote>Does is_fibonacci() even account for a large enough portion of the execution time to be worth optimizing?</blockquote><p>If you <a href="https://share.firefox.dev/3G3nCiJ">open my profile</a>, you can see in the “Flame Graph” or “Call Tree” views that the program spent 13% of the time in is_fibonacci, and if you subtract all the kernel time from fibonacci_vec it accounts for 23% of the time.</p><p>Since the execution times of these two functions are roughly comparable, it seems that eliminating bounds checks in is_fibonacci has indeed sped up this function very little.</p><p>To reiterate, seeing <strong>only a slight boost</strong> from eliminating bounds checks is <strong>the typical outcome</strong>. It’s the 15% improvement that’s the anomaly!</p><p>With that out of the way, let’s look at one final technique for dealing with bounds checks.</p><h3>What if I know literally nothing about the index?</h3><p>So far we’ve relied on some knowledge we had about the constraints of our index, or had a loop where we could check bounds once before it instead of doing it on every iteration. But what if…</p><p>What if you know <em>absolutely nothing</em> about the index?</p><p>Let’s say the index comes from untrusted user input, and you can assume absolutely nothing about it. You <em>have</em> to perform a bounds check. There is no other way. But the function is rather hot… what if you could speed it up? It might not gain you much, but then again it might, and it’s just one bounds check and how inescapable can it really be?!</p><p>You end up thinking about your conundrum at night. This bounds check haunts your dreams. Taunting you. You wake up in cold sweat, and resolve to do something about it. You try panic = abort in Cargo.toml, and maybe it helps or maybe not, it could be just noise and benchmarks are a lie and oh god why did you decide to go into programming?!</p><p>Fear not, for I shall deliver you from this predicament.</p><p>We’re going to create. <strong>The.</strong> <strong>Cheapest. Possible. Bounds. Check.</strong></p><p>Observe.</p><h3>The. Cheapest. Possible. Bounds. Check.</h3><p>So you have your lookup table for Fibonacci numbers, and a function nth_fibonacci() that simply performs a lookup by index.</p><p>Easy peasy, except the function <em>must</em> perform a bounds check because its inputs are completely unpredictable:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b7197348a5f2435676e600d85e10348d/href">https://medium.com/media/b7197348a5f2435676e600d85e10348d/href</a></iframe><p>Look at the assembly! Just, ugh. A single line, creating so much of it:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d1ae52435c583280b2721ba8c5f81230/href">https://medium.com/media/d1ae52435c583280b2721ba8c5f81230/href</a></iframe><p>Since we know nothing about the index, we <em>have</em> to perform a bounds check. But we can do it <strong>on our own terms.</strong></p><p>The branching instruction created by ifs or can be quite expensive, as far as CPU instructions go; we’ve seen its impact on benchmarks already. What if there is <strong>a cheaper alternative?</strong></p><p>Let’s recall why we need bounds checks in the first place: we want to make sure all lookups are confined to the Vec or slice we’re working with - because if they aren’t, we get <a href="https://en.wikipedia.org/wiki/Heartbleed">Hearbleed</a> or worse. But a panic on an invalid access that usual bounds checks create is not <em>strictly</em> mandatory; as long as all accesses are confined to the slice we’re working with, we’re good.</p><p>So technically we could use the <a href="https://en.wikipedia.org/wiki/Modulo_operation">modulo</a> operator, written as %, to confine all accesses to the slice, like this:</p><pre>fibonacci[i % fibonacci.len()]</pre><p>This won’t return an error on an invalid access like a regular bounds check would, it will just silently return an incorrect result… but is it cheaper?</p><p>Unfortunately the % operator is also an expensive instruction, but there is one <strong>very special case</strong> we can take advantage of. If the divisor <strong>is a constant</strong> and is known to be <strong>a power of two,</strong> any compiler worth its bytes will optimize it into a <a href="https://en.wikipedia.org/wiki/Bitwise_operation#AND">bitwise AND</a>, and bitwise operations are very cheap.</p><p>Our lookup table only holds 100 numbers, which is not a power of two, but we can <strong>extend it to the nearest power of two</strong> with dummy values to make it all work out — we’ve already accepted wrong results on invalid accesses, so we might as well go all the way!</p><p>And so our code becomes…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/893d7c9ef7e08d21e4f990e042be492b/href">https://medium.com/media/893d7c9ef7e08d21e4f990e042be492b/href</a></iframe><p>Let’s check the assembly…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8f81be3ffd260ab5ac4dae96cb2f7356/href">https://medium.com/media/8f81be3ffd260ab5ac4dae96cb2f7356/href</a></iframe><p><strong><em>Yes!</em></strong></p><p>We’ve done it!</p><p><strong>This is it! The cheapest possible bounds check!</strong></p><p>We have inflated the memory usage slightly and no longer report errors on invalid accesses, but we’ve achieved the goal of speeding up our function, <strong>even when we could assume nothing about the index.</strong></p><p>Whether it’s worth the trade-offs… that’s for you and your benchmarks to decide.</p><h3>Anti-patterns</h3><p>Finally I want to take a look at some patterns I keep seeing people write over and over, even though they hurt them instead of helping them.</p><h4>debug_assert! instead of assert!</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5714d4e818f2bb292a18dc65c2aaba68/href">https://medium.com/media/5714d4e818f2bb292a18dc65c2aaba68/href</a></iframe><p>debug_assert! is removed in release mode. Ironically, this makes the code <strong>slower</strong>: the compiler now cannot assume anything about the length of the input, and has to insert all three bounds checks in release mode!</p><p><strong>Do this instead:</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a402cc2b72f13e51aa8ebc5f043ff144/href">https://medium.com/media/a402cc2b72f13e51aa8ebc5f043ff144/href</a></iframe><p>As a rule of thumb, assert!s checking lengths help performance instead of hindering it. They are very difficult to overdo either — any redundant ones will be removed by the optimizer, just like bounds checks!</p><p>If in doubt, use assert! instead of debug_assert!.</p><p>Or, if you want to be super-duper extra sure you’re not doing extra work:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/defc83d20c37f979bd168b1f0e3dd885/href">https://medium.com/media/defc83d20c37f979bd168b1f0e3dd885/href</a></iframe><p>This also only performs one bounds check instead of three.</p><h4>Unsafe indexing by a constant value</h4><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d027e6d376ef57bd4dd29530cb78e47/href">https://medium.com/media/0d027e6d376ef57bd4dd29530cb78e47/href</a></iframe><p>This unsafe <strong>doesn’t</strong> make your code any faster. In fact, in debug mode this is <strong>slower </strong>than the safe version because get_unchecked creates function call overhead, and optimizations that would remove it are disabled.</p><p><strong>Do this instead:</strong></p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9d24fdda8e5e53fc489b958588c4ece9/href">https://medium.com/media/9d24fdda8e5e53fc489b958588c4ece9/href</a></iframe><p>The compiler <strong>always</strong> optimizes indexing by a constant into a slice of known length in release mode. And in debug mode this is even faster than unsafe.</p><h3>Parting thoughts</h3><p>Hopefully this menagerie of techniques for dealing with bounds checks will serve you well, and you will never have to resort to get_unchecked and risk creating code execution vulnerabilities in your code.</p><p>If you would like to practice these techniques, you can <a href="https://github.com/search?l=Rust&amp;q=get_unchecked&amp;type=Code">search Github for unsafe indexing</a> and see if you can convert it into safe code without regressing performance.</p><p>Happy hacking!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f65e618b4c1e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The simpler alternative to GCC-RS]]></title>
            <link>https://shnatsel.medium.com/the-simpler-alternative-to-gcc-rs-90da2b3685d3?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/90da2b3685d3</guid>
            <category><![CDATA[compilers]]></category>
            <category><![CDATA[rustlang]]></category>
            <category><![CDATA[gcc]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Sun, 30 May 2021 14:07:13 GMT</pubDate>
            <atom:updated>2021-05-30T17:07:33.326Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>The <a href="https://github.com/Rust-GCC/gccrs"><strong>GCC-RS</strong></a> project, which can be summed up as “Rewrite the Rust compiler in C++”, got a bit of media attention lately. In this post I’ll try to convince you that <strong>all the stated benefits</strong> of it can be achieved <strong>without necessitating a rewrite </strong>by leveraging <a href="https://github.com/antoyo/rustc_codegen_gcc"><strong>rustc_codegen_gcc</strong></a> instead.</p><p><em>All of the opinions expressed in this article are my own. They do not represent the opinions of any organizations I may be part of.</em></p><p><em>I am not directly affiliated with any of the projects discussed here.</em></p><h3>Background: LLVM vs GCC</h3><p><a href="https://github.com/rust-lang/rust">The official Rust compiler</a> currently uses <a href="https://en.wikipedia.org/wiki/LLVM">LLVM</a> for <a href="https://en.wikipedia.org/wiki/Code_generation_(compiler)">code generation</a>. LLVM is an open-source compiler and code generation library competing with <a href="https://en.wikipedia.org/wiki/GNU_Compiler_Collection">GCC</a>, the other major open-source compiler stack.</p><p>As a code generator, GCC has several advantages over LLVM:</p><ol><li>GCC can produce code that runs 10% or so faster <a href="https://www.phoronix.com/scan.php?page=article&amp;item=gcc10-clang10-x86&amp;num=1">on some x86 hardware</a> (but <a href="https://www.phoronix.com/scan.php?page=article&amp;item=gcc11-clang12-epyc7763&amp;num=1"><em>not</em> <em>all</em> x86 hardware</a>), at least when compiling C and C++</li><li>GCC supports more CPU architectures. LLVM already supports all desktop or server-grade CPUs manufactured in the last 15 years, but GCC also supports some hobbyist retrocomputing architectures, such as <a href="https://en.wikipedia.org/wiki/PA-RISC">HP PA</a>.</li></ol><p>So it would make sense to allow using GCC as the code generator when compiling Rust programs.</p><h3>Why GCC-RS?</h3><p>GCC-RS intends not only to use GCC for code generation, but also reimplement the <em>entire rest of the Rust compiler</em> from scratch, in C++.</p><p>For reference, the official Rust compiler is written in Rust. Rewriting Rust code in C++ seems a bit backwards. So why are they doing this?</p><p><a href="https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions#benefits">The FAQ for the project</a> lists the following benefits of GCC-RS:</p><h4>Support for more CPU architectures</h4><p>That’s true! But the official Rust compiler is not nailed down to LLVM. In fact, it supports <a href="https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/codegen-backend.html">pluggable code generation backends</a>. (If you’re not sure what code generation is or what other parts are there in the Rust compiler, <a href="https://jason-williams.co.uk/a-possible-new-backend-for-rust">read this</a>).</p><p>And <a href="https://github.com/antoyo/rustc_codegen_gcc">rustc_codegen_gcc</a>, you guessed it, <strong>simply plugs GCC into the existing Rust compiler</strong> as a code generation backend. This allows compiling code for all the architectures supported by GCC, <strong>without rewriting the entire rest of the compiler from scratch.</strong></p><h4><strong>Cross-language LTO</strong></h4><p>In order to use <a href="https://en.wikipedia.org/wiki/Interprocedural_optimization">link-time optimization</a> (LTO) across C and Rust, you need to use the same code generation stack in both C and Rust. Aside of producing smaller binaries and slightly faster code, LTO is also a prerequisite for <a href="https://clang.llvm.org/docs/ControlFlowIntegrity.html">CFI</a>, a new exploit mitigation technique.</p><p>However, this <strong>also would work perfectly fine with </strong><a href="https://github.com/antoyo/rustc_codegen_gcc"><strong>rustc_codegen_gcc</strong></a>.</p><p>And besides, cross-language LTO is <a href="https://blog.llvm.org/2019/09/closing-gap-cross-language-lto-between.html">already possible</a> with the LLVM backend, provided you’re using the LLVM-based Clang compiler for C code. Firefox now <a href="https://twitter.com/eroc/status/1152351944649744384">uses it in production on all platforms</a>.</p><p>The <a href="https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions#benefits">GCC-RS FAQ</a> lists Linux as the motivating example. Ironically, Linux <a href="https://www.phoronix.com/scan.php?page=news_item&amp;px=Linux-5.12-Clang-LTO-Merged">supports LTO with LLVM but <em>not</em> GCC</a>!</p><h4><strong>GCC Plugins</strong></h4><blockquote>Existing GCC plugins such as those in the Linux Kernel project should be reusable since they target GIMPLE in the middle-end.</blockquote><p>Even ignoring how weird and niche this use case is,<a href="https://github.com/antoyo/rustc_codegen_gcc"> rustc_codegen_gcc</a> also emits GIMPLE and would work just as well.</p><h4>Bootstrapping</h4><p>The Rust compiler is written in Rust. That presents a problem for CPU architectures that don’t have a Rust compiler built for them yet. It’s a chicken-and-egg problem, and resolving it is called “bootstrapping”.</p><p>To bootstrap a C compiler, typically you’d write a super simple C compiler in assembly, which you use to compile a bit more advanced C compiler written in C, which you then use to compile an early version of GCC, use that to compile a slightly newer GCC, and so on until you catch up to the latest version.</p><p>If you need C++ (and latest GCC is written in C++, so you do need it), you do the same trick and use a simple C++ compiler written in C to get the chain going. Same for any other language, really.</p><p>The Rust bootstrap chain is quite long because you need to get from C to OCaml and then compile pre-release Rust to compile Rust 1.0 to compile Rust 1.1 to compile Rust 1.2 and so on until you catch up to 1.53 (or whatever the latest version is when you’re reading this). So if you can have a Rust compiler written in C++ that compiles 1.53 directly, you can save yourself some time.</p><p><strong>So GCC-RS could help with this, right?</strong></p><p>Not really. In reality <strong>you only need to walk the entire chain on <em>one</em> architecture. </strong>Then you can use your fully-functional Rust compiler on e.g. x86 to build a compiler for ARM, HP PA or whatever else you might need. This is called <a href="https://en.wikipedia.org/wiki/Cross_compiler">cross-compilation</a>, and is fully supported by Rust.</p><p>And shortening the chain on one architecture <strong>is a solved problem.</strong></p><p>You see, you don’t need the full-blown compiler with all the validation and error messages and whatnot, you just need it to compile things correctly. That’s what <a href="https://github.com/thepowersgang/mrustc"><strong>mrustc</strong></a> is: a minimal Rust compiler written in C++ designed for bootstrapping and nothing else. It lets you bootstrap from C++ on x86 and cross-compile to any architecture from there.</p><h3>Other considerations</h3><p>As you can see, <strong>every single benefit</strong> <a href="https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions#benefits">that GCC-RS lists</a> can be provided by <a href="https://github.com/antoyo/rustc_codegen_gcc">rustc_codegen_gcc</a> and <a href="https://github.com/thepowersgang/mrustc">mrustc</a>, <strong>without rewriting the compiler from scratch </strong>and at dramatically lower development and maintenance costs<strong>.</strong></p><p>But what if they forgot to include some crucial benefit in the FAQ? Here are some other points people bring up in relation to GCC-RS:</p><p><strong><em>Isn’t having multiple implementations a good thing?</em></strong></p><p>Well, maybe? It <a href="https://www.reddit.com/r/rust/comments/njckp1/rustc_codegen_gcc_can_now_run_libcores_tests_and/gz9nzgk/">didn’t work out for C/C++</a>, but perhaps we can learn from that and do better. Still, the benefits of this are rather nebulous and I’m not convinced that they justify the costs.</p><p><strong><em>Wouldn’t having an alternative implementation help specify the language?</em></strong></p><p>Yes, that’s what <a href="https://github.com/rust-lang/miri/">miri</a> is for. You feed it some Rust code and it tells you if it’s valid and whether your unsafe code triggers any undefined behavior or not.</p><p><strong><em>Isn’t Rust vulnerable to the </em></strong><a href="https://wiki.c2.com/?TheKenThompsonHack"><strong><em>Ken Thompson hack</em></strong></a><strong><em>?</em></strong></p><p>No, it is not. The “trusting trust” problem is already solved by <a href="https://github.com/thepowersgang/mrustc">mrustc</a>.</p><p><a href="https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions#why-was-adding-gcc-backend-for-rustc----rejected"><strong><em>libgccjit.so is annoying to package for Linux!</em></strong></a></p><p>Speaking as a former Debian maintainer — yes, it is mildly annoying, but it has to be done anyway, so GCC-RS doesn’t help here.</p><p><strong><em>rustc_codegen_gcc relies on </em></strong><a href="https://blog.rust-lang.org/2016/04/19/MIR.html"><strong><em>MIR</em></strong></a><strong><em> which is unstable!</em></strong></p><p>Keeping up with the changes to <a href="https://blog.rust-lang.org/2016/04/19/MIR.html">MIR</a> is much easier than keeping up with the changes to the entire language. And that’s <em>ignoring</em> the enormous up-front investment that a full compiler rewrite would entail.</p><p><a href="https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions#why-not-write-the-front-end-in-rust-using-the-existing-compiler"><strong><em>Supporting multiple GCC versions in rustc_codegen_gcc will be difficult!</em></strong></a></p><p>Yeah, so just don’t do it! Every release of <a href="https://github.com/Rust-GCC/gccrs">GCC-RS</a> targets a single specific GCC version to avoid this issue. <a href="https://github.com/antoyo/rustc_codegen_gcc">rustc_codegen_gcc</a> could trivially do the same.</p><p><strong><em>Doesn’t GCC-RS reuse the borrow checker written in Rust?</em></strong></p><p>Not the production-ready borrow checker, but <a href="https://github.com/rust-lang/polonius">the experimental one.</a></p><p>But yes, it does! They’ve reused 5,000 lines of Rust. Only 465,000 lines to go!</p><p><strong><em>I hear GCC-RS has funding!</em></strong></p><p>Yes, <a href="https://opensrcsec.com/open_source_security_announces_rust_gcc_funding">one full-time developer and a part-time project manager</a> for one year. For rewriting the entire Rust compiler from scratch, that’s underwhelming.</p><p>The company providing the funding <a href="https://opensrcsec.com/open_source_security_announces_rust_gcc_funding">mentioned</a> that they’ve failed to get anyone else interested in funding GCC-RS. Coincidence? I think not!</p><h3>Conclusion</h3><p>I believe the rewrite of Rust compiler in C++ that the <a href="https://github.com/Rust-GCC/gccrs">GCC-RS</a> project is attempting is <strong>completely unjustified</strong>. The gargantuan effort required to make it a reality would be better spent elsewhere.</p><p>These projects will provide <a href="https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions#benefits"><strong>all the listed benefits</strong></a> at a dramatically lower cost:</p><ul><li><a href="https://github.com/antoyo/rustc_codegen_gcc">rustc_codegen_gcc</a> for portability to obscure platforms</li><li><a href="https://github.com/rust-lang/miri/">miri</a> and <a href="https://ferrous-systems.com/blog/sealed-rust-the-pitch/">ferrocene</a> for specifying behavior</li><li><a href="https://github.com/thepowersgang/mrustc">mrustc</a> for cutting down on bootstrapping time</li></ul><p>Ultimately, <a href="https://github.com/Rust-GCC/gccrs">GCC-RS</a> might provide some value that I’m not seeing. But if you care about portability to obscure platforms, language specification or bootstrapping time, <strong>I encourage you to support one of these projects </strong>rather than GCC-RS. They should provide a far greater return on investment.</p><p>I’m putting my money where my mouth is and will be supporting <a href="https://github.com/antoyo/rustc_codegen_gcc">rustc_codegen_gcc</a> on Github Sponsors starting in June.</p><p><a href="https://www.reddit.com/r/rust/comments/noby1t/t"><em>Discuss this article on Reddit</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=90da2b3685d3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Smoke-testing Rust HTTP clients]]></title>
            <link>https://shnatsel.medium.com/smoke-testing-rust-http-clients-b8f2ee5db4e6?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/b8f2ee5db4e6</guid>
            <category><![CDATA[software-testing]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[reliability]]></category>
            <category><![CDATA[https]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Thu, 16 Jan 2020 19:42:35 GMT</pubDate>
            <atom:updated>2020-06-08T15:34:29.927Z</atom:updated>
            <content:encoded><![CDATA[<p>Back in 2014 I was fetching frontpages of the top million websites to scan them for a particular vulnerability. Not only have I found 99,9% websites to be vulnerable to a trivial attack, I’ve also found that curl command was randomly crashing with a segmentation fault, indicating a likely vulnerability in <a href="https://curl.haxx.se/libcurl/">libcurl</a> — the HTTP client library that the whole world seems to depend on.</p><p>By that time I was already disillusioned in the security of software written in C and the willingness of maintainers to fix it, so I never followed up on the bug. However, this year I decided to repeat the test with software written in a language that’s less broken by design: Rust.</p><p>Here’s how 7 different HTTP clients fared.</p><p><strong><em>Update:</em></strong><em> this article was published in January 2020 and no longer represents the current state of affairs.</em></p><h3>Baseline</h3><p>Before we start talking about specific software, let’s define what we’re comparing it against. I’m going to hold all software to the standard of being actually dependable and maybe even secure — which is an incredibly high bar that the vast majority of software currently in use fails to meet. To wit:</p><p><a href="https://en.wikipedia.org/wiki/Linux_kernel">Linux kernel</a> gets <a href="https://events19.linuxfoundation.org/wp-content/uploads/2017/11/Syzbot-and-the-Tale-of-Thousand-Kernel-Bugs-Dmitry-Vyukov-Google.pdf">thousands</a> of potentially exploitable memory safety bugs per year that are <a href="https://events19.linuxfoundation.org/wp-content/uploads/2017/11/Syzbot-and-the-Tale-of-Thousand-Kernel-Bugs-Dmitry-Vyukov-Google.pdf">largely ignored</a> — or at best silently fixed without any kind of security announcement, so the fixes don’t get into Linux distributions and are then found <a href="https://googleprojectzero.blogspot.com/2019/11/bad-binder-android-in-wild-exploit.html">powering exploits in the wild</a>.</p><p>libcurl is fairly benign by comparison with only <a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=proj&amp;colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&amp;num=100&amp;q=-status%3AWontFix%2CDuplicate%20-Infra%20proj%3Acurl&amp;can=1">9 publicly reported security bugs per year</a> <a href="https://www.cvedetails.com/product/25084/Haxx-Curl.html?vendor_id=12682">(no matter how you count</a>). Which is, you know, a new exploit every couple of months or so. But that’s just the vulnerabilities that were properly disclosed and widely publicized; dozens more are silently fixed every year, so all you need to do to find an exploit is look through the commit log. Don’t believe me? <a href="https://github.com/curl/curl/commit/68ffe6c17d6e44b459d60805813f646d244a186b">Here is a probably-exploitable bug</a> that is still unpatched in the latest release of libcurl. You’re welcome.</p><p>And in case you’re wondering, this trick works for <em>every</em> open-source C library. Although if you want the exploit to work for the next 10 years, <a href="https://seclists.org/fulldisclosure/2013/Nov/83">look at the bug tracker instead</a>.</p><p>Oh, and all of that was just for libcurl <em>itself.</em> Underneath it there has to be a <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">TLS</a> implementation, which is usually OpenSSL.</p><p><a href="https://en.wikipedia.org/wiki/OpenSSL">OpenSSL</a> is infamous for its <a href="http://heartbleed.com/">Heartbleed</a>, but had <a href="https://www.cvedetails.com/product/383/Openssl-Openssl.html?vendor_id=217">numerous other bugs</a> and keeps getting more. The codebase has a custom reimplementation of most standard library functions, which makes it <a href="https://en.wikipedia.org/wiki/LibreSSL#Memory-related">intractable</a> to security analysis or exploit mitigation techniques. The quality of the code and documentation is said to be such that if there were a state agency program to sabotage publicly available cryptography, <a href="https://www.youtube.com/watch?v=fwcl17Q0bpk">OpenSSL would be its crown jewel</a>. There are even <a href="https://en.wikipedia.org/wiki/LibreSSL">multiple</a> <a href="https://boringssl.googlesource.com/boringssl/">forks</a> trying to fix OpenSSL that have failed to gain wide adoption.</p><p>I could go on like that about almost any widely used piece of C code. We live in a world where <a href="https://medium.com/message/everything-is-broken-81e5f33a24e1">all software is broken</a>. And while I wish maintainers of some projects would be more diligent, it would not fix the underlying problems: that complexity of practical codebases exceeds the human ability to reason about it, and that <em>humans inevitably make mistakes.</em> When you’re writing software in a memory-unsafe language such as C, any trivial mistake can lead to a security vulnerability. This makes writing secure software in C about as easy as performing appendectomy on yourself. <a href="https://en.wikipedia.org/wiki/Leonid_Rogozov">This one guy did it once in Antarctica</a>, why can’t you?</p><p>Even if you don’t care about security, every one of those is a really tricky reliability issue too — even the non-exploitable ones! That’s the kind of issues that get you paged in the middle of the night to deal with it in production and then completely fail to reproduce in a test environment. The best kind.</p><blockquote>“Hopelessly broken” is the baseline I will be comparing software against.</blockquote><p>Yet there is still hope: C is no longer the only language you can write performant and reusable software in. <a href="https://www.rust-lang.org/">Rust</a> is a new kid on the block that does everything that C can, and exactly as fast, but makes computers perform all the safety checks instead of requiring humans to think about them. The vulnerabilities that plague C codebases are <em>impossible</em> in Rust!</p><p>…unless you explicitly opt in to unsafe operations for some parts of the code, at which point you’re effectively back to the good old C in those places. But at least you can do that rarely and only when you <em>really</em> need to. Right?</p><h3>Methodology</h3><p>The test I ran was the simplest possible workload imaginable that curl has already failed: read the URL from the command line, fetch it and exit. No content parsing, no async I/O, no connection reuse, no authentication, nothing. Just the simplest possible thing in a real-world setting.</p><p>The default TLS backend was used for every library. The binary using each library (with code looking roughly like <a href="https://gist.github.com/deltaphc/2949ed292c7d1169e744e5ffa7fd0687">this</a>) was built with <a href="https://github.com/japaric/rust-san">Address Sanitizer</a> so that we’d notice a memory error if it happens instead of hoping the OS would suspect something is wrong and kill the process.</p><p>The list of top million websites is <a href="https://blog.majestic.com/development/majestic-million-csv-daily/">from Majestic-12</a>. I’ve used 50 concurrent connections, which is fairly conservative — you can have way more than that on a mid-range desktop, let alone a server, but it should prevent us from being mistaken for a DDoS attack. One such run takes at about 8 hours with HTTP connection timeout set to 5 seconds.</p><p>I’ve used Google Cloud to run this (which conveniently provides $300 free credit), but it should also work from any regular server or public VPN. I do not recommend doing this from a plain old consumer ISP without protection — they tend to frown upon tons and tons of HTTPS connections.</p><p>I also briefly looked through the dependencies of each client to get an idea of the amount of unsafe code it relies on and what kind of failure modes I can expect.</p><h3>reqwest</h3><p><a href="https://crates.io/crates/reqwest">reqwest</a> is the premier Rust HTTP client. Its number of downloads on <a href="http://crates.io">crates.io</a> leaves everything else in the dust. It rides on top of pure-Rust HTTP stack (with the notable exception of relying on OpenSSL by default, although <a href="https://github.com/ctz/rustls">rustls</a> is an option) and it just had a new major release that migrated it to futures-0.3 to support async/await.</p><p>First things first: <strong>it didn’t segfault!</strong></p><p>I am actually impressed because I had really low expectations going into this. <a href="https://github.com/anderejd/cargo-geiger">cargo-geiger</a> output on reqwest’s dependency tree does not instill confidence, and it relies on the HTTP stack that contains <a href="https://github.com/seanmonstar/httparse/issues/58">copies of standard library functions but with safety checks disabled</a> and where something labeled as “A set of types for representing HTTP requests and responses” also contains a <a href="https://github.com/hyperium/http/blob/master/src/header/map.rs">bespoke HashMap implementation</a> with 1500 LoC, 32 unsafe blocks and its own DoS protection because “it’s faster”, without ever mentioning any of that in the README. Plus that code seems to predate migration of std::HashMap to a faster implementation, so it’s not clear if all of that custom code is even useful anymore; could be harmful for all I can tell, since I couldn’t find any benchmarks comparing it against the standard library.</p><blockquote>I can’t help but wonder how many more bespoke unsafe primitives lurk in that HTTP stack.</blockquote><p>Writing bespoke unsafe code is a bad idea for the same reason why writing anything important in C is a bad idea: all human-written code has bugs, but when you have bugs in unsafe code they tend to be exploitable vulnerabilities<em>.</em> The aforementioned HashMap code was written by humans, so it is no exception:</p><ol><li><a href="https://github.com/hyperium/http/issues/354">https://github.com/hyperium/http/issues/354</a></li><li><a href="https://github.com/hyperium/http/issues/355">https://github.com/hyperium/http/issues/355</a></li></ol><p>The maintainers have not filed <a href="https://github.com/RustSec/advisory-db">security advisories</a> for these two bugs despite my call to do so (granted, you need some rather unusual client code to trigger them), but at least they were taken seriously and fixed <em>within days,</em> and that’s is already <em>incredibly</em> responsible by C standards.</p><p>I’m not sure why this HTTP stack is not getting a publicized exploit every couple of months. Are exploits truly more rare than that? Or is it because nobody’s looking for them? Or perhaps they’re just getting silently fixed and we simply never learn about them?</p><p>Anyway, reqwest didn’t segfault on a basic smoke test, which beats the state of the art from 5 years ago. It didn’t really <em>work</em> though. 6% of the time it downloaded and printed the data, <strong>then hung.</strong> I had to wrap my test binary in timeout to keep things going.</p><p>That hang turned out to be a <a href="https://github.com/seanmonstar/reqwest/issues/746">known deadlock</a>. It’s not really surprising that it happened because the Rust async/await ecosystem is so immature, with async/await being stabilized literally two months ago and <a href="https://github.com/rust-lang/rust/issues/66544">even the compiler itself failing to uphold its safety guarantees</a> right now. Plus the bug easily could be in some dependency, not in reqwest itself. What <em>is</em> surprising to me is that they issued a new stable release with a known deadlock.</p><p>The previous release (0.9 series) has been in use for a while, so it shouldn’t have such glaring bugs. But if you don’t need to have thousands of HTTP connections open at the same time, use something simpler. Like, <em>way</em> simpler, without any async in it.</p><h3>ureq</h3><blockquote>Minimal request library in rust.</blockquote><blockquote><strong>Motivation: </strong>Minimal dependency tree, Obvious API</blockquote><p>This, this is what my bitter, jaded eye likes to see.</p><p><a href="https://crates.io/crates/ureq">ureq</a> does not do any fancy cooperative multitasking or async I/O — just plain old blocking I/O that you can stuff into threads if you want concurrency.</p><p>You won’t be pulling your hair trying to catch a deadlock that happens on one request out of 100,000. <a href="https://lucumr.pocoo.org/2020/1/1/async-pressure/">Properly handling backpressure</a> is a breeze. And the threaded architecture scales to a few hundred concurrent connections just fine. <em>This is what the go-to HTTP client should look like.</em> The use of async I/O on the client should be a weird thing that you resort to for niche use cases, not the default.</p><p>According to <a href="https://github.com/anderejd/cargo-geiger">cargo-geiger</a>, ureq relies on 10 times less unsafe Rust than reqwest, and the only scary thing in its dependency tree is <a href="https://crates.io/crates/smallvec">SmallVec</a> — which, granted, is <a href="https://rustsec.org/advisories/RUSTSEC-2018-0003.html">still</a> <a href="https://rustsec.org/advisories/RUSTSEC-2019-0009.html">kinda</a> <a href="https://rustsec.org/advisories/RUSTSEC-2019-0012.html">scary</a>, but at least it’s the <em>only</em> such thing in there. Plus <a href="https://github.com/unicode-rs/unicode-normalization/commit/09091a3f313cfce5c9b08f3d6c376a93e02b080f">the subset of SmallVec API actually in use</a> looks easy enough to replace with safe code. I’ve tried swapping SmallVec for the 100% safe <a href="https://github.com/Lokathor/tinyvec">TinyVec</a> and there was <a href="https://pastebin.com/zJnMAGrk">no difference in performance</a>, so maybe I should open a PR for that.</p><p>ureq also decisively ditches OpenSSL for <a href="https://github.com/ctz/rustls">rustls</a>. Using rustls may or may not be a good idea depending on what exactly you’re doing — <a href="https://github.com/ctz/rustls/issues/189">it has <em>not</em> been audited</a> for attacks such as <a href="https://mitls.org/pages/attacks/SMACK">SMACK</a> that don’t have anything to do with memory safety— but at least Rust type system makes such mistakes easier to avoid.</p><p>As could have been expected, no segfaults! Also no deadlocks, because <em>there is basically nowhere for them to come from</em>. I did find a couple of panics though: one in <a href="https://github.com/algesten/ureq/issues/24">ureq DNS handling</a> on 13 websites out of a million (0.0013%) and also an assertion failure leading to panic on 7 websites on <a href="https://github.com/briansmith/ring/issues/929">RSA validation in ring</a> (0.0007%), which leads me to believe that the Rust parts of <a href="https://github.com/briansmith/ring">ring</a> are less robust than I had hoped. It sure could use some <a href="https://rust-fuzz.github.io/book/introduction.html">fuzzing</a>.</p><p>I’m sure there is plenty more panics in there — I’ve only just tested valid inputs, and feeding it invalid data should cause a whole lot more panics. Fortunately panics are designed to be possible to handle and recover from, so these conditions can be reasonably planned for, <em>unlike deadlocks.</em></p><p>There are also three unsafe blocks in ureq itself that I’m not sure are necessary. I would have to audit them before using ureq for anything serious. A quick glance at the bug tracker also reveals that <a href="https://github.com/algesten/ureq/issues/10">connection pooling is not really usable yet,</a> but if you don’t use that you should be good.</p><p>The only glaring omission that I see in ureq is that <a href="https://github.com/algesten/ureq/issues/28">it doesn’t allow setting a timeout for request completion</a>, so if the remote host keeps replying, the connection will be open forever. This is a great vector for <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">denial-of-service attacks</a>: if you can get ureq to open connections to URLs you control, you can get your server to keep the connections open forever and easily exhaust some kind of resource on the client: thread pool, network ports, RAM, whatever runs out first.</p><h3>isahc</h3><p><a href="https://crates.io/crates/isahc">isahc</a> provides an idiomatic Rust interface to libcurl. Yeah, you’re talking to the devil, but it’s the devil everyone has already struck a deal with, so it’s probably fine! Plus it supports the deprecated and broken crypto protocol from 20 years ago!</p><p>Jokes aside, I am glad it exists, because it provides maximum interoperability and sometimes you need that above everything else — e.g. if you have to integrate with a weird legacy system and all connections are already inside a secure VPN such as <a href="https://www.wireguard.com/">WireGuard</a>.</p><p>The API exposed by isahc does feel pretty nice and Rusty. But I did run into an interesting gotcha with it: was getting HTTP/2 protocol errors even though I’ve disabled HTTP/2. Turns out disabling http2 feature <a href="https://github.com/sagebind/isahc/issues/147">doesn’t actually disable HTTP/2</a>.</p><p>I didn’t expect any fireworks from within libcurl this time around because Google has <a href="https://github.com/google/oss-fuzz/tree/master/projects/curl">started continuously fuzzing it in 2017</a> as part of <a href="https://google.github.io/oss-fuzz/">OSS-fuzz</a> and found <a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=proj&amp;colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&amp;num=100&amp;q=-status%3AWontFix%2CDuplicate%20-Infra%20proj%3Acurl&amp;can=1">some 75 crashing bugs</a> since then, so the really low-hanging fruit detectable by a basic smoke test should be picked by now.</p><p>You’d think cargo-geiger output would be totally benign, since all the complexity is inside libcurl, but no. Isahc pulls in the same crate from reqwest’s stack advertised as “HTTP types” but actually containing a bespoke HashMap implementation. And that crate in turn pulls in <em>yet another</em> <a href="https://crates.io/crates/bytes">bespoke and highly unsafe data structure</a> with an equally misleading description.</p><p>Test results are promising: <strong>no segfaults!</strong> This <em>is</em> quite impressive because the bindings to libcurl’s C API are inherently unsafe and leave plenty of room for error. On the flip side, I might not be seeing the full picture because I’ve only compiled isahc with Address Sanitizer, not libcurl. If the bindings have caused some kind of memory corruption in libcurl data structures, this setup wouldn’t detect it.</p><p>I also didn’t see any other kind of runtime malfunction, which is a first!</p><p>However, I have some gripes not with the library itself, but with the tech stack it relies on, just like with reqwest. Specifically, curl-sys crate may choose to use its own bundled version of libcurl <em>even if explicitly asked not to do that</em>. This amounts to loading a gun, pointing it at your foot while you’re not looking and sticking a timer on the trigger.</p><p>You see, if the user has <em>explicitly requested</em> use of the shared library, they may reasonably assume that bug fixes or security patches in the system-wide library will also apply to the Rust application. But curl-sys may choose to violate that assumption depending on the build environment: if development headers for libcurl are not present, or if http2 support is requested and the system-wide libcurl is too old, curl-sys will statically link its own version of libcurl instead of erroring out.</p><p>Now your code will not only <em>unexpectedly receive no security patches, </em>which will make it trivially exploitable by the very next <em>publicly known vulneraiblity</em>, it will also be running a different version of libcurl in a wildly different configuration than everything else <em>when you don’t expect it to,</em> which is all kinds of fun to debug, especially in production at 3AM.</p><p>For example, you may think you made <a href="https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779">resource leaks</a> impossible by setting a default connection timeout in libcurl, and then curl-sys smuggles in a totally different libcurl behind your back, stealthily reopening this can of worms. Plus the probability of the absence of version-specific bugs or behavioral differences between versions is basically nil, so good luck debugging <em>that!</em></p><blockquote>Bye-bye, security patches. Hello, debugging inexplicable production outages at 3AM.</blockquote><p>Initially I had a hard time communicating to the maintainer that this is an issue, but they have eventually conceded that this could be improved and that PRs fixing this would be welcome.</p><p>I won’t even spend time commenting on the timeliness of security updates to the bundled libcurl (weeks after CVE disclosure) or the existence of <a href="https://github.com/RustSec/advisory-db">RustSec advisories</a> for such updates (none).</p><h3>http_req</h3><blockquote>Simple HTTP client with built-in HTTPS support. Currently it’s in heavy development and may frequently change.</blockquote><p>No bespoke unsafe, OpenSSL or rustls at your option and no other dependencies with unsafe code in them!</p><p>No segfaults (duh!), <a href="https://github.com/jayjamesjay/http_req/issues/29">one panic</a>, some hangs… wait, those seem to be legitimate hangs: the server is just submitting chunks of data really slowly! Then why didn’t ureq also hang here? Well, <a href="https://github.com/algesten/ureq/issues/30">let’s report that to ureq</a>.</p><p>Other than the panic, this looks like my idea of a perfect library, which sounds too good to be true. And it <em>is</em> too good to be true: http_req is so basic that there’s hardly anything in there to break. For example, it doesn’t follow redirects, so it didn’t even get to the frontpages of most websites I’ve thrown at it, it’s only seen a basic redirect response. There is also <a href="https://github.com/jayjamesjay/http_req/issues/28">no support for setting a timeout for the entire request,</a> so it’s susceptible to denial-of-service attacks.</p><p>In a nutshell: too basic for most uses right now, but perhaps something to keep an eye on.</p><h3>attohttpc</h3><p>I know I will have trouble telling anyone about this crate. Naming it something like “hytt” or “ht2p” would make it much easier to refer to.</p><p>It uses blocking I/O, which makes it a potential contender for being the go-to solution. The feature set is roughly on par with ureq, except it supports some features that are basically optimizations (like compression) and doesn’t support some features that are strictly required for some use cases (like cookies). Sadly it has no rustls option, so you’re locked into OpenSSL.</p><p>Dependencies are mostly sane and minimal, with one exception. That bespoke HashMap advertised as “HTTP types” <em>is in here too</em>, and it is of course pulling in <a href="https://crates.io/crates/bytes">that other data structure</a> which is 8,000 lines of code and full of unsafe. Using that thing makes sense if you want to pass small chunks of HTTP data across different threads and you have no idea what the access patterns are (so basically async code), but attohttpc does <em>none</em> of that insanity, it parses the entire response in the same thread. So all attohttpc gets for its trouble here is a slowdown due to non-contiguous data layout and atomic reference-counting plus an extra 8,000 LoC of unsafe code to its attack surface. This crate would be much better off using the standard library Vec.</p><p>Initially attohttpc lacked any kind of timeouts, but the maintainers have implemented connection and read timeouts upon my request. A timeout for the entire request <a href="https://github.com/sbstp/attohttpc/issues/35">is still missing</a> though, so DoS attacks remain possible.</p><p>A test of the version with timeouts triggered no segfaults, but did <a href="https://github.com/sbstp/attohttpc/issues/43">reveal one intermittent panic</a> and caused a few expected hangs due to the absence of a timeout for the entire request.</p><h3>minreq</h3><blockquote>Simple, minimal-dependency HTTP client. The library has a very minimal API, so you’ll probably know everything you need to after reading a few examples.</blockquote><p>Exactly what it says on the tin! No scary dependencies whatsoever, HTTPS via rustls and optional JSON through serde-json. The API is extremely minimal, and judging by the description that’s on purpose, so I do not expect this to be a viable choice in any kind of serious project. You will eventually needs something not covered by the API and have to migrate to another library.</p><p>The documentation stated that it supported a timeout for the entire request, but upon reading the code <a href="https://github.com/neonmoe/minreq/issues/22">I’ve found that it was not the case</a>. Fortunately this is <a href="https://github.com/neonmoe/minreq/commit/41ef16e33501712e64a5711a7f4bc132d9bfbe40">fixed now, but may come with a significant performance hit</a>.</p><p>The test has revealed no segfaults, <a href="https://github.com/neonmoe/minreq/issues/23">two</a> <a href="https://github.com/neonmoe/minreq/issues/24">panics</a> and I can’t really comment on hangs because I’ve tested it before the fix.</p><h3>cabot</h3><p>No, it’s not a certificate authority bot, it’s an HTTP client. And I thought “attohttpc” was a bad name!</p><p>It uses async I/O, but what makes it distinct from reqwest is that it uses <a href="https://github.com/async-rs/async-std">an entirely different async I/O stack</a> with dramatically less unsafe code. And by virtue of being a different implementation it’s also going to exhibit new and exciting failure modes!</p><p>Sadly cabot is currently too basic to exhibit anything exciting. It doesn’t even follow redirections, so it will actually process less than a half of the websites I’ll throw at it. It also <a href="https://github.com/mardiros/cabot/issues/7">supports no timeouts of any kind</a>, which makes it unusable in automated systems where the user can’t decide they’ve had enough and kill the process.</p><p>The amount of unsafe code in the dependencies is at 1/2 of that of reqwest. There is nothing particularly egregious in there except for two things. One, there is a hard dependency on <a href="https://crates.io/crates/pretty_env_logger">pretty_env_logger</a>, which alone pulls in more unsafe code than all of ureq’s dependencies combined. Two, <a href="https://github.com/mardiros/cabot/blob/24629157d03e9880ef9d3a06de32d49295d29ff9/src/constants.rs#L18">it uses regex</a> to parse some parts of HTTP — which is a bad idea, but given that it uses Rust’s supposedly DoS-resilient regex library and not PRCE, it’s not a <em>terrible</em> idea. Fortunately, both of these issues look simple enough to fix, and with these dependencies ditched it would go down to 1/3 of unsafety of reqwest.</p><p>The test results are basically in line with what I expected. No segfaults, but <a href="https://github.com/mardiros/cabot/issues/10">one frequent panic</a>. I also got 25,000 hangs and I have no idea if those are deadlocks or the upstream simply didn’t respond in time because cabot doesn’t support any kind of timeouts.</p><p>So nothing to get excited about in here yet, but perhaps it will evolve into a viable contender for the weird use cases that require async I/O.</p><h3>Not tested</h3><h4>surf</h4><p><a href="https://github.com/http-rs/surf">surf</a> is a common interface on top of isahc and the HTTP stack that’s underlying reqwest. I’ve already tried both backends, so not much to see here.</p><h4>yukikaze</h4><p><a href="https://crates.io/crates/yukikaze">yukikaze</a> is built on basically the same stack as reqwest, but provides a different API on top. Since all the complaints I had about reqwest were actually about the underlying HTTP stack, they also apply here.</p><h4>awc</h4><p>An HTTP client built on the <a href="https://actix.rs/">Actix framework</a>. The HTTP stack seems to be shard with actix-web. It uses async I/O and thus relies on Rust’s immature async/await ecosystem, which brings up the same issues with complexity and exciting new failure modes as with reqwest. Definitely should not be your first choice when shopping for an HTTP client. And cargo-geiger output is also not comforting.</p><p>A quick glance at the dependencies reveals that it relies on actix-service, which underpins all of Actix and has a bespoke and unsound Cell implementation. For example, <a href="https://github.com/actix/actix-net/blob/7dddeab2a8c4fdcd0c7de6aa4303aca8faffcd53/actix-service/src/cell.rs#L35">this method</a> violates memory safety by handing out multiple mutable references to the same data, which can lead to e.g. a use-after-free vulnerability. I have reported the issue to the maintainers, but <a href="https://github.com/actix/actix-net/issues/83">they have refused to investigate it</a>.</p><p>There are no comments on their bespoke Cell implementation — not only no comments to justify why it’s needed, but <em>no comments at all.</em> So I dug through the commit log to see <em>why</em> they rolled their own unsafe primitive instead of using Rc&lt;RefCell&gt;which would be doing the <strong>exact same thing,</strong> except <strong>safely.</strong> Here’s the commit message justifying the change:</p><blockquote>add custom cell</blockquote><p><a href="https://github.com/actix/actix-net/commit/20b03a478063d6bb293a6636632f976113812e13">That’s it.</a></p><p>And while I probably could pressure the maintainers into fixing <em>this particular </em>bug (or maybe even dropping their bespoke cell altogether, if I’m <em>really</em> lucky!), that will do nothing to improve their general approach to safety which determines the shape of all their future code. So I just give up and admit that I can’t put my money on the security or reliability of this thing.</p><p>I want to highlight that Actix is not unique in having its own bespoke and unsound unsafe primitives — the HTTP stack that’s underlying reqwest is facing largely the same issues, although its maintainers are much more willing to fix reported bugs. And the solution to this problem is very simple: <em>stop making bespoke unsafe primitives,</em> because for everything other than toy projects <strong><em>reliability trumps performance.</em></strong></p><h3>I need an HTTP client…</h3><p>So which library would I use if I needed one right now?</p><p>Well, I’ve found serious bugs in every single one of them, so none are usable as-is. Plus I would do more research than this before committing to one.</p><p>First I would check if the problem can be solved with blocking I/O, and if so, look no further. Clients with blocking I/O are simple, dependable, and boring — they have nothing in them that could break in an interesting way. I won’t be pulling my hair when debugging and nobody’s going to be woken up in the middle of the night because something has deadlocked in production.</p><p>Based on this cursory glance it seems that ureq and attohttpc could be hammered into a usable shape in a week or so each (assuming you’re willing to stick a panic catcher on them), plus however long it will take to add rustls as an optional TLS backend to attohttpc if you want to get rid of OpenSSL.</p><p>I’m letting the panics slide because they’re not a DoS vector: panics are trivial to guard against, especially if you’re spawning threads, and there is almost nothing in ureq dependency tree that could crash the entire process save for a panic-while-panicking.</p><p>But what if I had a use case that is not served by the clients with blocking I/O?</p><p>It pains me to say this, but… I wouldn’t go with reqwest. Credit where it’s due: unlike curl, it’s not <em>hopelessly</em> broken, and developers of the underlying async runtime <a href="https://tokio.rs/blog/2019-10-scheduler/#fearless-unsafe-concurrency-with-loom">go above and beyond due diligence</a> in some respects. But first, the async/await ecosystem as a whole is still immature, and second, I won’t be able to trust reqwest’s underlying HTTP stack until they ditch most of their bespoke unsafe primitives.</p><p>Sadly isahc is also not a great candidate. For starters, I would still need to rely on the immature async ecosystem if I need high performance and run into the same deadlocks as with reqwest (or cabot?). Also, when something goes wrong in libcurl it brings down the entire process, which aborts <em>all</em> requests currently in flight (unlike ureq or attohttpc, where you can abort just one), so <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">DoS</a> resilience of libcurl is basically nil while the attack surface is enormous. Which is why there is no way I’m linking libcurl+OpenSSL into my main process, otherwise it would bring down not only all in-flight requests but the entirety of my backend as well. So I’d have to put all the code that does HTTP fetching into a separate process, sandbox it and communicate with it using something like RPC… Ewww.</p><p>Actually, why am I even trying to do this in Rust at this point? If I’m breaking the HTTP fetching into a separate process anyway, I might as well go for a mature async I/O stack in there.</p><p>I’m not sure how the HTTP client in Go is implemented, but at least Go gets async I/O mostly right, so that’s worth a look. And it comes with <a href="https://golang.org/pkg/crypto/tls/">its own TLS implementation</a>, so we won’t be stuck with OpenSSL. On the other hand, Go makes concurrency very easy to mess up (<a href="https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-should-feel-bad/">and no, channels are not a solution</a>) and <a href="https://www.reddit.com/r/rust/comments/emyill/blog_why_rust_i_have_a_gc/fduq8dp/">error handling in Go is a minefield</a>, but hopefully fetching webpages would be simple enough not to run into these issues?</p><p>Alternatively, Erlang has a mature async I/O stack, its reliability is unparalleled and makes it really hard to mess up concurrency. But I don’t know if the HTTP client specifically is any good, and it may be hard to find people familiar with Erlang, so the decision to use it should not be taken lightly.</p><h3>Conclusion</h3><p>The place of the go-to Rust HTTP client is sadly vacant. Clients with async I/O will never be able to fill it due to the sheer operational complexity they bring, and none of the clients with blocking I/O have enough features to be viable contenders.</p><p>Instead of a single good HTTP client with blocking I/O Rust has at least 4 passable ones. I wish their maintainers got together and made a single good client instead.</p><p>Two months in, the async/await ecosystem is as immature as you’d expect. If I needed an HTTP client with async I/O right now, I’d use a different memory-safe language.</p><p>Only the libraries written in C and Rust can be integrated in code written in other languages, and the C libraries that the entire world relies on are hopelessly broken. Rust libraries are also broken, but not <em>hopelessly</em> so. Let’s fix them and usher in a new era of performant, secure and reliable software.</p><h4>Appendix: well, technically…</h4><p>In the interest of making an already long read more tractable I’ve omitted some technicalities that do not change the overall picture much. If you feel I’ve overlooked something, check if it’s covered in the <a href="https://medium.com/@shnatsel/smoke-testing-rust-http-clients-extended-notes-f4e674577a1a">extended notes</a>.</p><h4>Appendix: issues reported in the making of this article</h4><ul><li><a href="https://github.com/seanmonstar/reqwest/issues/746">https://github.com/seanmonstar/reqwest/issues/746</a></li><li><a href="https://github.com/algesten/ureq/issues/24">https://github.com/algesten/ureq/issues/24</a></li><li><a href="https://github.com/algesten/ureq/issues/28">https://github.com/algesten/ureq/issues/28</a></li><li><a href="https://github.com/algesten/ureq/issues/30">https://github.com/algesten/ureq/issues/30</a></li><li><a href="https://github.com/briansmith/ring/issues/929">https://github.com/briansmith/ring/issues/929</a></li><li><a href="https://github.com/sagebind/isahc/issues/147">https://github.com/sagebind/isahc/issues/147</a></li><li><a href="https://github.com/alexcrichton/curl-rust/issues/321">https://github.com/alexcrichton/curl-rust/issues/321</a></li><li><a href="https://github.com/jayjamesjay/http_req/issues/27">https://github.com/jayjamesjay/http_req/issues/27</a></li><li><a href="https://github.com/jayjamesjay/http_req/issues/28">https://github.com/jayjamesjay/http_req/issues/28</a></li><li><a href="https://github.com/jayjamesjay/http_req/issues/29">https://github.com/jayjamesjay/http_req/issues/29</a></li><li><a href="https://github.com/sbstp/attohttpc/issues/35">https://github.com/sbstp/attohttpc/issues/35</a></li><li><a href="https://github.com/sbstp/attohttpc/issues/36">https://github.com/sbstp/attohttpc/issues/36</a></li><li><a href="https://github.com/sbstp/attohttpc/issues/43">https://github.com/sbstp/attohttpc/issues/43</a></li><li><a href="https://github.com/actix/actix-net/issues/83">https://github.com/actix/actix-net/issues/83</a></li><li><a href="https://github.com/neonmoe/minreq/issues/22">https://github.com/neonmoe/minreq/issues/22</a></li><li><a href="https://github.com/neonmoe/minreq/issues/23">https://github.com/neonmoe/minreq/issues/23</a></li><li><a href="https://github.com/neonmoe/minreq/issues/24">https://github.com/neonmoe/minreq/issues/24</a></li><li><a href="https://github.com/mardiros/cabot/issues/7">https://github.com/mardiros/cabot/issues/7</a></li><li><a href="https://github.com/mardiros/cabot/issues/8">https://github.com/mardiros/cabot/issues/8</a></li><li><a href="https://github.com/mardiros/cabot/issues/9">https://github.com/mardiros/cabot/issues/9</a></li><li><a href="https://github.com/mardiros/cabot/issues/10">https://github.com/mardiros/cabot/issues/10</a></li><li><a href="https://github.com/anderejd/cargo-geiger/issues/79">https://github.com/anderejd/cargo-geiger/issues/79</a></li></ul><p><em>No crate maintainers were harmed in the making of this article.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b8f2ee5db4e6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Security as Rust 2019 goal]]></title>
            <link>https://shnatsel.medium.com/security-as-rust-2019-goal-6a060116ba39?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/6a060116ba39</guid>
            <category><![CDATA[security]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[rust2019]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Fri, 18 Jan 2019 18:20:42 GMT</pubDate>
            <atom:updated>2019-02-02T16:36:00.241Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o8W2s52rkZPSynlxNzonQQ.jpeg" /><figcaption>Our vision for Rust. Image courtesy of <a href="https://www.flickr.com/photos/sreejithk2000/3328197299/">Sreejith K</a>.</figcaption></figure><p><em>Note: I am publishing this on behalf of Secure Code Working Group because we do not have a WG blog established yet. Multiple people have contributed to this post.</em></p><p><a href="https://github.com/rust-secure-code/wg">Rust Secure Code Working Group</a> is a bunch of curious people hanging out in a public chat on the Internet. You can click <a href="https://rust-lang.zulipchat.com/#narrow/stream/146229-wg-secure-code">here</a> to hang out with us.</p><p><strong>Our mission is to make it easy to write secure code in Rust.</strong></p><p>We have the following goals for the Rust language and ecosystem:</p><ul><li>Most tasks shouldn’t require dangerous features such as unsafe. This includes FFI.</li><li>Mistakes in security-critical code should be easily caught by machines or, failing that, humans aided by machines.</li><li>It should be clear to programmers how to perform security-sensitive tasks.</li><li>Security-critical code which is relied on by Rust programmers should be bug free.</li></ul><p>This article details what we have agreed on as especially critical areas that we would like to see improved upon in 2019.</p><h3>Security updates</h3><p>Safe Rust eliminates entire classes of security bugs, which makes it very promising for security-critical applications such as web servers. However, even memory-safe code may contain logic bugs leading to security breaches. No code is perfect, so security bugs will occur, and <a href="https://rustsec.org/advisories/">are already occurring</a>.</p><p>Rust needs a mechanism to deliver security updates to any kind of production deployments in a timely manner. This involves finding good answers to the following questions:</p><ol><li>If you run Rust code in production, how do you get notified that you need to apply a security update? How do you set up a pipeline to apply these updates automatically? This is exacerbated by Rust’s static linking, since every affected program needs to be updated individually, even if a vulnerability is in a transitive dependency. We need solutions both for software installed via cargo installand via complex deployment pipelines used for production servers.</li><li>How should fixes in compiler or standard library bugs be applied? Currently there is no “rebuild everything that was ever installed” command in Cargo. Also, how do we notify people that they need to rebuild everything? What if the code is non-trivially deployed, like a shared library linked into another language?</li><li>How should security updates to statically linked C libraries be handled? What if the build is for Windows where the only reasonable way to build against C libraries is to bundle them with the -sys crate? Should the maintainer of Rust -sys crate be responsible for security updates to the C code, and if so, how do we make that manageable for the maintainer?</li></ol><h4>Prior art</h4><p><a href="https://rustsec.org/">RustSec</a> project hosts a <a href="https://github.com/RustSec/advisory-db">Rust security advisory database</a> and provides a <a href="https://github.com/RustSec/cargo-audit">command-line tool</a> that checks Cargo.lock for vulnerable dependencies. This is a great start, but currently you need to run it manually on each of your projects to check them, and doing that every day is impractical. It also doesn’t handle compiled binaries.</p><p>There is also <a href="https://gitlab.com/zachreizner/crates-audit/">a tool</a> to cross-reference the <a href="https://github.com/rust-lang/crates.io-index">crates.io index</a> with the RustSec database. It has identified, for example, a crate with 2500+ downloads per month that depends on a grossly outdated and <a href="https://rustsec.org/advisories/RUSTSEC-2016-0001.html">trivially exploitable</a> version of OpenSSL. Right now crates.io itself does not present this info in any way, so the crate in question may keep accumulating unsuspecting users.</p><p><a href="https://github.com/rust-lang/rfcs/pull/1752">An RFC for some of this functionality</a> has been proposed in 2016, but shelved. The issues already in the RustSec database are proof that it is needed. Reviving it is being discussed <a href="https://internals.rust-lang.org/t/pre-rfc-reviving-security-advisories-in-crates-io-rfc-pr-1752/9017">here</a>.</p><p>Rust compiler encodes the rustc, LLVM and standard library versions into all binaries it produces. This allows easily checking for binaries with vulnerable stdlib versions, regardless of deployment method. However, the versions of all the other libraries used to compile the binary are not encoded.</p><p><a href="https://theupdateframework.github.io/">The Update Framework</a> provides protocols for resilient and timely delivery of security updates, which is <a href="https://theupdateframework.github.io/security.html">harder than it sounds</a>. An implementation of it in Rust is <a href="https://github.com/heartsucker/rust-tuf">in progress</a>.</p><h3>Use of unsafe code</h3><p>Many widely used libraries use unsafe code where it’s not strictly necessary. Typically this is done for performance reasons, i.e. there are currently no safe abstractions to achieve the goal safely <em>and</em> efficiently.</p><p>The goal here is to reduce or eliminate the use of unsafe code throughout the ecosystem where it is not strictly necessary without regressing correctness or performance. The action items for that include:</p><ol><li>Investigate why exactly people resort to unsafe code on a case-by-case basis. Compile a list of case studies so that we can identify missing safe abstractions or idioms.</li><li>Try to rewrite unsafe code into safe without regressing performance. Document the patterns and anti-patterns, create guidelines and/or clippy warnings based on those.</li><li>Create safe abstractions to serve common cases that are currently served by unsafe code, such as <a href="https://github.com/rust-lang/rust/issues/54236">copying a part of a slice into itself.</a></li><li>Prioritize language and compiler work items that allow better verification at compilation stage, such as better bounds check elision or <a href="https://github.com/rust-lang/rust/issues/44580">const generics</a>.</li></ol><p>Rust ecosystem is fairly large these days, so there is a lot of code to cover. Perhaps a community effort akin to <a href="https://blog.rust-lang.org/2017/05/05/libz-blitz.html">libs blitz</a> is required.</p><h4>Prior art</h4><p><a href="https://rust-lang-nursery.github.io/edition-guide/rust-2018/ownership-and-lifetimes/non-lexical-lifetimes.html">Non-lexical lifetimes</a> that have landed in the 2018 edition of Rust made the borrow checker smarter, reducing the need for resorting to unsafe code. Kudos to everyone involved!</p><p>Some other highlights:</p><ul><li><a href="https://internals.rust-lang.org/t/pre-rfc-fixed-capacity-view-of-vec/8413">Analysis of unsafe code in decoding crates</a> that led to a safe abstraction proposal, <a href="https://github.com/rust-lang/rust/issues/54628">documentation improvement</a>, a <a href="https://github.com/rust-lang/rust-clippy/issues/3237">clippy warning</a>, and <a href="https://internals.rust-lang.org/t/pre-rfc-fixed-capacity-view-of-vec/8413/31?u=shnatsel">discovery</a> of a <a href="https://blog.rust-lang.org/2018/09/21/Security-advisory-for-std.html">security bug</a> in the standard library.</li><li>Developers <a href="https://github.com/rust-lang/rust/pull/39271">uplifting</a> their only unsafe block into the standard library. It is now a <a href="https://doc.rust-lang.org/std/primitive.f64.html#method.from_bits">widely used function</a> with the unsafe block encapsulated in a safe external interface, verified once and for all.</li><li>Crates such as <a href="https://github.com/BurntSushi/byteorder">byteorder</a> providing safe abstractions for commonly used and potentially unsafe operations long before they appeared in the standard library.</li><li><a href="https://github.com/rust-rfcs/unsafe-code-guidelines">Unsafe Code Guidelines effort</a> that has resulted in safer abstractions as such as <a href="https://github.com/rust-lang/rfcs/blob/master/text/1892-uninitialized-uninhabited.md">MaybeUninit</a>. It also paves the way for security-oriented static analysis of unsafe code and better compiler optimizations.</li></ul><h3>Verification of standard library</h3><p>The Rust standard library is a truly impressive piece of engineering. It sets the bar for Rust API design and incorporates the <a href="https://github.com/stjepang/pdqsort">latest advances</a> in algorithms and data structures, with more on the way.</p><p>Due to its role as the foundation of the language providing essential safe abstractions over the hardware it is also full of unsafe code.</p><p>Two <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=%20CVE-2018-1000657">serious</a> <a href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000810">vulnerabilities</a> have been discovered in libstd to date. Another one was introduced but reverted before release because it was so bad that it <a href="https://github.com/rust-lang/rust/issues/54477">caused crashes even on valid data</a>. All of these were introduced during optimization or refactoring, and have passed manual code review.</p><p>The fact that humans are no good at analyzing unsafe code<em> is the very reason for Rust’s existence.</em> <strong>We need computers to assist in verification of Rust’s standard library.</strong></p><p>There are several ways to go about that:</p><ol><li>Static analysis would be a relatively cheap and scalable way to gain more confidence in the code. Rust is much more amenable to static analysis than C/C++ or dynamically typed languages, but there is no go-to security-oriented static analyzer yet.</li><li><a href="https://en.wikipedia.org/wiki/Fuzzing">Fuzzing</a> or parametric testing could also scale well, assuming fuzzing harnesses could be automatically generated based on type definitions of stdlib functions. It would not find all the bugs, but it is easy to run continuously and feasible to scale to the entirety of the standard library with little maintenance burden.</li><li>Formal verification methods provide greater assurance in correctness, but require more effort and introduce a non-trivial maintenance burden. Even though verifying the entirety of standard library this way is probably not practical at this time, it would be great to apply them to verify the most essential parts of it.</li></ol><p>One of the already discovered vulnerabilities was <a href="https://github.com/rust-lang/rust/pull/54397">trivial</a> and would have been flagged by a static analyzer or easily discovered via fuzzing — if any of those were actually employed.</p><h4>Prior art</h4><p>Parametrized testing is easy to use in Rust, with two mature frameworks available: <a href="https://github.com/BurntSushi/quickcheck">QuickCheck</a> inspired by <a href="https://en.wikipedia.org/wiki/QuickCheck">Haskell tool of the same name</a> and <a href="https://github.com/altsysrq/proptest">Proptest</a> inspired by <a href="https://hypothesis.works/">Hypothesis</a>.</p><p>The guided fuzzer trifecta — <a href="https://github.com/rust-fuzz/afl.rs">AFL</a>, <a href="https://github.com/rust-fuzz/cargo-fuzz">libfuzzer</a> and <a href="https://github.com/rust-fuzz/honggfuzz-rs">honggfuzz</a> — are already adapted to work with Rust and <a href="https://fuzz.rs/book/">take 15 minutes to deploy</a>. The <a href="https://github.com/rust-fuzz/trophy-case">trophy case</a> is quite impressive.</p><p>A new fuzzer called <a href="https://github.com/AngoraFuzzer/Angora">Angora</a> has just been released; according to its authors, it is <a href="https://arxiv.org/abs/1803.01307">vastly superior</a> to the AFL-inspired status quo. It is itself written in Rust, but <a href="https://github.com/AngoraFuzzer/Angora/issues/10">cannot fuzz Rust code yet</a>.</p><p><a href="https://github.com/blt/bughunt-rust">Bughunt-rust</a> has experimented with probabilistic model checking for verifying standard library data structures inspired by <a href="https://medium.com/@jlouis666/breaking-erlang-maps-4-4ebc3c64068c">similar work in Erlang</a>, but used guided fuzzers instead of QuickCheck RNG to improve coverage.</p><p>Fuzzing relies on dynamic analyzers to detect issues. Rust supports the venerable <a href="https://github.com/japaric/rust-san">LLVM sanitizers</a>, although Address Sanitizer currently requires <a href="https://github.com/rust-secure-code/wg/issues/20">some workarounds</a> and <a href="https://github.com/rust-secure-code/wg/issues/21">nobody’s really sure</a> how to use Memory Sanitizer, which led some people to build <a href="https://github.com/Shnatsel/libdiffuzz">custom tooling</a> instead. There is also Rust-specific tool called <a href="https://github.com/solson/miri/">MIRI</a>, but so far it only supports a subset of Rust and does not compose well with fuzzing.</p><p><a href="https://github.com/rust-lang/rust-clippy">Clippy</a> is the go-to heuristic static analyzer for Rust, although it doesn’t have many safety lints yet. <a href="https://github.com/facebookexperimental/MIRAI">MIRAI</a> is a sound static analyzer for Rust based on <a href="https://www.di.ens.fr/~cousot/AI/IntroAbsInt.html">theory of abstract interpretation</a>, but it is in the early stages of development.</p><p>On the formal verification front, <a href="https://plv.mpi-sws.org/rustbelt/popl18/">RustBelt project</a> has proven certain properties of the Rust type system and verified correctness of several standard library primitives. <a href="http://smackers.github.io/">SMACK</a> software verification toolchain works with Rust and <a href="http://soarlab.org/publications/atva2018-bhr.pdf">has been used to find real bugs</a>, but does not take advantage of the Rust type system, which makes it somewhat cumbersome to use.</p><p><a href="https://www.research-collection.ethz.ch/handle/20.500.11850/311092">Some promising work</a> has been done on proving absence of overflows and panics or even proving user-defined properties on unmodified Rust code, without manually writing additional proofs in a verification language. This project is known as <a href="http://www.pm.inf.ethz.ch/research/prusti.html">Prusti</a>. It only works with a subset of safe Rust so far, but the prospect of formally verifying properties of Rust code with little to no additional effort is very exciting.</p><h3>Code authentication and trust</h3><p>Trust towards third-party code is a hot topic right now due to the recent <a href="https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident">event-stream incident</a> in NodeJS. Ironically, security researchers <a href="https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5">have warned about this years ago</a>.</p><p>This is an important problem, and there is work being done on that front. For example, something like <a href="https://github.com/dpc/crev">cargo-crev</a> may solve it in some cases. But trust towards external code is an unsolved problem in general, even in programming languages with built-in sandboxing capabilities.</p><p><strong>As such, we do not expect it to be completely solved in 2019.</strong> However, there are improvements that we can make right now.</p><p>Adopting better code authentication practices is one. Someone is going to get their account compromised sooner or later, and <a href="https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes">the recent ESLint compromise</a> is quite illustrative of why a strategy for mitigating this is needed. Even basics such as requiring signatures from several maintainers to upload a package are currently not supported.</p><p>This has been brought up <a href="https://github.com/rust-lang/crates.io/issues/75">as early as 2014</a>; the attitude towards it is generally positive, and <a href="https://github.com/heartsucker/rust-tuf">there is some work being done in this direction</a>, but nobody has stepped up to actually implement the remaining part of it yet.</p><h3>We need your help!</h3><p>Some of the items we’ve listed require participation from core Rust teams, but most of them really don’t.</p><p><strong>This is where you come in.</strong></p><p>Rust is a community-driven language. We are just random people on the Internet coming together to work on a shared goal.</p><p>If you feel that these goals are worthwhile, pick an interesting item from the <a href="https://github.com/rust-secure-code/wg/issues">WG issue tracker</a> and see if you can help. After all, it takes more than a village to build a successful programming language.</p><p>And stop by to say hello <a href="https://rust-lang.zulipchat.com/#narrow/stream/146229-wg-secure-code">on Zulip</a>!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6a060116ba39" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How I’ve found vulnerability in a popular Rust crate (and you can too)]]></title>
            <link>https://shnatsel.medium.com/how-ive-found-vulnerability-in-a-popular-rust-crate-and-you-can-too-3db081a67fb?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/3db081a67fb</guid>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[fuzzing]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Fri, 28 Sep 2018 03:51:45 GMT</pubDate>
            <atom:updated>2018-09-28T12:21:28.481Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>I have recently discovered a <a href="https://rustsec.org/advisories/RUSTSEC-2018-0004.html">zero-day vulnerability</a> in a fairly popular and well-designed Rust crate. In this article I’m going to discuss how I did it and why it wasn’t discovered earlier, and introduce a new tool, <a href="https://github.com/Shnatsel/libdiffuzz">libdiffuzz</a>, that I’ve created for the job. A recently discovered <a href="https://blog.rust-lang.org/2018/09/21/Security-advisory-for-std.html">vulnerability</a> in Rust standard library makes a cameo appearance.</p><p>In <a href="https://medium.com/@shnatsel/auditing-popular-rust-crates-how-a-one-line-unsafe-has-nearly-ruined-everything-fab2d837ebb1">my earlier article</a> about a one-line unsafe block that has nearly ruined everything I’ve explained how I’ve used <a href="https://en.wikipedia.org/wiki/Fuzzing">fuzzing</a> to look for vulnerabilities in widely used Rust code. However, the titular one-life unsafe was found not through an automated process, but by manually reading the code. Why didn’t fuzzers discover it?</p><p>Fuzzers work by feeding your program random input and seeing what happens. They only detect that something is wrong if the program crashes. So in order to get fuzzers to actually discover memory issues that lead to vulnerabilities, you need some way to notice improper handling of memory when it happens. There have been many attempts to build such tools over the years, but the most practical and popular tool is <a href="https://clang.llvm.org/docs/AddressSanitizer.html">Address Sanitizer</a>. It reliably detects all sorts of bugs, is supported by Rust compiler out of the box, and is in fact enabled by default in one of Rust fuzzers, <a href="https://github.com/rust-fuzz/cargo-fuzz">cargo-fuzz</a>.</p><p>However, there is a class of memory issues that Address Sanitizer cannot detect: reading from uninitialized memory. If you can get a program to output contents of uninitialized memory, that’s called a “memory disclosure” vulnerability. There are <a href="http://seclists.org/fulldisclosure/2013/Nov/83">multiple examples</a> of such bugs in common C code, and in certain contexts they can be devastating: how about stealing cookies and passwords from web browser simply by displaying an image and running a bit of JavaScript?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*d8__uTp2m3N-ycc9VfkvVw.png" /><figcaption>All of these are the same image. Due to a bug in the GIF decoder contents of browser memory shows up in the decoded images.</figcaption></figure><p>There is a tool that can detect reads from uninitialized memory, called <a href="https://clang.llvm.org/docs/MemorySanitizer.html">Memory Sanitizer</a>, but it currently <a href="https://github.com/rust-lang/rust/issues/39610">doesn’t work</a> with Rust standard library. So unless you completely avoid using Rust standard library, there is no tool that let you detect reads from uninitialized memory in Rust.</p><p>Well, bummer. That means I’ll have to build one.</p><h3>The birth of libdiffuzz</h3><p>Since I’m only interested in memory disclosure vulnerabilities, i.e. cases when contents of uninitialized memory show up in the program output, it should be sufficient to run the same operation twice and compare the results. If a program has decompressed the same zip file twice and got different results, that usually means that contents of uninitialized memory have shown up in the output.</p><p>With that in mind, I’ve written a simple test program that reads from uninitialized memory and tried to detect it using the “run twice, compare results” technique. I wanted to be able to check if results differ between runs at a glance without comparing huge amounts of data by hand, so this is what I ended up with:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/189a43a0a33ebe25a4eb80f068ba1718/href">https://medium.com/media/189a43a0a33ebe25a4eb80f068ba1718/href</a></iframe><p>This program will panic if the use of uninitialized memory is detected. Our goal here is to get it to panic reliably — we know it’s buggy, we just need to be able to detect the bug automatically.</p><p>Turns out it’s not that easy because what uninitialized memory actually contains varies depending on the memory allocator in use. And no matter what memory allocator I tried, I couldn’t get it to crash. When built with Rust’s default <a href="http://jemalloc.net/">jemalloc</a>, sum_uninitialized() would always return 0. When built with system allocator (as in the code above), the return value would differ between different runs of the process, but not between different invocations of the function within the same process. I have even tried <a href="https://lcamtuf.blogspot.com/2014/08/a-bit-more-about-american-fuzzy-lop.html">AFL</a>’s <a href="https://github.com/mirrorer/afl/tree/master/libdislocator">libdislocator</a> which is basically a poor man’s address sanitizer implemented as a memory allocator (which makes it usable on black-box binaries), and even that didn’t work: my sum_uninitialized() always produced a stable result.</p><p>At this point I’ve (mentally) screamed “How hard can it be?!”, opened the source code of libdislocator and <a href="https://github.com/Shnatsel/libdiffuzz/commit/c3d69d941fd17fb44a3b42928b51bd763260e8ae">trivially patched</a> it to fill every allocated buffer with a value that’s incremented on every allocation instead of a constant value. And it worked! This test program started crashing!</p><h3>From the lab to real world</h3><p>Armed with my newly-minted abomination I went looking for a prospective real-world target to use it on. I’ve picked <a href="https://github.com/ruuda/claxon">claxon</a>, a FLAC decoder written in Rust, for a few reasons:</p><ol><li>Code that does nontrivial binary parsing is the poster child for security vulnerabilities in memory management</li><li>It contains 8 unsafe blocks per ~2000 lines of code, which is entirely too many for my liking and cannot possibly be not exploitable in a library that does complicated binary parsing (seriously, don’t do unsafe)</li><li>The author claimed that the library has been extensively fuzzed</li><li>I have already fuzzed it myself for about 1 billion executions total, so I already had a bunch automatically generated files that exercise many different execution paths — a great starting point for further fuzzing</li><li>Nobody has looked for this particular class of vulnerabilities in Claxon before — the only thing that could detect it, Memory Sanitizer, would not have worked with Claxon because it uses the Rust standard library</li><li>This code has defied me before (see point 4) and I took that as a challenge</li></ol><p>So I’ve thrown together a <a href="https://github.com/Shnatsel/claxon-differential-fuzzing/blob/master/src/main.rs">fuzz target</a> that decoded the same file twice and checked that the result is the same (if you’re craving for fancy words, call this “differential fuzzing”), plugged it into <a href="https://github.com/rust-fuzz/afl.rs">AFL</a> and left it overnight. And lo and behold, I woke up to 3 automatically discovered crashes!</p><p>And just as expected, the crashes were indeed happening on the assert!() that was comparing results from two subsequent runs and failing, and it only happened under libdiffuzz; they went completely unnoticed otherwise.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F0VkrUG3OrPc%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D0VkrUG3OrPc&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F0VkrUG3OrPc%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/21907857864530273414c8490d715970/href">https://medium.com/media/21907857864530273414c8490d715970/href</a></iframe><p>I have <a href="https://github.com/ruuda/claxon/issues/10">reported</a> the vulnerability to crate maintainer, who has promptly investigated and fixed it, then audited the rest of the code for similar bugs and added fuzzing a target similar to mine as a CI job. Swift handling of security vulnerabilities by maintainers is always great to see, and Claxon’s maintainer went above and beyond the call of duty.</p><p><em>Side note: it later turned out that I forgot to disable checksum verification in Claxon, so most inputs generated by the fuzzer were rejected early because of checksum mismatch (random data doesn’t have valid CRC16 in it, duh). But thanks to the sheer amount of inputs AFL has thrown at Claxon it has generated some files with valid CRC16 anyway, by sheer luck. To give you some context: AFL tests roughly 1 billion inputs per day on my mid-range CPU.</em></p><p><em>I’ve </em><a href="https://github.com/ruuda/claxon/pull/11"><em>opened a PR</em></a><em> to automatically disable checksum verification in Claxon during fuzzing so we wouldn’t have to deal with it anymore. With checksums disabled it only takes a few minutes to discover the bug using libdiffuzz.</em></p><p>I have also tried fuzzing with AFL + libdiffuzz on <a href="https://github.com/kornelski/lodepng-rust">lodepng-rust</a> and <a href="https://github.com/Frommi/miniz_oxide/">miniz-oxide</a>, but got nothing. lodepng-rust was created as a largely automated translation of a C codebase where these issues have already been discovered with AFL, and miniz-oxide actually comes with a “run twice, compare results” fuzz harness that compares Rust and C implementations. For those projects it was mostly about not triggering false alarm.</p><p>However, <em>the entire rest of Rust ecosystem</em> has probably never been fuzzed with anything that could detect use of uninitialized memory. So if you want to claim some zero-day vulnerability discoveries to your name, just pick a crate that has unsafe blocks in it, ideally with something like mem::uninitialized() or vec.set_len(), and give it a spin in a “run twice, compare results” fuzzing harness with libdiffuzz. There should be plenty of low-hanging fruit because nobody’s tried picking any of it yet.</p><p>I have published a cleaned-up version of my tool in github, check it out if you want to learn more or give it a spin: <a href="https://github.com/Shnatsel/libdiffuzz">https://github.com/Shnatsel/libdiffuzz</a></p><p>It comes with a quickstart guide for Rust and a <a href="https://github.com/Shnatsel/lodepng-afl-fuzz-differential">sample test harness</a> that’s simpler than <a href="https://github.com/Shnatsel/claxon-differential-fuzzing">the one I’ve used for Claxon</a>. Also, a list of caveats. Lots of them.</p><h3><strong>Why didn’t Rust prevent this?</strong></h3><p>The short answer is “Because people have deliberately opted out of its safety guarantees.” But <em>why</em> did they opt out?</p><p>In Claxon it was for the sake of optimization. Here’s the commit that introduced unsafe code:</p><p><a href="https://github.com/ruuda/claxon/commit/cfeb761dd4487aba62eee68faeb2d85a1d175916">Do not fill buffer with useless zeros initially · ruuda/claxon@cfeb761</a></p><p>Note that before this commit the buffer is diligently initialized with zeroes using buffer.extend(repeat(0).take(new_len — len)); — quite a mouthful! Not only that’s complicated, it’s also slow — it compiles into something like a loop that fills the allocated memory with zeroes.</p><p>Other than the obvious issue with it being kinda slow on normal inputs, it can get excruciatingly slow on deliberately malformed inputs, which can be used to mount a denial-of-service attack. If the implementation is perfectly efficient and uses full memory bandwidth (roughly 100Gb/s for DDR4), filling the entire 64-bit address space would take 16,000,000,000 seconds - or 500 years. Even with memory usage limits it’s still not pretty, because a single file can do this over and over and over again.</p><p>However, modern operating systems let you request <em>already zeroed</em> memory, which not only is roughly 4x faster in my tests, but is also asynchronous and lazy: even if you allocate a lot of such memory, zeroing memory will not block your program you actually try to access the relevant parts of it.</p><p>Can you ask your OS to do that from Rust? Yes! std::vec::from_elem() will simply request zeroed memory from the OS if you pass 0 as the element to fill the vector with. This function is not public, but that’s what vec! macro <a href="https://doc.rust-lang.org/src/alloc/macros.rs.html#48-56">desugars into</a>, so the fastest way to initialize a vector of size max_len is actually vec![0; max_len];. After switching Claxon from using uninitialized memory to this macro there was <a href="https://github.com/ruuda/claxon/commit/fec24678c7086aa4b2528bd7542f31d978b81b90">no measurable performance difference</a>.</p><p>Sadly, none of this is documented. The vec! macro is used all over the place in <a href="https://doc.rust-lang.org/std/vec/struct.Vec.html">Vec documentation</a>, but it does not mention that this is the fastest way by far to safely initialize a vector, or discuss about efficient initialization at all.</p><blockquote>Documenting the fastest way to safely initialize a vector <strong>would have prevented this vulnerability.</strong></blockquote><p>I have <a href="https://github.com/rust-lang/rust/issues/54628">opened an issue</a> against Rust to document this more clearly.</p><h3>But wait, it gets weirder</h3><p>I have also investigated the vulnerability in inflate, <a href="https://medium.com/@shnatsel/auditing-popular-rust-crates-how-a-one-line-unsafe-has-nearly-ruined-everything-fab2d837ebb1">discussed here</a>.</p><p><em>Side note:</em><em>inflate was not actually exploitable, since the code calling the vulnerable function was structured in such a way that it never passed it the specific values required to exploit it. Still, the vulnerable function is an example of security bug in real-world code.</em></p><p>Unsafe code was used in inflate because there was no safe way to accomplish what they needed safely <em>and</em> efficiently. I have written <a href="https://internals.rust-lang.org/t/pre-rfc-fixed-capacity-view-of-vec/8413">a detailed analysis of it</a> on the Rust internals forum, which I will not duplicate here.</p><p>I have also included a proposal for a safe abstraction that would prevent such issues in the future. The day after writing it the proposal I’ve started contemplating how I would go about implementing it, and then found that somebody has <em>already</em> written and posted a working prototype. Overnight. I didn’t even have to do anything. <em>God I love Rust community.</em></p><p>In that thread Scott McMurray has brought up a similar function in the standard library, which could be used to solve the problem if it were generalized a bit. Then he took a closer look at it and realized that <strong>the standard library function was vulnerable too:</strong></p><p><a href="https://github.com/rust-lang/rust/pull/54397">[stable] std: Check for overflow in `str::repeat` by alexcrichton · Pull Request #54397 · rust-lang/rust</a></p><p>This is the second-ever security vulnerability in the standard library. In case you’ve missed it, I’ve written <a href="https://medium.com/@shnatsel/how-rusts-standard-library-was-vulnerable-for-years-and-nobody-noticed-aebf0503c3d6">an article detailing the first one</a>.</p><p>Just like the first stdlib vulnerability, this one was introduced during refactoring. Unlike the first one, it does not require a sequence of specific function calls, and would be easily discovered via fuzzing if anyone has actually fuzzed that particular function.</p><p>This led me to contemplate <a href="https://www.reddit.com/r/rust/comments/9hssab/security_announcement_for_strrepeat/e6ed7ca/">automatically generating fuzzing harnesses</a> for the standard library functions, but I haven’t gotten around to actually prototyping that yet.</p><h3>Conclusions</h3><p>First things first: if you haven’t fuzzed your code yet, you should. Doesn’t have to be with libdiffuzz either — most bugs and almost all really severe vulnerabilities can be discovered without it. In Rust it’s <a href="https://fuzz.rs/book/introduction.html">stupidly easy</a> and won’t take you more than 15 minutes to set up.</p><p>As the fuzzing trophy cases filled with bugs from real-world projects in both <a href="https://github.com/rust-fuzz/trophy-case">Rust</a> and <a href="http://lcamtuf.coredump.cx/afl/">everything else</a> can attest, you don’t know your code until you’ve fuzzed it.</p><p>My pet <a href="https://github.com/Shnatsel/libdiffuzz">libdiffuzz</a> might also be of use. Feel free to borrow it and subject your unsafe code to its unrelenting jaws.</p><p>However, <strong>fuzzing won’t find all of the bugs.</strong> Do not rely on it as proof that your 2-line unsafe block is actually secure! And even if it is secure now, someone will refactor it later and it will become exploitable - just like it happened in the standard library.</p><p>So if you can help it, <strong>try to refactor your unsafe code into safe</strong>. And if you can’t, post on rust-internals forum and describe what’s slow or what kind of safe abstractions you’re missing. For example, <a href="https://github.com/RustAudio/lewton">lewton</a> crate is 100% safe code because it has upstreamed <a href="https://doc.rust-lang.org/std/primitive.f64.html#method.to_bits">its only unsafe function</a> into the standard library, where it got a lot more eyeballs. And it’s beneficial for others too: I have recently used this very function at work without having to worry about auditing a transmute by myself.</p><p>Also, there is a <a href="https://github.com/blt/bughunt-rust">project to verify the implementations of data structures in Rust standard library</a>, and it could use all the help it can get. And if you’re interested in auto-generating fuzzing harnesses for stateless stdlib functions, let me know. I can handle generating fuzz harnesses, but I could use some help with listing stdlib functions and parsing parameter types.</p><p><a href="https://www.reddit.com/r/rust/comments/9jjovb/how_ive_found_vulnerability_in_a_popular_rust/"><em>Discuss this article on Reddit</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3db081a67fb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Rust’s standard library was vulnerable for years and nobody noticed]]></title>
            <link>https://shnatsel.medium.com/how-rusts-standard-library-was-vulnerable-for-years-and-nobody-noticed-aebf0503c3d6?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/aebf0503c3d6</guid>
            <category><![CDATA[cybersecurity]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[vulnerability]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[rust]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Sat, 18 Aug 2018 02:47:33 GMT</pubDate>
            <atom:updated>2018-10-04T19:48:02.747Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p><a href="https://www.rust-lang.org/">Rust</a> is a new systems programming language that prides itself on memory safety and speed. The gist of it is that if you write code in Rust, it goes as fast as C or C++, but you will not get mysterious intermittent crashes in production or horrific security vulnerabilities, unlike in the latter two.</p><p>That is, until you explicitly opt in to that kind of thing. Uh oh.</p><h3>Wait, what?</h3><p>You see, Rust provides safe abstractions that let you do useful stuff without having to deal with the complexities of memory layouts and other low-level arcana. But dealing with those things is necessary to run code on modern hardware, so something <em>has</em> to deal with it. In memory-safe languages like Python or Go this is usually handled by the language runtime — and Rust is no exception.</p><p>In Rust, the nutty-gritty of hazardous memory accesses is handled by the standard library. It implements the basic building blocks such as vectors that expose a safe interface to the outside, but perform potentially unsafe operations internally. To do that, they explicitly opt in to potentially unsafe operations (read: barely reproducible crashes, security vulnerabilities) by annotating a block with unsafe, like this: unsafe { Dragons::hatch(); }</p><p>However, Rust is different from languages like Python or Go in that it lets you use unsafe outside the standard library. On one hand, this means that you can write a library in Rust and call into it from other languages, e.g. Python. Language bindings are unsafe by design, so the ability to write such code in Rust is a major advantage over other memory-safe languages such as Go. On the other hand, this opens the floodgates for judicious use of unsafe. In fact, a couple of months ago a promising library <a href="https://www.reddit.com/r/rust/comments/8s7gei/">caught some flak for engaging in precisely this sort of thing</a>. So when I was trying to gauge whether Rust actually delivers on its promise of memory safety, that’s where I started.</p><p>I’ve messed with popular Rust libraries over the course of a month and then described my findings in <a href="https://medium.com/@shnatsel/auditing-popular-rust-crates-how-a-one-line-unsafe-has-nearly-ruined-everything-fab2d837ebb1">Auditing popular Rust crates: how a one-line unsafe has nearly ruined everything</a>. The TL;DR version of it is that Rust crates do sometimes use unsafe when it’s not absolutely necessary, and bugs that lead to denial of service are abundant, but after poking six different crates I have failed to get an actual exploit.</p><p>Clearly, I had to kick it up a notch.</p><h3>Ultimate Power, Bad Guys Only</h3><p>There is a highly effective technique for discovering vulnerabilities that I haven’t applied to Rust yet. It beats everything else by a long shot, and can be used only by the bad guys who want to break stuff, not the good guys who fix it. It’s… <em>searching the bug tracker.</em></p><p>You see, most people writing code in C or C++ are not actually security-minded. They just want their code to work and go fast. When they encounter a bug that makes the program output garbage or crash, they simply fix it and go investigate the next bug. What else is there to do?</p><p>Well, turns out in C and C++ many of those bugs are caused by mistakes in memory management. It’s those kinds of bugs that present remote code execution vulnerabilities, and that safe Rust is designed to prevent. The proper way to handle them is to file them into a database called <a href="https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures">Common Vulnerabilities and Exposures</a> (CVE for short) so that people who care about security are alerted to it and ship fixes to users. In practice such bugs are silently fixed in the next release at best, or remain open for years at worst, until either someone discovers them independently or the bug is caught powering some kind of malware in the wild.</p><blockquote>This leaves a lot of security vulnerabilities in plain sight on the public bug tracker, neatly documented, just waiting for someone to come along and weaponize them.</blockquote><p>I particularly like an <a href="http://seclists.org/fulldisclosure/2013/Nov/83">example of such a bug in libjpeg</a> that was discovered in 2003 but not recognized as a security issue. The patch to fix it ended up in limbo until 2013, at which point it was incorporated into an update so obscure that nobody received it anyway. The fix did not even get a changelog entry. It was independently discovered later in 2013 by Michal Zalewski, author of <a href="http://lcamtuf.coredump.cx/afl/">afl-fuzz</a>, and after 10 years since the vulnerability was discovered the fix has at last shipped.</p><p>That is, anyone who has bothered to just scroll through the bug tracker could steal cookies and passwords out of your web browser by simply loading an image and a bit of JavaScript for 10 years.</p><p>Touché.</p><p>The worst part is, bugs that are already fixed are not eligible for bug bounties. So the Bugtracker Search technique will not get you bug bounty money; it will, however, get you real exploits for production systems. This is why it’s unrivaled if you want to break stuff, and useless if you want to fix it and not go broke in the process.</p><p>Also, getting maintainers to take your “this is a security vulnerability” comments seriously can be problematic, and actually exploiting the bug to prove it can be a lot of work, which further discourages pro bono applications of this technique.</p><h3>Into the woods</h3><p>Actually applying the Bugtracker Search™ to Rust code was even easier than I expected. Turns out GitHub lets you search all the projects written in a certain language, so I’ve just typed “unsound” in search query, selected “Rust” as language and <a href="https://github.com/issues?utf8=%E2%9C%93&amp;q=is%3Aopen+is%3Aissue+unsound+archived%3Afalse+language%3Arust">off we go</a>! Bugs, bugs everywhere!</p><p>I did not have much time to spare at the moment, so typing “crash” instead of “unsound” in the search box is left as an exercise for the reader. Also, I’ve only searched for open bugs in recently updated projects and ignored the standard library (those guys <em>gotta</em> be responsible, right?).</p><p>This got me <a href="https://github.com/servo/rust-smallvec/issues/96">my first Rust zero-day exploit</a>! It was discovered two months before I’ve found it through github search and <a href="http://troubles.md/posts/improving-smallvec/">comes with its own blogpost,</a> albeit focusing on performance. After I pointed out that it is a security vulnerability, the crate maintainer has fixed it within two hours. And then has backported the fix to every affected series even though the crate is still in 0.x.x versions. Kudos!</p><p>Still, actually exploiting this bug in practice is tricky. It would be a good candidate for exploit chaining, but it’s hard to use by itself.</p><p>Okay, that was not ultimate enough. Time to kick it up <em>another</em> notch.</p><h3>In the belly of the beast</h3><p>At this point we’re looking for something that is straightforward to exploit (something like buffer overflow with data an attacker can control) and has not been recognized as a security vulnerability yet.</p><p>It doesn’t matter if the bug is fixed in the latest version of the code: people lack incentives to update to the latest version as long as whatever they’re using works for them, and have a very clear incentive to not upgrade because whatever they’re using is known to work well, while the latest update is not.</p><p>So even if there is an update that fixed the issue, a lot of people will not actually install it, because there is no reason to — <em>unless</em> it is marked as a security update.</p><p>I was contemplating my course of action when I’ve accidentally stumbled upon a <a href="https://www.reddit.com/r/rust/comments/909gsd">reddit thread</a> discussing the history of vulnerabilities in the Rust standard library, which pointed out this gem:</p><blockquote><strong>seg fault pushing on either side of a VecDeque</strong><br><a href="https://github.com/rust-lang/rust/issues/44800">https://github.com/rust-lang/rust/issues/44800</a></blockquote><p>This is a buffer overflow bug in the standard library’s implementation of a <a href="https://en.wikipedia.org/wiki/Double-ended_queue">double-ended queue</a>. The data written out of bounds is controlled by the attacker. This makes it a good candidate for a remote code execution exploit.</p><p>The bug affects Rust versions 1.3 to 1.21 inclusive. It is causing a crash that is relatively easy to observe, yet it has gone unnoticed for two years. In the <a href="https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1220-2017-11-22">release that fixed it</a> it did not even get a changelog entry. No CVE was filed about this vulnerability.</p><p>As a result, Debian Stable <a href="https://packages.debian.org/stretch/rustc">still ships vulnerable Rust versions</a> for some architectures. I expect many enterprise users to have vulnerable versions as well.</p><p>As usual, bad guys win.</p><h3>Whooops!</h3><p>I did not expect to find something like this in the standard library because Rust has a <a href="https://www.rust-lang.org/en-US/security.html">very well thought out and responsible security policy</a> (other projects, take note!), and the Rust security team consists of people who regularly work on the compiler and standard library. The fix <em>should not</em> have gone unnoticed.</p><p>I have contacted the Rust security team about the issue, asking them to make an announcement and file a CVE. The reply was:</p><blockquote>Hey Sergey,</blockquote><blockquote>This was fixed way back in September; we don’t support old Rusts. As such, it’s not really eligible for this kind of thing at this point, as far as our current policies goes. I’ll bring it up at a future core team meeting, just in case.</blockquote><p>And then, shortly:</p><blockquote>&lt;snip&gt;</blockquote><blockquote>We talked about this Wednesday evening.</blockquote><blockquote>- We do want to change our policy here<br> — The current policy is that we only support the latest Rust<br> — The general feeling is “if it’s important enough for a point release, it’s important enough for a CVE”<br> — This specific patch does seem like it should have gotten more attention at the time<br> — This stuff also obviously ties into LTS stuff as well<br>- We don’t have the time or inclination to work on updating this policy until after the [2018] edition ships<br> — We’d rather take the time to get it right, but don’t have the time right now</blockquote><p>Okay, I have to admit that this sounds reasonable.</p><p>They have subsequently reaffirmed that they have no intention to file a CVE for this issue, so I went ahead and applied for one myself via <a href="http://iwantacve.org/">http://iwantacve.org/</a>. This is supposed to involve a confirmation by email, and I am yet to hear back. I have no clue how long this will take.</p><p><strong>Update:</strong> this issue has been assigned <a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=%20CVE-2018-1000657">CVE-2018-1000657</a>.</p><h3>Bugs, bugs everywhere!</h3><p>This exposes a bigger issue with the standard library: insufficient verification. If this bug — which is relatively easy to observe! — has gone unnoticed for two years, surely something like it is still lurking in the depths of the standard library?</p><p>This problem is not unique to Rust. For example, Erlang — that funky language that people use to program systems with 99,9999999% uptime (<a href="https://web.archive.org/web/20120316112054/https://www.sics.se/~joe/thesis/armstrong_thesis_2003.pdf">no, that’s not an exaggeration</a>) — has repeatedly shipped with a broken implementation of Map data structure in its standard library. There is a fascinating <a href="https://medium.com/@jlouis666/breaking-erlang-maps-1-31952b8729e6">series</a> <a href="https://medium.com/@jlouis666/breaking-erlang-maps-2-362730a91400">of</a> <a href="https://medium.com/@jlouis666/breaking-erlang-maps-3-f008b5f714c5">four</a> <a href="https://medium.com/@jlouis666/breaking-erlang-maps-4-4ebc3c64068c">articles</a> detailing a systemic approach used to discover those issues.</p><p>To actually deliver on the safety guarantees, Rust standard library needs dramatically better testing and verification procedures. Some of its primitives were mathematically proven to be correct as part of <a href="http://plv.mpi-sws.org/rustbelt/">RustBelt</a> project, but that did not extend to implementations of data structures.</p><p>One way to do that would be to use the same approach as was used for verifying the Map structure in Erlang — building a model of the behavior of the structure in question and automatically generating tests based on it, then verifying that the outputs of the model and the implementation match for certain automatically generated inputs. Rust already has the tooling for that in the form of <a href="https://github.com/BurntSushi/quickcheck">QuickCheck</a> and <a href="https://github.com/altsysrq/proptest">proptest</a>.</p><p>Another way to verify the implementations is to use a symbolic execution framework such as <a href="https://klee.github.io/">KLEE</a> or <a href="https://saw.galois.com/">SAW</a>. They work by analyzing the code and figuring out all possible program states for all possible execution paths. This lets you either generate inputs that trigger faulty behavior or make sure that certain behavior is impossible. Sadly, neither of those tools supports recent versions of Rust.</p><p>Alas, both of those approaches are time-consuming and would require coordinated effort. It’s not something one can do for the entire standard library over a couple of weekends — otherwise I’d be opening a pull request for Rust standard library by now instead of writing this article.</p><p>Oh, and before you bring out the pitchforks and denounce Rust for all eternity: for reference, <a href="https://www.cvedetails.com/product/18230/Python-Python.html?vendor_id=10210">Python runtime gets at about 5 remote code execution vulnerabilities per year</a>. And that’s just the already discovered ones that got a CVE! How many were silently fixed or still lurk in the depths of Python runtime? Only the bad guys know.</p><h3>Everything is broken</h3><p>I have once reported a buffer overflow in a popular C library that is used in one of the major web browsers. It was the textbook example of a security vulnerability, and could be triggered simply by opening a webpage. I was told that the bug was silently fixed in a subsequent release that nobody has upgraded to yet. When I asked the maintainers to file a CVE, they said that if they filed one for every such bug they fixed they’d never get any actual work done.</p><p>Oh, and the worst thing? The vulnerability I’ve reported in that library was found by a fully automated tool in less than a day. All I did to discover the vulnerability was basically point and click. Imagine how many more exploitable bugs a dedicated security expert could discover!</p><blockquote>This was when I have actually understood and internalized that <a href="https://medium.com/message/everything-is-broken-81e5f33a24e1">everything is broken</a>.</blockquote><p>The horrifying thing for me is that I still use that web browser. It’s not like I have any alternatives — every practical web browser relies on a huge mess of C code. And it is evident that humans are unable to write secure C code, unless they <a href="http://www.aosabook.org/en/gpsd.html">swear off dynamic memory allocation altogether</a>.</p><p>This is why I’m so hopeful about Rust. It is the only language in existence that could really, truly, completely and utterly supplant C and C++ <em>while providing memory safety.</em> There is a <a href="https://dl.acm.org/citation.cfm?doid=3177123.3158154">mathematical proof of correctness</a> for a practical subset of safe Rust and even some inherently unsafe standard library primitives, and ongoing work on expanding it to cover even more of the language.</p><p>So we know that safe Rust actually works. The really hard theoretical problems are solved. But the inherently unsafe parts of the implementation, such as the language runtime, could use more attention.</p><p><strong>Update: </strong>Brian Troutwine has kicked off a project to validate Rust standard library primitives using QuickCheck! Check out <a href="https://github.com/blt/bughunt-rust">bughunt-rust on GitHub</a>, and join the hunt!</p><p><a href="https://www.reddit.com/r/rust/comments/988euh/"><em>Discuss this article on Reddit</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aebf0503c3d6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Auditing popular Rust crates: how a one-line unsafe has nearly ruined everything]]></title>
            <link>https://shnatsel.medium.com/auditing-popular-rust-crates-how-a-one-line-unsafe-has-nearly-ruined-everything-fab2d837ebb1?source=rss-fb9e4cd5e1ee------2</link>
            <guid isPermaLink="false">https://medium.com/p/fab2d837ebb1</guid>
            <category><![CDATA[rust]]></category>
            <category><![CDATA[vulnerability]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[cybersecurity]]></category>
            <dc:creator><![CDATA[Sergey "Shnatsel" Davidoff]]></dc:creator>
            <pubDate>Thu, 19 Jul 2018 01:38:11 GMT</pubDate>
            <atom:updated>2018-08-24T22:04:40.364Z</atom:updated>
            <cc:license>http://creativecommons.org/licenses/by/4.0/</cc:license>
            <content:encoded><![CDATA[<p>Following the <a href="https://www.reddit.com/r/rust/comments/8s7gei/">actix-web incident</a> (which is <a href="https://www.reddit.com/r/rust/comments/8wlkbe/actixweb_has_removed_all_unsound_use_of_unsafe_in/">fixed now</a>, at least mostly) I decided to poke other popular Rust libraries and see what comes of it.</p><p>The good news is I’ve poked at 6 popular crates now, and I’ve got not a single actually exploitable vulnerability. I am impressed. When I poked popular C libraries a few years ago it quickly ended in tears. The bad news is I’ve found one instance that was not a security vulnerability by sheer luck, plus a whole slew of denial-of-service bugs. And I can’t fix all of them by myself. Read on to find out how I did it, and how you can help!</p><p>My workflow was roughly like this:</p><ol><li>See if the crate has been <a href="https://en.wikipedia.org/wiki/Fuzz_testing">fuzzed</a> yet to identify low-hanging fruit.</li><li>If it has been fuzzed, check sanity of fuzzing harness.</li><li>If something is amiss, fuzz the crate.</li><li>In case fuzzing turns up no bugs, eyeball the unsafes and try to check them for memory errors.</li><li>If no horrific memory errors turn up, try to replace whatever’s under unsafe with safe code without sacrificing performance.</li></ol><p>Turns out Rust community is awesome and not only has excellent integration for all <a href="https://github.com/rust-fuzz/afl.rs">three</a> <a href="https://github.com/rust-fuzz/cargo-fuzz">practical</a> <a href="https://github.com/rust-fuzz/honggfuzz-rs">fuzzers</a> along with a quick start guide for each, but also <a href="https://github.com/rust-fuzz/targets">a huge collection of fuzz targets</a> that covers a great deal of popular crates. Ack! Getting low-hanging fruit at step 1 is foiled!</p><p>So I’ve started checking whether fuzzing targets were written properly. Specifically, I’ve started looking for stuff that could block fuzzing — like checksums. A lot of formats have them internally, and PNG has not one but two — crc32 in png format and adler32 in deflate. And lo and behold, none of the crates were actually disabling checksums when fuzzing! This means that random input from fuzzer was rejected early (random data does not have a valid checksum in it, duh) and never actually reached the interesting decoding bits. So I’ve opened PRs for disabling checksums during fuzzing in <a href="https://github.com/Frommi/miniz_oxide/issues/29">miniz_oxide</a>, <a href="https://github.com/PistonDevelopers/image-png/pull/81">png</a>, <a href="https://github.com/kornelski/lodepng-rust/pull/29">lodepng-rust</a>, and <a href="https://github.com/RustAudio/ogg/pull/6">ogg</a>, and then fuzzed them with checksums disabled. This got me:</p><ul><li><a href="https://github.com/PistonDevelopers/image-png/issues/79">4 distinct panics</a> and a <a href="https://github.com/PistonDevelopers/image-png/issues/80">memory exhaustion</a> in png</li><li><a href="https://github.com/kornelski/lodepng-rust/issues/28">Memory leak</a> in lodepng-rust</li><li>A <a href="https://github.com/RustAudio/lewton/issues/27">panic</a> in lewton, the Vorbis decoder in Rust. There are probably more panics hiding behind this one.</li></ul><p>inflate crate was the first where fuzzing has turned up nothing at all, so I&#39;ve started eyeballing its unsafes and trying to rewrite them into safe code. I&#39;ve added a <a href="https://github.com/Shnatsel/inflate/blob/benchmarking/benches/inflate_samples.rs">benchmarking harness</a> and started measuring whether reverting back to safe code hurts performance. cargo bench was too noisy, but I&#39;ve quickly discovered <a href="https://japaric.github.io/criterion.rs/book/index.html">criterion</a> which got me the precision I needed (did I mention Rust tooling is awesome?). I got lucky - there were two unsafes with two-line safe equivalent commented out, and reverting back to safe code created no measurable performance difference. Apparently the compiler got smarter since that code was written, so I&#39;ve just reverted back to safe code.</p><p>This left <a href="https://github.com/PistonDevelopers/inflate/blob/1a810dba8467fc9cf1e8c60155d3266790072220/src/lib.rs#L663">just one unsafe</a> with a single line in it. <strong>Spot the security vulnerability.</strong> I would have missed it if the crate maintainer hadn’t pointed it out. If you can’t, there are hints at the end of this post.</p><p>By sheer luck the rest of the crate just so happens to be structured in a way that never passes input parameters that trigger the vulnerability, so it is not really exploitable. Probably. I could not find a way to exploit it, and the crate maintainer assures me it’s fine. Perhaps we just haven’t figured out how to do it yet. After all, <a href="https://googleprojectzero.blogspot.com/2014/08/the-poisoned-nul-byte-2014-edition.html">almost everything is exploitable if you try hard enough</a>.</p><p>Sadly, simply replacing the unsafe .set_len() with .resize() regressed the decompression performance by 10%, so instead I&#39;ve added an extra check preventing this particular exploit from happening, and then liberally sprinkled the function with asserts that panic on every other way this unsafe could go wrong that I could think of.</p><p>Is the function secure now? Well, maybe. Maybe not. Unless we either rewrite it in safe rust (or prove its correctness, which is <em>a lot</em> harder) we will never know.</p><p>The thing is, I’m pretty sure it’s possible to rewrite this in safe Rust without performance penalty. I’ve tried <a href="https://github.com/PistonDevelopers/inflate/pull/43#discussion_r197629445">some local optimizations</a> briefly, to no avail. Just like with high-level languages, writing fast safe Rust requires staying on the optimizer’s happy paths, and I have not found any documentation or tooling for doing that. <a href="https://godbolt.org/">https://godbolt.org/</a> that lets you inspect the LLVM IR as well as assembler and shows what line of Rust turned into what line of assembly, but you can’t feed your entire project to it. (As pointed out in comments, <a href="https://github.com/gnzlbg/cargo-asm">cargo-asm</a> can do that to an entire project). And you also need tools to understand why a certain optimization was not applied by rustc. LLVM flags -Rpass-missed and -Rpass-analysis seem to be capable of doing that, but there is literally no documentation on them in conjunction with Rust.</p><p>Discussing the vulnerability further would be spoilerrific (seriously, try to locate it yourself), so I’ll leave further technical discussion until the end of the post. I want to say that I was very satisfied with how the crate maintainer reacted to the potential vulnerability — he seemed to take it seriously and investigated it promptly. Coming from C ecosystem it is refreshing to be taken seriously when you point out those things.</p><p>By contrast, nobody seems to care about denial of service vulnerabilities. In the 3 crates I’ve reported such vulnerabilities for, after 3 weeks not a single one was investigated or fixed by maintainers of those crates, or anyone else really. And the DoS bugs are not limited to panics that you can just isolate into another thread and forget about.</p><p>After not getting any reaction from crate maintainers for a while I tried fixing those bugs myself, starting with the png crate. In stark contrast to C, it is surprisingly easy to jump into an existing Rust codebase and start hacking on it, even if it does rather involved things like PNG parsing. I&#39;ve fixed all the panics that fuzzers discovered based on nothing but debug mode backtraces, and I don&#39;t even know Rust all that well. Also, this is why there are 4 distinct panics listed for PNG crate: I&#39;ve fixed one and kept fuzzing until I discovered the next one. lewton probably has many more panics in it, I just didn&#39;t got beyond the first one. Sadly, three weeks later my PR is still not merged, reinforcing the theme of &quot;nobody cares about denial of service&quot;. And png still has a <a href="https://github.com/PistonDevelopers/image-png/issues/80">much nastier DoS bug</a> that cannot be isolated in a thread.</p><p>(To be clear, this is not meant as bashing any particular person or team; there may be perfectly valid reasons for why it is so. But this does seem to be the trend throughout the ecosystem, and I needed some examples to illustrate it).</p><p>Also, shoutout to <a href="https://github.com/snapview/tungstenite-rs">tungstenite</a> — it was the only crate that did not exhibit <em>any</em> kinds of bugs when being fuzzed for the first time. Kudos.</p><p>Conclusions:</p><ul><li>Unlike C libraries, Rust crates do not dispense security vulnerabilities when you poke them with a fuzzer for the first time (or sometimes even <a href="https://blog.fuzzing-project.org/45-ImageMagick-heap-overflow-and-out-of-bounds-read.html">the third time</a>). Humans make all the same mistakes, but Rust prevents them from turning into exploits. Mostly.</li><li>Rust tooling is diverse, high-quality and accessible. <a href="https://github.com/rust-fuzz/afl.rs">afl.rs</a>, <a href="https://github.com/rust-fuzz/cargo-fuzz">cargo-fuzz</a>, <a href="https://github.com/rust-fuzz/honggfuzz-rs">honggfuzz-rs</a>, <a href="https://github.com/japaric/rust-san">sanitizers</a>, <a href="https://github.com/japaric/criterion.rs">criterion</a>, <a href="https://github.com/AltSysrq/proptest">proptest</a> and <a href="https://github.com/rust-lang-nursery/rust-clippy">clippy</a> not only exist, but also come with quickstart guides that makes deploying any of them take less than 15 minutes.</li><li>Cargo and docs.rs combined with Rust language features that allow expressively encoding application logic make an existing complex codebase surprisingly easy to understand and hack on, making drive-by contributions a breeze. And I don’t even know Rust all that well.</li><li>Hardly anyone uses #![forbid(unsafe_code)]. Rust offers to rid you of paranoia and arbitrary code execution exploits, but people don&#39;t seem to take up on the offer. (Shoutout to <a href="https://github.com/RustAudio/lewton">lewton</a> developers who did).</li><li>Safe Rust code can be as fast as one with unsafe (shoutout to <a href="https://github.com/serde-rs/json">serde-json</a> that is the fastest JSON parser in the world, written in fully safe Rust), but squeezing out those last 20% requires you to adjust your code in arcane ways to hit the optimizer happy paths, kinda like with high-level languages. There is no documentation or tooling for doing such a thing, although the building blocks are there. Until such documentation and tooling is created, the only viable option is trial and error.</li><li>A lot of crates contain 2–3 unsafe blocks that can probably be refactored into safe code without losing performance. This is probably related to the lack of tooling. Rust isolates unsafe code and that makes auditing code easier, but in practice it is not actually audited. We need a libs-blitz-like effort to get rid of such unsafes, I can&#39;t process the entire ecosystem alone. (If you also put #![forbid(unsafe_code)] on the cleansed crate, I will love you forever).</li><li>Fuzzing would not have discovered this vulnerability at all, unless you had a very specific fuzzing setup looking specifically for this kind of thing. Even then, the chances of ever hitting it were pretty darn low. Fuzzing is a very easy way to prove presence of bugs, but it cannot prove their absence.</li><li>Symbolic execution tools like <a href="https://klee.github.io/">KLEE</a> or <a href="https://saw.galois.com/">SAW</a> that can be used to prove correctness do not have Rust integration, even though both operate on LLVM IR. KLEE <a href="https://github.com/jawline/klee-rust">used to have it</a>, but sadly the LLMV version used in KLEE is now grossly outdated. <strong>Update:</strong> <a href="https://github.com/smackers/smack">SMACK</a> has been <a href="http://soarlab.org/publications/atva2018-bhr.pdf">adapted</a> to work with Rust!</li><li>If you want to write DoS-critical code in Rust and use some existing libraries, you’re out of luck. Nobody cares about denial of service attacks. You can poke popular crates with a fuzzer and get lots of those. When you report them, they do not get fixed. <a href="https://github.com/Technolution/rustig">There is a linter to detect potential panics</a>, but if a linter for stuff like stack overflows or unbounded memory allocations exists, I am not aware of it.</li><li>Rust has no mechanism for propagating security updates through the ecosystem. I was surprised to find that Cargo does not alert you when you’re using an outdated library version with a security vulnerability, and <a href="https://crates.io">crates.io</a> does not reject uploads of new crates that depend on vulnerable library versions, and does not alert maintainers of existing crates that their dependencies are vulnerable. A third-party tool to check for security vulnerabilities <a href="https://github.com/rustsec/cargo-audit">exists</a>, but you’ve never heard of it and you have better things to do than run that on all of your crates every day anyway.</li></ul><p>Originally I thought this would be a fun exercise for a few weekends, but the scope of the work quickly grew way beyond what I can hope to achieve alone. This is where you come in, though! Here’s a list of things you can try, in addition to the hard tooling tasks listed above:</p><ol><li>Fuzz all the things! It takes 15 minutes to set up per crate, there is no reason not to. Also, there is a <a href="https://github.com/rust-fuzz/trophy-case">trophy case</a>.</li><li>Fix bugs already discovered. For example: <a href="https://github.com/RustAudio/lewton/issues/27">panic in lewton</a> (easy), <a href="https://github.com/PistonDevelopers/image-png/issues/80">unbounded memory consumption in png</a> (intermediate), <a href="https://github.com/kornelski/lodepng-rust/issues/28">lodepng memory leak</a> (C-hard). You can also fuzz lewton afterwards to get more panics, just don’t forget to use ogg dependency from git. You can reuse my <a href="https://github.com/Shnatsel/lewton-afl">fuzz</a> <a href="https://github.com/Shnatsel/lewton-fuzz">harnesses</a> if you wish.</li><li>Refactor unsafes in popular crates into safe code, ideally without sacrificing performance. For example, inflate crate has <a href="https://github.com/PistonDevelopers/inflate/blob/a36a517194b91b8279aa28f6ed84a0c3a87372e1/src/lib.rs#L656">just one</a> unsafe block remaining, png has <a href="https://github.com/PistonDevelopers/image-png/issues/60">two</a>. There are many more crates like that out there.</li><li>There are easy tasks on docs and tooling too: <a href="https://rust-fuzz.github.io/book/afl.html">AFL.rs documentation</a> is outdated and describes only version 0.3. Version 0.4 has added in-process fuzzing that’s ~10x faster, it needs to be mentioned. Also, AFL could use more Rusty integration with Cargo, closer to what cargo-fuzz does. Also, disabling checksums is a common pitfall that needs to be mentioned.</li></ol><p>I’d love to keep fixing all the things, but at least in the coming month I will not able to dedicate any time to the project. I hope I’ve managed to at least lead by example.</p><p>And now, details on that vulnerability! If you haven’t found it yourself, here’s a hint: <a href="http://seclists.org/fulldisclosure/2013/Nov/83">similar bugs in C libraries</a>.</p><p>If you still haven’t found it, see the <a href="https://github.com/PistonDevelopers/inflate/commit/11c73f2c545183b51a10c339267e16fc2dd23d60">fix</a>.</p><p><strong>Spoilerrific discussion of the vulnerability below.</strong></p><p><a href="https://github.com/PistonDevelopers/inflate/blob/1a810dba8467fc9cf1e8c60155d3266790072220/src/lib.rs#L625">Vulnerable code from git history for reference</a></p><p>The function run_len_dist() does a fairly trivial thing: resizes a vector to fit a specified amount of data and copies data from element i to element i+dist until i+dist hits the end of the vector. For performance, contents of the vector are not initialized to zeroes when resizing, as it would have been done by vec.resize(); instead, vec.set_len() is used, creating a vector with a number of elements set to uninitialized memory at the end.</p><p>The function never checks that dist is not zero. Indeed, if you call it with dist set to 0, it will simply read uninitialized memory and write it right back, exposing memory contents in the output.</p><p>If this vulnerability were actually exploitable from the external API (which it isn’t, probably), inflate would have output contents of uninitialized memory in the decompressed output. inflate crate is used in png crate to decompress PNGs. So if png crate was used in a web browser (e.g. servo) to decode images, an attacker could pass a crafted PNG to the client, then read the decoded image using javascript. This lets the attacker read memory contents from the browser - cookies, passwords, you name it. This is not quite as bad as Heartbleed or Meltdown, but it&#39;s up there.</p><p>Sadly, regular fuzzing would not have discovered this vulnerability. If it were actually exploitable, at least one way to trigger it would involve setting several distinct bytes in the input to very specific values. And even the best current generation fuzzers cannot trigger any behavior that requires changing more than one byte simultaneously, except <a href="https://lcamtuf.blogspot.com/2014/11/afl-fuzz-nobody-expects-cdata-sections.html">in rare cases</a> or if you explicitly tell what consecutive byte strings it should try. And there is nothing in the code that would guide the fuzzers to these specific values.</p><p>Even if fuzzers did discover such an input by random chance, they would not have recognized it as a vulnerability, unless you do either of these things:</p><ul><li>Fuzz your code under memory sanitizer (not to be confused with address sanitizer), which is impossible for any crate that links to C code and is compatible with only one fuzzer — AFL, and only in its slower stdin mode (possibly honggfuzz too in its slower binary-only instrumentation mode, but I haven’t checked).</li><li>Create a fuzz harness that decodes the same input twice and verifies that the output matched, <em>and</em> somehow ensure that the memory allocation was not reused. AFAIK Rust’s default jemalloc allocator can reuse allocated memory, so you&#39;re probably again limited to AFL in stdin mode.</li></ul><p>This just goes to show that fuzzing unsafe code does not actually guarantee absence of bugs.</p><p>Safe Rust, however, does guarantee absence of memory errors that lead to arbitrary code execution exploits and other unspeakable horrors. So let’s use it.</p><p><a href="https://www.reddit.com/r/rust/comments/8zpp5f/auditing_popular_crates_how_a_oneline_unsafe_has/"><em>Join the discussion this article on Reddit</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fab2d837ebb1" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>