Edit

Share via


Enable BinaryFormatter clipboard support (not recommended)

Caution

BinaryFormatter support isn't recommended. Use it only as a temporary migration bridge for legacy applications that can't immediately migrate to the new type-safe APIs. This approach carries significant security risks.

This article shows how to configure limited BinaryFormatter support for Windows Forms clipboard operations in .NET 10. While BinaryFormatter was removed from the runtime in .NET 9 due to security vulnerabilities, restore limited functionality through explicit configuration for legacy applications that need time to migrate.

For complete migration guidance to the new type-safe APIs, see Windows Forms clipboard and DataObject changes in .NET 10.

Important

This content only applies to modern .NET and not .NET Framework, unless otherwise specified.

Prerequisites

Before continuing, review these concepts:

  • How your application currently uses BinaryFormatter in clipboard operations.
  • The security vulnerabilities that led to the removal of BinaryFormatter.
  • Your migration timeline to the new type-safe clipboard APIs.

For more information, see these articles:

Security warnings and risks

BinaryFormatter is inherently insecure and deprecated for these reasons:

  • Arbitrary code execution vulnerabilities: Attackers can execute malicious code during deserialization, exposing your application to remote attacks.
  • Denial of service attacks: Malicious clipboard data can consume excessive memory or CPU resources, causing crashes or instability.
  • Information disclosure risks: Attackers might extract sensitive data from memory.
  • No security boundaries: The format is fundamentally unsafe, and configuration settings can't secure it.

Enable this support only as a temporary bridge while you update your application to use the new type-safe APIs.

Install the compatibility package

Add the unsupported BinaryFormatter compatibility package to your project. This package provides the necessary runtime support for BinaryFormatter operations:

<ItemGroup>
  <PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0*-*"/>
</ItemGroup>

Note

This package is marked as unsupported and deprecated. Use it only for temporary compatibility during migration.

Enable unsafe serialization in your project

Set the EnableUnsafeBinaryFormatterSerialization property to true in your project file. This property tells the compiler to allow BinaryFormatter usage:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>

Without this setting, your application generates compilation errors when it attempts to use BinaryFormatter APIs.

Configure the Windows Forms runtime switch

Create or update your application's runtimeconfig.json file to enable the Windows Forms-specific clipboard switch. This configuration allows clipboard operations to fall back to BinaryFormatter when necessary:

{
  "runtimeOptions": {
    "configProperties": {
      "Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization": true
    }
  }
}

Important

Without this specific runtime switch, clipboard operations won't fall back to BinaryFormatter even if general serialization support is enabled. This switch is required specifically for Windows Forms and WPF clipboard functionality.

Implement security-focused type resolvers

Even with BinaryFormatter enabled, implement type resolvers to restrict deserialization to explicitly approved types. Type resolvers provide your only defense against malicious payload attacks.

Create a secure type resolver

// Create a security-focused type resolver
private static Type SecureTypeResolver(TypeName typeName)
{
    // Explicit allow-list of permitted types—add only what you need
    var allowedTypes = new Dictionary<string, Type>
    {
        ["MyApp.Person"] = typeof(Person),
        ["MyApp.AppSettings"] = typeof(AppSettings),
        ["System.String"] = typeof(string),
        ["System.Int32"] = typeof(int),
        // Add only the specific types your application requires
    };

    // Only allow explicitly listed types - exact string match required
    if (allowedTypes.TryGetValue(typeName.FullName, out Type allowedType))
    {
        return allowedType;
    }

    // Reject any type not in the allow-list with clear error message
    throw new InvalidOperationException(
        $"Type '{typeName.FullName}' is not permitted for clipboard deserialization");
}
' Create a security-focused type resolver
Private Shared Function SecureTypeResolver(typeName As TypeName) As Type
    ' Explicit allow-list of permitted types—add only what you need
    Dim allowedTypes As New Dictionary(Of String, Type) From {
        {"MyApp.Person", GetType(Person)},
        {"MyApp.AppSettings", GetType(AppSettings)},
        {"System.String", GetType(String)},
        {"System.Int32", GetType(Integer)}
    } ' Add only the specific types your application requires

    ' Only allow explicitly listed types - exact string match required
    Dim allowedType As Type = Nothing
    If allowedTypes.TryGetValue(typeName.FullName, allowedType) Then
        Return allowedType
    End If

    ' Reject any type not in the allow-list with clear error message
    Throw New InvalidOperationException(
        $"Type '{typeName.FullName}' is not permitted for clipboard deserialization")
