CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize
Property | Value |
---|---|
Rule ID | CA2302 |
Title | Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize |
Category | Security |
Fix is breaking or non-breaking | Non-breaking |
Enabled by default in .NET 8 | No |
Cause
A System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialization method was called or referenced and the Binder property may be null.
This rule is similar to CA2301, but analysis can't determine if the Binder is definitely null.
By default, this rule analyzes the entire codebase, but this is configurable.
Warning
Restricting types with a SerializationBinder can't prevent all attacks. For more information, see the BinaryFormatter security guide.
Rule description
Insecure deserializers are vulnerable when deserializing untrusted data. An attacker could modify the serialized data to include unexpected types to inject objects with malicious side effects. An attack against an insecure deserializer could, for example, execute commands on the underlying operating system, communicate over the network, or delete files.
This rule finds System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialization method calls or references when the Binder might be null. If you want to disallow any deserialization with BinaryFormatter regardless of the Binder property, disable this rule and CA2301, and enable rule CA2300.
How to fix violations
- Use a secure serializer instead, and don't allow an attacker to specify an arbitrary type to deserialize. For more information see the Preferred alternatives.
- Make the serialized data tamper-proof. After serialization, cryptographically sign the serialized data. Before deserialization, validate the cryptographic signature. Protect the cryptographic key from being disclosed and design for key rotations.
- This option makes code vulnerable to denial of service attacks and possible remote code execution attacks in the future. For more information, see the BinaryFormatter security guide. Restrict deserialized types. Implement a custom System.Runtime.Serialization.SerializationBinder. Before deserializing, set the
Binder
property to an instance of your custom SerializationBinder in all code paths. In the overridden BindToType method, if the type is unexpected, throw an exception to stop deserialization.
When to suppress warnings
BinaryFormatter
is insecure and can't be made secure.
Configure code to analyze
Use the following options to configure which parts of your codebase to run this rule on.
You can configure these options for just this rule, for all rules it applies to, or for all rules in this category (Security) that it applies to. For more information, see Code quality rule configuration options.
Exclude specific symbols
You can exclude specific symbols, such as types and methods, from analysis. For example, to specify that the rule should not run on any code within types named MyType
, add the following key-value pair to an .editorconfig file in your project:
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType
Allowed symbol name formats in the option value (separated by |
):
- Symbol name only (includes all symbols with the name, regardless of the containing type or namespace).
- Fully qualified names in the symbol's documentation ID format. Each symbol name requires a symbol-kind prefix, such as
M:
for methods,T:
for types, andN:
for namespaces. .ctor
for constructors and.cctor
for static constructors.
Examples:
Option Value | Summary |
---|---|
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType |
Matches all symbols named MyType . |
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType1|MyType2 |
Matches all symbols named either MyType1 or MyType2 . |
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS.MyType.MyMethod(ParamType) |
Matches specific method MyMethod with the specified fully qualified signature. |
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS1.MyType1.MyMethod1(ParamType)|M:NS2.MyType2.MyMethod2(ParamType) |
Matches specific methods MyMethod1 and MyMethod2 with the respective fully qualified signatures. |
Exclude specific types and their derived types
You can exclude specific types and their derived types from analysis. For example, to specify that the rule should not run on any methods within types named MyType
and their derived types, add the following key-value pair to an .editorconfig file in your project:
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType
Allowed symbol name formats in the option value (separated by |
):
- Type name only (includes all types with the name, regardless of the containing type or namespace).
- Fully qualified names in the symbol's documentation ID format, with an optional
T:
prefix.
Examples:
Option Value | Summary |
---|---|
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType |
Matches all types named MyType and all of their derived types. |
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType1|MyType2 |
Matches all types named either MyType1 or MyType2 and all of their derived types. |
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS.MyType |
Matches specific type MyType with given fully qualified name and all of its derived types. |
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS1.MyType1|M:NS2.MyType2 |
Matches specific types MyType1 and MyType2 with the respective fully qualified names, and all of their derived types. |
Pseudo-code examples
Violation 1
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
public class BookRecordSerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
// One way to discover expected types is through testing deserialization
// of **valid** data and logging the types used.
////Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')");
if (typeName == "BookRecord")
{
return typeof(BookRecord);
}
else if (typeName == "AisleLocation")
{
return typeof(AisleLocation);
}
else
{
throw new ArgumentException("Unexpected type", nameof(typeName));
}
}
}
[Serializable]
public class BookRecord
{
public string Title { get; set; }
public AisleLocation Location { get; set; }
}
[Serializable]
public class AisleLocation
{
public char Aisle { get; set; }
public byte Shelf { get; set; }
}
public class Binders
{
public static SerializationBinder BookRecord =
new BookRecordSerializationBinder();
}
public class ExampleClass
{
public BookRecord DeserializeBookRecord(byte[] bytes)
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Binder = Binders.BookRecord;
using (MemoryStream ms = new MemoryStream(bytes))
{
return (BookRecord)formatter.Deserialize(ms); // CA2302 violation
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
Public Class BookRecordSerializationBinder
Inherits SerializationBinder
Public Overrides Function BindToType(assemblyName As String, typeName As String) As Type
' One way to discover expected types is through testing deserialization
' of **valid** data and logging the types used.
'Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')")
If typeName = "BinaryFormatterVB.BookRecord" Then
Return GetType(BookRecord)
Else If typeName = "BinaryFormatterVB.AisleLocation" Then
Return GetType(AisleLocation)
Else
Throw New ArgumentException("Unexpected type", NameOf(typeName))
End If
End Function
End Class
<Serializable()>
Public Class BookRecord
Public Property Title As String
Public Property Location As AisleLocation
End Class
<Serializable()>
Public Class AisleLocation
Public Property Aisle As Char
Public Property Shelf As Byte
End Class
Public Class Binders
Public Shared Property BookRecord As SerializationBinder = New BookRecordSerializationBinder()
End Class
Public Class ExampleClass
Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
Dim formatter As BinaryFormatter = New BinaryFormatter()
formatter.Binder = Binders.BookRecord
Using ms As MemoryStream = New MemoryStream(bytes)
Return CType(formatter.Deserialize(ms), BookRecord) ' CA2302 violation
End Using
End Function
End Class
Solution 1
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
public class BookRecordSerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
// One way to discover expected types is through testing deserialization
// of **valid** data and logging the types used.
////Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')");
if (typeName == "BookRecord")
{
return typeof(BookRecord);
}
else if (typeName == "AisleLocation")
{
return typeof(AisleLocation);
}
else
{
throw new ArgumentException("Unexpected type", nameof(typeName));
}
}
}
[Serializable]
public class BookRecord
{
public string Title { get; set; }
public AisleLocation Location { get; set; }
}
[Serializable]
public class AisleLocation
{
public char Aisle { get; set; }
public byte Shelf { get; set; }
}
public class Binders
{
public static SerializationBinder BookRecord =
new BookRecordSerializationBinder();
}
public class ExampleClass
{
public BookRecord DeserializeBookRecord(byte[] bytes)
{
BinaryFormatter formatter = new BinaryFormatter();
// Ensure that Binder is always non-null before deserializing
formatter.Binder = Binders.BookRecord ?? throw new Exception("Expected non-null binder");
using (MemoryStream ms = new MemoryStream(bytes))
{
return (BookRecord)formatter.Deserialize(ms);
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
Public Class BookRecordSerializationBinder
Inherits SerializationBinder
Public Overrides Function BindToType(assemblyName As String, typeName As String) As Type
' One way to discover expected types is through testing deserialization
' of **valid** data and logging the types used.
'Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')")
If typeName = "BinaryFormatterVB.BookRecord" Then
Return GetType(BookRecord)
Else If typeName = "BinaryFormatterVB.AisleLocation" Then
Return GetType(AisleLocation)
Else
Throw New ArgumentException("Unexpected type", NameOf(typeName))
End If
End Function
End Class
<Serializable()>
Public Class BookRecord
Public Property Title As String
Public Property Location As AisleLocation
End Class
<Serializable()>
Public Class AisleLocation
Public Property Aisle As Char
Public Property Shelf As Byte
End Class
Public Class Binders
Public Shared Property BookRecord As SerializationBinder = New BookRecordSerializationBinder()
End Class
Public Class ExampleClass
Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
Dim formatter As BinaryFormatter = New BinaryFormatter()
' Ensure that Binder is always non-null before deserializing
formatter.Binder = If(Binders.BookRecord, New Exception("Expected non-null"))
Using ms As MemoryStream = New MemoryStream(bytes)
Return CType(formatter.Deserialize(ms), BookRecord)
End Using
End Function
End Class
Violation 2
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class BookRecord
{
public string Title { get; set; }
public AisleLocation Location { get; set; }
}
[Serializable]
public class AisleLocation
{
public char Aisle { get; set; }
public byte Shelf { get; set; }
}
public class ExampleClass
{
public BinaryFormatter Formatter { get; set; }
public BookRecord DeserializeBookRecord(byte[] bytes)
{
using (MemoryStream ms = new MemoryStream(bytes))
{
return (BookRecord) this.Formatter.Deserialize(ms); // CA2302 violation
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
<Serializable()>
Public Class BookRecord
Public Property Title As String
Public Property Location As AisleLocation
End Class
<Serializable()>
Public Class AisleLocation
Public Property Aisle As Char
Public Property Shelf As Byte
End Class
Public Class ExampleClass
Public Property Formatter As BinaryFormatter
Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
Using ms As MemoryStream = New MemoryStream(bytes)
Return CType(Me.Formatter.Deserialize(ms), BookRecord) ' CA2302 violation
End Using
End Function
End Class
Solution 2
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
public class BookRecordSerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
// One way to discover expected types is through testing deserialization
// of **valid** data and logging the types used.
////Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')");
if (typeName == "BookRecord")
{
return typeof(BookRecord);
}
else if (typeName == "AisleLocation")
{
return typeof(AisleLocation);
}
else
{
throw new ArgumentException("Unexpected type", nameof(typeName));
}
}
}
[Serializable]
public class BookRecord
{
public string Title { get; set; }
public AisleLocation Location { get; set; }
}
[Serializable]
public class AisleLocation
{
public char Aisle { get; set; }
public byte Shelf { get; set; }
}
public class ExampleClass
{
public BookRecord DeserializeBookRecord(byte[] bytes)
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Binder = new BookRecordSerializationBinder();
using (MemoryStream ms = new MemoryStream(bytes))
{
return (BookRecord) formatter.Deserialize(ms);
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
Public Class BookRecordSerializationBinder
Inherits SerializationBinder
Public Overrides Function BindToType(assemblyName As String, typeName As String) As Type
' One way to discover expected types is through testing deserialization
' of **valid** data and logging the types used.
'Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')")
If typeName = "BinaryFormatterVB.BookRecord" Then
Return GetType(BookRecord)
Else If typeName = "BinaryFormatterVB.AisleLocation" Then
Return GetType(AisleLocation)
Else
Throw New ArgumentException("Unexpected type", NameOf(typeName))
End If
End Function
End Class
<Serializable()>
Public Class BookRecord
Public Property Title As String
Public Property Location As AisleLocation
End Class
<Serializable()>
Public Class AisleLocation
Public Property Aisle As Char
Public Property Shelf As Byte
End Class
Public Class ExampleClass
Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
Dim formatter As BinaryFormatter = New BinaryFormatter()
formatter.Binder = New BookRecordSerializationBinder()
Using ms As MemoryStream = New MemoryStream(bytes)
Return CType(formatter.Deserialize(ms), BookRecord)
End Using
End Function
End Class