Safeguard
Developer Tools•

Sound versus unsound: wrapping a C library safely in Rust

A safe Rust wrapper around C is only sound if no safe caller can trigger undefined behavior. What soundness means and a review checklist for FFI wrappers.

Safeguard Security Team
4 min read

A Rust wrapper around a C library is sound if no sequence of safe calls can cause undefined behavior, and unsound if one can. The Rust Reference puts the responsibility on the programmer who writes the unsafe code. A wrapper that compiles cleanly can still be unsound.

What the Reference says

The Rust Reference says that unsafe shifts responsibility for avoiding undefined behavior to the programmer and does not permit it. Programmers must ensure safe code cannot trigger undefined behavior through their unsafe code. Unsafe code that guarantees this for any safe client is called sound. If safe code can misuse it, it is unsound.

It also warns that undefined behavior in one language, C reached through FFI included, affects the whole program. So the boundary is where your review time belongs.

Behaviors that count as undefined

The Reference list includes data races, loading or storing through a dangling or misaligned pointer, violating aliasing rules for references, producing an invalid value and unwinding through a frame that does not allow it. Many of these show up at FFI edges.

A review checklist

  1. Pointer validity. Does every pointer passed to C point to live, correctly aligned memory for as long as C uses it?
  2. Lengths. Is the length passed to C always the real length of the buffer?
  3. Ownership. Who frees what? Memory allocated by C should be freed by C, and a Rust owner type should wrap the release call.
  4. Aliasing. Can C keep a pointer after the call returns while Rust later mutates the data?
  5. Threads. Is the C library thread safe? If not, does the wrapper prevent sharing across threads?
  6. Invalid values. Can C hand back a value that is not a valid Rust type, such as a boolean that is neither zero nor one, or an enum with an unknown value?
  7. Unwinding. Can a panic cross the FFI boundary in a way the call convention does not allow?
  8. Null. Are null returns converted into an option, not assumed away?

Keep the unsafe surface small

Put all unsafe calls in a small module with documented safety conditions on each function. Expose only safe functions that enforce those conditions through types, such as a wrapper type that owns the raw handle and frees it on drop.

A small example of an unsound wrapper

Imagine a safe function that takes a slice and a length and passes both to a C function. If the length argument is supplied by the caller independently of the slice, a safe caller can pass a length larger than the slice, and C will read or write past the end. The function has no unsafe in its signature, yet safe code can trigger undefined behavior through it. That is unsound.

The sound version derives the length from the slice itself, so no safe caller can make them disagree. This pattern, shaping the safe API so the invalid state cannot be expressed, is the main technique for sound wrappers.

Documentation habits

Write a safety comment on every unsafe block that states which conditions hold and why. When a reviewer cannot tell from the comment why the block is sound, the block is not ready. Keep the comments next to the code, so they change when the code changes.

Test the wrapper hard

Run the wrapper tests under a memory checker, and feed it odd lengths and null values. A fuzzer is a good fit here, since the bugs sit in the gaps between two languages.

Quick check

Is my wrapper sound because it has no unsafe in its public API?

Not necessarily. Soundness depends on whether any safe caller can trigger undefined behavior through the unsafe code inside.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.