From the point of view of a programmer or security engineer, we can describe three categories of features provided by the Java language and environment:
- features that enhance a program's ability to protect its privilege from being subverted by malicious input and a malicious environment--in other words, features that let you write secure programs,
- features that limit a program's privilege--such as those that let you execute programs securely, and
- features that provide auxiliary security services.
The developers of Java have created a system that excels in some areas but is weak in others.
Writing a Secure Program
Before the advent of Java, when people spoke of writing a secure program, usually they were referring to writing a privileged program resistant to attacks that would subvert its privilege. A privileged program is one that interacts with other programs that do not have the same access rights, such as network services, SUID/SGID programs and network clients. The attacks on these programs usually are the result of software faults, though configuration errors and design flaws can be blamed, too. A number of these attacks are well known, such as buffer-overflow attacks, file race-condition attacks, and input-validation attacks.
A programming language can render an application more or less vulnerable to attack, based on the likelihood that the programmer will introduce exploitable software faults. For example, the C language's lack of strings as a first-class data type and automatic-array bounds checking makes it easy for programmers to introduce bounds-checking errors, thus leaving programs written in C open to buffer-overflow attacks.
To limit the risk introduced by these software faults, programmers can employ the "principle of least privilege." This principle states that a piece of code should execute with the fewest privileges required for it to accomplish its task. This limits the privileges that can be gained if the program is subverted. Furthermore, a program that requires some privilege to operate should enable the privilege only when it is required and disable it when it no longer is. This limits the amount of code that executes with privileges.
One of the problems Java faces is that a number of platforms have different concepts of exactly what privileges are and how they are implemented. Java's need for cross-platform compatibility and the decision to provide an API based on a minimum common denominator of the features supported by the platforms means that Java programs cannot follow the principle of least privilege as it relates to the underlying platform.
Java has the same problem when it comes to interacting with other security services of the underlying platform. For example, Java programs have no notion of file permissions or access control lists (ACLs). A Java program that wants to make use of the security features and services of the underlying OS must resort to using nonportable code such as Java's NMI (Native Method Invocation).
A better approach would have been to abstract those security features and services common to a subset of the supported platforms (such as the concept of multiple users and file ACLs), provide an API for them, and return an appropriate error under those platforms that did not support the feature. To be fair, some aspects of the security architectures implemented by the various supported platforms can be quite distinct from one another, but they have enough similar features to justify this abstraction process.
For tips on how to write a secure Java program, see "Writing Secure Java Programs".