title: How can I implement ISerializable in .NET 4+ without violating inheritance security rules?
body: <p>Background: <a href="https://nodatime.org" rel="noreferrer">Noda Time</a> contains many
  serializable structs. While I dislike binary serialization, we
  received many requests to support it, back in the 1.x timeline.
  We support it by implementing the <code>ISerializable</code> interface.</p>
  
  <p>We've received a recent <a href="https://github.com/nodatime/nodatime/issues/1060" rel="noreferrer">issue
  report</a> of Noda
  Time 2.x <a href="https://dotnetfiddle.net/vTCzMJ" rel="noreferrer">failing within .NET
  Fiddle</a>. The same code using Noda
  Time 1.x works fine. The exception thrown is this:</p>
  
  <blockquote>
    <p>Inheritance security rules violated while overriding member:
    'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo,
    System.Runtime.Serialization.StreamingContext)'. Security
    accessibility of the overriding method must match the security
    accessibility of the method being overriden.</p>
  </blockquote>
  
  <p>I've narrowed this down to the framework that's targeted: 1.x
  targets .NET 3.5 (client profile); 2.x targets .NET 4.5. They have
  big differences in terms of support PCL vs .NET Core and the
  project file structure, but it looks like this is irrelevant.</p>
  
  <p>I've managed to reproduce this in a local project, but I haven't
  found a solution to it.</p>
  
  <p>Steps to reproduce in VS2017:</p>
  
  <ul>
  <li>Create a new solution</li>
  <li>Create a new classic Windows console application targeting .NET
  4.5.1. I called it "CodeRunner".</li>
  <li>In the project properties, go to Signing and sign the assembly with
  a new key. Untick the password requirement, and use any key file name.</li>
  <li>Paste the following code to replace <code>Program.cs</code>. This is an
  abbreviated version of the code in <a href="https://docs.microsoft.com/en-us/dotnet/framework/misc/how-to-run-partially-trusted-code-in-a-sandbox#example" rel="noreferrer">this Microsoft
  sample</a>.
  I've kept all the paths the same, so if you want to go back to the
  fuller code, you shouldn't need to change anything else.</li>
  </ul>
  
  <p>Code:</p>
  
  <pre><code>using System;
  using System.Security;
  using System.Security.Permissions;
  
  class Sandboxer : MarshalByRefObject  
  {  
      static void Main()  
      {  
          var adSetup = new AppDomainSetup();  
          adSetup.ApplicationBase = System.IO.Path.GetFullPath(@"..\..\..\UntrustedCode\bin\Debug");  
          var permSet = new PermissionSet(PermissionState.None);  
          permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));  
          var fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence&lt;System.Security.Policy.StrongName&gt;();  
          var newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);  
          var handle = Activator.CreateInstanceFrom(  
              newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,  
              typeof(Sandboxer).FullName  
              );  
          Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();  
          newDomainInstance.ExecuteUntrustedCode("UntrustedCode", "UntrustedCode.UntrustedClass", "IsFibonacci", new object[] { 45 });  
      }  
  
      public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)  
      {  
          var target = System.Reflection.Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
          target.Invoke(null, parameters);
      }  
  }
  </code></pre>
  
  <ul>
  <li>Create another project called "UntrustedCode". This should be a
  Classic Desktop Class Library project.</li>
  <li>Sign the assembly; you can use a new key or the same one as for
  CodeRunner. (This is partially to mimic the Noda Time situation,
  and partly to keep Code Analysis happy.)</li>
  <li>Paste the following code in <code>Class1.cs</code> (overwriting what's there):</li>
  </ul>
  
  <p>Code:</p>
  
  <pre><code>using System;
  using System.Runtime.Serialization;
  using System.Security;
  using System.Security.Permissions;
  
  // [assembly: AllowPartiallyTrustedCallers]
  
  namespace UntrustedCode
  {
      public class UntrustedClass
      {
          // Method named oddly (given the content) in order to allow MSDN
          // sample to run unchanged.
          public static bool IsFibonacci(int number)
          {
              Console.WriteLine(new CustomStruct());
              return true;
          }
      }
  
      [Serializable]
      public struct CustomStruct : ISerializable
      {
          private CustomStruct(SerializationInfo info, StreamingContext context) { }
  
          //[SecuritySafeCritical]
          //[SecurityCritical]
          //[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
          void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
          {
              throw new NotImplementedException();
          }
      }
  }
  </code></pre>
  
  <p>Running the CodeRunner project gives the following exception (reformatted for readability):</p>
  
  <blockquote>
    <p>Unhandled Exception: System.Reflection.TargetInvocationException:<br>
    Exception has been thrown by the target of an invocation.<br>
    ---&gt;<br>
    System.TypeLoadException:<br>
    Inheritance security rules violated while overriding member:<br>
    'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData(...).<br>
    Security accessibility of the overriding method must match the security<br>
    accessibility of the method being overriden.</p>
  </blockquote>
  
  <p>The commented-out attributes show things I've tried:</p>
  
  <ul>
  <li><code>SecurityPermission</code> is recommended by two different MS articles (<a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/serialization-guidelines" rel="noreferrer">first</a>,
  <a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization" rel="noreferrer">second</a>), although
  interestingly they do different things around explicit/implicit interface implementation</li>
  <li><code>SecurityCritical</code> is what Noda Time currently has, and is what <a href="https://stackoverflow.com/questions/3055792">this question's answer</a> suggests</li>
  <li><code>SecuritySafeCritical</code> is somewhat suggested by Code Analysis rule messages</li>
  <li>Without <em>any</em> attributes, Code Analysis rules are happy - with either <code>SecurityPermission</code> or <code>SecurityCritical</code>
  present, the rules tell you to remove the attributes - unless you <em>do</em> have <code>AllowPartiallyTrustedCallers</code>. Following the suggestions in either case doesn't help.</li>
  <li>Noda Time has <code>AllowPartiallyTrustedCallers</code> applied to it; the example here doesn't work either with or without the attribute applied.</li>
  </ul>
  
  <p>The code runs without an exception if I add <code>[assembly: SecurityRules(SecurityRuleSet.Level1)]</code> to the <code>UntrustedCode</code> assembly (and uncomment the <code>AllowPartiallyTrustedCallers</code> attribute),  but I believe that's a poor solution to the problem that could hamper other code.</p>
  
  <p>I fully admit to being pretty lost when it comes to this sort of
  security aspect of .NET. So what <em>can</em> I do to target .NET 4.5 and
  yet allow my types to implement <code>ISerializable</code> and still be used in
  environments such as .NET Fiddle?</p>
  
  <p>(While I'm targeting .NET 4.5, I believe it's the .NET 4.0 security policy changes that caused the issue, hence the tag.)</p>