Security issues with boxed types in ECMA CLI (.NET)

Constantine Plotnikov


Table of Contents

Problem description
How to see it?
Conclusion

Problem description

Boxed type design in ECMA CLI could provide communication channel that is very hard to close. This channel is provided in the name of optimization.

When type is boxed, and there are some public methods that modify boxed value could be called, if they are declared on some interface. This could be considered as feature. But it could provide unexpected communication channel between components. Also some issues with modifying object equality hashcode will be applicable here, but they are more explicit as they are in some way described in interface.

Other problem that boxed type is unboxed to pointer to value, so value inside box object could be modified.

This particular problem manifests itself with collections. Item that got out of collection could be changed. So maps where keys or values are boxed values are not safe and could be corrupted. For example key or values could be modified on Hashtable even on unmodifiable one, In case of key modification calculated hash value will be no more valid. This feature is not easy to understand. Specification tries to justify it as optimization.

The worst thing that this feature is not visible in C#, but available in assembler so while generic programmers could be not aware of it, determined attacker could exploit it.

It looks like ECMA CLI relies too much on its permission security model for protection and leaves many holes in other places. It is good that this feature could not be exploited by completely remote code like in web services scenario.

How to see it?

There was some hope that there are other ways to prevent it in CLI runtime. To check it I have created a small program.

To start with program I have created small that does almost all that I need. As boxing/unboxing returns real values in C#, I were not able to all that I wanted C#.

Example 1. Starting source code

using System;
using System.Collections;

public class Test {

  static public void Main() {
    Hashtable t = new Hashtable();
    t[2]=3;
    Console.WriteLine(t[2]);
    Helper1((int)t[2]);
    Console.WriteLine(t[2]);
  }

  static void Helper1(int a) {
  }

  static void Helper(ref int a) {
    a = 4;
  }

}

Then I have disassembled the program with ildasm tool and changed Main method to call Helper(ref int a) rather then Helper1.

Example 2. Modified main method

  .method public hidebysig static void  Main() cil managed
  {
    .entrypoint
    // Code size       82 (0x52)
    .maxstack  3
    .locals init (class [mscorlib]System.Collections.Hashtable V_0)
    IL_0000:  newobj     instance void [mscorlib]System.Collections.Hashtable::.ctor()
    IL_0005:  stloc.0
    IL_0006:  ldloc.0
    IL_0007:  ldc.i4.2
    IL_0008:  box        [mscorlib]System.Int32
    IL_000d:  ldc.i4.3
    IL_000e:  box        [mscorlib]System.Int32
    IL_0013:  callvirt   instance void [mscorlib]System.Collections.Hashtable::set_Item(object,
                                                                                        object)
    IL_0018:  ldloc.0
    IL_0019:  ldc.i4.2
    IL_001a:  box        [mscorlib]System.Int32
    IL_001f:  callvirt   instance object [mscorlib]System.Collections.Hashtable::get_Item(object)
    IL_0024:  call       void [mscorlib]System.Console::WriteLine(object)
    IL_0029:  ldloc.0
    IL_002a:  ldc.i4.2
    IL_002b:  box        [mscorlib]System.Int32
    IL_0030:  callvirt   instance object [mscorlib]System.Collections.Hashtable::get_Item(object)
    IL_0035:  unbox      [mscorlib]System.Int32
    //IL_003a:  ldind.i4 // this instruction puts value on stack
    //IL_003b:  call       void Test::Helper1(int32)
    IL_003b:  call       void Test::Helper(int32&) //added call of helper method
    IL_0040:  ldloc.0
    IL_0041:  ldc.i4.2
    IL_0042:  box        [mscorlib]System.Int32
    IL_0047:  callvirt   instance object [mscorlib]System.Collections.Hashtable::get_Item(object)
    IL_004c:  call       void [mscorlib]System.Console::WriteLine(object)
    IL_0051:  ret
  } // end of method Test::Main

I'm not providing a complete source here. Because other places does not need to be changed. And because disassebled source is quite big.

Then I have successfully compiled source code of program. After compilation it produced following output.

3
4

This output means that value in the map were successfully modified w/o calling any method on map.

Conclusion

Under ECMA CLI, I suggest never to pass boxed value to outside, if it will be used in boxed form in the component. For example do not provide references to immutable collections of boxed values to outside client, if the same collection is used internally. Immutable proxy collection should not return the same boxed values as are held in original mutable collection. During collection copy operation over boxed values, copied collection should not contain references from boxed values in original collection.