(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
It seems that James Gosling has an opinion on .NET's support for C++. He claims that this:
"has left open a security hole large enough to drive many, many large trucks through"
I thought it would be fun to subject his conclusions to some kind of objective test. Apparently either Gosling didn't bother with this, or he didn't quite understand what he was doing.
(My determination to debunk Gosling's claim with solid evidence means that I'm a bit late on this story - others have already responded. However, I thought it was still worthwhile to show some worked examples, so I'm going ahead and posting anyway, even though this is really just filling in the details behind what others have already pointed out.)
The crux of Gosling's complaint appear to be this:
"C++ allowed you to do arbitrary casting, arbitrary adding of images and pointers, and converting them back and forth between pointers in a very, very unstructured way"
Later, he talks about how Java won't let you do these things:
"So if somebody gives you an object and says 'This is an image', then it is an image. It's not like a pointer to a stream, where it just casts an image"
I'm not sure if he was quoted correctly there, because the tail end of that is not especially lucid. But it looks like he's talking about this sort of code:
using namespace System::Drawing; using namespace System::IO; void main() { Stream* pStream = new MemoryStream(); Image* pImg = reinterpret_cast<Image*>(pStream); }
And just in case the problem isn't obvious, here's what the managed C++ compiler has to say about the line highlighted in bold:
BadCast.cpp(26) : warning C4669: 'reinterpret_cast' : unsafe conversion: 'System::IO::Stream' is a managed type object
Note that this is a warning, so although the compiler has told us that there's a potential problem here, that hasn't stopped it from going ahead and compiling the code. Presumably that's what Gosling is unhappy about - that the compiler is prepared to spit out code that quite clearly violates type safety rules.
However, see what happens if you actually try to run the code. If you're running Internet Explorer, and you have v1.1 of the .NET framework installed (i.e. the latest version), you'll be able to try running it by clicking here. (Note that if you see Internet Explorer's usual dialog offering to save or open the program, that means that you don't have the .NET Framework, or at least not the right version, or that it's not installed correctly. In this case you should just click Cancel.) Here's what I see when I try to run that program from within IE on my machine:
I have several versions of the Visual Studio .NET IDE installed on my system, so when a program hits an error, I get offered the opportunity to look at it in a debugger. If you don't have VS.NET installed, you might see a different dialog. The important thing is that you will see an error dialog some kind, because a SecurityException just got thrown.
Why did a SecurityException get thrown? Because an attempt was made to load a component that failed verification. .NET has a bunch of IL verification rules just like Java has bytecode verification rules. Gosling has this to say of Java's dependency on verified code integrity:
"If you look at the security model in Java and the reliability model, and a lot of things in the exception handling, they depend really critically on the fact that there is some integrity to the properties of objects"
The same is true for .NET's security and reliability. So when you try to load the compiled code above, the .NET framework attempts to verify
it, and discovers that the code is unverifiable. (Of course it is - it attempts to cast a Stream to an Image, and those are two different things.) As
Gosling points out, running unverifable code would be a massive security hole. This is why .NET refuses to run the code. And
if you want to see specifically what it was complaining about, you can run the peverify
command line tool. This tests
.NET code for verifiability. Here is the error it produces for my BadCast.exe
code:
[IL]: Error: [c:\try\clr\mcpp\badcast\debug\badcast.exe : <Module>::main] [offset 0x0000000B] [opcode stloc.1] [found
objref 'System.IO.Stream'] [expected objref 'System.Drawing.Image'] Unexpected type on the stack.
1 Errors Verifying BadCast.exe
This is pretty straightforward: although C++ was prepared to let us attempt that bad cast from Stream to Image, the IL verifier rejects it. So the code won't be allowed to run. (The exception is thrown at the point at which the attempt to load the offending code is made. So because this code fails verification, it never even loads.)
It's almost as though Gosling wasn't aware of .NET's code verification. That's a little odd since (a) it's been there since day 1, (b) it's pretty similar to how Java does it and (c) it's dead easy to demonstrate its existence - it didn't take long to put this example together.
This seems like a pretty basic mistake to have made until you realise one thing. The .NET security system supports a permission
known as SkipVerification
. It would possible for an administrator to configure a system to grant this permission to
that code, at which point it would be able to run despite not passing the verification rules.
So perhaps this is what Gosling means. Indeed, it's possible to spin it so it sounds pretty bad - if you were looking to make one of those authoritative-sounding snappy dismissive statements so beloved of certain Microsoft detractors, you might choose to say something like: with .NET it's possible to run unsafe code, ergo .NET is unsafe. But I don't agree with that argument. (And not just because of the pretentious use of 'ergo' - there is substance to my objection.)
Solaris allows you to run unsafe code, for example. (Unless I missed the press release where Sun announced that they were dropping support for running C++ applications on Solaris.) So does Java, come to that, as I'll demonstrate shortly. The simple fact is that whether your systems run on .NET, Java, or a mixture of both, the machine administrator remains in control of what unsafe code gets to run. Unsafe code is a fact of life. (For example, you can't write a JVM without writing unsafe code.) The important thing is that a suitable authority decides what runs (e.g. the owner of the machine), not some miscreant. So if Gosling believes .NET has a security hole you could drive many, many large trucks through, he either thinks the same thing about Java and Solaris, or he hasn't thought about this terribly hard. (Or, much worse, he's being wilfully disingenuous. But let's hope not - that would be very disappointing.)
It would be astonishing if Gosling really didn't know that this same issue exists in Java, but that's how it looks from the quotes I've seen. So I'd better substantiate my claim.
Think for a moment about what unsafe code is. It's an interop technology - it is a way of bridging the gap between the managed world and the unmanaged world. .NET supports several forms of interop for convenience. Managed C++ is just one of these. (Strictly speaking, managed C++ isn't an interop mechanism per se - it's just a way of using an underlying runtime interop mechanism. However, C++ is the only language that uses this particular interop mechanism today.)
Java's interop mechanism is JNI. And it of course is just as unsafe as .NET's interop mechanisms. How could it be otherwise? The whole point of an interop mechanism like JNI is to bridge the gap between a managed execution environment such as the JVM or the CLR, and an unmanaged C/C++ style environment.
Consider this simple Java class:
import java.awt.Image; public class UseImage { public static void UseIt(Image i) { System.out.println("Class: " + i.getClass().getName()); System.out.println("Is Image: " + (i instanceof Image)); } }
If the Java type system were as inviolable as Gosling suggests, we'd expect this code always to print out the name of some Image-derived class,
followed by the text "Is Image: true
". (Or the routine will throw a NullPointerException
.) But look what I just got it to print out:
Class: java.io.ByteArrayInputStream Is Image: false
That's more or less exactly what Gosling said wouldn't happen under Java. And yet here it is, happening under Java with Sun's JVM.
I made this happen writing the Java equivalent of the .NET C++ example above. Of course Java only supports the one style of interop: JNI, so that's what I had to use. It's rather more verbose than the .NET equivalent - apparently Java's designers either think it's OK to make life hard for Java developers, or they felt the need to discourage interop by making it hard work:
#include <jni.h> void main() { // Fire up a JVM // (As the invocation sample in the JNI docs say: // "For clarity, we omit error checking." // i.e. this is super verbose even without doing // it properly.) JavaVM *jvm; JNIEnv *env; JDK1_1InitArgs vm_args; vm_args.version = JNI_VERSION_1_4; JNI_GetDefaultJavaVMInitArgs (&vm_args); JNI_CreateJavaVM(&jvm, (void**) &env, &vm_args); // Create a ByteArrayInputStream instance jclass streamClass = env->FindClass("java/io/ByteArrayInputStream"); jobject streamObject = env->AllocObject(streamClass); // Get hold of the UseImage.UseIt method. jclass testClass = env->FindClass("UseImage"); jmethodID imageMethod = env->GetStaticMethodID(testClass, "UseIt", "(Ljava/awt/Image;)V"); // Pass in the ByteArrayInputStream instance to UseIt, which // is expecting to be passed an Image. env->CallStaticVoidMethod(testClass, imageMethod, streamObject); // Shut down the JVM jvm->DestroyJavaVM(); }
The line towards the end just before we shut down the JVM (highlighted in bold) is the interesting one. The rest is
just what Java makes you jump through to achieve interop. Although most people would agree the .NET version is a whole lot
simpler, that's not the point. The important thing is that this JNI example ultimately has the same effect as the earlier .NET C++
code - it causes a reference to Stream
to be used by code that expects an Image
. And
remember that JNI is a standard Java feature.
So Java is in fact quite capable of doing the very thing Gosling complains about in .NET. It makes it more effort, but it doesn't prevent it. Nor could it - this is the inevitable price of supporting interop.
There is one issue with .NET which is closely related to all of this that is, in my opinion, a problem. It's not a fundamental flaw in the system, it's just a bad piece of default configuration. And that's the default .NET security policy for local code.
Remember that verification skipping is a permission - it's something that the machine admin can choose to grant to pieces of code. So it is treated in much the same way as any secured operation (e.g. file IO, network IO, the ability to display bits of user interface). The good news is that out of the box, the only code that .NET ever grants verification skipping privileges to is code that has been granted 'full trust'. (Fully trusted code is code which the security system has been told not to restrict in any way - such code is effectively as powerful as any traditional C++ application, and can do anything the user running the code can do.) In other words, by default, .NET won't let your code skip verification unless it is code that it has been instructed to trust it completely and utterly.
This is why you get a security exception if you try to run the .NET C++ example shown earlier in your web browser. .NET's default configuration, unsurprisingly, doesn't grant full trust to code run from the internet!
The bad news becomes pretty clear as soon as you try to save that example to your local hard disk and run it from there. Obviously you'll want to make sure you use a very low privilege account, or better still, a virtual PC of some kind in disk differencing mode so you can revert the machine to whatever state it was in before you ran the program. Not that the program does anything evil - it's just that I don't have an SSL cert on this server, so you've no way of knowing that what you downloaded is necessarily what I uploaded.
But hold on just a second - why am I having to give you all these health warnings, when previously I suggested you run the program right where it is in IE? It's because the default configuration for the .NET Framework is to grant full trust to code that is on the local hard disk.
Woah!
This has always struck me as a bad default. If .NET's default security policy had been devised after the big security push at Microsoft, I don't think it would have come out this way - this is anything but secure by default.
It's not the end of the world - it's just configuration, so you can change it. It's straightforward for the administrator to lock things down, and only grant full trust to code that needs it. Although if your end users are downloading executables and running them, you have bigger problems - the hacker can just write a good old fashioned C++ program in that scenario. Indeed, this is how most of the more popular email viruses have spread in recent times - they use social engineering attacks designed to get users to run email attachments. (In some cases, going as far as getting them to unpack a password-protected ZIP file and then running the contents!) Locking down .NET's security configuration in this scenario would be like installing high security locks on all your windows when your lodger is letting thieves in through the front door.
The only solution to that is to lock down the system so that users don't get to run arbitrary executables. Or if you're happy to let them trash their own data, you can simply make sure they don't have a privileged account. Both approaches are viable, as long as the administrator is not the same person as the end user; for home users this is a much harder problem. But I digress - this really has little to do with .NET, Java, or verifiable type safety.
(Note, although details of what Longhorn will be are often murky and always subject to change, there do seem to be indications that in some future version of .NET, local code will not get full trust by default.)
By the way, my JNI code was also fully trusted by the JVM by default. It didn't exactly have a choice, since it was my code that loded the JVM in the first place...
Verification and type safety are fundamental to the security and robustness of both .NET and Java. Interop between a managed execution environment and old-fashioned C++ has the potential to subvert type safety guarantees because the code on the unmanaged side is not (and cannot be) subject to verification. Java and .NET are no different in this respect. This does not necessarily constitute a security hole in Java or .NET because both systems ensure that the administrator remains in full control of what unmanaged code gets to run. And it is easy to demonstrate that neither Java nor .NET will allow code of unknown or untrusted origin to use of these unsafe interop mechanisms.