Updates On Microsoft’s BinaryFormatter Obsoletion Strategy

News
07 November 2023

As you may be aware, Microsoft published their paper titled BinaryFormatter Obsoletion Strategy a while ago, detailing exactly how they intend to phase out the BinaryFormatter for .NET 5.0 and later. Quoting directly from the paper:

Applications which target .NET Framework 2.x, .NET Framework 4.x, or .NET Core 3.1 are not affected by this proposal. BinaryFormatter will continue to work as expected in those applications. Consumers of BinaryFormatter are still advised to consult the BinaryFormatter security guide to help inform their risk assessment.

With the availability of .NET 8, the fourth step of the timeline has been reached in November 2023, which leaves only the final removal BinaryFormatter infrastructure for .NET 9 next year. At DevExpress, we have followed developments since the paper was first published, and taken steps to make sure our codebase is up to date and our customers needn’t concern themselves with the details of required migration work. In this post I’d like to summarize what we did and what remains to be done.

We carefully check for serializability

Binary deserialization is a concept that has been a deeply integrated part of the Base Class Library (BCL) since the days of the early .NET Framework. Many types required special handling over the years, and the property Type.IsSerializable was historically used to determine whether a type could be serialized or not.

As part of our security strategy (read a summary here), we created our own abstraction layer to encapsulate all operations involving types and their resolution. The main entry point to that layer is a class called SafeTypeResolver, and our codebase makes extensive use of it today.

To cover aspects of serialization and deserialization, we added special handling to this layer, and we introduced safe alternatives for now-obsolete APIs like Type.IsSerializable. Since our code targets many different versions of .NET Framework, .NET Core/Standard and .NET, it was important to us to centralize serialization decisions in one secure location.

We conditionally removed serialization constructors from exception types

Custom exception types have included serialization constructors since the first .NET Framework versions. To disable this mechanism, our exception types now don’t include such constructors anymore:

#if !NET
namespace DevExpress.DataAccess.Json {
    using System.Runtime.Serialization;

    [System.Serializable]
    partial class JsonDataSourceException {
        protected JsonDataSourceException(SerializationInfo info, StreamingContext context)
            : base(info, context) {
        }
    }
}
#endif

Microsoft documents the NET symbol on the page Target Frameworks, Preprocessor symbols, to apply to .NET 5+ as well as .NET Core. However, our current version codebase has been moved to .NET 6+ starting with our 23.2 branch, so NET technically refers only to .NET 6+ for DevExpress code.

We avoid breaking changes by using safe formatters as needed

Some technologies and techniques used by our controls, as well as by standard platform controls introduced by Microsoft, rely on serialization mechanisms. Logically, binary serialization will always need to occur in some places — think about persisting pixel image data with a form definition, for instance.

We implemented a safe formatter called DXBinaryFormatter to cover these use cases. It is backwards compatible with the deprecated BinaryFormatter, but it does not include implementations of the “dangerous” operations that led to security concerns — the standard BinaryFormatter was trying to do too much for its own good! Specifically, DXBinaryFormatter does not support (de)serialization of delegates and methods, or techniques related to surrogates, Remoting, and COM proxies. Of course this means that our new implementation is not a full replacement, and it’s not meant to be that.

The DXBinaryFormatter is an integral component of our secure infrastructure, designed to ensure type and content safety when working with binary data in any save/restore functionality provided by our components. Several classes use the DXBinaryFormatter, including these:

  • The SafeBinaryFormatter selects the correct formatter implementation to use based on runtime circumstances. It also handles all type resolution requests during deserialization, using the SafeTypeResolver mentioned previously. “Allow” and “block” lists are used to guarantee that serialization occurs only where intended.
  • The BinaryTypeConverter and some of its descendant types use the SafeBinaryFormatter to serialize and deserialize standard types like SvgImage.
Note: We don’t plan to make these types publicly available. We are cautious not to introduce any new security concerns, and we cannot make promises about the use of these helpers with arbitrary custom types. Our implementation can also change at any time without notice.

DevExpress security guidance for safe deserialization explains the use of the two methods RegisterTrustedClass and RegisterTrustedAssembly on the type DeserializationSettings. These helpers add to the “allow” list used by the SafeBinaryFormatter mentioned above. On the same page, you’ll find information about the BindToTypePolicy, which enables you to control precisely how assemblies and types are allowed to load dynamically, in cases where static registration of trusted types and assemblies is insufficient.

The method InvokeTrusted, also explained on that page, skips the checks of “allow” and “block” lists normally performed but he SafeBinaryFormatter.

Note that the methods of the DeserializationSettings discussed here do not revert back to the deprecated BinaryFormatter under any circumstances.

Our build pipeline analyses both code and generated IL of release assemblies

