-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(unsafe) Object Header Verification may read to finalizer queue acting on ValueType or interface #39554
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
If you are hacking on runtime internals like this using unsafe code, you are in unsupported territory. We do not make any guarantees about the behavior in this case. Your program can hang, crash, silently corrupt data, format your hard drive, ... . All these behaviors would be by design. |
Thank you for your time! |
using System;
public class C {
public interface Test{}
public abstract class WhoCares : Test{}
public struct Important : Test{}
public static void M<T>(T t) where T :struct, Test
{
//ref T x = ref System.Runtime.CompilerServices.Unsafe.AsRef(t);
ref T y = ref t;
ref byte b = ref System.Runtime.CompilerServices.Unsafe.As<T, byte>(ref y);
ref T ptr = ref System.Runtime.CompilerServices.Unsafe.As<byte, T>(ref b);
ref byte mt2 = ref System.Runtime.CompilerServices.Unsafe.AddByteOffset(ref b, new IntPtr(-1));
ref T mt = ref System.Runtime.CompilerServices.Unsafe.AddByteOffset<T>(ref ptr, new IntPtr(-1));
//Should just be the interface bit
IntPtr fakeHeader = (IntPtr)((1 == ((uint)1) << 4) ? uint.MaxValue : ulong.MaxValue);
//System.Runtime.InteropServices.Marshal.WriteIntPtr((IntPtr)mt, fakeHeader);
}
public static void Main()
{
M<Important>(default);
}
} Consider also ref byte b = ref System.Runtime.CompilerServices.Unsafe.AddByteOffset(ref System.Runtime.CompilerServices.Unsafe.As<T, byte>(ref y), new IntPtr((-1)); |
@juliusfriedman are you asking a question? |
No I am just saying that: using System;
public class C {
public interface Test{}
public abstract class WhoCares : Test{}
public struct Important : Test{}
public static void M<T>(T t) where T :struct, Test
{
//ref T x = ref System.Runtime.CompilerServices.Unsafe.AsRef(t);
ref T y = ref t;
ref byte b = ref System.Runtime.CompilerServices.Unsafe.AddByteOffset(ref System.Runtime.CompilerServices.Unsafe.As<T, byte>(ref y), new IntPtr((-1)));
b = byte.MaxValue;
for(int i = 1; i < IntPtr.Size; ++i)
{
b = ref System.Runtime.CompilerServices.Unsafe.AddByteOffset(ref System.Runtime.CompilerServices.Unsafe.As<T, byte>(ref y), new IntPtr((-1)));
b = byte.MaxValue;
}
}
public static void Main()
{
M<Important>(default(Important));
}
} compiles and results in:
And this is not unsafe code and I don't have a class in use. I could take that step further but I will stop there as the issue was closed but I just wanted to assert I can do it without unsafe either thanks to Unsafe. (really ref) |
Right, |
Consider also: using System;
public class C {
public interface Test{}
public abstract class WhoCares : Test{}
public struct Important : Test{}
public static void M<T>(T t) where T :struct, Test
{
ref T y = ref t;
ref byte b = ref System.Runtime.CompilerServices.Unsafe.AddByteOffset(ref System.Runtime.CompilerServices.Unsafe.As<T, byte>(ref y), new IntPtr((-2)));
b = 0;
for(int i = 1; i < IntPtr.Size; ++i)
{
b = ref System.Runtime.CompilerServices.Unsafe.AddByteOffset(ref System.Runtime.CompilerServices.Unsafe.As<T, byte>(ref y), new IntPtr((1)));
b = byte.MaxValue;
}
}
public static void Main()
{
M<Important>(default(Important));
}
} Runs fine indicating there is only memory checking on partial components of the object header... Since the HashCode is known I think we can do better at the expense of some additional memory but not much considering we already track the refs. |
There isn't "memory checking" anywhere causing your error. You just wrecked the header to the point where a component tried to dereference what it expected to be a valid pointer wasn't so it AV'd. The object header is pretty well packed with data, i can't remember the file the flags for it are in but I've looked at it before a couple of times |
Access violations are always interpreted as NRE because in safe/UB-free code accessing a null pointer is the only way to trigger an AV. |
If I have to go to the typeof(T) it's not much different...
Now this object gets into the finalizer queue!
Is this intended behavior and shouldn't there be checks for these kinds of hacks?
cc @jkotas @danmosemsft @GrabYourPitchforks
The text was updated successfully, but these errors were encountered: