dotnet / dotnet/dotnet-api-docs
MemoryMarshal.Cast should mention that this does not preserve type safety
- Dominant language
- C#
- Stars
- 949
- Forks
- 1.7k
- Avg merge
- 3d 27m
- Merged PRs (30d)
- 49
Description
Doc page: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.memorymarshal.cast
The discussion below also applies to `MemoryMarshal.AsBytes` (https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.memorymarshal.asbytes), as `MemoryMarshal.AsBytes` is really just a glorified wrapper around `MemoryMarshal.Cast`.
The documentation makes the claim "Neither `TFrom` nor `TTo` can contain pointers or references. `TFrom` and `TTo` are checked at runtime in order to preserve type safety." However, this is incorrect. Both `TFrom` and `TTo` are allowed to contain pointers. The only restriction is that they can't contain references (pointers do not count as references).
Aside from the aforementioned reference checking, the runtime _does not_ otherwise ensure that the two types being converted are compatible. This could allow type safety violations by bypassing `TTo`'s normal constructor logic and setting the private backing fields to arbitrary bit patterns.
__It is almost never correct to use `MemoryMarshal.Cast` for serialization or I/O purposes.__
If untrusted binary data (such as from a web request) is fed to `MemoryMarshal.Cast` in order to generate a new `Span`, an adversary may be able to craft binary data whose bit pattern is invalid for the destination type `TTo`. This could lead to undefined behaviors at runtime, including opening security holes in the web application.
The developer is responsible for ensuring that the bit pattern specified in the `Span` is a valid bit pattern for the contents of the `Span`. The developer is also responsible for handling any endianness conversion issues that may arise from widening or narrowing integer data.
`MemoryMarshal.Cast` may also fail at runtime if the resulting `Span` would have a length that would overflow a normal `int`. For example, if given an input `Span` of length 1 billion elements, the call to `MemoryMarshal.Cast(theSpan)` will fail because the resulting `Span` would contain 4 billion elements, which would cause `Span.Length` to overflow.
All of this is in addition to the standard disclaimer re: misaligned memory accesses.
For a list of `MemoryMarshal.Cast` conversions which are always safe, consult the following table.
| `TFrom` | `TTo` |
|-------|-----|
| `byte` or `sbyte` | `byte` or `sbyte` |
| `short` or `ushort` | `short`, `ushort`, `byte`, or `sbyte` |
| `int`, `uint`, or `float` | `int`, `uint`, `float`, `short`, `ushort`, `byte`, or `sbyte` |
| `long`, `ulong`, or `double` | `long`, `ulong`, `double`, `int`, `uint`, `float`, `short`, `ushort`, `byte`, or `sbyte` |
Note that the above table only includes conversions which are safe from both a type-safety and a memory alignment perspective. However, the conversions might not be meaningful (such as converting from a `Span` to a `Span`), and they might fail at runtime due to overflow considerations mentioned previously. And developers would still need to consider any endianness conversions between the types in the above table.
Contributor guide
Assessment
This issue has not been assessed yet.