|
|||
|
|
Comparing Java and .NET security: Lessons learned and missed
Nathanael Paul
, a,
and David Evans
, a,
aUniversity of Virginia, Department of Computer
Science, VA, USA
Received 24 February 2005; revised 27 December
2005; accepted 6 February 2006. Available online 24 May 2006.
Many systems execute untrusted programs in virtual machines (VMs) to mediate their access to system resources. Sun introduced the Java VM in 1995, primarily intended as a lightweight platform for executing untrusted code inside web pages. More recently, Microsoft developed the .NET platform with similar goals. Both platforms share many design and implementation properties, but there are key differences between Java and .NET that have an impact on their security. This paper examines how .NET's design avoids vulnerabilities and limitations discovered in Java and discusses lessons learned (and missed) from experience with Java security.
Keywords: Virtual machine security; Java security; .NET
security; Security design principles; Bytecode verifier; Malicious code; Code
safety
Java and .NET are both platforms for executing untrusted programs with security restrictions. Although they share similar goals and their designs are similar in most respects, there appear to be significant differences in the likelihood of security vulnerabilities in the two platforms.
Fig. 1 shows the number of major security vulnerabilities reported for each platform. As of December 2005, the Common Vulnerabilities and Exposures (CVE) database contains 150 entries concerning Java vulnerabilities (Mitre Corporation, Common Vulnerabilities), 38 of which we classify as major Java platform security vulnerabilities (we do not include application-specific bugs unrelated to the VM itself). The remaining vulnerabilities included in Fig. 1 but not in the CVE are from Sun (Sun Microsystems, 2002) (9 vulnerabilities) and McGraw and Felten (1999) (5 vulnerabilities). The contrast with the .NET platform, in which no major security vulnerabilities have yet been found, appears striking. This paper considers whether or not the difference in the number of security vulnerabilities found in the two platforms stems from fundamental differences in their designs.
Fig. 1. Major security vulnerabilities reported. The value plotted is the cumulative number of major security vulnerabilities reported in each platform since the first official release (Java 1.0 in January 1996 (Sun Microsystems, Java); .NET 1.0 in January 2002 (Microsoft Corporation, Technology Overview)).
Table 1 summarizes Java security vulnerabilities reported in the past 10 years. Hopwood (1996), Princeton's Secure Internet Programming team (Dean et al., 1996, Wallach et al., 1997 and Wallach and Felten, 1998) and McGraw and Felten (1999) identified several vulnerabilities in early Java implementations. The rest are those documented in Sun’s chronology (Sun Microsystems, 2002; Sun Microsystems Sun Alert) and the CVE database (Mitre Corporation, Common Vulnerabilities).
Java security vulnerabilities
Category Count Instances API bugs 12 CVE-2000-0676, CVE-2000-0711, CVE-2000-0563, CVE-2002-0865, CVE-2002-0866, CVE-2002-1260, CVE-2002-1293, CVE-2002-1290, CVE-2002-1288, CVE-2002-0979, CVE-2005-3905, CVE-2005-3906 Verification 13 Sun Chronology (4), McGraw and Felten (2), CVE-1999-0766, CVE-1999-0141, CVE-1999-0440, CVE-2000-0327, CVE-2002-0076, CVE-2003-0111, CVE-2004-2627 Class loading 8 Sun Chronology (5), CVE-2002-1287, CVE-2003-0896,a CVE-2004-0723 Other or unknown 3 CVE-2001-1008, CVE-2002-1325, CVE-2005-3907 Missing policy checks 3 CVE-1999-0142, CVE-1999-1262, McGraw and Felten (1) Configuration 5 CVE-2000-0162, CVE-2002-0058, CVE-2005-0471, McGraw and Felten (2) DoS attacks (crash) 4 CVE-2002-0867, CVE-2002-1289, CVE-2003-0525, CVE-2004-0651 DoS attacks (consumption) 4 CVE-2002-1292, CVE-2004-2540, CVE-2005-3583, CVE-2004-1503 Vulnerabilities reported in Java platform in CVE database (Mitre Corporation, Common Vulnerabilities), Sun's web site (Sun Microsystems, 2002 and Sun Microsystems, Sun Alert), and McGraw and Felten (1999).
Vulnerabilities reported in more than one source were counted once.
a Revealed under keyword search for JVM vulnerabilities instead of Java vulnerabilities.
By contrast, no security vulnerabilities in the .NET virtual machine platform have been reported to date. The most widely publicized security issue in .NET was W32.Donut, a virus that took control of the executable before the .NET runtime had control (Szor). Since the vulnerability occurs before the .NET runtime takes control, we consider this a problem with the way the operating system transfers control to .NET and not with the .NET platform. Eight other security issues that have been identified in the .NET are listed in Microsoft's Knowledge Base (Farkas) and the CVE database (Mitre Corporation, Common Vulnerabilities), but none of them are platform security vulnerabilities by the standard we use in this paper. Appendix Aexplains these issues and why we do not count them.
There are many possible explanations for the .NET platform's apparent lack of security vulnerabilities. One possibility is that .NET is a less desirable platform for attackers to compromise than Java so it has not received the scrutiny necessary to reveal vulnerabilities. This is unlikely, however, since the .NET framework is now provided as a Windows update. Since Windows has over 90% of the desktop market with a large number of machines using .NET, the .NET platform presents an attractive target.
Another possibility is that more vulnerabilities have been found in Java implementations because there are several different Java VM implementations whereas .NET's number is from Microsoft's sole implementation. From the available information, the one implementation that did have many of its own unique vulnerabilities was Microsoft's Java implementation, and this is largely due, in part, to 10 vulnerabilities reported in November 2002 by Pynnonen. As early as March 1996, both Microsoft and Netscape had licensed Java, two months after the 1.0 release date (Sun Microsystems, Java). As Java licensees, both Microsoft and Netscape implementations are based on the Sun implementation (Sun Microsystems, 2002) so much of the code and design are shared across the three implementations. The first 9 reported Java vulnerabilities did affect all three implementations, including the first 5 of 13 total verifier vulnerabilities. Although popular open source .NET platform implementations exist, such as Mono, and dotGNU, neither has fully implemented code access security to enable the execution of partially trusted code.
Another possibility is that .NET just avoided the specific security vulnerabilities that were already known because of previous experience with Java. This may be true in a few cases, but in general it is not the case. There are enough differences between the platforms that most security vulnerabilities would not have a direct analog. Further, vulnerabilities continue to be found in new versions of Java even after .NET's release.
In this paper we explore the more optimistic hypothesis that .NET's design is fundamentally more secure than Java's, and in particular, that it benefits from following general security principles that have been learned and reinforced from experience with Java. The general lessons to be learned from experience with Java are not new. Most of them go back at least to Saltzer and Schroeder's (1973) classic paper, and none should be surprising to security analysts. In particular: economy of mechanism, least privilege, and fail-safe defaults are design principles that enhance security, but can often conflict with other goals including usability and complexity. Other lists of security principles, including Viega and McGraw's (2001), include similar properties such as defense in depth and securing the weakest link. Viega and McGraw emphasize that security principles should be followed within an application's context and following these universal security principles allows a programmer to weigh different design trade-offs while preparing for unknown attacks that may not fit past attack patterns (Viega and McGraw, 2001). The concrete experience with Java shows how failure to apply these well known principles has lead to vulnerabilities in a particular, security-critical system.
Previous work, including Pilipchuk's article, has compared security mechanisms and features in Java and .NET from an operational perspective. In this paper, we consider how they differ from the perspective of what has and has not been learned from experience with Java. The primary contributions of this paper are as follows: (1) an illustration of how the history of Java security vulnerabilities reveals failures to follow established security principles; (2) an identification of how .NET's security mechanisms have addressed the vulnerabilities and limitations of Java; and (3) a discussion on how differences in the design of .NET and Java are likely to impact their security properties.
Next, we provide an overview of both platforms. Section 3 describes low-level code safety highlighting the importance of simplicity. Section 4 examines policy definition and permissions emphasizing the principles of fail-safe defaults, least privilege, and complete mediation. Associating policies with code according to the code attributes is discussed in Section 5. Next, Section 6 describes how the JVM and CLR enforce policies on executions evaluating them in their application of the principles of least privilege, fail-safe defaults, and complete mediation. Section 7 discusses the shortcomings of both platforms with respect to psychological acceptability.
Both Java and .NET use a virtual machine to enforce policies on executing programs as depicted in Fig. 2. The term Java is used to refer to both a high-level programming language and a platform. We use Java to refer to the platform consisting of everything used to execute the Java class containing Java virtual machine language code (JVML, also known as “Java bytecodes”) in the left part of Fig. 1 except the operating system and the protected resource. A Java archive (JAR) file encapsulates Java classes and may also contain other resources such as a digital signature or pictures. Java was designed primarily to provide a trusted environment for executing small programs embedded in web pages known as applets.
Fig. 2. Architecture overview.
The .NET platform includes the .NET part of the figure involved in executing an assembly except for the operating system and the protected resource. A .NET assembly, analogous to Java's JAR file, is an executable or dynamically linked library containing Microsoft intermediate language instructions (MSIL), some metadata about the assembly, and some optional resources. .NET differentiates between managed (safe) and unmanaged (unsafe) codes. Since a security policy cannot be enforced on unmanaged code, we only consider managed code.
Both Java and .NET have large trusted computing bases (TCBs) allowing many possible points of failure. The TCB includes everything in Fig. 1 except for the external untrusted program (the Java class or .NET assembly). In Java, a flaw in the bytecode verifier, class loader, JVM or underlying operating system can be exploited to violate security properties. With .NET, a flaw in the policy manager, class loader, JIT verifier, CLR, or underlying operating system can be exploited to violate security properties. The size of the TCB makes it infeasible to make formal claims about the overall security of either platform; instead, we can analyze individual components using the assumption that other components (in particular, the underlying operating system) behave correctly.
The JVML or MSIL code may be generated by a compiler from source code written in a high-level program such as Java or C#, but these files can be created in other ways. Although high-level programming languages may provide certain security properties, there is no way to ensure that the delivered JVML or MSIL code was generated from source code in a particular language with a trusted compiler. Hence, the only security provided against untrusted code is what the platform provides. This paper does not consider the relative merits of the Java and C# programming languages but only compares the security properties of the two execution platforms.
Since the Java platform was introduced in 1995, Java's security model has evolved to incorporate additional security mechanisms including code signing and increasingly flexible policies. When specific implementation issues are considered, we address the current standard implementations of each platform: the Java 2 Software Development Kit 1.4.2 and the .NET Framework 1.1.
Both Java and .NET use a combination of static analysis and dynamic checking to enforce policies on executing programs. The bytecode verifier in Java and the just-in-time (JIT) verifier in .NET statically verify some low-level code properties necessary (but not sufficient) for type safety, memory safety and control-flow safety before allowing programs to execute. Other properties must be checked dynamically to ensure low-level code safety. Section 3 describes the principle of simplicity in low-level code safety properties. Six of the 30 Java platform security vulnerabilities in the Common Vulnerabilities and Exposures database (Mitre Corporation, Common Vulnerabilities), and 6 of the earlier vulnerabilities (McGraw and Felten, 1999 and Sun Microsystems, 2002) are directly attributed to flaws in implementations of the Java bytecode verifier. Programs that pass the verifier are executed in the Java virtual machine (JVM) or .NET Common Language Runtime (CLR). Both virtual machines use a reference monitor to mediate access to protected system resources.
Low-level code safety comprises the properties of code that make it type, memory, and control-flow safe. Without these properties, applications could circumvent nearly all high-level security mechanisms (Yellin, 1995). The primary lesson learnt from Java's experience with low-level code safety goes back to one of the earliest security principles: keep things simple.
Type safety ensures that objects of a given type will be used in a way that is appropriate for that type. In particular, type safety prevents a non-pointer from being dereferenced to access memory. Without type safety, a program could construct an integer value that corresponds to a target address, and then use it as a pointer to reference an arbitrary location in memory. Memory safety ensures that a program cannot access memory outside of properly allocated objects. Buffer overflow attacks violate memory safety by overwriting other data by writing beyond the allocated storage (AlephOne, 1996). Control safety ensures that all jumps are to valid addresses. Without control safety, a program could jump directly to system code fragments or injected code, thereby bypassing security checks.
Java and .NET achieve low-level code safety through static verification and runtime checks. In typical Java implementations, static verification is done by the Java bytecode verifier at load time. An entire class is verified before it is executed in the virtual machine. In .NET, parts of the verification are done as part of the JIT compilation. All code must pass the verifier, however, before it is permitted to execute.
The first step in the verification process is the validation of the file format of the code (ECMA International, 2002 and Lindholm and Yellin, 1999). The file is checked according to the Java class file or .NET PE/COFF file specifications (Lindholm and Yellin, 1999 and Microsoft Corporation, Microsoft Portable Executable). Following the verification of the file format, the verifier also checks some static rules to ensure that the objects, methods, and classes are well formed.
Next, the verifier simulates each instruction along each potential execution path to check for type and other violations. Since JVML and MSIL are stack-based languages, executions are simulated by modeling the state of the stack while tracking information about each instruction to help ensure low-level code safety. Verification fails if a type violation could occur, or a stack operation could cause underflow or overflow. In addition, control-flow safety is ensured by checking that all branch instructions target valid locations.
The general problem of verifying type safety is undecidable (Pierce, 1992), so certain assumptions must be made to make verification tractable. Both verifiers are conservative: if a program passes verification it is guaranteed to satisfy prescribed safety properties, however, programs exist that are type safe but fail verification. A more sophisticated verifier could accept more of the safe programs (still rejecting all unsafe programs), but increasing the complexity of the verifier is likely to introduce additional vulnerabilities.
Code passing the verifier is permitted to run in the virtual machine, but additional runtime checks are needed that could not be checked statically. Runtime checks are required to ensure that array stores and fetches are within the allocated bounds, elements stored into array have the correct type (because of covariant typing of arrays in both JVML and MSIL this cannot be checked statically (Cook, 1989)), and down cast objects are of the correct type.
A bug in the Java bytecode verifier or Microsoft's JIT verifier can be exploited by a hostile program to circumvent all security measures, so complexity in the verifier should be avoided whenever possible.
The JVML and MSIL verifiers are both relatively small, but complex, programs. Sun's 1.4.2 verifier (Sun Microsystems, Java 2 SDK) is 4077 lines of code (not including code for checking the file format). For .NET, we examined Rotor, the shared source code that is a beta version of Microsoft's implementation of the ECMA CLI standard (Stutz). The JIT verifier in the production .NET release is either very similar or identical to the Rotor verifier (Lewin, 2004). Rotor's integrated verifier and JIT compiler total about 9400 lines, roughly 4300 of which are needed for verification.
Since the verifier's complexity is directly tied to the instruction set of the virtual machine, examining the instruction sets provides some measure of the verifier's complexity. Each platform uses about 200 opcodes, but some important differences in their instruction sets impact on the complexity of their verifiers. This section considers the differences between the JVML and MSIL instruction sets from the perspective of how complex it is to verify low-level code safety properties.
Table 2 summarizes the instruction sets for each platform. One obvious difference between the instruction sets is that JVML has separate versions of instructions for each type, whereas .NET uses a single instruction to perform the same operation on different types. For example, Java has four different add instructions depending on the type of the data (iadd adds integers, fadd adds floats, etc.) where .NET has one instruction that works on different types. Using generic instructions to perform an operation with multiple types instead of just two types makes verification slightly more difficult, but means that .NET has more instruction opcodes available for other purposes. .NET uses some of these instructions to provide overflow and unsigned versions of the arithmetic operations. The overflow versions of arithmetic operations throw exceptions if the calculation overflows, enabling applications to better handle overflows and avoid security vulnerabilities related to arithmetic overflows (such as the Snort TCP Stream Reassembly Integer Overflow Vulnerability reported in Core Security Technologies Advisory).
Instruction sets comparison
Type
JVML
MSIL
Number Examples Number Examples arithmetic 36 iadd, fadd, ladd, iand 21 add, add_ovf, xor stack 11 pop, dup2, swap 2 pop, dup compare 21 ifeq, ifnull, if_icmpeq 29 ceq, beq, brfalse load 51 Ldc, iload, iaload 65 Ldarg, ldftn, ldstr store 33 Istore, lstore_1, castore 27 Starg, stloc_s, stelem_R8 conversions 15 i2f, d2i, l2d 33 conv_i2, conv_ovf_u8, conv_u2 method calls 4 invokevirtual, invokestatic, invokespecial, invokeinterface 3 callvirt, call, calli object creation 4 new, newarrary, anewarray, multianewarray 2 newobj, newarr exceptions 3 athrow, jsr, ret 5 leave, leave_s, rethrow, endfilter, endfinally
Complex, multi-purpose instructions further increase verification complexity. For example, the invokespecial instruction in JVML serves three purposes: calling a superclass method, invoking a private method, and invoking an initialization method. The multiple uses of this instruction make it difficult to verify correctly. Sun's verifier uses 260 lines to verify the invokespecial instruction (counting major methods used for verification). A 2001 verifier bug involving the invokespecial instruction (Sun Microsystems, Sun Security) affected many implementations of the JVM, and could be exploited to violate type safety (Last Stage of Delirium Research Group).
.NET has two main instructions for calling methods: call and callvirt (another MSIL calling instruction, calli, is used for calling functions indirectly through a pointer to native code). The call instruction is similar to Java's invokespecial and invokestatic instructions. The callvirt instruction is similar to Java's invokeinterface and invokevirtual instructions. The main difference between the call and callvirt instructions is how the target address is computed. The address of a call is known at link-time while callvirt determines the method to call based on the runtime type of the calling object. Combining Java's four different calling instructions into two instructions may make it easier for a compiler writer (Meijer and Gough), but given Java's history of trouble it may have been better to have several single-purpose call instructions rather than a few instructions with multiple functions. The call and callvirt instructions each have their own method for JIT compilation and verification totaling approximately 200 lines in the Rotor implementation.
To efficiently support tail recursion, the MSIL call instructions may also be preceded by a tail prefix which is treated as a special case by the verifier (ECMA International, 2002). The tail prefix reuses the same activation record on the stack instead of creating a new record every time a call is made. About 250 extra lines are required for verification and compilation of the tail prefix including the extra lines needed to deal with call, calli, and callvirt. It is too soon to judge whether the performance advantages of supporting tail outweigh the additional security risks associated with the added complexity.
Sometimes a complex single instruction is better than using many separate instructions. For example, a Java program creates a new object by using new to allocate memory for the new object, dup to place an additional reference to the newly created object on the stack, and theninvokespecial to call the object's initializing constructor. After returning from the constructor, a reference to the (now initialized) object is on top of the stack because of dup. In MSIL, the single newobj instruction calls a constructor, creating and initializing a new object in one step. This sacrifices flexibility, but verification of newobj is much easier than Java's sequence of instructions since the verifier knows that the object is initialized as soon the instruction is executed.
A Java verifier must check whether any new object is initialized before use (Leroy, 2001). Java's verifier has difficulty with two areas in object creation. In cases where the new, dup and invokespecial instructions are separated by instructions, this can pose problems for the verifier. The second problematic area is the complexity of the invokespecial instruction. Microsoft and Netscape's Java verifiers have both had vulnerabilities relating to improper object initialization. The Microsoft verifier bug involved calling a constructor within an exception handler inside a child class (Last Stage of Delirium Research Group). Once the code called the constructor from inside the child class, the parent class constructor would be called to create a ClassLoader object, but the child class had not been given permission to instantiate a class loader. The resulting exception was caught by the exception handler in the constructor of the child class, and the initialization was incorrectly assumed to have completed.
Java's exception handling instructions impose additional complexity compared to MSIL's simpler approach. The JVML instruction jsr is used to implement the Java programming language try-finally construct that transfers execution to a finally block (Lindholm and Yellin, 1999) and is one of the most complex instructions to verify. To jump to a finally block, control transfers to an offset from the address of the jsr instruction, and the return address of the next instruction after the jsr instruction is pushed onto the stack. The main problem is the use of the operand stack to store the return address since this makes an attractive target for an attacker who may try to insert a different address while fooling the verifier. With the return address on the operand stack, more difficulty exists in a finally block's verification in the multiple ways one could execute a finally block: a jsr called after execution of the try clause, a jsr used upon a break/continue within the try clause, or a return executed within the try block.
Several vulnerabilities have been found in Java verifiers due to the complexity of the jsr instruction. One relating to subroutines in exception handling was found in 1999 in the Microsoft JVM (Last Stage of Delirium Research Group). To exploit this flaw, two return addresses are placed on top of the stack using different jsr instructions. Next, a swap instruction is executed. The verifier failed to account for the change of return addresses on the stack (ignoring the swap since the return addresses are of the same type). The switched return address is used by the ret instruction to return to the instruction that is now referenced by the address. The verifier continues to verify the method as if the swap had not executed, thus breaking type safety.
.NET avoids the complexity associated with Java's jsr instruction by providing a simpler instruction. The leave instruction used to exit a try or catch clause clears the operand stack and uses information stored in an exception handling clause for control flow.
Recently, Sun has announced a radical redesign of its bytecode verifier. The new verifier that is part of the new Java SE Mustang release will have two very important simplifications: the separation of the type inferencing process and the removal of any code that generates a jsr or ret instruction (Sun Microsystems, JSR 202; Sun Microsystems, New Java SE). The verifier can now use type information embedded in the class file represented as a code attribute instead of having to infer the type information. To ease this transition to the new verifier, the verification process reverts to using the old verifier if a class file is not recognized as a newer version 50.0 class file (Sun Microsystems, New Java SE).
Disabling the JVM from running older class files can be done by passing a flag to the JVM. This design can break backward compatibility for the benefit of the simplicity of verification, but users should have more confidence in the security of the Java verifier. This is an encouraging step towards simplicity, in contrast to nearly all of the modifications to the Java platform since 1995 that increased complexity.
We tested .NET to check that the verifier was behaving correctly according to the ECMA specification and attempted to carry out exploits that have previously worked on the Java verifier, but were unable to construct any successful exploits. Of course, this does not mean that there are no exploitable bugs in the .NET verifier, but it is encouraging that no verifier bugs have been reported to date. .NET's designers avoided many of the pitfalls in early Java implementations benefiting from Java's history of problems with exception handling, creating objects, and calling methods. The MSIL instruction set design simplifies the verification process by avoiding instructions similar to the most complex instructions to verify in JVML.
Low-level code safety mechanisms prevent hostile applets from circumventing the high-level code safety mechanisms, but security depends on high-level mechanisms to enforce a policy on program executions. A policy specifies what actions code may perform. If a program attempts an action contrary to the policy, a security exception is raised.
The amount of control possible over system resources depends on the available permissions. Except for those permissions that are platform specific, Java and .NET provide similar permissions for controlling access to the file system, network, display, system properties and clipboard (Microsoft Corporation, Security Briefs and Sun Microsystems, Permissions). The permissions provided by each platform are summarized in Table 3.
Permissions summary (Meijer and Gough and .NET Framework Developer's Guide)
Resource Restricted operations Java permissions .NET permissions File system Read/write/execute/delete files FilePermission FileIOPermission, SecurityPermission Append access information on path itself No separate permissions (append = write) FileIOPermissionAccess (Append, PathDiscovery) Access data in current directory from executing program Can read any file in current directory or sub-directory of current directory IsolatedStoragePermission Network Accept/connect/listen/resolve a host at an optional port range SocketPermission SocketPermission Display Show an applet-created window without warning, restrict access to event queue AWTPermission Events handled differently without special permissions Controlling different properties of a window (e.g., setting caption, hiding cursor) Not provided UIPermission.Window Reflection Use of reflection ReflectPermission ReflectionPermission Reflection on visible or invisible members of a type Level of control not provided (all or nothing) ReflectionPermission, different flag values control the level of use System clipboard Read/write clipboard (all or nothing) AWTPermission UIPermission.Clipboard Read clipboard (write unrestricted) Level of control not provided (all or nothing) UIPermission.Clipboard (OwnClipboard) Threading Control threads RuntimePermission SecurityPermission Control any thread, code's own threads, control a group of threads RuntimePermission, different target values control level of privileges Threadpool provides safety through implementation Database Set the logging stream of SQL actions SQLPermission Logging can be configured through registry or provided tools (Meier et al.) Allow blank passwords for a database user, access databases Specific database API not included by default OdbcPermission, OleDbPermission, OraclePermission, SqlClientPermission Printer RuntimePermission PrintingPermission Printing to any printer (default only) and through restricted print dialog boxes All or nothing restriction by RuntimePermission PrintingPermission Platform specific Read/write/delete registry keys/values Resource not exposed (no permission needed) RegistryPermission
The platforms differ in which resources are protected by permissions, and in the granularity of control over specific operations and resources the available permissions provide. In general, .NET and Java protect the same set of resources with permissions, except for platform-specific resources. For instance, Java must provide permissions that protect some resources that are not exposed in .NET including the SecurityManager and AccessControlContext (see Section 6.2). Although .NET has a registry permission to protect the Windows registry, Java does not expose this resource through their API by default. Similarly, Java has an AudioPermission for restricting access to audio system resources, but .NET's API provides no access to audio resource. Microsoft's DirectX 9.0 SDK provides access to audio resources, and adds the SoundPermission to control access to those resources.
In general, .NET provides permissions with finer granularity of control. For example, both platforms provide permissions to restrict access to the file system. But, whereas in Java the same permission controls deleting, writing to, and appending to files, in .NET it is possible to provide just append access to a file.
Table 3 reveals that both platforms suffer from the lack of a systematic design in their permissions. Many of these permissions are based on protecting methods provided by the platform API, rather than protecting security-critical resources. For example, Java's SQLPermission allows a program to set the logging stream that may contain private SQL data; it is checked before setLogWriter methods in several classes. Java's AWTPermission.createRobot permission protects java.awt.Robot objects that allow the creation of low-level mouse and keyboard events. .NET's PerformanceCounterPermission protects diagnostic information exposed by the API. These permissions do not correspond well to security properties a user could relate to, but rather correspond to dangerous API methods.
Designing permissions around API methods rather than security-critical resources is dangerous since it means granting a permission may provide unexpected capabilities. For example, Java's ReflectPermission indirectly allows a program to access private methods and fields; effectively, this allows a program to circumvent other security checks and is equivalent to granting most other permissions. .NET provides a similar ReflectionPermission, but allows a finer granularity of control over what can be accessed. Other permissions provided by Java that effectively grant code arbitrary access include FilePermission.execute (which allows a program to execute a system command) and SecurityPermission.setPolicy (which allows a program to change the security policy).
Knowing the resulting policy after granting certain permissions is an area of difficulty common to both architectures. A developer may not understand that granting file execute or reflection permissions to any code that asks for it is essentially the same as fully trusting the code. An attacker also has an additional method of attack to compromise a system if a permission can give higher privileges in a different way.
Neither platform supports complete mediation: only actions associated with an associated predefined permission are checked and many resources (for example, allocating memory) have no associated permission. Further, there is no support to restrict the amount of a resource that is consumed, so many denial-of-service attacks are possible without circumventing the security policy. These limitations are serious (LaDue), but more complete mediation is possible through the reference monitoring framework only by significantly reducing performance. Richer policy expression and efficient enforcement is an active research area (Erlingsson, 2003, Evans and Twyman, 1999 and Walker, 2000).
Policies associate sets of permissions with executions. For security, policies should follow the principle of least privilege and fail-safe defaults, however, these principles often conflict with convenience and are not always followed.
In Java, policies are defined by specifying the permissions granted in a policy file based on properties of an execution: the origin of the code, the digital code signers (if any), and the principal executing the code. Java's policies are also affected by a system-wide properties file, java.security, which specifies paths to other policy files, a source of randomness for the random number generator, and other important properties.
A Java policy file contains a list of grant entries. Each entry specifies a context that determines when the grant applies and lists a set of granted permissions in that context. The context may specify the code signers (a list of names, all of whom signed the code for the context to apply), the code origin (code base URL), and one or more principals (on whose behalf the code is executing). If no principals are listed, the context applies to all principals.
Java is installed with one system-wide policy file, but a user can augment this policy with her/his own policy file. The granted permission set is the union of the permissions granted in all the policy files. This is dangerous since it means more permissions are granted than those that appear in the user's policy file. Further, it means a user can make the policy less restrictive than the system policy, but cannot make the policy more restrictive. Java users may not exclude permissions a system administrator allows unless they are able to edit java.security, the Policy implementation, or the policy file granting the unwanted permissions.
.NET provides policy definition mechanisms that overcome these limitations by providing flexible, multi-level policies, but at the cost of greater complexity. A .NET policy is specified by a group of policy levels: Enterprise (intended for the system administrator), Machine (machine administrator), User, and Application Domain (AppDomain). The permissions granted to an assembly are the intersection of the permissions granted at