To be absolutely sure that we don’t suffer some kind of regression and accidentally deploy code to customers that uses unsafe serialization mechanisms, we have automated checks in source code and IL which run in our build pipeline. At the time of writing, the following list of types and APIs is checked and flagged as necessary:

  • BinaryFormatter type,
  • IFormatter interface,
  • Formatter type,
  • IFormatterConverter interface and FormatterConverter type,
  • FormatterServices static class,
  • IObjectReference interface,
  • ISurrogateSelector and ISerializationSurrogate interfaces, and the SurrogateSelector type,
  • ISafeSerializationData and SafeSerializationEventArgs,
  • StreamingContextStates enum,
  • ObjectIDGenerator,ObjectManager, SerializationObjectManager types,
  • FormatterTypeStyle, FormatterAssemblyStyle, and TypeFilterLevel enums,
  • IFieldInfo interface,
  • Type.IsSerializable public property (and all overridden implementations),
  • TypeAttributes.Serializable enum value,
  • FieldInfo.IsNotSerialized public property,
  • FieldAttributes.NotSerialized enum value,
  • ISerializable.GetObjectData method, but not the type itself,
  • IObjectReference.GetRealObject method, but not the type itself,
  • SerializationInfo and StreamingContext, all constructors

If you would like to check your own application source code in a similar way, a good recommendation is to begin with Visual Studio diagnostics. Specially, the items SYSLIB0050 and SYSLIB0051 can be useful since they flag the use of APIs declared obsolete in .NET 8. IL-based static analysis can provide extra security.

We provide safe alternatives for the ImageList component

If your application uses the Windows Forms ImageList component, you will see exceptions like this when the image list content in deserialized from resources.

System.NotSupportedException: ‘BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information.’

If this applies to you, there will be code in your designer files that looks like this:

imageList1.ColorDepth = ColorDepth.Depth8Bit;
imageList1.ImageStream = (ImageListStreamer)resources.GetObject("imageList1.ImageStream");
imageList1.TransparentColor = Color.Transparent;

This issue in the .NET WinForms repository on GitHub shows that the problem is not specific to applications that are technically Windows Forms applications — the use of certain references is enough to cause the exception. As described in that issue, you could use the temporary workaround (that will go away with .NET 9, according to plans) of setting EnableUnsafeBinaryFormatterSerialization to true in the project file.

A better option however is to migrate to the DevExpress components ImageCollection or SvgImageCollection. These implement safe serialization mechanisms and will remain compatible when .NET 9 arrives.

We enable you to selectively opt in to safe serialization

If you decide — or have decided in the past — to run your application using the EnableUnsafeBinaryFormatterSerialization project setting, you can utilize helpers on the DeserializationSettings type to selectively run with the safe DevExpress serialization mechanisms instead.

To switch on use of the DXBinaryFormatter, you can call this (in startup code, like Program.cs:

DeserializationSettings.ForceDXBinaryFormatter();

Alternatively, you can force the use of the DXBinaryFormatter for a block of code:

DeserializationSettings.InvokeWithForcedDXBinaryFormatter(
  () => {
    // some critical code
});

Of course, based on Microsoft’s published plans, this option is likely to become irrelevant when .NET 9 arrives, because EnableUnsafeBinaryFormatterSerialization should go away then.

Note that the enforcement only applies to DevExpress code. If you trigger execution of the deprecated BinaryFormatter in some other code path, these methods will not make a difference.

We enable you to run without binary serialization

Most DevExpress components have seen changes in the last few years that implement text-based persistence mechanisms to replace the older binary serialization approaches. The now-deprecated standard .NET Framework system is therefore becoming less relevant, and your application probably uses new text serialization already.

Note that on occasion such changes have led to breaking changes. Here is one random example for column views. As always, we documented everything we changed, especially where breaking changes occurred. Please get in touch with us if you are concerned about any specific use case, or the status of a control you use.

In some cases, applications may be able to work entirely without binary serialization. It is possible to call this helper on startup to disable binary serialization for your entire project:

DeserializationSettings.DenyBinaryFormatter();

Alternatively, you can use selectively disable binary serialization for a block of code:

DeserializationSettings.InvokeWithBinaryFormatterDenied(
  () => {
    // some critical code
});

Note that, again, this does not mean that we revert to the deprecated BinaryFormatter under any circumstances. Technically these directives simply make any binary serialization process return an empty or null object. As long as your code uses DevExpress controls that do not rely on binary serialization, it is safe to disable it completely. You need to make this decision for your own projects.

What remains?

At this time there are a few points that have not been finally decided by Microsoft — we need to wait for .NET 9 to arrive before we know exactly which steps are required.

Specifically, the drag & drop related types System.Windows.DataObject and System.Windows.Forms.DataObject rely on the deprecated BinaryFormatter, and so does System.Windows.Forms.AxHost (COM inter) and even the System.Data.DataSet. The latter has elaborate security guidance already, and you can find GitHub issues relating to the other types. Some work will certainly be done by the time .NET 9 comes around, if the obsoletion plans are followed as intended.

Drag & drop is an area that concerns us at DevExpress, but we can’t act before Microsoft proposes plans from their end. Clipboard functionality is a second similar area, where we have reviewed clipboard operations in our codebase and prepared a foundation and compatibility layer to accommodate future changes Microsoft proposes. We are watching GitHub issues like this one for updates. Please read this section about our new Clipboard Access Policy related to this topic.

We will keep you posted as we move closer to the .NET 9 release!

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.