From 3f63aab5d1ba709598f3b31f36528efcddaab71e Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Thu, 8 Aug 2024 02:40:16 +0200 Subject: [PATCH] BinaryFormatter Migration Guide (#41564) Add BinaryFormatter migration guide Co-authored-by: Jeff Handley Co-authored-by: Jeremy Barton Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Co-authored-by: Loni Tra Co-authored-by: Andrew Arnott Co-authored-by: Marc Gravell Co-authored-by: Rishabh Chauhan Co-authored-by: Immo Landwerth Co-authored-by: Eirik Tsarpalis Co-authored-by: Tanya Solyanik --- docs/fundamentals/toc.yml | 26 ++ .../choose-a-serializer.md | 74 ++++++ .../compatibility-package.md | 37 +++ .../functionality-reference.md | 89 +++++++ .../binaryformatter-migration-guide/index.md | 72 ++++++ .../migrate-to-datacontractserializer.md | 40 +++ .../migrate-to-messagepack.md | 33 +++ .../migrate-to-protobuf-net.md | 61 +++++ .../migrate-to-system-text-json.md | 49 ++++ .../read-nrbf-payloads.md | 235 ++++++++++++++++++ .../winforms-applications.md | 98 ++++++++ .../winforms-wpf-ole-guidance.md | 39 +++ .../wpf-applications.md | 71 ++++++ .../binaryformatter-security-guide.md | 8 +- includes/binary-serialization-warning.md | 2 +- 15 files changed, 931 insertions(+), 3 deletions(-) create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/choose-a-serializer.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/compatibility-package.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/functionality-reference.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/index.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/migrate-to-datacontractserializer.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/migrate-to-messagepack.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/migrate-to-protobuf-net.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/migrate-to-system-text-json.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/read-nrbf-payloads.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/winforms-applications.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/winforms-wpf-ole-guidance.md create mode 100644 docs/standard/serialization/binaryformatter-migration-guide/wpf-applications.md diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index f6691433ef58b..0c47e2eaa3305 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -863,6 +863,32 @@ items: href: ../standard/serialization/xml-schema-definition-tool-xsd-exe.md - name: Binary serialization items: + - name: BinaryFormatter migration guide + items: + - name: Overview + href: ../standard/serialization/binaryformatter-migration-guide/index.md + - name: Choose a serializer + href: ../standard/serialization/binaryformatter-migration-guide/choose-a-serializer.md + - name: Migrate to System.Text.Json (JSON) + href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-system-text-json.md + - name: Migrate to DataContractSerializer (XML) + href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-datacontractserializer.md + - name: Migrate to MessagePack (binary) + href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-messagepack.md + - name: Migrate to protobuf-net (binary) + href: ../standard/serialization/binaryformatter-migration-guide/migrate-to-protobuf-net.md + - name: Read BinaryFormatter (NRBF) payloads + href: ../standard/serialization/binaryformatter-migration-guide/read-nrbf-payloads.md + - name: Compatibility package + href: ../standard/serialization/binaryformatter-migration-guide/compatibility-package.md + - name: BinaryFormatter functionality reference + href: ../standard/serialization/binaryformatter-migration-guide/functionality-reference.md + - name: Windows Forms apps + href: ../standard/serialization/binaryformatter-migration-guide/winforms-applications.md + - name: WPF apps + href: ../standard/serialization/binaryformatter-migration-guide/wpf-applications.md + - name: Clipboard and drag-and-drop guidance + href: ../standard/serialization/binaryformatter-migration-guide/winforms-wpf-ole-guidance.md - name: BinaryFormatter security guide href: ../standard/serialization/binaryformatter-security-guide.md - name: BinaryFormatter event source diff --git a/docs/standard/serialization/binaryformatter-migration-guide/choose-a-serializer.md b/docs/standard/serialization/binaryformatter-migration-guide/choose-a-serializer.md new file mode 100644 index 0000000000000..5bde19ef46018 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/choose-a-serializer.md @@ -0,0 +1,74 @@ +--- +title: "BinaryFormatter migration guide: Choose a serializer" +description: "Compare the capabilities and trade-offs of serializers to choose a replacement for BinaryFormatter." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization] +helpviewer_keywords: + - "BinaryFormatter" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# Choose a serializer + +There is no drop-in replacement for BinaryFormatter, but there are several serializers recommended for serializing .NET types. Regardless of which serializer you choose, changes will be needed for integration with the new serializer. During these migrations, it's important to consider the trade-offs between coercing the new serializer to handle existing types with as few changes as possible vs. refactoring types to enable idiomatic serialization with the chosen serializer. Once a serializer is chosen, its documentation should be studied for best practices. + +If a binary serialization format is not a requirement, you can consider using JSON or XML serialization formats. These serializers are included in .NET and are officially supported. + +1. JSON using [System.Text.Json](./migrate-to-system-text-json.md) +2. XML using [`System.Runtime.Serialization.DataContractSerializer`](./migrate-to-datacontractserializer.md) + +If a compact binary representation is important for your scenarios, the following serialization formats and open-source serializers are recommended: + +1. [MessagePack](https://msgpack.org/) using [MessagePack for C#](./migrate-to-messagepack.md) +2. [Protocol Buffers](https://protobuf.dev/) using [protobuf-net](./migrate-to-protobuf-net.md) + +Whether you have control to change the API shape of the serialized type will influence your direction and approach to serialization. Migration to these serializers may be more straightforward with the ability to annotate types with new attributes, add new constructors, make types/members public, and change fields to properties. Without that ability, using modern serializers might require implementation of custom converters or resolvers. + +| Feature | BinaryFormatter | System.Text.Json | DataContractSerializer | MessagePack for C# | protobuf-net | +|------------------------------------------------|------------------|-------------------------|------------------------|--------------------------|-----------------------------------| +| Serialization format | binary (NRBF) | JSON | XML | binary (MessagePack) | binary (Protocol Buffers) | +| Compact representation | ✔️ | ❌ | ❌ | ✔️ | ✔️ | +| Human-readable | ❌️ | ✔️ | ✔️ | ❌️ | ❌️ | +| Performance | ❌️ | ✔️ | ❌ | ✔️ | ✔️ | +| `[Serializable]` attribute support | ✔️ | ❌ | ✔️ | ❌ | ❌ | +| Serializing public types | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| Serializing non-public types | ✔️ | ✔️ | ✔️ | ✔️ (resolver required) | ✔️ | +| Serializing fields | ✔️ | ✔️ (opt in) | ✔️ | ✔️ (attribute required) | ✔️ (attribute required) | +| Serializing non-public fields | ✔️ | ✔️ (resolver required) | ✔️ | ✔️ (resolver required) | ✔️ (attribute required) | +| Serializing properties | ✔️* | ✔️ | ✔️ | ✔️ (attribute required) | ✔️ (attribute required) | +| Deserializing readonly members | ✔️ | ✔️ (attribute required) | ✔️ | ✔️ | ✔️ (parameterless ctor required) | +| Polymorphic type hierarchy | ✔️ | ✔️ (attribute required) | ✔️ | ✔️ (attribute required) | ✔️ (attribute required) | +| AOT support | ❌️ | ✔️ | ❌ | ✔️ | ❌ (planned) | + +## JSON using System.Text.Json + +The [`System.Text.Json`](../system-text-json/overview.md) library is a modern serializer that emphasizes security, high performance, and low memory allocation for the JavaScript Object Notation (JSON) format. JSON is human-readable and has broad cross-platform support. While text-based format is not as compact as binary formats, it can be significantly reduced in size through compression. + +Serialization excludes non-public and readonly members unless specifically handled through attributes and constructors. System.Text.Json also supports [custom serialization and deserialization](../system-text-json/custom-contracts.md) for more control over how types are converted into JSON and vice versa. System.Text.Json does not support the `[Serializable]` attribute. + +[Migrate to System.Text.Json (JSON)](./migrate-to-system-text-json.md). + +## XML using DataContractSerializer + +[`System.Runtime.Serialization.DataContractSerializer`](/dotnet/api/system.runtime.serialization.datacontractserializer) was introduced in .NET Framework 3.0 and is used to serialize and deserialize data sent in Windows Communication Foundation (WCF) messages. `DataContractSerializer` is an XML serializer that **fully supports the serialization programming model that was used by the `BinaryFormatter`**, which means it honors the `[Serializable]` attribute and implementation of `ISerializable`. Hence, it's the serializer that requires the least amount of effort to migrate to. It does, however, require the known types to be specified up-front (but most .NET collections and primitive types are on a default allow-list and don't need to be specified). + +While `DataContractSerializer` carries those functional benefits when migrating from BinaryFormatter, it is not as modern or performant as the other choices. + +[Migrate to DataContractSerializer (XML)](./migrate-to-datacontractserializer.md). + +> [!NOTE] +> Do not confuse `DataContractSerializer` with [`NetDataContractSerializer`](/dotnet/api/system.runtime.serialization.netdatacontractserializer). `NetDataContractSerializer` is also identified as a [dangerous serializer](../binaryformatter-security-guide.md#dangerous-alternatives). + +## Binary using MessagePack + +[MessagePack](https://msgpack.org/) is a compact binary serialization format, resulting in smaller message sizes compared to JSON and XML. The open source [MessagePack for C#](https://github.com/MessagePack-CSharp/MessagePack-CSharp) library is highly performant and offers built-in super-fast LZ4 compression for an even smaller data size. It works best when data types are annotated with either `DataContractSerializer` or the library's own attributes. It can be configured to support AOT environments, non-public types and members, and read-only types and members. + +[Migrate to MessagePack (binary)](./migrate-to-messagepack.md). + +## Binary using protobuf-net + +The [protobuf-net](https://github.com/protobuf-net/protobuf-net) library is a contract-based serializer for .NET that uses the binary [Protocol Buffers](https://protobuf.dev) serialization format. The API follows typical .NET patterns and is broadly comparable to `XmlSerializer` and `DataContractSerializer`. This popular library is also feature-rich and can handle non-public types and fields, but many scenarios do require applying attributes to members. + +[Migrate to protobuf-net (binary)](./migrate-to-protobuf-net.md). diff --git a/docs/standard/serialization/binaryformatter-migration-guide/compatibility-package.md b/docs/standard/serialization/binaryformatter-migration-guide/compatibility-package.md new file mode 100644 index 0000000000000..9e70d5b8276fd --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/compatibility-package.md @@ -0,0 +1,37 @@ +--- +title: "BinaryFormatter migration guide: Compatibility Package" +description: "Using the BinaryFormatter compatibility package." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization] +helpviewer_keywords: + - "BinaryFormatter" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# BinaryFormatter compatibility package + +> [!CAUTION] +> The compatibility package is not supported and unsafe. We strongly recommend against taking a dependency on this package and instead [migrate away from BinaryFormatter](./index.md#migration-topics). + +.NET 9+ users who can't migrate away from `BinaryFormatter` can install the unsupported [System.Runtime.Serialization.Formatters](https://www.nuget.org/packages/System.Runtime.Serialization.Formatters) NuGet package and set the `System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization` AppContext switch to `true`. + +> [!NOTE] +> Please note that this package doesn't change the type identity of BinaryFormatter. Existing libraries don't need to be updated to depend on this package in order to use it. The only place that needs to depend on this package is the application project. + +The package replaces the in-box implementation of BinaryFormatter with a functioning one, including its vulnerabilities and risks. It's meant as a stop gap if you can't wait with migrating to .NET 9 and later while not having replaced the usages of BinaryFormatter yet. We still strongly recommend you [migrate away from BinaryFormatter](./index.md#migration-topics). + +```xml + + net9.0 + true + + + + + +``` + +> [!CAUTION] +> The compatibility package is not supported and unsafe. We strongly recommend against taking a dependency on this package and instead [migrate away from BinaryFormatter](./index.md#migration-topics). diff --git a/docs/standard/serialization/binaryformatter-migration-guide/functionality-reference.md b/docs/standard/serialization/binaryformatter-migration-guide/functionality-reference.md new file mode 100644 index 0000000000000..83d23cba851c8 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/functionality-reference.md @@ -0,0 +1,89 @@ +--- +title: "BinaryFormatter migration guide: Functionality reference" +description: "A reference for BinaryFormatter's functionality that may need to be considered during migrations." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization, WinForms] +dev_langs: + - CSharp +helpviewer_keywords: + - "BinaryFormatter" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# BinaryFormatter functionality reference + +The `BinaryFormatter` was first introduced with the initial release of .NET Framework in 2002. To understand how to replace usage of BinaryFormatter, it helps to know how BinaryFormatter works. + +`BinaryFormatter` can serialize any instance of any type that's annotated with `[Serializable]` or implements the `ISerializable` interface. + +## Member names + +In most common scenario, the type is annotated with `[Serializable]` and the serializer uses reflection to serialize **all fields** (both public and non-public) except those that are annotated with `[NonSerialized]`. By default, the serialized member names will match the type's field names. This historically led to incompatibilities when even private fields are renamed on `[Serializable]` types. During migrations away from BinaryFormatter, it becomes necessary to understand how serialized field names were handled and overridden. + +### C# auto properties + +For C# [auto-implemented properties](/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties) (`{ get; set; }`), BinaryFormatter will serialize the backing fields that are generated by the C# compiler, not the properties. The names of those serialized backing fields contain illegal C# characters and cannot be controlled. A C# decompiler (such as [https://sharplab.io/](https://sharplab.io/) or [ILSpy](https://github.com/icsharpcode/ILSpy)) can demonstrate how C# auto properties are presented to the runtime. + +```csharp +[Serializable] +internal class PropertySample +{ + public string Name { get; set; } +} +``` + +The previous class is translated by the C# compiler to: + +```csharp +[Serializable] +internal class PropertySample +{ + private string k__BackingField; + + public string Name + { + get + { + return k__BackingField; + } + set + { + k__BackingField = value; + } + } +} +``` + +In this case, `k__BackingField` is **the name of the member that `BinaryFormatter` uses in the serialized payload**. It's not possible to use `nameof` or any other C# operator to get this name. + +The [ISerializable](/dotnet/api/system.runtime.serialization.iserializable) interface comes with [GetObjectData(SerializationInfo info, StreamingContext context)](/dotnet/api/system.runtime.serialization.iserializable.getobjectdata) method that allows the users to control the names, by using one of the [SerializationInfo.AddValue](/dotnet/api/system.runtime.serialization.serializationinfo.addvalue) methods. + +```csharp +// Note lack of any special attribute. +public void GetObjectData(SerializationInfo info, StreamingContext context) +{ + info.AddValue("Name", this.Name); +} +``` + +If such customization has been applied, the information needs to be provided during deserialization as well. That's possible by using the **serialization constructor**, where all values are read from `SerializationInfo` with one of the `Get` methods it provides. + +```csharp +private PropertySample(SerializationInfo info, StreamingContext context) +{ + this.Name = info.GetString("Name"); +} +``` + +> [!NOTE] +> The `nameof` operator was purposely not used here, as the payload can be persisted and the property can get renamed at a later time. So even if it gets renamed (say to `FirstName` because you decide to also introduce a `LastName` property), to remain backward compatible, the serialization should still use the old name that could have been persisted somewhere. + +## Serialization binder + +It's recommended to use [SerializationBinder](/dotnet/api/system.runtime.serialization.serializationbinder) to control class loading and mandate what class to load. That minimizes security vulnerabilities (so only allowed types get loaded, even if the attacker modifies the payload to deserialize and load something else). + +Using this type requires inheriting from it and overriding the [Type BindToType(string assemblyName, string typeName)](/dotnet/api/system.runtime.serialization.serializationbinder.bindtotype#system-runtime-serialization-serializationbinder-bindtotype(system-string-system-string)) method. + +Ideally the list of serializable types is closed set because it means you know which types can be instantiated which will help reduce security vulnerabilities. diff --git a/docs/standard/serialization/binaryformatter-migration-guide/index.md b/docs/standard/serialization/binaryformatter-migration-guide/index.md new file mode 100644 index 0000000000000..0697ac21066e9 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/index.md @@ -0,0 +1,72 @@ +--- +title: "BinaryFormatter migration guide" +description: "This guide covers the deprecation and removal of BinaryFormatter from .NET and recommends migration paths." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization] +helpviewer_keywords: + - "BinaryFormatter" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# BinaryFormatter migration guide + +> [!CAUTION] +> We strongly recommend against using BinaryFormatter due to the [associated security risks](#whats-the-risk-in-using-binaryformatter). Existing users [should migrate away from BinaryFormatter](#migration-topics). + +Starting with .NET 9, we no longer include an implementation of BinaryFormatter in the runtime. The APIs are still present, but their implementation always throws an exception, regardless of project type. Hence, setting the existing backwards compatibility flag is no longer sufficient to use BinaryFormatter. + +You have two options to address that: + +* **Migrate away from BinaryFormatter**. We strongly recommend you to investigate options to stop using BinaryFormatter due to the associated [security risks](#whats-the-risk-in-using-binaryformatter). We list [several options](#migration-topics) below. + +* **Keep using BinaryFormatter**. If you need to continue using BinaryFormatter in .NET 9, you need to depend on the [unsupported System.Runtime.Serialization.Formatters NuGet package](./compatibility-package.md), which replaces the throwing implementation. + +## What's the risk in using BinaryFormatter? + +Any deserializer, binary or text, that allows its input to carry information about the objects to be created is a security problem waiting to happen. There is a common weakness enumeration (CWE) that describes the issue: [CWE-502 "Deserialization of Untrusted Data"](https://cwe.mitre.org/data/definitions/502.html). BinaryFormatter, included in the the initial release of .NET Framework in 2002, is such a deserializer. We also cover this in the [BinaryFormater security guide](../binaryformatter-security-guide.md). + +Due to the known risks of using BinaryFormatter, the functionality was excluded from .NET Core 1.0. But without a clear migration path to using something safer, customer demand led to BinaryFormatter being included in .NET Core 1.1. Since then, the .NET team has been on the path to removing BinaryFormatter, slowly turning it off by default in multiple project types but letting consumers opt-in via flags if still needed for backward compatibility. + +For more details about the decision, see the [BinaryFormatter is being removed in .NET 9](https://github.com/dotnet/announcements/issues/293) announcement. + +If you experience issues related to BinaryFormatter's removal not addressed in this migration guide, please file an issue at [github.com/dotnet/runtime](https://github.com/dotnet/runtime/issues) and indicate that the issue is related to the removal of BinaryFormatter. + +## Migration topics + +Migrating away from BinaryFormatter usually means [choosing a different serializer](#choose-a-serializer). However, that's usually only doable if you control both the producer and consumer of the encoded data. In case you don't control the producer, you can also move to our [new API for reading BinaryFormatter payloads](#read-binaryformatter-nrbf-payloads) without instantiating any of the encoded types. + +Both options are explored below. + +### Choose a serializer + +The first step of migrating from `BinaryFormatter` is to [choose a serializer](./choose-a-serializer.md) to use in its place. Depending on your specific needs, the .NET team recommends migrations to four different serializers. + +* [Migrate to System.Text.Json (JSON)](./migrate-to-system-text-json.md) +* [Migrate to DataContractSerializer (XML)](./migrate-to-datacontractserializer.md) +* [Migrate to MessagePack (binary)](./migrate-to-messagepack.md) +* [Migrate to protobuf-net (binary)](./migrate-to-protobuf-net.md) + +### Read BinaryFormatter (NRBF) payloads + +Many applications load and deserialize payloads that have been persisted to storage and it's not always possible to transform all persisted payloads upfront. Other scenarios may involve systems or services that receive data produced by BinaryFormatter, where these systems need to be migrated independently. + +In these scenarios and others, it becomes necessary to retain support for reading the supplied payloads and transition to a new format over time. To meet these needs, it is now possible to securely [read NRBF payloads](./read-nrbf-payloads.md) created with `BinaryFormatter` without performing general-purpose and vulnerable deserialization. + +### Migrate Windows Forms and WPF applications + +Windows Forms and WPF applications might require additional changes. See [Windows Forms applications](./winforms-applications.md), [WPF applications](./wpf-applications.md), and [WinForms/WPF clipboard and drag/drop guidance](./winforms-wpf-ole-guidance.md) for further migration guidance. + +### Migrate managed resources (ResX) + +The most common resource types (such as strings and icons) will work without BinaryFormatter. For custom types, you need to bring in BinaryFormatter and enable a compatibility switch, see [Loading resource during runtime](./winforms-applications.md#loading-resource-during-runtime). + +### Use the compatibility package + +For scenarios where a migration away from BinaryFormatter cannot be accomplished at the time of upgrading to .NET 9, an unsupported compatibility package is available. The [System.Runtime.Serialization.Formatters](https://www.nuget.org/packages/System.Runtime.Serialization.Formatters) NuGet package contains the functioning implementation of BinaryFormatter, including its vulnerabilities and risks. + +While **unsupported and not recommended**, the guide for [using the compatibility package](./compatibility-package.md) includes the details for installing the package and enabling the functionality. + +> [!CAUTION] +> We strongly recommend against using BinaryFormatter due to the [associated security risks](#whats-the-risk-in-using-binaryformatter). Existing users [should migrate away from BinaryFormatter](#migration-topics). diff --git a/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-datacontractserializer.md b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-datacontractserializer.md new file mode 100644 index 0000000000000..6fbfab2141fe1 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-datacontractserializer.md @@ -0,0 +1,40 @@ +--- +title: "BinaryFormatter migration guide: Migrate to DataContractSerializer (XML)" +description: "Migrate from BinaryFormatter to DataContractSerializer for XML serialization." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization] +dev_langs: + - CSharp +helpviewer_keywords: + - "BinaryFormatter" + - "serialization [WCF]" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# Migrate to DataContractSerializer (XML) + +The .NET base class libraries provide two XML serializers: [XmlSerializer](../introducing-xml-serialization.md) and [DataContractSerializer](../../../fundamentals/runtime-libraries/system-runtime-serialization-datacontractserializer.md). There are some subtle differences between these two, but for the purpose of the migration, this section focuses only on `DataContractSerializer`. Why? Because it **fully supports the serialization programming model that was used by `BinaryFormatter`**. All the types that are already marked as `[Serializable]` or implement `ISerializable` can be serialized with `DataContractSerializer`. Where is the catch? Known types must be specified up front. You need to know them and be able to get the `Type`, **even for private types**. + +It's not required to specify most popular collections or primitive types like `string` or `DateTime` (the serializer has its own default allow-list), but there are exceptions like `DateTimeOffset`. For more information about the supported types, see [Types supported by the data contract serializer](../../../framework/wcf/feature-details/types-supported-by-the-data-contract-serializer.md). + +[Partial trust](../../../framework/wcf/feature-details/partial-trust.md) is a .NET Framework feature that wasn't ported to .NET (Core). If your code runs on .NET Framework and uses this feature, read about the [limitations](../../../framework/wcf/feature-details/types-supported-by-the-data-contract-serializer.md#limitations-of-using-certain-types-in-partial-trust-mode) that might apply to such a scenario. + +## Step by step migration + +1. Find all the usages of `BinaryFormatter`. +2. Ensure that the serialization code paths are covered with tests, so you can verify your changes and avoid introducing bugs. +3. You don't need to install any packages, as `DataContractSerializer` is part of the .NET core libraries. +4. Find all the types that are being serialized with `BinaryFormatter`. You don't need to modify any of them, but you may need to list them via `knownTypes` argument of the `DataContractSerializer` constructor. +5. Replace the usage of `BinaryFormatter` with `DataContractSerializer`. + +```csharp +DataContractSerializer serializer = new( + type: input.GetType(), + knownTypes: new Type[] + { + typeof(MyType1), + typeof(MyType2) + }); +``` diff --git a/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-messagepack.md b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-messagepack.md new file mode 100644 index 0000000000000..06f612ea2c6c6 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-messagepack.md @@ -0,0 +1,33 @@ +--- +title: "BinaryFormatter migration guide: Migrate to MessagePack (binary)" +description: "Migrate from BinaryFormatter to MessagePack for binary serialization." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization] +helpviewer_keywords: + - "BinaryFormatter" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# Migrate to MessagePack (binary) + +[MessagePack](https://msgpack.org/) is a compact binary serialization format, resulting in smaller message sizes compared to JSON and XML. The open source [MessagePack for C#](https://github.com/MessagePack-CSharp/MessagePack-CSharp) library is highly performant and offers built-in super-fast LZ4 compression for an even smaller data size. It works best when data types are annotated with either `DataContractSerializer` or the library's own attributes. It can be configured to support AOT environments, non-public types and members, and read-only types and members. + +Some behaviors and features of MessagePack for C# will be notable during migrations from BinaryFormatter, especially if changes to the serialized types' APIs cannot be made or need to be minimized. + +- By default, only public types are serializable. Private and internal structs and classes can be serialized only when `StandardResolverAllowPrivate.Options` is provided as an argument to `MessagePackSerializer.Serialize` and `MessagePackSerializer.Deserialize` methods. +- MessagePack requires each serializable type to be annotated with the `[MessagePackObject]` attribute. It's possible to avoid that by using the [ContractlessStandardResolver](https://github.com/MessagePack-CSharp/MessagePack-CSharp?tab=readme-ov-file#object-serialization), but it might cause issues with versioning in the future. +- Every serializable non-static field and a property needs to be annotated with the `[Key]` attribute. If you annotate the type with the `[MessagePackObject(keyAsPropertyName: true)]` attribute, then members don't require explicit annotations. In such case, to ignore certain public members, use the `[IgnoreMember]` attribute. +- To serialize private members, use [StandardResolverAllowPrivate](https://github.com/MessagePack-CSharp/MessagePack-CSharp?tab=readme-ov-file#object-serialization). +- `System.Runtime.Serialization` annotations can be used instead of MessagePack annotations: `[DataContract]` instead of `[MessagePackObject]`, `[DataMember]` instead of `[Key]`, and `[IgnoreDataMember]` instead of `[IgnoreMember]`. These annotations can be useful if you want to avoid a dependency on MessagePack in the library that defines serializable types. +- It supports readonly/immutable types and members. The serializer will try to use the public constructor with the best matched argument list. The constructor can be specified in an explicit way by using `[SerializationConstructor]` attribute. +- Serialization of arbitrary types are supported via custom formatters that are simple to author. This removes all requirements for attributes and specific constructor or member patterns. +- The serializer supports most frequently used built-in types and collections provided by the .NET base class libraries. You can find the full list in [official docs](https://github.com/MessagePack-CSharp/MessagePack-CSharp?tab=readme-ov-file#built-in-supported-types). It has [extension points](https://github.com/MessagePack-CSharp/MessagePack-CSharp?tab=readme-ov-file#extensions) that allow for customization. + +> [!WARNING] +> MessagePack has APIs to allow for deserializing data without type restrictions. Per [MessagePack Security Notes](https://github.com/MessagePack-CSharp/MessagePack-CSharp?tab=readme-ov-file#security), these APIs should be avoided. + +> [!WARNING] +> Some MessagePack APIs have behavior that is customizable via *mutable statics*, meaning your code may succeed or fail based on what other code in the same process, AssemblyLoadContext or AppDomain might do. +> You can keep your code resilient by also referencing the [MessagePackAnalyzer](https://www.nuget.org/packages/messagepackanalyzer) package and enabling the MsgPack001 and MsgPack002 analyzers, which call out any uses of APIs with changeable behavior. diff --git a/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-protobuf-net.md b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-protobuf-net.md new file mode 100644 index 0000000000000..556cb01276c26 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-protobuf-net.md @@ -0,0 +1,61 @@ +--- +title: "BinaryFormatter migration guide: Migrate to protobuf-net (binary)" +description: "Migrate from BinaryFormatter to protobuf-net for binary serialization." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization] +helpviewer_keywords: + - "BinaryFormatter" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# Migrate to protobuf-net (binary) + +The [protobuf-net](https://github.com/protobuf-net/protobuf-net) library is a contract-based serializer for .NET that uses the binary [Protocol Buffers](https://protobuf.dev) serialization format. The API follows typical .NET patterns and is broadly comparable to `XmlSerializer` and `DataContractSerializer`. + +Some behaviors and features of protobuf-net will be notable during migrations from BinaryFormatter, and many scenarios require applying attributes to members. + +- By default, both public and non-public types are serializable, with the serializer expecting a parameterless constructor. +- protobuf-net requires each serializable type to be annotated with `[ProtoContract]` attribute; this attribute can optionally specify the `SkipConstructor = true` property, which *removes* the need for any particular constructor. +- Every serializable non-static field and a property needs to be annotated with `[ProtoMember(int identifier)]` attribute. The member names aren't encoded in the data. Instead, the users must pick a positive integer to identify each member which must be unique within that type. +- [Inheritance](https://github.com/protobuf-net/protobuf-net?tab=readme-ov-file#inheritance) must be explicitly declared via `[ProtoInclude(...)]` attribute on each type with known subtypes. +- Read-only fields are supported by default. +- Alternatively, some non-attributed tuple-like types are recognized by *constructor pattern*; a type with a constructor that has parameters that match (by name) all of the declared public members will be interpreted as a tuple, and the parameter order will be used to infer the identifier for that member. +- The use of the [`protobuf-net.BuildTools`](https://protobuf-net.github.io/protobuf-net/build_tools) design-time package is highly recommended; this offers compile-time warnings of common errors. + +## Step by step migration + +1. Find all the usages of `BinaryFormatter`. +2. Ensure that the serialization code paths are covered with tests, so you can verify your changes and avoid introducing bugs. +3. Install `protobuf-net` package (and optionally `protobuf-net.BuildTools`). +4. Find all the types that are being serialized with `BinaryFormatter`. +5. For types that you can modify: + - Annotate with `[ProtoContract]` attribute all types that are marked with `[Serializable]` or implement the `ISerializable` interface. If these types are not exposed to other apps (example: you are writing a library) that may use different serializers like `DataContractSerializer`, you can remove the `[Serializable]` and `ISerializable` annotations. + - For derived types, apply `[ProtoInclude(...)]` to their base types (see the example below). + - For every type that declares any constructor that accepts parameters, add a parameterless constructor, or specify `SkipConstructor = true` on the `[ProtoContract]` attribute. Leave a comment that explains the protobuf-net requirement (so nobody removes it by accident). + - Mark all the members (fields and properties) that you wish to serialize with `[ProtoMember(int identifier)]`. All identifiers must be unique within a single type, but the same numbers can be re-used in sub-types if inheritance is enabled. +6. For types that you can't modify: + - For types provided by the .NET itself, you can use `ProtoBuf.Meta.RuntimeTypeModel.Default.CanSerialize(Type type)` API to check if they are natively supported by protobuf-net. + - You can create dedicated data transfer objects (DTO) and map them accordingly (you could use implicit cast operator for that). + - Use the `RuntimeTypeModel` API to define everything that the attributes allow for. +7. Replace the usage of `BinaryFormatter` with `ProtoBuf.Serializer`. + +```diff +-[Serializable] ++[ProtoContract] ++[ProtoInclude(2, typeof(Point2D))] +public class Point1D +{ ++ [ProtoMember(1)] + public int X { get; set; } +} + +-[Serializable] ++[ProtoContract] +public class Point2D : Point1D +{ ++ [ProtoMember(2)] + public int Y { get; set; } +} +``` diff --git a/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-system-text-json.md b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-system-text-json.md new file mode 100644 index 0000000000000..84ffd656f4b06 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/migrate-to-system-text-json.md @@ -0,0 +1,49 @@ +--- +title: "BinaryFormatter migration guide: Migrate to System.Text.Json (JSON)" +description: "Migrate from BinaryFormatter to System.Text.Json for JSON serialization." +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization, System.Text.Json] +dev_langs: + - CSharp +helpviewer_keywords: + - "BinaryFormatter" + - "System.Text.Json" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# Migrate to System.Text.Json (JSON) + +The [`System.Text.Json`](../system-text-json/overview.md) library defaults to emphasizing literal, deterministic behavior and avoids any guessing or interpretation on the caller's behalf. The library is intentionally designed this way for security and performance. While `System.Text.Json` is highly configurable and its features can be used to minimize changes needed to serialized types, it's important to consider the trade-offs between handling existing types with as few changes as possible vs. refactoring types to enable idiomatic and secure serialization. + +When migrating from BinaryFormatter to `System.Text.Json`, it's crucial to note the following behaviors and options: + +- By default, **fields are not serialized or deserialized** by `System.Text.Json`, but they can be [annotated for serialization](../system-text-json/fields.md). Alternatively, `JsonSerializerOptions.IncludeFields` can be cautiously set to `true` to include all public fields for the types being serialized. + + ```csharp + JsonSerializerOptions options = new() + { + IncludeFields = true + }; + ``` + +- By default, System.Text.Json **ignores private fields and properties**. You can enable use of a non-public accessor on a property by using the `[JsonInclude]` attribute. Including private fields requires some [non-trivial extra work](../system-text-json/custom-contracts.md#example-serialize-private-fields). + +- System.Text.Json **[cannot deserialize read-only fields](/dotnet/api/system.text.json.jsonserializeroptions.ignorereadonlyfields?view#remarks)** or properties, but the `[JsonConstructor]` attribute can be used to indicate that the specified constructor should be used to create instances of the type on deserialization. The constructor can set the read-only fields and properties. + +- To override the default serialization behavior for a specific type, you can [write custom converters](../system-text-json/converters-how-to.md). + +- It supports serialization and deserialization of many collections, but there are limitations. See the [supported collection types](../system-text-json/supported-collection-types.md) documentation for details on which collections are supported for serialization and deserialization. + +- Under [certain conditions](../system-text-json/supported-collection-types.md#custom-collections-with-deserialization-support), it supports serialization and deserialization of custom generic collections. + +- Other types [without built-in support](../system-text-json/migrate-from-newtonsoft.md#types-without-built-in-support) are: `DataSet`, `DataTable`, `DBNull`, `TimeZoneInfo`, `Type`, `ValueTuple`. However, you can write a custom converter to support these types. + +- It [supports polymorphic type hierarchy serialization and deserialization](../system-text-json/polymorphism.md) where the types have been explicitly opted in via the `[JsonDerivedType]` attribute or custom converter. Open inheritance hierarchies are not supported, and round-tripping with polymorphism requires type discriminator identifiers for all known derived types. + +- The `[JsonIgnore]` attribute on a property causes the property to be omitted from the JSON during serialization. + +- To preserve references and handle circular references in `System.Text.Json`, set `JsonSerializerOptions.ReferenceHandler` to `ReferenceHandler.Preserve`. + +- Serialization can be extensively customized with [custom contracts](../system-text-json/custom-contracts.md), unblocking many scenarios while minimizing changes to serialized types. diff --git a/docs/standard/serialization/binaryformatter-migration-guide/read-nrbf-payloads.md b/docs/standard/serialization/binaryformatter-migration-guide/read-nrbf-payloads.md new file mode 100644 index 0000000000000..7958f641fa607 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/read-nrbf-payloads.md @@ -0,0 +1,235 @@ +--- +title: "BinaryFormatter migration guide: Read BinaryFormatter (NRBF) payloads" +description: "Safely read payloads created by BinaryFormatter in '.NET Remoting: Binary Format (NRBF).'" +ms.date: 5/31/2024 +no-loc: [BinaryFormatter, Serialization] +dev_langs: + - CSharp +helpviewer_keywords: + - "BinaryFormatter" + - "serializing objects" + - "serialization" + - "objects, serializing" +--- + +# Read BinaryFormatter (NRBF) payloads + +BinaryFormatter used the [.NET Remoting: Binary Format](/openspecs/windows_protocols/ms-nrbf/) for serialization. This format is known by its abbreviation of MS-NRBF or just NRBF. A common challenge involved in migrating from BinaryFormatter is dealing with BinaryFormatter payloads persisted to storage as reading these payloads previously required BinaryFormatter. Some systems need to retain the ability to read these payloads for gradual migrations to new serializers while avoiding a reference to BinaryFormatter itself. + +As part of .NET 9, a new `NrbfDecoder` class was introduced to decode NRBF payloads without performing _deserialization_ of the payload. This API can safely be used to decode trusted or untrusted payloads without any of the risks that BinaryFormatter deserialization carries. However, `NrbfDecoder` merely decodes the data into structures an application can further process. Care must be taken when using `NrbfDecoder` to safely load the data into the appropriate instances. + +## NrbfDecoder + +`NrbfDecoder` is part of the new [System.Formats.Nrbf](https://www.nuget.org/packages/System.Formats.Nrbf) NuGet package. It targets not only .NET 9, but also older monikers like .NET Standard 2.0 and .NET Framework. That multi-targeting makes it possible for everyone who uses a supported version of .NET to migrate away from `BinaryFormatter`. `NrbfDecoder` can read payloads that were serialized with `BinaryFormatter` using `FormatterTypeStyle.TypesAlways` (the default). + +`NrbfDecoder` is designed to treat all input as untrusted. As such it has these principles: + +- No type loading of any kind (to avoid risks such as remote code execution). +- No recursion of any kind (to avoid unbound recursion, stack overflow, and denial of service). +- No buffer pre-allocation based on size provided in the payload, if the payload is too small to contain the promised data (to avoid running out of memory and denial of service). +- Decode every part of the input only once (to perform the same amount of work as the potential attacker who created the payload). +- Use collision-resistant randomized hashing to store records referenced by other records (to avoid running out of memory for dictionary backed by an array whose size depends on the number of hash-code collisions). +- Only primitive types can be instantiated in an implicit way. Arrays can be instantiated on demand. Other types are never instantiated. + +When using `NrbfDecoder`, it is important not to reintroduce those capabilities in general-purpose code as doing so would negate these safeguards. + +### Deserialize a closed set of types + +`NrbfDecoder` is useful only when the list of serialized types is a known, closed set. To put it another way, you need to know up front what you want to read, because you also need to create instances of those types and populate them with data that was read from the payload. Consider two opposite examples: + +- All `[Serializable]` types from [Quartz.NET](https://github.com/search?q=repo%3Aquartznet%2Fquartznet+%5BSerializable%5D+language%3AC%23&type=code&l=C%23) that can be persisted by the library itself are `sealed`. So there are no custom types that users could create, and the payload can contain only known types. The types also provide public constructors, so it's possible to recreate these types based on the information read from the payload. +- The `SettingsPropertyValue` type from the `System.Configuration.ConfigurationManager` library exposes an `object PropertyValue` that might internally use `BinaryFormatter` to serialize and deserialize any object that was stored in the configuration file. It could be used to store an integer, a custom type, a dictionary, or literally anything. Because of that, **it's impossible to migrate this library without introducing breaking changes to the API**. + +### Identify NRBF payloads + +`NrbfDecoder` provides two `StartsWithPayloadHeader` methods that let you check whether a given stream or buffer starts with the NRBF header. It's recommended to use these methods when you're migrating payloads persisted with `BinaryFormatter` to a [different serializer](./choose-a-serializer.md): + +- Check if the payload read from storage is an [NRBF](/openspecs/windows_protocols/ms-nrbf/) payload. +- If so, read it with `NrbfDecoder`, serialize it back with a new serializer, and overwrite the data in the storage. +- If not, use the new serializer to deserialize the data. + +```csharp +internal static T LoadFromFile(string path) +{ + bool update = false; + T value; + + using (FileStream stream = File.OpenRead(path)) + { + if (NrbfDecoder.StartsWithPayloadHeader(stream)) + { + value = LoadLegacyValue(stream); + update = true; + } + else + { + value = LoadNewValue(stream); + } + } + + if (update) + { + File.WriteAllBytes(path, NewSerializer(value)); + } + + return value; +} +``` + +### Safely read NRBF payloads + +The NRBF payload consists of serialization records that represent the serialized objects and their metadata. To read the whole payload and get the root object, you need to call the `Decode` method. + +The `Decode` method returns a `SerializationRecord` instance. `SerializationRecord` is an abstract class that represents the serialization record and provides three self-describing properties: `Id`, `RecordType`, and `TypeName`. It exposes one method, `public bool TypeNameMatches(Type type)`, which compares the type name read from the payload (and exposed via `TypeName` property) against the specified type. This method ignores assembly names, so users don't need to worry about type forwarding and assembly versioning. It also does not consider member names or their types (because getting this information would require type loading). + +```csharp +using System.Formats.Nrbf; + +static T Pseudocode(Stream payload) +{ + SerializationRecord record = NrbfDecoder.Read(payload); + if (!record.TypeNameMatches(typeof(T)) + { + throw new Exception($"Expected the record to match type name `{typeof(T).AssemblyQualifiedName}`, but got `{record.TypeName.AssemblyQualifiedName}`." + } +} +``` + +There are more than a dozen different serialization [record types](/openspecs/windows_protocols/ms-nrbf/). This library provides a set of abstractions, so you only need to learn a few of them: + +- `PrimitiveTypeRecord`: describes all primitive types natively supported by the NRBF (`string`, `bool`, `byte`, `sbyte`, `char`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, `decimal`, `TimeSpan`, and `DateTime`). + - Exposes the value via the `Value` property. + - `PrimitiveTypeRecord` derives from the non-generic `PrimitiveTypeRecord`, which also exposes a `Value` property. But on the base class, the value is returned as `object` (which introduces boxing for value types). +- `ClassRecord`: describes all `class` and `struct` besides the aforementioned primitive types. +- `ArrayRecord`: describes all array records, including jagged and multi-dimensional arrays. +- `SZArrayRecord`: describes single-dimensional, zero-indexed array records, where `T` can be either a primitive type or a `ClassRecord`. + +```csharp +SerializationRecord rootObject = NrbfDecoder.Decode(payload); // payload is a Stream + +if (rootObject is PrimitiveTypeRecord primitiveRecord) +{ + Console.WriteLine($"It was a primitive value: '{primitiveRecord.Value}'"); +} +else if (rootObject is ClassRecord classRecord) +{ + Console.WriteLine($"It was a class record of '{classRecord.TypeName.AssemblyQualifiedName}' type name."); +} +else if (rootObject is SZArrayRecord arrayOfBytes) +{ + Console.WriteLine($"It was an array of `{arrayOfBytes.Length}`-many bytes."); +} +``` + +Beside `Decode`, the `NrbfDecoder` exposes a `DecodeClassRecord` method that returns `ClassRecord` (or throws). + +#### ClassRecord + +The most important type that derives from `SerializationRecord` is `ClassRecord`, which represents **all `class` and `struct` instances beside arrays and natively supported primitive types**. It allows you to read all member names and values. To understand what *member* is, see the [BinaryFormatter functionality reference](./functionality-reference.md). + +The API it provides: + +- `MemberNames` property that gets the names of serialized members. +- `HasMember` method that checks if member of given name was present in the payload. It was designed for handling versioning scenarios where given member could have been renamed. +- A set of dedicated methods for retrieving primitive values of the provided member name: `GetString`, `GetBoolean`, `GetByte`, `GetSByte`, `GetChar`, `GetInt16`, `GetUInt16`, `GetInt32`, `GetUInt32`, `GetInt64`, `GetUInt64`, `GetSingle`, `GetDouble`, `GetDecimal`, `GetTimeSpan` and `GetDateTime`. +- `GetClassRecord` and `GetArrayRecord` methods to retrieve instance of given record types. +- `GetSerializationRecord` to retrieve any serialization record and `GetRawValue` to retrieve any serialization record or a raw primitive value. + +The following code snippet shows `ClassRecord` in action: + +```csharp +[Serializable] +public class Sample +{ + public int Integer; + public string? Text; + public byte[]? ArrayOfBytes; + public Sample? ClassInstance; +} + +ClassRecord rootRecord = NrbfDecoder.DecodeClassRecord(payload); +Sample output = new() +{ + // using the dedicated methods to read primitive values + Integer = rootRecord.GetInt32(nameof(Sample.Integer)), + Text = rootRecord.GetString(nameof(Sample.Text)), + // using dedicated method to read an array of bytes + ArrayOfBytes = ((SZArrayRecord)rootRecord.GetArrayRecord(nameof(Sample.ArrayOfBytes))).GetArray(), + // using GetClassRecord to read a class record + ClassInstance = new() + { + Text = rootRecord + .GetClassRecord(nameof(Sample.ClassInstance))! + .GetString(nameof(Sample.Text)) + } +}; +``` + +#### ArrayRecord + +`ArrayRecord` defines the core behavior for NRBF array records and provides a base for derived classes. It provides two properties: + +- `Rank` which gets the rank of the array. +- `Lengths` which get a buffer of integers that represent the number of elements in every dimension. + +It also provides one method: `GetArray`. When used for the first time, it allocates an array and fills it with the data provided in the serialized records (in case of the natively supported primitive types like `string` or `int`) or the serialized records themselves (in case of arrays of complex types). + +`GetArray` requires a mandatory argument that specifies the type of the expected array. For example, if the record should be a 2D array of integers, the `expectedArrayType` must be provided as `typeof(int[,])` and the returned array is also `int[,]`: + +```csharp +ArrayRecord arrayRecord = (ArrayRecord)NrbfDecoder.Decode(stream); +int[,] array2d = (int[,])arrayRecord.GetArray(typeof(int[,])); +``` + +If there is a type mismatch (example: the attacker has provided a payload with an array of two billion strings), the method throws `InvalidOperationException`. + +`NrbfDecoder` does not load or instantiate any custom types, so in case of arrays of complex types, it returns an array of `ClassRecord`. + +```csharp +[Serializable] +public class ComplexType3D +{ + public int I, J, K; +} + +ArrayRecord arrayRecord = (ArrayRecord)NrbfDecoder.Decode(payload); +ClassRecord[] records = (ClassRecord[])arrayRecord.GetArray(expectedArrayType: typeof(ComplexType3D[])); +ComplexType3D[] output = records.Select(classRecord => new ComplexType3D() +{ + I = classRecord.GetInt32(nameof(ComplexType3D.I)), + J = classRecord.GetInt32(nameof(ComplexType3D.J)), + K = classRecord.GetInt32(nameof(ComplexType3D.K)), +}).ToArray(); + +``` + +.NET Framework supported non-zero indexed arrays within NRBF payloads, but this support was never ported to .NET (Core). `NrbfDecoder` therefore does not support decoding non-zero indexed arrays. + +#### SZArrayRecord + +`SZArrayRecord` defines the core behavior for NRBF single dimensional, zero-indexed array records and provides a base for derived classes. The `T` can be one of the natively supported primitive types or `ClassRecord`. + +It provides `Length` property and a `GetArray` overload that returns `T[]`. + +```csharp +[Serializable] +public class PrimitiveArrayFields +{ + public byte[]? Bytes; + public uint[]? UnsignedIntegers; +} + +ClassRecord rootRecord = NrbfDecoder.DecodeClassRecord(payload); +SZArrayRecord bytes = (SZArrayRecord)rootRecord.GetArrayRecord(nameof(PrimitiveArrayFields.Bytes)); +SZArrayRecord uints = (SZArrayRecord)rootRecord.GetArrayRecord(nameof(PrimitiveArrayFields.UnsignedIntegers)); +if (bytes.Length > 100_000 || uints.Length > 100_000) +{ + throw new Exception("The array exceeded our limit"); +} + +PrimitiveArrayFields output = new() +{ + Bytes = bytes.GetArray(), + UnsignedIntegers = uints.GetArray() +}; +``` diff --git a/docs/standard/serialization/binaryformatter-migration-guide/winforms-applications.md b/docs/standard/serialization/binaryformatter-migration-guide/winforms-applications.md new file mode 100644 index 0000000000000..56d28a83996e2 --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/winforms-applications.md @@ -0,0 +1,98 @@ +--- +title: "BinaryFormatter migration guide: WinForms applications" +description: "Learn about the effects of the deprecation and removal of BinaryFormatter from .NET on Windows Forms and how to migrate." +ms.date: 7/31/2024 +no-loc: [BinaryFormatter, Windows Forms, WinForms] +helpviewer_keywords: + - "BinaryFormatter" + - "WinForms" + - "Windows Forms" +--- + +# Windows Forms migration guide for BinaryFormatter + +## BinaryFormatter removal + +Starting with .NET 9, `BinaryFormatter` is no longer supported due to its known [security risks](/docs/standard/serialization/binaryformatter-security-guide.md) and its APIs always throw an exception for all project types, including Windows Forms apps. For more information about the risks BinaryFormatter poses and the reason for its removal, see the [BinaryFormatter migration guide](index.md). + +With BinaryFormatter's removal, it's expected that many Windows Forms applications will be impacted, and you'll need to take action to complete your migration to .NET 9 or a later version. + +## How BinaryFormatter affects Windows Forms + +Prior to .NET 9, Windows Forms used `BinaryFormatter` to serialize and deserialize data for scenarios such clipboard, drag-and-drop, and storing or loading resources at design time. Starting with .NET 9, Windows Forms and WPF use a subset of the `BinaryFormatter` implementation internally for these scenarios. While BinaryFormatter's risks cannot be addressed in general-purpose serialization/deserialization, measures have been taken to mitigate the risks in these very specific use cases with a known set of types. A fall-back to `BinaryFormatter` is still in place for unknown or unsupported types, which will throw exceptions unless migration steps are taken in the application. + +Windows Forms and WPF apps both handle the following types, along with arrays and lists of these types. Clipboard, drag-and-drop, and design-time resources will continue to work with these types without any migration steps needed. + +- `bool` +- `byte` +- `char` +- `decimal` +- `double` +- `int` +- `sbyte` +- `float` +- `TimeSpan` +- `DateTime` +- `uint` +- `string` +- `nint` +- `nuint` +- `long` +- `ulong` +- `short` +- `ushort` +- `PointF` +- `RectangleF` + +Windows Forms also supports the following additional types: + +- `Bitmap` +- `ImageListStreamer` + +### OLE scenarios + +For information about the effects BinaryFormatter removal has on OLE scenarios such as clipboard and drag-and-drop as well as migration guidance see [Windows Forms and Windows Presentation Foundation BinaryFormatter OLE guidance](./winforms-wpf-ole-guidance.md). + +### Resources (ResX) + +#### The Windows Forms Designer + +The Windows Forms Out-Of-Process Designer also uses `BinaryFormatter` internally for ResX serialization and deserialization. + +Types and properties might participate in serialization without you realizing due to the standard behavior of the Windows Forms Designer. One way that BinaryFormatter is used that you might not be aware of is when a `public` property on a is introduced and that property is populated or edited at design time. That property is serialized into resource files under the following conditions: + +- A public property contains data at the time when a `Form` in the Designer is saved. +- That property is not read-only. +- That property is not attributed with `DesignerSerializationVisibility(false)`. +- That property does not have a DefaultValueAttribute. +- That property does not have a respective `bool ShouldSerialize[PropertyName]` method that returns `false` at the time of the CodeDOM serialization process. (Note: the method can have `private` scope.) +- That property is a type that does not have a [`DesignerSerializer`](/dotnet/api/microsoft.visualstudio.modeling.dsldefinition.designerserializer) + +If these statements are true, the Designer determines if that property's type has a type converter. If it does, the Designer uses the type converter to serialize the property content. Otherwise, it uses BinaryFormatter to serialize the content into the resource file. Windows Forms has added analyzers along with code fixes to help bring awareness to this type of behavior where BinaryFormatter serialization might be occurring without the developer's knowledge. + +#### Loading resource during runtime + +Types that had been previously serialized into resource files via `BinaryFormatter` will continue to deserialize as expected without the need for `BinaryFormatter` as the content of ResX files are considered trusted data. In the rare case that deserialization cannot occur without `BinaryFormatter`, it can be added back with an unsupported compatibility package. See [BinaryFormatter migration guide: Compatibility Package](compatibility-package.md) for details. Note that an extra step of setting `System.Resources.Extensions.UseBinaryFormatter` app context switch to `true` is required to use `BinaryFormatter` for resources. + +## Migrate away from BinaryFormatter + +If types that aren't intrinsically handled during serialization and deserialization are used in the affected scenarios, you'll need to take action to complete migration to .NET 9 or a later version. + +### OLE scenarios + +See [Windows Forms and Windows Presentation Foundation BinaryFormatter OLE guidance](./winforms-wpf-ole-guidance.md) for more information on how to migrate away from BinaryFormatter in scenarios such as clipboard and drag-and-drop. + +### Loading and saving resources during design time + +For types that aren't intrinsically handled during serialization into resources, such as in the case of the Designer with ResX scenarios, the prescribed way of migrating away from BinaryFormatter is to ensure a `TypeConverter` is registered for the type or property that's participating in serialization. This way, during serialization and deserialization, the `TypeConverter` is used in lieu of where `BinaryFormatter` was once used. For more information on implementing a type converter, see [`TypeConverter` Class](/dotnet/api/system.componentmodel.typeconverter#notes-to-inheritors). + +## Compatibility workaround (not recommended) + +.NET 9 users who can't migrate away from `BinaryFormatter` can install an unsupported compatibility package. For more information, see [BinaryFormatter migration guide: Compatibility Package](compatibility-package.md). + +> [!CAUTION] +> BinaryFormatter is dangerous and not recommended as it puts consuming apps at risk for attacks such as denial of service (DoS), information disclosure, or remote code execution. For more information about the risks `BinaryFormatter` poses, see [Deserialization risks in use of BinaryFormatter and related types](/docs/standard/serialization/binaryformatter-security-guide.md). + +## Issues + +If you experience unexpected behavior with your Windows Forms app regarding BinaryFormatter serialization or deserializing, please file an issue at [github.com/dotnet/winforms](https://github.com/dotnet/winforms/issues) and indicate that the issue is related to the removal of BinaryFormatter. diff --git a/docs/standard/serialization/binaryformatter-migration-guide/winforms-wpf-ole-guidance.md b/docs/standard/serialization/binaryformatter-migration-guide/winforms-wpf-ole-guidance.md new file mode 100644 index 0000000000000..7f96b9937d28a --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/winforms-wpf-ole-guidance.md @@ -0,0 +1,39 @@ +--- +title: "BinaryFormatter migration guide: WinForms and WPF OLE guidance" +description: "Learn about the effects of the deprecation and removal of BinaryFormatter from .NET on clipboard and drag-and-drop operations in Windows Forms and Windows Presentation Foundation." +ms.date: 7/31/2024 +no-loc: [BinaryFormatter, Windows Forms, WPF, Windows Presentation Foundation, OLE] +helpviewer_keywords: + - "BinaryFormatter" + - "WinForms" + - "Windows Forms" + - "WPF" + - "Windows Presentation Foundation" + - "Clipboard" + - "Drag-and-drop" + - "OLE" +--- + +# Windows Forms and Windows Presentation Foundation BinaryFormatter OLE guidance + +This document outlines the effects the BinaryFormatter removal has on OLE scenarios in Windows Forms and Windows Presentation Foundation (WPF). For information about the effects of BinaryFormatter removal in Windows Forms in general see [Windows Forms migration guide for BinaryFormatter](./winforms-applications.md). For information about the effects of BinaryFormatter removal in WPF in general see [WPF migration guide for BinaryFormatter](./wpf-applications.md). + +## BinaryFormatter in OLE scenarios + +### Clipboard + +All standard OLE DataFormats in [`System.Windows.Forms.DataFormats`](/dotnet/api/system.windows.forms.dataformats#fields) and [`System.Windows.DataFormats`](/dotnet/api/system.windows.dataformats#remarks) don't go through BinaryFormatter, except for `DataFormats.Serializable` and any custom format. If you're using `DataFormats.Serializable` or a custom format, `BinaryFormatter` is used if your clipboard scenario involves a type that isn't intrinsically handled as outlined in [Windows Forms migration guide for BinaryFormatter](./winforms-applications.md) and [WPF Migration Guide – Binary Formatter](./wpf-applications.md). Particularly, `BinaryFormatter` is used when [`System.Windows.Forms.Clipboard.SetData`](/dotnet/api/system.windows.forms.clipboard.setdata) or [`System.Windows.Clipboard.SetData`](/dotnet/api/system.windows.clipboard.setdata) is called with your type and when [`System.Windows.Forms.Clipboard.GetData`](/dotnet/api/system.windows.forms.clipboard.getdata) or [`System.Windows.Clipboard.GetData`](/dotnet/api/system.windows.clipboard.getdata) is called to get your type. BinaryFormatter is also used if [`System.Windows.Forms.Clipboard.SetDataObject(object, copy: true)`](/dotnet/api/system.windows.forms.clipboard.setdataobject) or [`System.Windows.Clipboard.SetDataObject(object, copy: true)`](/dotnet/api/system.windows.clipboard.setdataobject) is called. With the BinaryFormatter removal, you won't see an exception when setting the data on the clipboard if BinaryFormatter was needed. Instead, you'll see a string about BinaryFormatter being removed when you attempt to get the type that isn't intrinsically handled from the clipboard. + +### Drag-and-drop feature + +If your drag-and-drop scenario involves types that aren't intrinsically handled during serialization and deserialization, `BinaryFormatter` is used when [`System.Windows.Forms.Control.DoDragDrop`](/dotnet/api/system.windows.forms.control.dodragdrop) or [`System.Windows.DragDrop.DoDragDrop`](/dotnet/api/system.windows.forms.dataobject.getdata) is called and the data has been dragged out of process. `BinaryFormatter` is also used when [`System.Windows.Forms.DataObject.GetData`](/dotnet/api/system.windows.dataobject.getdata) or [`System.Windows.DataObject.GetData`](/dotnet/api/system.windows.dataobject.getdata) is called to retrieve the data that originated from another process if the type isn't intrinsically handled. With the BinaryFormatter removal, you'll now see a string about BinaryFormatter being removed when you attempt retrieve the data that has originated from another process for types that aren't intrinsically handled. + +## Migrating away from BinaryFormatter + +### Clipboard and drag-and-drop + +For types that aren't intrinsically handled that are used in clipboard and drag-and-drop operations, it's recommended that you format those types as a `byte[]` or `string` payload before passing the data to clipboard or drag-and-drop APIs. Using JSON is one way to achieve this. You'll need to make adjustments to handle receiving a JSON formatted type similar to adjustments made to place JSON formatted types on clipboard or drag-and-drop operations. For more information on how to serialize and deserialize the type with JSON, see [How to write .NET objects as JSON (serialize)](../system-text-json/how-to.md). + +## Issues + +If you experience unexpected behavior with your Windows Forms or WPF app regarding BinaryFormatter serialization or deserializing, please file an issue at [github.com/dotnet/winforms](https://github.com/dotnet/winforms/issues) or [github.com/dotnet/wpf](https://github.com/dotnet/wpf/issues) respectively. diff --git a/docs/standard/serialization/binaryformatter-migration-guide/wpf-applications.md b/docs/standard/serialization/binaryformatter-migration-guide/wpf-applications.md new file mode 100644 index 0000000000000..a52d55f6f563d --- /dev/null +++ b/docs/standard/serialization/binaryformatter-migration-guide/wpf-applications.md @@ -0,0 +1,71 @@ +--- +title: "BinaryFormatter migration guide: WPF applications" +description: "Learn about the effects of the deprecation and removal of BinaryFormatter from .NET on WPF and how to migrate." +ms.date: 8/05/2024 +no-loc: [BinaryFormatter, WPF] +helpviewer_keywords: + - "BinaryFormatter" + - "WPF" +--- + +# Windows Presentation Foundation(WPF) migration guide for BinaryFormatter + +## BinaryFormatter removal + +Starting with .NET 9, `BinaryFormatter` is no longer supported due to its known [security risks](/docs/standard/serialization/binaryformatter-security-guide.md) and its APIs always throw an exception for all project types, including WPF apps. For more information about the risks BinaryFormatter poses and the reason for its removal, see the [BinaryFormatter migration guide](index.md). + +With BinaryFormatter’s removal, it's expected that many WPF applications will be impacted, and you'll need to take action to complete your migration to .NET 9 or a later version. + +## How BinaryFormatter affects WPF + +Prior to .NET 9, Windows Presentation Foundation (WPF) used `BinaryFormatter` to serialize and deserialize data for scenarios such clipboard, drag-and-drop, and load/store state in Journal. Starting with .NET 9, WPF and Windows Forms use a subset of the `BinaryFormatter` implementation internally for these scenarios. While BinaryFormatter's risks cannot be addressed in general-purpose serialization/deserialization, measures have been taken to mitigate the risks in these very specific use cases with a known set of types. A fall-back to `BinaryFormatter` is still in place for unknown or unsupported types, which will throw exceptions unless migration steps are taken in the application. + +WPF and WinForms apps both handle the following types, along with arrays and lists of these types. Clipboard, drag-and-drop, and Avalon Binding in Journal will continue to work with these types without any migration steps needed. + +- `bool` +- `byte` +- `char` +- `decimal` +- `double` +- `int` +- `sbyte` +- `float` +- `TimeSpan` +- `DateTime` +- `uint` +- `string` +- `nint` +- `nuint` +- `long` +- `ulong` +- `short` +- `ushort` +- `PointF` +- `RectangleF` + +### OLE Scenarios + +For information about the effects BinaryFormatter removal has on OLE scenarios such as clipboard and drag-and-drop as well as migration guidance see [Windows Forms and Windows Presentation Foundation BinaryFormatter OLE guidance](./winforms-wpf-ole-guidance.md). + +You can refer to the function where we have used `BinaryFormatter` as fallback to read/save object to handle: [SaveObjectToHandle](https://github.com/dotnet/wpf/blob/0354a597996adae43b12efc72bd705f76d4ba497/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/dataobject.cs#L1677) and [ReadObjectFromHandle](https://github.com/dotnet/wpf/blob/0354a597996adae43b12efc72bd705f76d4ba497/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/dataobject.cs#L3051) for OLE scenarios + +### Journaling + +In the case when we need to store or load a state while managing the navigation history in WPF. + +To load/save we call [LoadSubStreams](https://github.com/dotnet/wpf/blob/0354a597996adae43b12efc72bd705f76d4ba497/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/DataStreams.cs#L244)/ [SaveSubStreams](https://github.com/dotnet/wpf/blob/0354a597996adae43b12efc72bd705f76d4ba497/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/DataStreams.cs#L86) of `DataStream` class. If the element used in not part of know type handled by the new implementation, it will use `BinaryFormatter`. + +When a developer navigates through JournalEntry using `Navigate`,`GoForward`,or `GoBack`, the node's data is loaded or saved to a stream to save the state. If the type involved is not intrinsically handled during serialization/deserialization, `BinaryFormatter` is used. + +Ref: [DataStream.cs](https://github.com/dotnet/wpf/blob/4e977f5fe8c73094ee5826dbfa7a0f37c3bf0e33/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/DataStreams.cs) + +### Compatibility Workaround (Not Recommended) + +.NET 9 users who can't migrate away from `BinaryFormatter` can install an unsupported compatibility package. For more information, see [BinaryFormatter migration guide: Compatibility Package](compatibility-package.md). + +> [!CAUTION] +> BinaryFormatter is dangerous and not recommended as it puts consuming apps at risk for attacks such as denial of service (DoS), information disclosure, or remote code execution. For more information about the risks `BinaryFormatter` poses, see [Deserialization risks in use of BinaryFormatter and related types](/docs/standard/serialization/binaryformatter-security-guide.md). + +### Issues + +If you experience unexpected behavior with your WPF application regarding `BinaryFormatter`, please file an issue at [dotnet/wpf/issues](https://github.com/dotnet/wpf/issues) and indicate that the issue is related to the removal of BinaryFormatter. diff --git a/docs/standard/serialization/binaryformatter-security-guide.md b/docs/standard/serialization/binaryformatter-security-guide.md index dd6dbd4ee7f75..bc84a1bab2362 100644 --- a/docs/standard/serialization/binaryformatter-security-guide.md +++ b/docs/standard/serialization/binaryformatter-security-guide.md @@ -21,8 +21,11 @@ This article applies to the following .NET implementations: * .NET Core 2.1 - 3.1 * .NET 5 and later -> [!WARNING] -> The type is dangerous and is ***not*** recommended for data processing. Applications should stop using `BinaryFormatter` as soon as possible, even if they believe the data they're processing to be trustworthy. `BinaryFormatter` is insecure and can't be made secure. +> [!CAUTION] +> The type is dangerous and is ***not*** recommended for data processing. Applications [should stop using `BinaryFormatter`](../serialization/binaryformatter-migration-guide/index.md) as soon as possible, even if they believe the data they're processing to be trustworthy. `BinaryFormatter` is insecure and can't be made secure. + +> [!NOTE] +> Starting in .NET 9, the in-box implementation throws exceptions on use, even with the settings that previously enabled its use. Those settings are also removed. Refer to the [BinaryFormatter migration guide](./binaryformatter-migration-guide/index.md) for more information. ## Deserialization vulnerabilities @@ -81,6 +84,7 @@ Another scenario is where the data file is stored in cloud storage and automatic ## See also +* [BinaryFormatter migration guide](./binaryformatter-migration-guide/index.md) * [Binary serialization](/previous-versions/dotnet/fundamentals/serialization/binary/binary-serialization) * [YSoSerial.Net](https://github.com/pwntester/ysoserial.net) for research into how adversaries attack apps that utilize `BinaryFormatter`. * General background on deserialization vulnerabilities: diff --git a/includes/binary-serialization-warning.md b/includes/binary-serialization-warning.md index 84cf2aaedf9e1..fca697ce0fc94 100644 --- a/includes/binary-serialization-warning.md +++ b/includes/binary-serialization-warning.md @@ -1,2 +1,2 @@ > [!WARNING] -> Binary serialization with `BinaryFormatter` can be dangerous. For more information, see [BinaryFormatter security guide](../docs/standard/serialization/binaryformatter-security-guide.md). +> Binary serialization with `BinaryFormatter` can be dangerous. For more information, see the [BinaryFormatter security guide](../docs/standard/serialization/binaryformatter-security-guide.md) and the [BinaryFormatter migration guide](../docs/standard/serialization/binaryformatter-migration-guide/index.md).