I stumbled upon this code:
static void Main()
{
typeof(string).GetField("Empty").SetValue(null, "evil");//from DailyWTF
Console.WriteLine(String.Empty);//check
//how does it behave?
if ("evil" == String.Empty) Console.WriteLine("equal");
//output:
//evil
//equal
}
and I wonder how is it even possible to compile this piece of code. My reasoning is:
According to MSDN String.Empty is read-only therefore changing it should be impossible and compiling should end with “A static readonly field cannot be assigned to” or similar error.
I thought Base Class Library assemblies are somehow protected and signed and whatnot to prevent exactly this kind of attack. Next time someone may change System.Security.Cryptography or another critical class.
I thought Base Class Library assemblies are compiled by NGEN after .NET installation therefore changing fields of String class should require advanced hacking and be much harder.
And yet this code compiles and works. Can somebody please explain what is wrong with my reasoning?
You’re not assigning to it. You’re calling public functions in the
System.Reflectionnamespace. No reason for the compiler to complain about that.Besides,
typeof(string).GetField("Empty")could use variables entered in by the user instead, there’s no sure way for the compiler to tell in all cases whether the argument toGetFieldwill end up being"Empty".I think you’re wanting
Reflectionto see that the field is markedinitonlyand throw an error at runtime. I can see why you would expect that, yet for white-box testing, even writing toinitonlyfields has some application.The reason NGEN has no effect is that you’re not modifying any code here, only data. Data is stored in memory with .NET just as with any other language. Native programs may use readonly memory sections for things like string constants, but the pointer to the string is generally still writable and that is what is happening here.
Note that your code must be running with full-trust to use reflection in this questionable way. Also, the change only affect one program, this isn’t any sort of a security vulnerability as you seem to think (if you’re running malicious code inside your process with full trust, that design decision is the security problem, not reflection).
Further note that the values of
initonlyfields insidemscorlib.dllare global invariants of the .NET runtime. After breaking them, you can’t even reliably test whether the invariant was broken, because the code to inspect the current value of System.String.Empty has also broken, because you’ve violated its invariants. Start violating system invariants and nothing can be relied on.By specifying these values inside the .NET specifications, it enables the compiler to implement a whole bunch of performance optimizations. Just a simple one is that
and
are equivalent, but the latter is much faster (relatively speaking).
Also the compiler can determine that
is never true, and generate an unconditional jump to the else block (it still has to call
Int32.Parseto have the same exception behavior, but the comparison can be removed).System.String.Emptyis also used extensively inside BCL implementations. If you overwrite it, all sorts of crazy things can happen, including damage that leaks outside your program (for example you might write to a file whose name is built using string manipulation… when string breaks, you might overwrite the wrong file)And the behavior might easily differ between .NET versions. Normally when new optimization opportunities are found, they don’t get backported to previous versions of the JIT compiler (and even if they were, there could be installations from before the backport was implemented). In particular.
String.Empty-related optimizations are observably different between .NET 2.x and Mono and .NET 4.5+.