End Function

Use the type resolver with clipboard operations

// Use the resolver with clipboard operations
private static Type SecureTypeResolver(TypeName typeName)
{
    // Implementation from SecureTypeResolver example
    // ... (allow-list implementation here)
    throw new InvalidOperationException($"Type '{typeName.FullName}' is not permitted");
}

public static void UseSecureTypeResolver()
{
    // Retrieve legacy data using the secure type resolver
    if (Clipboard.TryGetData("LegacyData", SecureTypeResolver, out MyCustomType data))
    {
        ProcessLegacyData(data);
    }
    else
    {
        Console.WriteLine("No compatible data found on clipboard");
    }
}
' Use the resolver with clipboard operations
Private Shared Function SecureTypeResolver(typeName As TypeName) As Type
    ' Implementation from SecureTypeResolver example
    ' ... (allow-list implementation here)
    Throw New InvalidOperationException($"Type '{typeName.FullName}' is not permitted")
End Function

Public Shared Sub UseSecureTypeResolver()
    ' Retrieve legacy data using the secure type resolver
    Dim data As MyCustomType = Nothing
    If Clipboard.TryGetData("LegacyData", AddressOf SecureTypeResolver, data) Then
        ProcessLegacyData(data)
    Else
        Console.WriteLine("No compatible data found on clipboard")
    End If
End Sub

Security guidelines for type resolvers

Follow these essential security guidelines when implementing type resolvers:

Use explicit allowlists

  • Reject by default: Allow only explicitly listed types.
  • No wildcards: Avoid pattern matching or namespace-based permissions.
  • Exact matching: Require exact string matches for type names.

Validate all inputs

  • Type name validation: Ensure type names match expected formats.
  • Assembly restrictions: Limit types to known, trusted assemblies.
  • Version checking: Consider version-specific type restrictions.

Handle unknown types securely

  • Throw exceptions: Always throw for unauthorized types.
  • Log attempts: Consider logging unauthorized access attempts.
  • Clear error messages: Provide specific rejection reasons for debugging.

Regular maintenance

  • Audit regularly: Review and update the allowed type list.
  • Remove unused types: Eliminate permissions for types no longer needed.
  • Document decisions: Maintain clear documentation of why each type is permitted.

Test your configuration

After configuring BinaryFormatter support, test your application to ensure it works correctly:

  1. Verify clipboard operations: Test both storing and retrieving data with your custom types.
  2. Test type resolver: Confirm that unauthorized types are properly rejected.
  3. Monitor security: Watch for any unexpected type resolution attempts.
  4. Performance testing: Ensure the type resolver doesn't significantly impact performance.
