Reputation: 1039
I have just found something that blew my mind. In retrospect, I feel naive not seeing this earlier, but at the same time, it still comes as a shock to me.
We call from C# into native (C++) code. Currently, we use both P/Invoke and/or C++/CLI for our tests. In both technologies, we found that it is possible to pass a pointer to managed memory to the native code, and in the native code, perform an out-of-bounds read or write access!
The read or write access does not always terminate the application, and sometimes leads to no error whatsoever. Therefore, I assume, that there are no safeguards in place against this?
While I can understand that any kind of safeguarding of memory may come at a (potentially big) runtime penalty, it is still blowing my mind that we seem to circumvent the memory safeguards of C# in such an easy way. Our C# code, and the native bindings, are not marked unsafe
in any way - yet they certainly are unsafe, are they not?
I'm not trying to criticize C# here. Possibly, this is the only way the native integration can work, or at least the only way with good performance(?) But I'm still slightly shocked that this kind of "problem" was not discussed in any of the pages about P/Invoke and C++/CLI that I have been reading. And I wonder, why not all native code is implicitly unsafe
, at least when passing pointers to it?
Last, not least, it would be great if there are any ways to safeguard against this. I'm admittedly super naive when it comes to memory write protection. I know that the OS safeguards against writes to memory that is not owned by the process. However, here we are in the same process, or are we not? Are there ways (specifically in Windows) to forbid the native code to write to any managed memory?
Upvotes: -1
Views: 123
Reputation: 134045
There are some warnings about the dangers of P/Invoke. For example, there's this paragraph at the end of https://learn.microsoft.com/en-us/dotnet/standard/managed-code
Of course, the CLR allows passing the boundaries between managed and unmanaged world, and there's a lot of code that does that, even in the .NET class libraries. This is called interoperability, or interop for short. These provisions would allow you to, for example, wrap up an unmanaged library and call into it. However, it is important to note that once you do this, when the code passes the boundaries of the runtime, the actual management of the execution is again in the hand of unmanaged code, and thus falls under the same restrictions.
(The italics are mine)
Also, the Interop Best Practices page is chock full of hints, cautions, and warnings that hint pretty strongly about the dangers of calling unmanaged code.
Any way of insulating the runtime from corruption by unmanaged code that you call would be prohibitively expensive and not 100% reliable. And as somebody mentioned in comments, make it harder to track down memory corruption and marshaling problems.
Upvotes: 1
Reputation: 36586
Yes, that is correct. But all native code is implicitly unsafe, and because it is implicitly unsafe, there is no need for a special keyword.
If you call into native code, or any code really, you trust that it does what it is supposed to. Keep in mind that managed memory is not a security feature, it is an assistance to help avoid bugs. And the .net memory system is obviously not available in native code. There are various forms of smart memory management in C++, but P/Invoke prevents most of them to be used in the interface. A possibly safer alternative might be C++/CLI, since that lets you access both managed types and C++ types.
If this is a problem for you, the solution is to move the problematic code to another process, and use some form of IPC to communicate with the processes. This adds some complexity, but that is a necessary tradeoff.
Upvotes: 4