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 ofBinaryFormatter
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 theSafeTypeResolver
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 theSafeBinaryFormatter
to serialize and deserialize standard types likeSvgImage
.
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 andFormatterConverter
type,FormatterServices
static class,IObjectReference
interface,ISurrogateSelector
andISerializationSurrogate
interfaces, and theSurrogateSelector
type,ISafeSerializationData
andSafeSerializationEventArgs
,StreamingContextStates
enum,ObjectIDGenerator
,ObjectManager
,SerializationObjectManager
types,FormatterTypeStyle
,FormatterAssemblyStyle
, andTypeFilterLevel
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
andStreamingContext
, 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!