Why does this code work without the unsafe keyword?
Asked Answered
B

3

19

In an answer to his own controversial question, Mash has illustrated that you don't need the "unsafe" keyword to read and write directly to the bytes of any .NET object instance. You can declare the following types:

   [StructLayout(LayoutKind.Explicit)]
   struct MemoryAccess
   {

      [FieldOffset(0)]
      public object Object;

      [FieldOffset(0)]
      public TopBytes Bytes;
   }

   class TopBytes
   {
      public byte b0;
      public byte b1;
      public byte b2;
      public byte b3;
      public byte b4;
      public byte b5;
      public byte b6;
      public byte b7;
      public byte b8;
      public byte b9;
      public byte b10;
      public byte b11;
      public byte b12;
      public byte b13;
      public byte b14;
      public byte b15;
   }

And then you can do things like change an "immutable" string. The following code prints "bar" on my machine:

 string foo = "foo";
 MemoryAccess mem = new MemoryAccess();
 mem.Object = foo;
 mem.Bytes.b8 = (byte)'b';
 mem.Bytes.b10 = (byte)'a';
 mem.Bytes.b12 = (byte)'r';
 Console.WriteLine(foo);

You can also trigger an AccessViolationException by corrupting object references with the same technique.

Question: I thought that (in pure managed C# code) the unsafe keyword was necessary to do things like this. Why is it not necessary here? Does this mean that pure managed "safe" code is not really safe at all?

Britisher answered 27/4, 2009 at 8:59 Comment(3)
Thanks for changing a way of asking same question. Previous thread was overflamed.Marder
@Mash: No problem. Hopefully this will direct some more positive attention to your original question.Britisher
@wcoenen: It's not important, really, even if I was thinking about it - my question is community content and I'm not earning anything from it. So the only important thing is positive discussion. And seems like your question looks better :)Marder
B
12

OK, that is nasty... the dangers of using a union. That may work, but isn't a very good idea - I guess I'd compare it to reflection (where you can do most things). I'd be interested to see if this works in a constrained access environment - if so, it may represent a bigger problem...


I've just tested it without the "Full Trust" flag, and the runtime rejects it:

Could not load type 'MemoryAccess' from assembly 'ConsoleApplication4, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because objects overlapped at offset 0 and the assembly must be verifiable.

And to have this flag, you already need high trust - so you can already do more nasty things. Strings are a slightly different case, because they aren't normal .NET objects - but there are other examples of ways to mutate them - the "union" approach is an interesting one, though. For another hacky way (with enough trust):

string orig = "abc   ", copy = orig;
typeof(string).GetMethod("AppendInPlace",
    BindingFlags.NonPublic | BindingFlags.Instance,
    null, new Type[] { typeof(string), typeof(int) }, null)
    .Invoke(orig, new object[] { "def", 3 });
Console.WriteLine(copy); // note we didn't touch "copy", so we have
                         // mutated the same reference
Bilateral answered 27/4, 2009 at 9:4 Comment(6)
@Marc : could you elaborate on the other methods of mutating strings you have in mind, please ?Haifa
This code works ok without unsafe flag. But it requires full trust at first and using CAS for setting permissions not helping (by denying things looking like related). Could you give an example which changes string content and not using unsafe blocks?Marder
Edited re whether it is the unsafe flag or Full Trust - I changed various things, so I may have lost track... Re "changes string content" - not off the top of my head. Very grotty ;-pBilateral
Oh, you could probably use reflection (since you have full trust) to invoke AppendInPlace or similar; example added.Bilateral
AppendInPlace good example (it requires private methods access), but from source it's clear that AppendInPlace is just an unsafe array expansion and doesn't allows you to change unmanaged parts of managed objects or break the boundaries of object. Anyway thanks for example.Marder
The reason it words in Full Trust is because the verifier is disabled in Full TrustScrofulous
M
5

Whoops, I've muddled unsafe with fixed. Here's a corrected version:

The reason that the sample code does not require tagging with the unsafe keyword is that it does not contain pointers (see below quote for why this is regarded as unsafe). You are quite correct: "safe" might better be termed "run-time friendly". For more information on this topic I refer you to Don Box and Chris Sells Essential .NET

To quote MSDN,

In the common language runtime (CLR), unsafe code is referred to as unverifiable code. Unsafe code in C# is not necessarily dangerous; it is just code whose safety cannot be verified by the CLR. The CLR will therefore only execute unsafe code if it is in a fully trusted assembly. If you use unsafe code, it is your responsibility to ensure that your code does not introduce security risks or pointer errors.

The difference between fixed and unsafe is that fixed stops the CLR from moving things around in memory, so that things outside the run-time can safely access them, whereas unsafe is about exactly the opposite problem: while the CLR can guarantee correct resolution for a dotnet reference, it cannot do so for a pointer. You may recall various Microsofties going on about how a reference is not a pointer, and this is why they make such a fuss about a subtle distinction.

Millerite answered 27/4, 2009 at 9:40 Comment(5)
This code indeed allows you to go out of object boundaries and change any part of process-accessible memory. You can reconstruct sync blocks and type descriptor of any objects, go inside GC data. What actually unsafe gives more than?Marder
That's what we are talking about. This code doesn't have signs that CLR can't verify it's safety (it's actually can't). Every object in .NET internally using unsafe code, but that doesn't leads to ability to construct code declared as safe which can access and change data as unsafe one.Marder
There is indeed something fishy here. The code is "safe", yet allows us to read the memory adjacent to a certain object. Therefore, you can read data which might be moved by the run-time at any point unless you use the "fixed" keyword. That's exactly the same problem that pointers have.Britisher
Interesting thing is that union of reference types and value types prohibited by compiler, but not 2 reference types. Runtime has checks about that somewhere when you aren't in full trust mode, but still you're getting unsafe access from clearly safe code. And that's a different kind of stuff - trust mode and "safe" code. Unsafe code as I've already said, always in use from safe code, but it's stated that in case your library not using unsafe code (except BCL internals) - it is safe by nature. And this sample show that's not a true.Marder
except that the verifier is disabled when in Full Trust, which means that the CLR doesn't really check anything. The CLR is relying on the compiler to create verifiable/safe codeScrofulous
R
0

You are still opting out of the 'managed' bit. There is the underlying assumption that if you can do that then you know what you're doing.

Rale answered 27/4, 2009 at 9:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.