dotnet / dotnet/runtime

Annotate libraries with caller-unsafe keyword

Open
#125,145 5 comments 0 reactions 1 assignee Claimed by @EgorBo View on GitHub
area-System.Runtime.CompilerServices
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

Per the description in https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.md, we need to annotate functions that should be considered unsafe.

Links to API Proposals:

- https://github.com/dotnet/runtime/issues/126956
- https://github.com/dotnet/runtime/issues/127098
- https://github.com/dotnet/runtime/issues/129750
- https://github.com/dotnet/runtime/issues/128075
- https://github.com/dotnet/runtime/issues/129751

### Criteria

The [doc](https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.md) defines a safe API when it meets two criteria:

- Code never accesses memory that is not managed by the runtime
- No access to uninitialized memory

Here are a few more fine-grained criteria based on those two:

- **May create a potentially incorrect managed pointer**. Examples include all `Unsafe.Add*` and `Unsafe.Subtract*`. Such pointers may crash an app even when not directly dereferenced anywhere.
- **May create a managed pointer that should never be directly dereferenced**. Example: `MemoryMarshal.GetArrayDataReference` over an empty array.
- **Dereferences a caller-supplied unmanaged address** - reads or writes through a raw `IntPtr`/`nint`/`T*` parameter (or any kind of wrapper with those e.g. `SafeHandle`) directly or just lets it flow into a system where it might be dereferenced later.
- **May strip readonly-ness from a pointer**. Example: `Unsafe.AsRef` removing readonly and making a pointer to a readonly memory (e.g. RVA) writeable. This also may violate security invariants a type may hold.
- **May read uninitialized data**. Examples include reading padding/alignment in structs via various `MemoryMarshal` APIs, `Unsafe.BitCast`, `Unsafe.As`. Also, various `Unsafe.SkipInit`, `GC.AllocateUninitialized*`, etc.
- **May set an unsafe field without an `unsafe` context.** A good example is `MemoryMarshal.Read(ROS)` where T might theoretically have an unsafe field that this API lets you set without an `unsafe` context.
- **Produces a misaligned pointer** from a properly aligned input. Examples: `MemoryMarshal.Cast`, `[StructLayout(LayoutKind.Explicit)]`.

### Edge-cases

A section for edge-cases if any.
- Sometimes it's just not realistic to mark some widely used interface as unsafe e.g. `IDisposable.Dispose` to make some particular dangerous Dispose unsafe to call. In that case we need annotate all APIs that create that kind object as unsafe. Example: `GCHandle.Dispose` may lead to a global state corruption if called twice (e.g. on two copies of the same handle).
- All `extern`/`[LibraryImport]`/`InternalCall` APIs are considered unsafe by default unless explicitly annotated with `safe` keyword.

### Out of scope

- Reflection. Eventually we will likely make it mostly unsafe, but today it's completely out of scope.
- Shapes considered safe today because C# doesn't have lifetimes/ownership, allows static mutable state, etc.
- Thread-safety (although there are some intersections, it's generally considered orthogonal to memory safety)
- APIs in OOB packages (for now)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.