关于C#:要求Reflection API覆盖System.String.Empty的含义是什么?

What are the implications of asking Reflection APIs to overwrite System.String.Empty?

我偶然发现了这个代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

 }

我想知道如何编译这段代码。我的理由是:

根据msdn String.Empty是只读的,因此更改它应该是不可能的,编译应该以"静态只读字段不能分配给"或类似错误结束。

我认为基类库程序集受到某种程度的保护和签名,而什么都不能防止这种攻击。下次有人可能会更改System.Security.Cryptography或其他关键类。

我认为基类库程序集是由ngen在.NET安装之后编译的,因此更改字符串类的字段需要高级黑客攻击,而且要困难得多。

但这段代码可以编译并运行。有人能解释一下我的推理有什么问题吗?


A static readonly field cannot be assigned to

你没有分配给它。您在System.Reflection名称空间中调用公共函数。编译器没有理由对此抱怨。

另外,typeof(string).GetField("Empty")可以使用用户输入的变量来代替,编译器在所有情况下都无法确定GetField的参数最终是否会是"Empty"的。

我认为您希望Reflection看到字段标记为initonly,并在运行时抛出错误。我可以理解为什么您会期望,但是对于白盒测试,甚至写入initonly字段也有一些应用程序。

ngen不起作用的原因是您没有在这里修改任何代码,只修改数据。与任何其他语言一样,数据使用.NET存储在内存中。本机程序可以对字符串常量之类的东西使用只读内存段,但指向字符串的指针通常仍然是可写的,这就是这里发生的事情。

请注意,您的代码必须以完全信任的方式运行,才能以这种可疑的方式使用反射。此外,更改只影响一个程序,这并不像您想象的那样是一个安全漏洞(如果您在流程内以完全信任的方式运行恶意代码,那么设计决策就是安全问题,而不是反射)。

进一步注意,mscorlib.dllinitonly字段的值是.NET运行时的全局不变量。打破它们之后,甚至无法可靠地测试不变量是否被破坏,因为检查system.string.empty当前值的代码也被破坏了,因为您违反了它的不变量。开始违反系统不变量,什么都不能依赖。

通过在.NET规范中指定这些值,编译器可以实现一系列性能优化。简单一点就是

1
s == System.String.Empty

1
(s != null) && (s.Length == 0)

是等效的,但后者要快得多(相对而言)。

编译器也可以确定

1
if (int.Parse(s) > int.MaxValue)

从不为真,并生成到else块的无条件跳转(它仍然必须调用Int32.Parse以具有相同的异常行为,但可以删除比较)。

System.String.Empty在BCL实现中也被广泛使用。如果覆盖它,可能会发生各种疯狂的事情,包括程序外部泄漏的损坏(例如,您可能会写入一个文件,该文件的名称是使用字符串操作构建的…当字符串中断时,您可能会覆盖错误的文件)

而且.NET版本之间的行为可能很容易有所不同。通常,当发现新的优化机会时,它们不会被反向移植到以前版本的JIT编译器(即使是这样,也可能在实现反向移植之前进行安装)。特别地。与String.Empty相关的优化在.NET 2.x和mono以及.NET 4.5+之间存在显著差异。


代码编译是因为代码的每一行都是完全合法的。您认为哪一行是语法错误?那里没有分配给只读字段的代码行。有一行代码调用一个反射的方法,它分配给只读字段,但已经编译好了,最终破坏了安全性的东西甚至没有用C语言编写,它是用C++编写的。它是运行时引擎本身的一部分。

代码运行成功,因为完全信任意味着完全信任。您在完全信任的环境中运行代码,由于完全信任意味着完全信任,因此运行时假设您在执行这种愚蠢的危险操作时知道自己在做什么。

如果您尝试在部分受信任的环境中运行代码,那么您将看到反射抛出了一个"不允许您这样做"异常。

是的,集会已经签署了什么。如果您运行的是完全信任的代码,那么当然,它们可以随心所欲地使用这些程序集。这就是完全信任的含义。部分受信任的代码不能做到这一点,但是完全受信任的代码可以做任何你能做的事情。只有完全信任你真正信任的代码,才不会代表你做疯狂的事情。


反射可以让你违反物理定律做任何事情。甚至可以设置私有成员的值。

反射不遵循规则,您可以在msdn上阅读。

另一个例子:我可以使用反射更改C中的私有只读字段吗?

如果您在Web应用程序上,则可以设置应用程序的信任级别。

1
level="[Full|High|Medium|Low|Minimal]"

这些是信任级别的限制,与msdn一致,在Medium Trust中,您限制反射访问。

编辑:不要运行完全信任以外的Web应用程序,这是ASP.NET团队的直接建议。为保护应用程序,请为每个网站创建一个应用程序池。

此外,不建议使用反射来进行任何事情。它有正确的使用地点和时间。


这里有一点以前没人提到过:这段代码在不同的.NET实现/平台上会导致不同的行为。实际上,在Mono上,它什么也不返回:参见IDeone(Mono2.8),我的本地Mono2.6.7(Linux)生成相同的"输出"。

我还没有看过低级代码,但是我想它是特定于编译器的,正如prashant p或运行时环境所提到的那样。

更新

在Windows(MS Dotnet 4)上运行Mono编译的exe可生成

1
2
evil
equal

在Linux上运行Windows编译的exe是不可能的(dotnet 4…),所以我用dotnet 2重新编译(它在Windows上仍然表示邪恶和平等)。没有输出。当然,从第一个WriteLine开始,必须至少有"
"
,事实上,它就在那里。我将输出通过管道传输到一个文件,并启动hexeditor查看单个字符0x0A

长话短说:它似乎是特定于运行时环境的。


只读仅强制

在编译器级别

因此

可能在当前低于级别时发生更改