(Disclaimer:如果需要转载请先与我联系。
作者:RednaxelaFX -> rednaxelafx.iteye.com)
系列文章:
要让CLR挂掉的话……
[url=http://rednaxelafx.iteye.com/blog/461787]要让CLR挂掉的话(第二弹)……[/url]
前几天跟[url=http://yuesefa.iteye.com/]浩飞[/url]老兄闲聊的时候,聊到说一个不知道什么地方在面试人的时候,如果面试者说自己精通Java,他们就出题考面试者如何让JVM挂掉。这种面试方式或许是比较激进,不过倒也可以考考别人对特定JVM的实现的认识。
于是在[url=http://stackoverflow.com/]爆栈[/url]上有这么一帖:[url=http://stackoverflow.com/questions/65200/how-do-you-crash-a-jvm]How do you crash a JVM?[/url]。跟帖中一些同学的观点一样,我也不认为爆栈或者爆堆能算得上是“crash”,因为JVM还能正确捕捉到错误,并且执行合适的异常处理。真正的“crash”应该是连正常的异常处理都没起作用,直接就出crash log了;要是能连出crash log的步骤都破坏掉那就更彻底了。
爆栈帖里有人建议说:
[quote="ralfs"]1. Use JNI and crash in the native code.
2. If no security manager is installed you can use reflection to crash the VM. This is VM specific, but normally a VM stores a bunch of pointers to native resources in private fields (e.g. a pointer to the native thread object is stored in a long field in [b]java.lang.Thread[/b]). Just change them via reflection and the VM will crash sooner or later.
3. All VMs have bugs, so you just have to trigger one.[/quote]
都是些有趣的建议……
对应到.NET上的话,
第一点基本上就映射到P/Invoke的使用了。如果被P/Invoke的native code里有非常糟糕的错误而且不使用[url=http://msdn.microsoft.com/en-us/library/ms680657(VS.85).aspx]SEH[/url],那CLR什么办法也没有,只能让程序crash了。
第二点是关于操纵VM内部实现用到的指针。各种JVM实现里在不同位置暴露了一些指针(即便是Compressed Oops那也是指针),改变它们的值确实能达到crash的效果,虽然如果更进一步能它它们改成“有意义”的值的话就能更有效的操纵破坏的具体行为。
CLR里也有许多看起来很无辜的东西实际上是指针来的(注意我是说CLR不是CLI)。一个典型的例子是[url=http://msdn.microsoft.com/en-us/library/system.type.typehandle.aspx]Type.TypeHandle[/url]属性,在CLR里它实际上就是指向类型的MethodTable的指针。通过它我们可以找到很多关于类型的“裸”信息。“裸”是指CLR内部的实现细节,本来不应该暴露出来的部分)。还有一个典型的例子是.NET的类型安全函数指针,委托。下面会看看委托的例子。
要操纵VM内部的指针,势必要通过反射去获取或设置一些私有变量的值。这种操作一般都会受到VM的安全管理器监管,在没有足够权限的情况下无法执行。所以其实也不算危险……不,应该说原本用native code的话就有这种危险了,用了VM并没有变得更危险。
第三点是说VM自身的实现有bug。嗯这种状况常有,像先前我就看到[url=http://rednaxelafx.iteye.com/blog/456647]HotSpot的JIT有bug挂掉了[/url]。CLR小组也没少遇到内部发生内存管理错误的问题,组里有专人盯着这种问题在修。如果发现这样的bug并有意利用的话,也能有效让VM挂掉,甚至进一步做别的事情……呵呵
===========================================================================
.NET的委托,在不考虑多播(multicast)状况时,完成调用所需要的Delegate类上最关键的3个成员是_target、_methodPtr和_methodPtrAux。其中只有_target是以Delegate.Target属性的形式公开出来的。看看它们都有什么用:
_[b]target[/b]:委托调用的目标对象。
.NET的委托是类型安全的,不但指在构造委托实例时会检查其类型与目标方法的signature是否匹配,也指委托能够捕获目标对象的引用,进而能够由其得到相关的类型和方法的元数据,以供执行引擎监管类型的安全性。
在CLR的实现中,_target可能有两种情况:
1、如果委托指向的方法是成员方法,那么_target就会是指向目标方法所属的对象实例的指针;
2、如果委托指向的是静态方法,或者是涉及native方法,那么_target会指向委托实例自身。
有趣的是,虽然指向静态方法时_target指向委托实例自身,但Delegate.Target却会返回null。
[b]_methodPtr[/b]:委托调用的目标方法的指针。
这个是“函数指针”的真面目,跟C里的函数指针没什么两样。
它的值也分两大种情况:
1、如果委托指向的方法是成员方法,那么_methodPtr就可能指向一个JIT stub(假如创建委托时目标方法尚未被JIT),或者可能是直接指向目标方法JIT后的地址;
2、如果委托指向的方法是静态方法,那么_methodPtr指向的是一个stub,去掉原本调用时隐藏的第一个参数(_target),然后调用_methodPtrAux。这个stub是所有signature相同的委托共享的。
如果涉及native方法的话我还没弄清楚具体是什么状况 =v=
[b]_methodPtrAux[/b]:委托调用的目标方法的第二个指针。
联系前两个成员的介绍,这个也不例外分两种情况:
1、如果委托指向的是成员方法,那么_methodPtrAux就是null(0)。Delegate.Target属性实际的实现是_methodPtrAux.IsNull() ? _target : null,可以看到目标是成员方法与否的影响。
2、如果委托指向的是静态方法,那么_methodPtrAux可能指向类似JIT stub的东西,该stub在多次调用后可能会被改写为jmp到实际调用目标方法;也可能一开始就指向目标方法JIT后的地址。
(CLRv2中,“多次”是3次;采取哪个版本的_methodPtrAux取决于创建委托实例所在的方法在被JIT编译时,目标方法是否已经被JIT编译)
抽象的描述还是让人摸不着头脑,来看看代码例子:
先注意一些C#的实现细节。Main里的iden与succ所指向的lambda都没有捕获任何自由变量,所以由C#编译器先改写生成对应的私有静态方法。这样,iden与succ就属于“指向静态方法的委托”的情况,可以留意一下相应的_target、_methodPtr与_methodPtrAux的表现。特别的,iden与succ的_target成员指向各自自身;它们的_methodPtr都指向同一个stub,用于剥离第一个隐藏参数并调用_methodPtrAux;由于Main()方法被JIT的时候,两个lambda对应的静态方法尚未被JIT,所以iden与succ的_methodPtrAux各自指向不同的stub(而不是直接指向实际调用目标方法)。
在代码中,我们把succ的_methodPtrAux提取出来,并设置到iden对应的域里。然后在调用iden时,可以看到实际被调用的是succ指向的那个lambda。
既然能把函数指针改到一个有效的函数地址上,那要是改为null的话呢?
我们就让CLR挂掉而出现AV(access violation)了:
[quote]Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.[/quote]
可惜CLR的实现比较严谨,AV也还是被默认的异常处理捕捉到了。不过如果指向什么别的地方,说不定就能在触发AV前先干点好事了,呵呵。
再次注意到像这样操纵VM内部的指针需要足够的安全权限才行,否则通过反射也无法像这样修改私有变量的值。所以并不会很不安全,可以放心。
说真的,即便写个会爆栈的程序,CLR也会扔出类似的错误信息:
[quote]Process is terminated due to StackOverflowException.[/quote]
改委托内部的函数指针不够好玩……
===========================================================================
回复中cescshen同学问了个有趣的问题,说为什么改变_target也可以改变实际被调用的对象。我把我的回帖复制上来~
以下内容都是以PC上的32位x86的CLR,版本2.0.50727.3082为前提的讨论。
[quote="cescshen"]发错,这儿不能删自己的留言。。
改成这样的话,也能出结果,这个怎么回事?[/quote]
如果你说的不是遇到了错误,而是看到修改_target后iden的行为变成了succ的,那是因为在_methodPtr所指向的那个stub里,代码是这样的:
注意到CLR里JIT编译的代码的calling convention是类似fastcall的,头两个参数分别位于ECX和EDX。在调用iden的时候,代码是这样的:
知道从_methodPtr到_methodPtrAux的过程之后,就可以理解为什么改变_target的值也足以改变指向静态方法的委托的行为:因为关键的_methodPtrAux是通过_target来引用的。在正常情况下,_target就指向委托自身,所以没有问题;而改变了_target的值之后,实际被调用的_methodPtrAux就跟着一起变了。
===========================================================================
[url=http://stackoverflow.com/questions/65200/how-do-you-crash-a-jvm]爆栈帖[/url]里有一个回复可以mark一下:
[quote="eckes"]On Linux/Unix you can easyly make a JVM crash by sending it a Signal to the running process. Note: you should not use "SIGSEGV" for this, since Hotspot catches this signal and rethrows it as a NullPointerException in most places. So it is better to send a SIGBUS for example.[/quote]
作者:RednaxelaFX -> rednaxelafx.iteye.com)
系列文章:
要让CLR挂掉的话……
[url=http://rednaxelafx.iteye.com/blog/461787]要让CLR挂掉的话(第二弹)……[/url]
前几天跟[url=http://yuesefa.iteye.com/]浩飞[/url]老兄闲聊的时候,聊到说一个不知道什么地方在面试人的时候,如果面试者说自己精通Java,他们就出题考面试者如何让JVM挂掉。这种面试方式或许是比较激进,不过倒也可以考考别人对特定JVM的实现的认识。
于是在[url=http://stackoverflow.com/]爆栈[/url]上有这么一帖:[url=http://stackoverflow.com/questions/65200/how-do-you-crash-a-jvm]How do you crash a JVM?[/url]。跟帖中一些同学的观点一样,我也不认为爆栈或者爆堆能算得上是“crash”,因为JVM还能正确捕捉到错误,并且执行合适的异常处理。真正的“crash”应该是连正常的异常处理都没起作用,直接就出crash log了;要是能连出crash log的步骤都破坏掉那就更彻底了。
爆栈帖里有人建议说:
[quote="ralfs"]1. Use JNI and crash in the native code.
2. If no security manager is installed you can use reflection to crash the VM. This is VM specific, but normally a VM stores a bunch of pointers to native resources in private fields (e.g. a pointer to the native thread object is stored in a long field in [b]java.lang.Thread[/b]). Just change them via reflection and the VM will crash sooner or later.
3. All VMs have bugs, so you just have to trigger one.[/quote]
都是些有趣的建议……
对应到.NET上的话,
第一点基本上就映射到P/Invoke的使用了。如果被P/Invoke的native code里有非常糟糕的错误而且不使用[url=http://msdn.microsoft.com/en-us/library/ms680657(VS.85).aspx]SEH[/url],那CLR什么办法也没有,只能让程序crash了。
第二点是关于操纵VM内部实现用到的指针。各种JVM实现里在不同位置暴露了一些指针(即便是Compressed Oops那也是指针),改变它们的值确实能达到crash的效果,虽然如果更进一步能它它们改成“有意义”的值的话就能更有效的操纵破坏的具体行为。
CLR里也有许多看起来很无辜的东西实际上是指针来的(注意我是说CLR不是CLI)。一个典型的例子是[url=http://msdn.microsoft.com/en-us/library/system.type.typehandle.aspx]Type.TypeHandle[/url]属性,在CLR里它实际上就是指向类型的MethodTable的指针。通过它我们可以找到很多关于类型的“裸”信息。“裸”是指CLR内部的实现细节,本来不应该暴露出来的部分)。还有一个典型的例子是.NET的类型安全函数指针,委托。下面会看看委托的例子。
要操纵VM内部的指针,势必要通过反射去获取或设置一些私有变量的值。这种操作一般都会受到VM的安全管理器监管,在没有足够权限的情况下无法执行。所以其实也不算危险……不,应该说原本用native code的话就有这种危险了,用了VM并没有变得更危险。
第三点是说VM自身的实现有bug。嗯这种状况常有,像先前我就看到[url=http://rednaxelafx.iteye.com/blog/456647]HotSpot的JIT有bug挂掉了[/url]。CLR小组也没少遇到内部发生内存管理错误的问题,组里有专人盯着这种问题在修。如果发现这样的bug并有意利用的话,也能有效让VM挂掉,甚至进一步做别的事情……呵呵
===========================================================================
.NET的委托,在不考虑多播(multicast)状况时,完成调用所需要的Delegate类上最关键的3个成员是_target、_methodPtr和_methodPtrAux。其中只有_target是以Delegate.Target属性的形式公开出来的。看看它们都有什么用:
_[b]target[/b]:委托调用的目标对象。
.NET的委托是类型安全的,不但指在构造委托实例时会检查其类型与目标方法的signature是否匹配,也指委托能够捕获目标对象的引用,进而能够由其得到相关的类型和方法的元数据,以供执行引擎监管类型的安全性。
在CLR的实现中,_target可能有两种情况:
1、如果委托指向的方法是成员方法,那么_target就会是指向目标方法所属的对象实例的指针;
2、如果委托指向的是静态方法,或者是涉及native方法,那么_target会指向委托实例自身。
有趣的是,虽然指向静态方法时_target指向委托实例自身,但Delegate.Target却会返回null。
[b]_methodPtr[/b]:委托调用的目标方法的指针。
这个是“函数指针”的真面目,跟C里的函数指针没什么两样。
它的值也分两大种情况:
1、如果委托指向的方法是成员方法,那么_methodPtr就可能指向一个JIT stub(假如创建委托时目标方法尚未被JIT),或者可能是直接指向目标方法JIT后的地址;
2、如果委托指向的方法是静态方法,那么_methodPtr指向的是一个stub,去掉原本调用时隐藏的第一个参数(_target),然后调用_methodPtrAux。这个stub是所有signature相同的委托共享的。
如果涉及native方法的话我还没弄清楚具体是什么状况 =v=
[b]_methodPtrAux[/b]:委托调用的目标方法的第二个指针。
联系前两个成员的介绍,这个也不例外分两种情况:
1、如果委托指向的是成员方法,那么_methodPtrAux就是null(0)。Delegate.Target属性实际的实现是_methodPtrAux.IsNull() ? _target : null,可以看到目标是成员方法与否的影响。
2、如果委托指向的是静态方法,那么_methodPtrAux可能指向类似JIT stub的东西,该stub在多次调用后可能会被改写为jmp到实际调用目标方法;也可能一开始就指向目标方法JIT后的地址。
(CLRv2中,“多次”是3次;采取哪个版本的_methodPtrAux取决于创建委托实例所在的方法在被JIT编译时,目标方法是否已经被JIT编译)
抽象的描述还是让人摸不着头脑,来看看代码例子:
using System;
using System.Reflection;
namespace TestCLR2Crash {
static class Program {
static void Main( string[ ] args ) {
Func<int, int> iden = x => x;
Func<int, int> succ = x => x + 1;
var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance );
var succPtrAux = ( IntPtr ) methPtrAuxInfo.GetValue( succ );
methPtrAuxInfo.SetValue( iden, succPtrAux );
Console.WriteLine( iden( 0xBEEF ).ToString( "X" ) ); // BEF0
}
}
}
先注意一些C#的实现细节。Main里的iden与succ所指向的lambda都没有捕获任何自由变量,所以由C#编译器先改写生成对应的私有静态方法。这样,iden与succ就属于“指向静态方法的委托”的情况,可以留意一下相应的_target、_methodPtr与_methodPtrAux的表现。特别的,iden与succ的_target成员指向各自自身;它们的_methodPtr都指向同一个stub,用于剥离第一个隐藏参数并调用_methodPtrAux;由于Main()方法被JIT的时候,两个lambda对应的静态方法尚未被JIT,所以iden与succ的_methodPtrAux各自指向不同的stub(而不是直接指向实际调用目标方法)。
在代码中,我们把succ的_methodPtrAux提取出来,并设置到iden对应的域里。然后在调用iden时,可以看到实际被调用的是succ指向的那个lambda。
既然能把函数指针改到一个有效的函数地址上,那要是改为null的话呢?
using System;
using System.Reflection;
namespace TestCLR2Crash {
static class Program {
static void Main( string[ ] args ) {
Func<int, int> iden = x => x;
var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance );
methPtrAuxInfo.SetValue( iden, IntPtr.Zero );
Console.WriteLine( iden( 0xBEEF ).ToString( "X" ) );
}
}
}
我们就让CLR挂掉而出现AV(access violation)了:
[quote]Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.[/quote]
可惜CLR的实现比较严谨,AV也还是被默认的异常处理捕捉到了。不过如果指向什么别的地方,说不定就能在触发AV前先干点好事了,呵呵。
再次注意到像这样操纵VM内部的指针需要足够的安全权限才行,否则通过反射也无法像这样修改私有变量的值。所以并不会很不安全,可以放心。
说真的,即便写个会爆栈的程序,CLR也会扔出类似的错误信息:
[quote]Process is terminated due to StackOverflowException.[/quote]
改委托内部的函数指针不够好玩……
===========================================================================
回复中cescshen同学问了个有趣的问题,说为什么改变_target也可以改变实际被调用的对象。我把我的回帖复制上来~
以下内容都是以PC上的32位x86的CLR,版本2.0.50727.3082为前提的讨论。
[quote="cescshen"]发错,这儿不能删自己的留言。。
var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance );
改成这样的话,也能出结果,这个怎么回事?[/quote]
如果你说的不是遇到了错误,而是看到修改_target后iden的行为变成了succ的,那是因为在_methodPtr所指向的那个stub里,代码是这样的:
mov eax,ecx // 把第一参数(_target)复制到EAX
mov ecx,edx // 把原本的第二参数(0xBEEF)变为第一参数
add eax,10h // 把_target._methodPtrAux的地址设到EAX
jmp dword ptr [eax] // 间接调用EAX,也就是调用_target._methodPtrAux
注意到CLR里JIT编译的代码的calling convention是类似fastcall的,头两个参数分别位于ECX和EDX。在调用iden的时候,代码是这样的:
mov ecx,edi // 把iden的引用从EDI复制到ECX
mov edx,0BEEFh // 0xBEEF复制到EDX作为第二参数
mov eax,dword ptr [ecx+0Ch] // 把iden._methodPtr复制到EAX
mov ecx,dword ptr [ecx+4] // 把iden._target复制到ECX作为第一参数
call eax // 调用_methodPtr
知道从_methodPtr到_methodPtrAux的过程之后,就可以理解为什么改变_target的值也足以改变指向静态方法的委托的行为:因为关键的_methodPtrAux是通过_target来引用的。在正常情况下,_target就指向委托自身,所以没有问题;而改变了_target的值之后,实际被调用的_methodPtrAux就跟着一起变了。
===========================================================================
[url=http://stackoverflow.com/questions/65200/how-do-you-crash-a-jvm]爆栈帖[/url]里有一个回复可以mark一下:
[quote="eckes"]On Linux/Unix you can easyly make a JVM crash by sending it a Signal to the running process. Note: you should not use "SIGSEGV" for this, since Hotspot catches this signal and rethrows it as a NullPointerException in most places. So it is better to send a SIGBUS for example.[/quote]