1

I have some Exception derived types, which add additional properties to the Exception. Searching the web for examples and guidelines on how to handle the serialization of such Exception-based types led to descriptions and code samples which were rather old. Trying these samples led always to security-errors. To make it work, I had to additionally decorate the GetObjectData-Method with the System.Security.SecurityCritical attribute.

Interestingly, the SecurityPermission-Attribute, which was contained in all of the examples, but in various ways, seemed not to be necessary. Some examples only added it to the GetObjectData, some added it also to the deserializing-constructor. Some examples used the strong SecurityAction.Demand-action, most of the examples used the SecurityAction.LinkDemand-action and others declared SecurityAction.RequestMinimum.

My question is, whether the below Exception-derived type (for the serialization part of it) is correct for being used with nowadays .net frameworks (4.7.[x]+, Core[x], Standard2.[x]+) or if there is something missing, or if I even can remove some parts? Please note that I don’t want to seal the type. Other Exception-types should be able to derive from it.

[Serializable]
public class FooException : Exception{

    readonly int m_fooValue;

    public FooException(string message,int fooValue,Exception innerException=null) : base(message,innerException){
        m_fooValue = fooValue;
    }
    [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
    protected FooException(SerializationInfo info, StreamingContext context) : base(info, context) {
        m_fooValue = info.GetInt32(nameof(FooValue));            
    }

    [SecurityCritical]
    [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context) {
        if (info == null) throw new ArgumentNullException(nameof(info));
        info.AddValue(nameof(FooValue), m_fooValue);            
        base.GetObjectData(info, context);
    }

    public int FooValue => m_fooValue;
}

The derived type (FooBarException) additionally must then also set the SecurityCritical-attribute on the deserializung-constructor for satisfying the LinkDemand:

[Serializable] 
public class FooBarException : FooException{

    readonly int m_barValue;

    public FooBarException(string message,int fooValue, int barValue, Exception innerException = null) : base(message, fooValue, innerException) {
        m_barValue = barValue;
    }
    [SecurityCritical] // Additional for satisfying the LinkDemand on the base constructor
    [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
    protected FooBarException(SerializationInfo info, StreamingContext context) : base(info, context) {
        m_barValue = info.GetInt32(nameof(BarValue));
    }

    [SecurityCritical]
    [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context) {
        if (info == null) throw new ArgumentNullException(nameof(info));
        info.AddValue(nameof(BarValue), m_barValue);
        base.GetObjectData(info, context);
    }

    public int BarValue => m_barValue;


}

Sources
https://stackoverflow.com/a/100369/340628

https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable.getobjectdata?view=netframework-4.8

What is the correct way to make a custom .NET Exception serializable?

https://blog.gurock.com/articles/creating-custom-exceptions-in-dotnet/

HCL
  • 36,053
  • 27
  • 163
  • 213
  • 1
    If this needs to work in .NETCore then there is no point in making the exception [Serializable]. No support for remoting and appdomains. – Hans Passant Feb 20 '20 at 12:54
  • What serializer are you using? `BinaryFormatter`? Or something else such as Json.NET? – dbc Feb 21 '20 at 00:54
  • Also, did you see [How can I implement ISerializable in .NET 4+ without violating inheritance security rules?](https://stackoverflow.com/q/48355591)? – dbc Feb 21 '20 at 01:07
  • @dbc: No specific formatter. I'm finalizing some libraries and FX-cop nagged me about the GetObjectData-override. This drew my interest to the question on how to do this right. As far as I can see, my implementation (see my post) should be ok with actual frameworks. As MS does on the base class, I use the SecurityCritical on the GetObjectData - no need for partial trust or untrusted code to access this method. Its really only for serialization purposes. And if understand Hans Passant right, the importance of the serialization feature is fading away anyway, since .net Core is not supporting it. – HCL Feb 21 '20 at 05:45

1 Answers1

1

After digging a little deeper, I came to the following pattern:

[Serializable]
public class FooException : Exception{

    readonly int m_fooValue;

    public FooException(string message, int fooValue, Exception innerException = null) : base(message, innerException) {
        m_fooValue = fooValue;
    }

    [SecuritySafeCritical]
    protected FooException(SerializationInfo info, StreamingContext context) : base(info, context) {
        m_fooValue = info.GetInt32(nameof(FooValue));
    }

    [SecurityCritical]      
    public override void GetObjectData(SerializationInfo info, StreamingContext context) {
        if (info == null) throw new ArgumentNullException(nameof(info));
        info.AddValue(nameof(FooValue), m_fooValue);
        base.GetObjectData(info, context);
    }

    public int FooValue => m_fooValue;
}

[Serializable]
public class FooBarException : FooException
{

    readonly int m_barValue;

    public FooBarException(string message, int fooValue, int barValue, Exception innerException = null) : base(message, fooValue, innerException) {
        m_barValue = barValue;
    }
    [SecuritySafeCritical]        
    protected FooBarException(SerializationInfo info, StreamingContext context) : base(info, context) {
        m_barValue = info.GetInt32(nameof(BarValue));
    }

    [SecurityCritical]        
    public override void GetObjectData(SerializationInfo info, StreamingContext context) {
        if (info == null) throw new ArgumentNullException(nameof(info));
        info.AddValue(nameof(BarValue), m_barValue);
        base.GetObjectData(info, context);
    }

    public int BarValue => m_barValue;


}

For the SecurityAction.LinkDemand-part of the Question, this value should not be used anymore (.net 4+). Either is SecurityAction.Demand to be used, or even the the whole SecurityPermission declaration may be replaced with SecurityCritical (depending on the case, such as for the above case).

HCL
  • 36,053
  • 27
  • 163
  • 213
  • You could also use the `ISafeSerializationData` pattern as shown in the [reference source](https://referencesource.microsoft.com/#mscorlib/system/runtime/serialization/safeserializationmanager.cs,175), however that pattern is not supported by all serializers. – dbc Feb 21 '20 at 18:16
  • Specifically, you need to do some tricks to get it to work with Json.NET, see [Is this a bug in JSON.NET or Entity Framework or am I doing something wrong while trying to serialize a list of Exceptions with JSON.NET?](https://stackoverflow.com/a/40121963/3744182) and [Unable to cast object of type 'Newtonsoft.Json.Linq.JObject' to type 'System.Runtime.Serialization.ISafeSerializationData'](https://stackoverflow.com/q/23771228/3744182). – dbc Feb 21 '20 at 18:16
  • I think its ok like this. FX Cop is not complaining anymore and I have unit tests testing which work fine. No need to expand complexity. But thanks, maybe I will use ISafeSerializationData some day. For now, the libraries are finished and my boss is happy. – HCL Feb 21 '20 at 18:48