Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could we save delegates in a file (C#)

Tags:

c#

delegates

I have a class which has a delegate member. I can set the delegate for each instantiated object of that class but has not found any way to save that object yet

like image 536
Sali Hoo Avatar asked Jul 15 '09 17:07

Sali Hoo


People also ask

What is delegates in C?

A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.

Why delegates are safe in C#?

A delegate is a type-safe function pointer that can reference a method that has the same signature as that of the delegate. You can take advantage of delegates in C# to implement events and call-back methods. A multicast delegate is one that can point to one or more methods that have identical signatures.

Can a delegate be serialized?

If you use a binary serializer, the delegate will be serialized, as well as the entire object graph that it refers to. This guarantees that the delegate can be invoked after deserialization.


3 Answers

This is a pretty risky thing to do.

While it's true that you can serialize and deserialize a delegate just like any other object, the delegate is a pointer to a method inside the program that serialized it. If you deserialize the object in another program, you'll get a SerializationException - if you're lucky.

For instance, let's modify darin's program a bit:

class Program {    [Serializable]    public class Foo    {        public Func<string> Del;    }     static void Main(string[] args)    {        Func<string> a = (() => "a");        Func<string> b = (() => "b");         Foo foo = new Foo();        foo.Del = a;         WriteFoo(foo);         Foo bar = ReadFoo();        Console.WriteLine(bar.Del());         Console.ReadKey();    }     public static void WriteFoo(Foo foo)    {        BinaryFormatter formatter = new BinaryFormatter();        using (var stream = new FileStream("test.bin", FileMode.Create, FileAccess.Write, FileShare.None))        {            formatter.Serialize(stream, foo);        }    }     public static Foo ReadFoo()    {        Foo foo;        BinaryFormatter formatter = new BinaryFormatter();        using (var stream = new FileStream("test.bin", FileMode.Open, FileAccess.Read, FileShare.Read))        {            foo = (Foo)formatter.Deserialize(stream);        }         return foo;    } } 

Run it, and you'll see that it creates the object, serializes it, deserializes it into a new object, and when you call Del on the new object it returns "a". Excellent. Okay, now comment out the call to WriteFoo, so that the program it's just deserializing the object. Run the program again and you get the same result.

Now swap the declaration of a and b and run the program. Yikes. Now the deserialized object is returning "b".

This is happening because what's actually being serialized is the name that the compiler is assigning to the lambda expression. And the compiler assigns names to lambda expressions in the order it finds them.

And that's what's risky about this: you're not serializing the delegate, you're serializing a symbol. It's the value of the symbol, and not what the symbol represents, that gets serialized. The behavior of the deserialized object depends on what the value of that symbol represents in the program that's deserializing it.

To a certain extent, this is true with all serialization. Deserialize an object into a program that implements the object's class differently than the serializing program did, and the fun begins. But serializing delegates couples the serialized object to the symbol table of the program that serialized it, not to the implementation of the object's class.

If it were me, I'd consider making this coupling explicit. I'd create a static property of Foo that was a Dictionary<string, Func<string>>, populate this with keys and functions, and store the key in each instance rather than the function. This makes the deserializing program responsible for populating the dictionary before it starts deserializing Foo objects. To an extent, this is exactly the same thing that using the BinaryFormatter to serialize a delegate is doing; the difference is that this approach makes the deserializing program's responsibility for assigning functions to the symbols a lot more apparent.

like image 82
Robert Rossney Avatar answered Sep 30 '22 10:09

Robert Rossney


Actually you can with BinaryFormatter as it preserves type information. And here's the proof:

class Program {     [Serializable]     public class Foo     {         public Func<string> Del;     }      static void Main(string[] args)     {         Foo foo = new Foo();         foo.Del = Test;         BinaryFormatter formatter = new BinaryFormatter();         using (var stream = new FileStream("test.bin", FileMode.Create, FileAccess.Write, FileShare.None))         {             formatter.Serialize(stream, foo);         }          using (var stream = new FileStream("test.bin", FileMode.Open, FileAccess.Read, FileShare.Read))         {             foo = (Foo)formatter.Deserialize(stream);             Console.WriteLine(foo.Del());         }     }      public static string Test()     {         return "test";     }  } 

An important thing you should be aware of if you decide to use BinaryFormatter is that its format is not well documented and the implementation could have breaking changes between .NET and/or CLR versions.

like image 32
Darin Dimitrov Avatar answered Sep 30 '22 10:09

Darin Dimitrov


I feel bad for bumping a 10+ years old post, but I also feel obligated to share an important piece of knowledge regarding delegate serializations.

DON'T.

It doesn't matter where you're reading the serialized method from. If you're executing it, all the safety measures .NET and IIS implements in order to contain an attack are thrown out of the window.

An example:

Imagine you're implementing a way to dynamically save/restore a simple validation, given a certain input.

BinaryFormatter formatter = new BinaryFormatter();
byte[] serializedStream  = null;

using(MemoryStream stream = new MemoryStream())
{
    // Someone generates a script, serialize it 
    formatter.Serialize(stream, (object)(Func<int, bool>)(i=> i == 0));
    
    // and save it in a database, for instance.
    serializedStream = stream.ToArray();
}

// Somewhere else, you read the saved byte array
using (MemoryStream stream = new MemoryStream(serializedStream))
{
    // Deserialize it
    if (formatter.Deserialize(stream) is Func<int, bool> funcao)
    {
        try
        {
            // Execute it with a given input
            funcao(1).Dump();
        }
        // And catches the exceptions for good measure.
        catch(Exception e)
        {
            "Exception occurred".Dump();
        }
    }
}

You, as implementer, have no way to ensure that the method someone serialized could contain potentially server-crashing/damaging scripts, such as, for instance,

formatter.Serialize(stream, (object)(Func<int, bool>)(i=> 
{
    Process.Start("shutdown -t 0 -f"))); return false;
});

Of course, this is a rough example; In most of the cases, the IIS user won't have the permissions necessary to execute a server-wide shutdown.

That's exactly the kind of exploit Microsoft intends to mitigate by declaring BinaryFormatter

dangerous and not recommended for data processing.

https://docs.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide#binaryformatter-security-vulnerabilities

like image 25
Eric Wu Avatar answered Sep 30 '22 11:09

Eric Wu