Interesting security change in .NET remoting Thursday, May 08 2008
I ran the sample and it worked just fine in the 1.0 .NET Framework. The client would create and pass the objects from the shared library to the server. The code is as follows:
(Shared Library)
namespace RemShared
{
[Serializable]
public class ByValueObject
{
public string SomeValue = string.Empty;
}
public class ByRefObject : MarshalByRefObject
{
public void DoSomethingWith(ByValueObject valObj)
{
Console.WriteLine(valObj.SomeValue);
}
}
}
(Remoting Server)
namespace RemServer
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
TcpChannel channel = new TcpChannel(1111);
ChannelServices.RegisterChannel(channel);
RemShared.ByRefObject bro = new RemShared.ByRefObject();
ObjRef objRef = RemotingServices.Marshal(bro, "remoted.object", typeof(RemShared.ByRefObject));
Console.WriteLine("Server started, press enter to exit...");
Console.ReadLine();
RemotingServices.Unmarshal(objRef);
ChannelServices.UnregisterChannel(channel);
}
}
}
(Remoting Client)
namespace RemClient
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
try
{
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
object remoteObject = Activator.GetObject(typeof(RemShared.ByRefObject),
"tcp://localhost:1111/remoted.object");
RemShared.ByRefObject bro = (RemShared.ByRefObject) remoteObject;
RemShared.ByValueObject bvo = new RemShared.ByValueObject();
bvo.SomeValue = "Hello Remoting";
bro.DoSomethingWith(bvo);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
Console.WriteLine("Finished...");
Console.ReadLine();
}
}
}
}
Very, very simple code. I am using RemotingServices.Marshal for this example just because it's easier. You could rightfully do any other method of exposing a remote object and get the same results.
This works in the .NET 1.0 Framework (remember that the shared library is signed), however the moment you convert it to 1.0 it does not. Take a look in the folder "2. Does not work" in my download below. Since I couldn't get it to work I decided to attempt to take off the signing on a whim. Look at folder "1. Works". Both sets of code are the same except for one difference: the code in "2. Does not work" has the shared library signed. In fact, when you execute the "2. Does not work" code, you get an exception:
System.Runtime.Serialization.SerializationException: Because of security restrictions, the type RemShared.ByValueObject cannot be accessed ---> System.Security.SecurityException: Request failed.
Very, very odd if you ask me. Both console applications are running with full trust, so why would there be a security exception? If you run "caspol -s off" from a VS2k3 Command Prompt then try the "2. Does not work" sample again, the code execute successfully! Why would a full trust application receive a security exception from code access security? I thought for a second that since the shared library was signed that there was some problem because the client and server were not signed, so I signed both the client and server with the same key. That in fact, does not solve the problem (look at "3. Does not work" folder in the download).
Tired off this stupid problem I google for the exception. I come up with
this.
Someone has the same problem as me and someone else suggests applying the System.Security.AllowPartiallyTrustedCallersAttribute attribute to the assembly. I tried this on my shared library (see "4. Works" folder) and it made everything work. Not quite understanding why, I pulled up the documentation for it and get this:
"AllowPartiallyTrustedCallersAttribute is only effective when applied by a strong-named assembly at the assembly level. For more information about applying attributes at the assembly level, see Applying Attributes.
By default, a strong-named assembly that does not explicitly apply this attribute at assembly level to allow its use by partially trusted code can be called only by other assemblies that are granted full trust by security policy. This restriction is enforced by placing a LinkDemand for FullTrust on every public or protected method on every publicly accessible class in the assembly. Assemblies that are intended to be called by partially trusted code can declare their intent through the use of the AllowPartiallyTrustedCallersAttribute."
From what I see here, is that anything that executes and doesn't have FullTrust applied will raise an exception without this attribute. Well, I am running two console apps from the command line with an administrator account. Does that not give me FullTrust? I'm pretty sure it does. So I took a look at the breaking security changes from 1.0 to 1.1. I come up with this (which is also pointed to from some google groups searches). From what I see, basically it says anything can be remoted that implements ISerializable or has the SerializableAttribute applied to it (or that is a primitive type). It says that ObjRefs, ISponsors, and anything inserted between the proxy and client by the IContributeEnvoySink interface cannot be remoted by default and must set the "typeFilterLevel" to full. This doesn't really sound like it applies, but I tried it anyway but to no avail. Some people on some newsgroups suggest that this is the change in 1.1 that is causing this error, but I'm not so sure.
So really I am 100% unsure as to why the hell I receive this exception, as it doesn't seem to make much sense. I do not have a partially trusted application in either my client or my server. My strongly-named assembly is able to be loaded, which means I am in a fully trusted environment (according to the documentation, something that is not fully trusted cannot load a strongly named assembly that doesn't have the AllowPartiallyTrustedCallerAttribute applied to it). However, I cannot remote an object from one fully trusted application to another without applying this attribute.
Is this a bug? Can someone explain this? My example code is here and is runnable if you have the .NET Framework 1.1 installed. You can see the code with notepad or open the solution/project files with VS2k3. All I know now is that at work we have all signed assemblies and we need to remote quite a few of them, so we have to apply the AllowPartiallyTrustedCallersAttribute to every one of them, which doesn't quite make sense to me.


Bear with me, I definitely not an expert on the subject.
I would imagine that the calling code is executing in a context that you're not expecting. The CLR doesn't really rely on the security context of the user, rather the security context of the zone from which the code comes.
In this case, the code is coming from the local intranet zone and is thus considered partially trusted code. As far as I know, there is no way to programmatically allow the code access without AllowPartiallyTrustedCallersAttribute. I think you could still add your own LinkDemand to require that the calling code be signed with your key, just to work around the security worries that AllowPartiallyTrustedCallers presents.
The only thing that troubles me about this theory is that it worked in 1.0, and now doesn't. I don't know whether the restriction was present in 1.0, so I could be way off base here.
The best resource I've found for an explanation of the code security infrastructure is here. It has got some helpful code snippets in there to help further diagnose the issue.