← Back to list

6 Rust Unsafe Pitfalls That Break Soundness and How to Audit Them

Nithin Bharadwaj in TechKoala Insights · 2026-04-23 18:48 · 0 claps · 4.4 min read
#unsafe-rust #soundness-bugs #undefined-behavior #cargo-miri #memory-corruption
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3

6 Rust Unsafe Pitfalls That Break Soundness and How to Audit Them

Learn 6 common Rust unsafe pitfalls that cause soundness bugs, memory corruption, and UB — and how to audit code with Miri before release.

As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!

I still remember the first time a bug in my unsafe code only showed up under load on a customer's ARM machine. Locally, everything passed tests. Then a misaligned pointer caused a silent data corruption that took weeks to trace. That experience taught me that "it compiles" and "it passes tests" are not the same as "it's sound." Rust's unsafe blocks are sometimes necessary for FFI, custom allocators, or performance tricks, but they suspend the compiler's safety net. Left unchecked, they can introduce memory corruption that only manifests in production after months of innocent behavior. Today I want to walk you through six common ways unsafe breaks soundness in real codebases—and how you can audit your own code before customers find them for you.

Have you ever assumed that casting a *const u8 to *const u64 is safe because the value fits? It isn't, unless you guarantee proper alignment. Transmuting a pointer to a wider type without checking alignment creates undefined behavior (UB). The compiler may generate code that works on x86 but crashes on ARM. Instead of *(ptr as *const u64), use ptr.cast::<u64>().read_unaligned() inside the unsafe block. This tells the CPU to handle any alignment gracefully. I now make it a habit to always check alignment—or use read_unaligned—when reading through raw pointers.

One of the most common traps I see in code reviews involves storing a raw pointer from a Vec and then forgetting about reallocation. You call vec.as_ptr(), capture the address, and later push a new element. The push may reallocate the internal buffer, invalidating your pointer. The compiler gives no warning because it assumes you know what you're doing. But now you're reading from freed or moved memory. The fix is simple: never hold a raw pointer across an operation that could invalidate it. If you must, work with the Vec by index instead of retaining the pointer. Without a tool like Miri, this bug sits quiet until the stars align and memory gets reused.

Wrapping FFI handles often forces us to implement Send and Sync manually on wrapper structs. I've seen teams mark a *mut c_void wrapper as Send because "the C library says it's thread-safe." But what if the library's documentation is ambiguous, or the build uses a different threading model? Violating Send/Sync invariants is a soundness hole that can cause data races in safe Rust code. The audit rule I follow: implement Send only after verifying the underlying C object is truly Send-safe, and document your reasoning in a comment next to the unsafe impl. Run cargo miri with -Zmiri-disable-isolation to catch missing thread safety.

Constructing slices with slice::from_raw_parts is another minefield. You pass a pointer and a length, but if the length is wrong—even by one element—you're reading or writing outside the allocation. In a recent project, a colleague accidentally passed the number of bytes instead of the number of elements for a u16 slice. That created a slice whose length was double the actual allocation. Miri flagged it immediately. The question is: how often do you review every call to from_raw_parts for correct length and lifetime? Your answer should be "always."

Double drops are rare but catastrophic when they happen. Using ManuallyDrop is a common pattern to prevent the compiler from dropping a value prematurely, but misuse can lead to double-free. For example, you wrap a String in ManuallyDrop, then extract it with ManuallyDrop::take, and later call drop on the ManuallyDrop wrapper. The String is dropped twice—once by your explicit drop, once when the wrapper goes out of scope. The fix is to never call drop on the wrapper if you've already extracted the inner value. I now treat ManuallyDrop as a safety crate that requires a mental audit before every use.

Finally, stacked borrows violations go undetected by the compiler but are caught by Miri. Creating two mutable references to the same memory location through pointer arithmetic compiles fine but violates Rust’s aliasing rules. For example, taking &mut x twice—even if you store them as raw pointers—is UB. The compiler assumes references do not alias, and optimizations based on that assumption can produce incorrect machine code. When was the last time you ran cargo miri test on your project? If the answer is "never," you're flying blind.

These six pitfalls are not exotic; they appear in production code every day. The good news is that you can audit them systematically. Use cargo miri to catch UB during tests. Use cargo careful for runtime checks in safe and unsafe code. And add a CI step that runs cargo geiger to track how many unsafe blocks your dependencies introduce. The single highest-ROI safety investment for any crate using unsafe is running cargo miri test before every merge. It has saved me from shipping bugs more times than I can count.

If you found this breakdown useful, hit the like button — it helps others discover these patterns. Share it with your team so you can all audit your unsafe code more effectively. And leave a comment about the weirdest unsafe bug you've encountered. I'd love to compare notes.

📘 Checkout my latest ebook for free on my channel! Be sure to like, share, comment, and subscribe to the channel!

101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low — some books are priced as low as $4 — making quality knowledge accessible to everyone.

Check out our book **Golang Clean Code** available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

**Investor Central | [Investor Central Spanish](https://spanish.investorcentral.co.uk/) | [Investor Central German](https://german.investorcentral.co.uk/) | [Smart Living](https://smartliving.investorcentral.co.uk/) | [Epochs & Echoes](https://epochsandechoes.com/) | [Puzzling Mysteries](https://www.puzzlingmysteries.com/) | [Hindutva](http://hindutva.epochsandechoes.com/) | [Elite Dev](https://elitedev.in/) | [Java Elite Dev](https://java.elitedev.in/) | [Golang Elite Dev](https://golang.elitedev.in/) | [Python Elite Dev](https://python.elitedev.in/) | [JS Elite Dev](https://js.elitedev.in/) | [JS Schools](https://jsschools.com/)**

We are on Medium

**Tech Koala Insights | [Epochs & Echoes World](https://world.epochsandechoes.com/) | [Investor Central Medium](https://medium.investorcentral.co.uk/) | [Puzzling Mysteries Medium](https://medium.com/puzzling-mysteries) | [Science & Epochs Medium](https://science.epochsandechoes.com/) | [Modern Hindutva](https://modernhindutva.substack.com/)**


메타데이터
post_id
4c55e5434e6e
slug
6-rust-unsafe-pitfalls-that-break-soundness-and-how-to-audit-them-4c55e5434e6e
url
https://medium.techkoalainsights.com/6-rust-unsafe-pitfalls-that-break-soundness-and-how-to-audit-them-4c55e5434e6e
canonical_url
https://medium.techkoalainsights.com/6-rust-unsafe-pitfalls-that-break-soundness-and-how-to-audit-them-4c55e5434e6e
author_url
https://medium.com/@nithin-bharadwaj
status
ok
fetched_at
2026-07-20 17:17:34