6

考虑以下代码:

using System;
using System.Runtime.InteropServices;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            const string test = "ABCDEF"; // Strings are immutable, right?
            char[] chars = new StringToChar{str=test}.chr;
            chars[0] = 'X';

            // On an x32 release or debug build or on an x64 debug build, 
            // the following prints "XBCDEF".
            // On an x64 release build, it prints "ABXDEF".
            // In both cases, we have changed the contents of 'test' without using
            // any 'unsafe' code...

            Console.WriteLine(test);
        }
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct StringToChar
    {
        [FieldOffset(0)]
        public string str;
        [FieldOffset(0)]
        public char[] chr;
    }
}

通过运行此代码,我们能够更改字符串的内容而不会发生异常。我们不必为此声明任何不安全的代码。这段代码显然非常狡猾!

我的问题很简单:你认为上面的代码应该抛出异常吗?

[EDIT1:请注意,其他人已经为我尝试过,有些人得到了不同的结果 - 考虑到我正在做的事情的讨厌,这并不太令人惊讶......;)]

[EDIT2:请注意,我在 Windows 7 Ultimate 64 位上使用 Visual Studio 2010]

[EDIT3:将测试字符串设为 const,只是为了让它更加狡猾!]

4

2 回答 2

6

The SSCLI20 source code for clr/src/vm/class.cpp, MethodTableBuilder::HandleExplicitLayout can provide some insight. It is unusually heavily commented, this comment describes the rules (edited for readability):

// go through each field and look for invalid layout
// (note that we are more permissive than what Ecma allows. We only disallow 
// the minimum set necessary to close security holes.)
//
// This is what we implement:
//
// 1. Verify that every OREF is on a valid alignment
// 2. Verify that OREFs only overlap with other OREFs.
// 3. If an OREF does overlap with another OREF, the class is marked unverifiable.
// 4. If an overlap of any kind occurs, the class will be marked NotTightlyPacked (affects ValueType.Equals()).

Rule 1 ensures that a reference assignment stays atomic. Rule 2 says why you can do what you did, any object type reference may overlap. Overlap with a value type value is not permitted, that screws up the garbage collector. Rule 3 states the consequence, it only makes the type non-verifiable.

It is otherwise not the only way to screw up a string without the unsafe keyword. Just pinvoke a function that stomps the string. It gets a pointer to the string content on the GC heap or loader heap (interned strings), no copy is made. That's unverifiable code as well and just as un-exploitable when running in a sandbox.

Driving the point home: the C# unsafe keyword is not at all related to what the CLR considers verifiable, and thus actually safe code. It takes care of the blatant cases, using pointers or custom value types (fixed). Whether that's a leak in the C# language spec is debatable. Pinvoke being the more obvious edge case. Pinvoking a operating system function is pretty doggone safe. Pinvoking some 3rd party C library is not.

But I have to agree with @fej, [FieldOffset] should have gotten the "are you sure" treatment. Too bad there's no syntax for that. Admittedly, I haven't figured out yet why this actually needed to affect the managed layout. It would make much more sense that this attribute would only apply to the marshaled layout. Weird, somebody holding a ace his sleeve in the early days, maybe.

于 2010-12-23T17:55:58.267 回答
4

我的投票是让 FieldOffset 变得不安全。

于 2010-12-23T14:31:30.043 回答