public static void TestBinaryFormatterConfiguration()
{
    // Test data to verify configuration
    var testPerson = new Person { Name = "Test User", Age = 30 };

    try
    {
        // Test storing data (this should work with proper configuration)
        Clipboard.SetData("TestPerson", testPerson);
        Console.WriteLine("Successfully stored test data on clipboard");

        // Test retrieving with type resolver
        if (Clipboard.TryGetData("TestPerson", SecureTypeResolver, out Person retrievedPerson))
        {
            Console.WriteLine($"Successfully retrieved: {retrievedPerson.Name}, Age: {retrievedPerson.Age}");
        }
        else
        {
            Console.WriteLine("Failed to retrieve test data");
        }

        // Test that unauthorized types are rejected
        try
        {
            Clipboard.TryGetData("TestPerson", UnauthorizedTypeResolver, out Person _);
            Console.WriteLine("ERROR: Unauthorized type was not rejected!");
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine($"SUCCESS: Unauthorized type properly rejected - {ex.Message}");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Configuration test failed: {ex.Message}");
    }
}

private static Type SecureTypeResolver(TypeName typeName)
{
    var allowedTypes = new Dictionary<string, Type>
    {
        ["ClipboardExamples.Person"] = typeof(Person),
    };

    if (allowedTypes.TryGetValue(typeName.FullName, out Type allowedType))
    {
        return allowedType;
    }

    throw new InvalidOperationException($"Type '{typeName.FullName}' is not permitted");
}

private static Type UnauthorizedTypeResolver(TypeName typeName)
{
    // Intentionally restrictive resolver to test rejection
    throw new InvalidOperationException($"No types are permitted by this test resolver");
}
Public Shared Sub TestBinaryFormatterConfiguration()
    ' Test data to verify configuration
    Dim testPerson As New Person With {.Name = "Test User", .Age = 30}

    Try
        ' Test storing data (this should work with proper configuration)
        Clipboard.SetData("TestPerson", testPerson)
        Console.WriteLine("Successfully stored test data on clipboard")

        ' Test retrieving with type resolver
        Dim retrievedPerson As Person = Nothing
        If Clipboard.TryGetData("TestPerson", AddressOf SecureTypeResolver, retrievedPerson) Then
            Console.WriteLine($"Successfully retrieved: {retrievedPerson.Name}, Age: {retrievedPerson.Age}")
        Else
            Console.WriteLine("Failed to retrieve test data")
        End If

        ' Test that unauthorized types are rejected
        Try
            Dim testResult As Person = Nothing
            Clipboard.TryGetData("TestPerson", AddressOf UnauthorizedTypeResolver, testResult)
            Console.WriteLine("ERROR: Unauthorized type was not rejected!")
        Catch ex As InvalidOperationException
            Console.WriteLine($"SUCCESS: Unauthorized type properly rejected - {ex.Message}")
        End Try

    Catch ex As Exception
        Console.WriteLine($"Configuration test failed: {ex.Message}")
    End Try
End Sub

Private Shared Function SecureTypeResolver(typeName As TypeName) As Type
    Dim allowedTypes As New Dictionary(Of String, Type) From {
        {"ClipboardExamples.Person", GetType(Person)}
    }

    Dim allowedType As Type = Nothing
    If allowedTypes.TryGetValue(typeName.FullName, allowedType) Then
        Return allowedType
    End If

    Throw New InvalidOperationException($"Type '{typeName.FullName}' is not permitted")
End Function

Private Shared Function UnauthorizedTypeResolver(typeName As TypeName) As Type
    ' Intentionally restrictive resolver to test rejection
    Throw New InvalidOperationException($"No types are permitted by this test resolver")
End Function

Plan your migration strategy

While BinaryFormatter support provides temporary compatibility, develop a migration plan to move to the new type-safe APIs:

  1. Identify usage: Catalog all clipboard operations using custom types.
  2. Prioritize migration: Focus on the most security-sensitive operations first.
  3. Update incrementally: Migrate one operation at a time to reduce risk.
  4. Test thoroughly: Ensure new implementations provide equivalent functionality.
  5. Remove BinaryFormatter: Disable support once migration is complete.

Clean up resources

Once you've migrated to the new type-safe clipboard APIs, remove the BinaryFormatter configuration to improve security:

  1. Remove the System.Runtime.Serialization.Formatters package reference.
  2. Remove the EnableUnsafeBinaryFormatterSerialization property from your project file.
  3. Remove the Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization setting from your runtimeconfig.json.
  4. Delete type resolver implementations that are no longer needed.