我刚刚阅读了一个新的C ++挑战:http://blogs.msdn.com/b/vcblog/archive/2014/02/04/challenge- 弱势 </跨度> -code.aspx
提供的代码如果充满问题,对于任何有良好 实例的人来说都是显而易见的(实际上它是unique_ptr)并且 二进制 </跨度> 它被视为可执行(可能是恶意的)代码。
虽然我能理解这一点,但我想知道为什么这确实有效。
CustomImage是
据推测,内存布局使得vptr位于基础子对象之前,如下所示:
class CustomImage { void * __vptr; Image __base; // empty unique_ptr<whatever> evil; };
这意味着有效的转换 Image* 至 CustomImage* 需要从指针中减去几个字节。但是,你发布的邪恶演员出现在类定义之前,所以它不知道如何正确调整指针。相反,它的行为就像 reinterpret_cast ,只是假装指针指向 CustomImage 没有调整它的价值。
Image*
CustomImage*
reinterpret_cast
CustomImage
现在,由于基类是空的,指针在里面 unique_ptr 将被误解为vptr。这指向另一个指针,它将被误解为vtable指向第一个虚拟成员函数的指针。这又指向从文件加载的数据,当调用该虚函数时,该数据将作为代码执行。作为锦上添花,内存保护标志从文件加载,而不是调整以防止执行。
unique_ptr
这里的一些教训是:
dynamic_cast
m_imageData
我的看法(我很高兴得到纠正):
这里, CustomImage 是一个多态类(使用vptr作为Windows ABI下的第一个“成员”),但是 Image 不是。定义的顺序意味着 ImageFactory 功能知道 CustomImage 是一个 Image 但是 main() 才不是。
Image
ImageFactory
main()
所以当工厂做的时候:
Image* ImageFactory::LoadFromDisk(const char* imageName) { return new (std::nothrow) CustomImage(imageName); }
该 CustomImage* 指针转换为 Image* 一切都很好。因为 Image 不是多态的,指针调整为指向(POD) Image 里面的实例 CustomImage - 但很可是,这是 后 vptr,因为它总是在MS ABI中的多态类中首先出现(我假设)。
但是,当我们到达时
ImageFactory::DebugPrintDimensions((ImageFactory::CustomImage*)image);
编译器从一个类中看到一个C风格的转换,它对另一个类一无所知。它只需要获取它拥有的地址并假装它是一个 CustomImage* 代替。这个地址实际上指的是 Image 在自定义图像内;但是因为 Image 是空的,并且可能是空基类优化有效,它最终指向其中的第一个成员 CustomImage ,即 unique_ptr 。
现在, ImageFactory::DebugPrintDimensions() 假设它已被指向一个完全完整的指针 CustomImage ,以便地址等于的地址 vptr 。但它没有 - 它已被交给了地址 unique_ptr ,因为在调用它的时候,编译器不知道更好。所以现在它取消引用它认为是vptr(实际上是我们控制的数据)的内容,寻找虚函数的偏移并盲目地解释 - 现在我们遇到了麻烦。
ImageFactory::DebugPrintDimensions()
vptr
有几件事可以帮助缓解这个问题。首先,因为我们通过基类指针操作派生类, Image 应该有一个虚拟析构函数。这本来就是 Image 多态,并且很可能我们不会遇到问题(我们也不会泄漏内存)。
其次,因为我们是从基础派生到派生的, dynamic_cast 应该使用而不是C样式转换,这将涉及运行时检查和正确的指针调整。
最后,如果编译器在编译时拥有所有要处理的信息 main() ,它可能能够警告我们(或正确地执行演员,调整的多态性 CustomImage )。所以移动上面的类定义 main() 也是推荐的。