From b871c9eec0b2de9e50bab03d44776487e4abbcff Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Thu, 4 Jul 2013 18:51:45 +0200 Subject: [PATCH] Java: Add notes about the security manager --- defensive-coding/en-US/Java/Java.xml | 1 + .../en-US/Java/SecurityManager.xml | 292 ++++++++++++++++++ .../snippets/SecurityManager-Callback.xml | 40 +++ .../SecurityManager-CurrentDirectory.xml | 8 + .../snippets/SecurityManager-Privileged.xml | 19 ++ .../snippets/SecurityManager-Unprivileged.xml | 28 ++ .../src/JavaSecurityManagerPrivileged.java | 80 +++++ .../src/JavaSecurityManagerUnprivileged.java | 80 +++++ .../src/data/java/grant-all.policy | 5 + defensive-coding/src/src.mk | 2 + 10 files changed, 555 insertions(+) create mode 100644 defensive-coding/en-US/Java/SecurityManager.xml create mode 100644 defensive-coding/en-US/Java/snippets/SecurityManager-Callback.xml create mode 100644 defensive-coding/en-US/Java/snippets/SecurityManager-CurrentDirectory.xml create mode 100644 defensive-coding/en-US/Java/snippets/SecurityManager-Privileged.xml create mode 100644 defensive-coding/en-US/Java/snippets/SecurityManager-Unprivileged.xml create mode 100644 defensive-coding/src/JavaSecurityManagerPrivileged.java create mode 100644 defensive-coding/src/JavaSecurityManagerUnprivileged.java create mode 100644 defensive-coding/src/data/java/grant-all.policy diff --git a/defensive-coding/en-US/Java/Java.xml b/defensive-coding/en-US/Java/Java.xml index 7c5700c..d7bf3ef 100644 --- a/defensive-coding/en-US/Java/Java.xml +++ b/defensive-coding/en-US/Java/Java.xml @@ -6,5 +6,6 @@ + diff --git a/defensive-coding/en-US/Java/SecurityManager.xml b/defensive-coding/en-US/Java/SecurityManager.xml new file mode 100644 index 0000000..2e18ac7 --- /dev/null +++ b/defensive-coding/en-US/Java/SecurityManager.xml @@ -0,0 +1,292 @@ + + +
+ Interacting with the security manager + + The Java platform is largely implemented in the Java language + itself. Therefore, within the same JVM, code runs which is part + of the Java installation and which is trusted, but there might + also be code which comes from untrusted sources and is restricted + by the Java sandbox (to varying degrees). The security + manager draws a line between fully trusted, partially + trusted and untrusted code. + + + The type safety and accessibility checks provided by the Java + language and JVM would be sufficient to implement a sandbox. + However, only some Java APIs employ such a capabilities-based + approach. (The Java SE library contains many public classes with + public constructors which can break any security policy, such as + java.io.FileOutputStream.) Instead, critical + functionality is protected by stack + inspection: At a security check, the stack is walked + from top (most-nested) to bottom. The security check fails if a + stack frame for a method is encountered whose class lacks the + permission which the security check requires. + + + This simple approach would not allow untrusted code (which lacks + certain permissions) to call into trusted code while the latter + retains trust. Such trust transitions are desirable because they + enable Java as an implementation language for most parts of the + Java platform, including security-relevant code. Therefore, there + is a mechanism to mark certain stack frames as trusted (). + + + In theory, it is possible to run a Java virtual machine with a + security manager that acts very differently from this approach, + but a lot of code expects behavior very close to the platform + default (including many classes which are part of the OpenJDK + implementation). + + +
+ Security manager compatibility + + A lot of code can run without any additional permissions at all, + with little changes. The following guidelines should help to + increase compatibility with a restrictive security manager. + + + + + When retrieving system properties using + System.getProperty(String) or similar + methods, catch SecurityException + exceptions and treat the property as unset. + + + + + Avoid unnecessary file system or network access. + + + + + Avoid explicit class loading. Access to a suitable class + loader might not be available when executing as untrusted + code. + + + + + If the functionality you are implementing absolutely requires + privileged access and this functionality has to be used from + untrusted code (hopefully in a restricted and secure manner), + see . + +
+ +
+ Activating the security manager + + The usual command to launch a Java application, + java, does not activate the security manager. + Therefore, the virtual machine does not enforce any sandboxing + restrictions, even if explicitly requested by the code (for + example, as described in ). + + + The option activates + the security manager, with the fairly restrictive default + policy. With a very permissive policy, most Java code will run + unchanged. Assuming the policy in + has been saved in a file grant-all.policy, + this policy can be activated using the option + (in + addition to the + option). + + + Most permissve OpenJDK policy file + +grant { + permission java.security.AllPermission; +}; + + + + With this most permissive policy, the security manager is still + active, and explicit requests to drop privileges will be + honored. + +
+ +
+ Reducing trust in code + + + shows how to run a piece code of with reduced privileges. + + + Using the security manager to run code with reduced + privileges + + + + The example above does not add any additional permissions to the + permissions object. If such permissions are + necessary, code like the following (which grants read permission + on all files in the current directory) can be used: + + + + + + + Calls to the + java.security.AccessController.doPrivileged() + methods do not enforce any additional restriction if no + security manager has been set. Except for a few special + exceptions, the restrictions no longer apply if the + doPrivileged() has returned, even to + objects created by the code which ran with reduced privileges. + (This applies to object finalization in particular.) + + + The example code above does not prevent the called code from + calling the + java.security.AccessController.doPrivileged() + methods. This mechanism should be considered an additional + safety net, but it still can be used to prevent unexpected + behavior of trusted code. As long as the executed code is not + dynamic and came with the original application or library, the + sandbox is fairly effective. + + + The context argument in + is extremely important—otherwise, this code would increase + privileges instead of reducing them. + + + + For activating the security manager, see . + Unfortunately, this affects the virtual machine as a whole, so + it is not possible to do this from a library. + +
+ +
+ Re-gaining privileges + + Ordinarily, when trusted code is called from untrusted code, it + loses its privileges (because of the untrusted stack frames + visible to stack inspection). The + java.security.AccessController.doPrivileged() + family of methods provides a controlled backdoor from untrusted + to trusted code. + + + + By design, this feature can undermine the Java security model + and the sandbox. It has to be used very carefully. Most + sandbox vulnerabilities can be traced back to its misuse. + + + + In essence, the doPrivileged() methods + cause the stack inspection to end at their call site. Untrusted + code further down the call stack becomes invisible to security + checks. + + + The following operations are common and safe to perform with + elevated privileges. + + + + + Reading custom system properties with fixed names, + especially if the value is not propagated to untrusted code. + (File system paths including installation paths, host names + and user names are sometimes considered private information + and need to be protected.) + + + + + Reading from the file system at fixed paths, either + determined at compile time or by a system property. Again, + leaking the file contents to the caller can be problematic. + + + + + Accessing network resources under a fixed address, name or + URL, derived from a system property or configuration file, + information leaks not withstanding. + + + + + + shows how to request additional privileges. + + + Using the security manager to run code with increased + privileges + + + + Obviously, this only works if the class containing the call to + doPrivileged() is marked trusted (usually + because it is loaded from a trusted class loader). + + + When writing code that runs with elevated privileges, make sure + that you follow the rules below. + + + + + Make the privileged code as small as possible. Perform as + many computations as possible before and after the + privileged code section, even if it means that you have to + define a new class to pass the data around. + + + + + Make sure that you either control the inputs to the + privileged code, or that the inputs are harmless and cannot + affect security properties of the privileged code. + + + + + Data that is returned from or written by the privileged code + must either be restricted (that is, it cannot be accessed by + untrusted code), or must be harmless. Otherwise, privacy + leaks or information disclosures which affect security + properties can be the result. + + + + + If the code calls back into untrusted code at a later stage (or + performs other actions under control from the untrusted caller), + you must obtain the original security context and restore it + before performing the callback, as in . + (In this example, it would be much better to move the callback + invocation out of the privileged code section, of course.) + + + Restoring privileges when invoking callbacks + + +
+ +
+ diff --git a/defensive-coding/en-US/Java/snippets/SecurityManager-Callback.xml b/defensive-coding/en-US/Java/snippets/SecurityManager-Callback.xml new file mode 100644 index 0000000..634d62f --- /dev/null +++ b/defensive-coding/en-US/Java/snippets/SecurityManager-Callback.xml @@ -0,0 +1,40 @@ + + + + +interface Callback<T> { + T call(boolean flag); +} + +class CallbackInvoker<T> { + private final AccessControlContext context; + Callback<T> callback; + + CallbackInvoker(Callback<T> callback) { + context = AccessController.getContext(); + this.callback = callback; + } + + public T invoke() { + // Obtain increased privileges. + return AccessController.doPrivileged(new PrivilegedAction<T>() { + @Override + public T run() { + // This operation would fail without + // additional privileges. + final boolean flag = Boolean.getBoolean("some.property"); + + // Restore the original privileges. + return AccessController.doPrivileged( + new PrivilegedAction<T>() { + @Override + public T run() { + return callback.call(flag); + } + }, context); + } + }); + } +} + diff --git a/defensive-coding/en-US/Java/snippets/SecurityManager-CurrentDirectory.xml b/defensive-coding/en-US/Java/snippets/SecurityManager-CurrentDirectory.xml new file mode 100644 index 0000000..1a4d022 --- /dev/null +++ b/defensive-coding/en-US/Java/snippets/SecurityManager-CurrentDirectory.xml @@ -0,0 +1,8 @@ + + + + +permissions.add(new FilePermission( + System.getProperty("user.dir") + "/-", "read")); + diff --git a/defensive-coding/en-US/Java/snippets/SecurityManager-Privileged.xml b/defensive-coding/en-US/Java/snippets/SecurityManager-Privileged.xml new file mode 100644 index 0000000..b700a0e --- /dev/null +++ b/defensive-coding/en-US/Java/snippets/SecurityManager-Privileged.xml @@ -0,0 +1,19 @@ + + + + +// This is expected to fail. +try { + System.out.println(System.getProperty("user.home")); +} catch (SecurityException e) { + e.printStackTrace(System.err); +} +AccessController.doPrivileged(new PrivilegedAction<Void>() { + public Void run() { + // This should work. + System.out.println(System.getProperty("user.home")); + return null; + } + }); + diff --git a/defensive-coding/en-US/Java/snippets/SecurityManager-Unprivileged.xml b/defensive-coding/en-US/Java/snippets/SecurityManager-Unprivileged.xml new file mode 100644 index 0000000..29bb4f5 --- /dev/null +++ b/defensive-coding/en-US/Java/snippets/SecurityManager-Unprivileged.xml @@ -0,0 +1,28 @@ + + + + +Permissions permissions = new Permissions(); + ProtectionDomain protectionDomain = + new ProtectionDomain(null, permissions); + AccessControlContext context = new AccessControlContext( + new ProtectionDomain[] { protectionDomain }); + +// This is expected to succeed. +try (FileInputStream in = new FileInputStream(path)) { + System.out.format("FileInputStream: %s%n", in); +} + +AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { + @Override + public Void run() throws Exception { + // This code runs with reduced privileges and is + // expected to fail. + try (FileInputStream in = new FileInputStream(path)) { + System.out.format("FileInputStream: %s%n", in); + } + return null; + } + }, context); + diff --git a/defensive-coding/src/JavaSecurityManagerPrivileged.java b/defensive-coding/src/JavaSecurityManagerPrivileged.java new file mode 100644 index 0000000..46da7a0 --- /dev/null +++ b/defensive-coding/src/JavaSecurityManagerPrivileged.java @@ -0,0 +1,80 @@ +// Test with: +// +// java -Djava.security.manager \ +// JavaSecurityManagerPrivileged + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; + +public class JavaSecurityManagerPrivileged { + public static void main(String[] args) throws Exception { + Permissions permissions = new Permissions(); + ProtectionDomain protectionDomain = + new ProtectionDomain(null, permissions); + AccessControlContext context = new AccessControlContext( + new ProtectionDomain[] { protectionDomain }); + + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + //+ Java SecurityManager-Privileged + // This is expected to fail. + try { + System.out.println(System.getProperty("user.home")); + } catch (SecurityException e) { + e.printStackTrace(System.err); + } + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { + // This should work. + System.out.println(System.getProperty("user.home")); + return null; + } + }); + //- + return null; + } + }, context); + + } + + //+ Java SecurityManager-Callback + interface Callback { + T call(boolean flag); + } + + class CallbackInvoker { + private final AccessControlContext context; + Callback callback; + + CallbackInvoker(Callback callback) { + context = AccessController.getContext(); + this.callback = callback; + } + + public T invoke() { + // Obtain increased privileges. + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public T run() { + // This operation would fail without + // additional privileges. + final boolean flag = Boolean.getBoolean("some.property"); + + // Restore the original privileges. + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public T run() { + return callback.call(flag); + } + }, context); + } + }); + } + } + //- +} diff --git a/defensive-coding/src/JavaSecurityManagerUnprivileged.java b/defensive-coding/src/JavaSecurityManagerUnprivileged.java new file mode 100644 index 0000000..61da297 --- /dev/null +++ b/defensive-coding/src/JavaSecurityManagerUnprivileged.java @@ -0,0 +1,80 @@ +// Test with: +// +// java -Djava.security.manager \ +// -Djava.security.policy=data/java/grant-all.policy \ +// JavaSecurityManagerUnprivileged JavaFinally.java {true|false} + +import java.io.FileInputStream; +import java.io.FilePermission; +import java.io.IOException; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Permissions; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; + +public class JavaSecurityManagerUnprivileged { + public static void main(String[] args) throws Exception { + final String path = args[0]; + boolean grant = Boolean.parseBoolean(args[1]); + if (grant) { + withGrant(path); + return; + } + + //+ Java SecurityManager-Unprivileged + Permissions permissions = new Permissions(); + ProtectionDomain protectionDomain = + new ProtectionDomain(null, permissions); + AccessControlContext context = new AccessControlContext( + new ProtectionDomain[] { protectionDomain }); + + // This is expected to succeed. + try (FileInputStream in = new FileInputStream(path)) { + System.out.format("FileInputStream: %s%n", in); + } + + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + // This code runs with reduced privileges and is + // expected to fail. + try (FileInputStream in = new FileInputStream(path)) { + System.out.format("FileInputStream: %s%n", in); + } + return null; + } + }, context); + //- + } + + private static void withGrant(String path) throws Exception { + Permissions permissions = new Permissions(); + //+ Java SecurityManager-CurrentDirectory + permissions.add(new FilePermission( + System.getProperty("user.dir") + "/-", "read")); + //- + ProtectionDomain protectionDomain = + new ProtectionDomain(null, permissions); + AccessControlContext context = new AccessControlContext( + new ProtectionDomain[] { protectionDomain }); + + // This is expected to succeed. + try (FileInputStream in = new FileInputStream(path)) { + System.out.format("FileInputStream: %s%n", in); + } + + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + // This code runs with reduced privileges and is + // expected to fail. + try (FileInputStream in = new FileInputStream(path)) { + System.out.format("FileInputStream: %s%n", in); + return null; + } + } + }, context); + } +} diff --git a/defensive-coding/src/data/java/grant-all.policy b/defensive-coding/src/data/java/grant-all.policy new file mode 100644 index 0000000..ce74daa --- /dev/null +++ b/defensive-coding/src/data/java/grant-all.policy @@ -0,0 +1,5 @@ +// Used by the JavaSecurityManagerUnprivileged example. + +grant { + permission java.security.AllPermission; +}; diff --git a/defensive-coding/src/src.mk b/defensive-coding/src/src.mk index 1bd5f45..c916d01 100644 --- a/defensive-coding/src/src.mk +++ b/defensive-coding/src/src.mk @@ -19,6 +19,8 @@ CFLAGS_Java-JNI-Pointers = \ # List Java files which sould be compiled compile_java += JavaFinally +compile_java += JavaSecurityManagerUnprivileged +compile_java += JavaSecurityManagerPrivileged compile_java += TLSClientOpenJDK JCFLAGS_TLSClientOpenJDK = -source 1.6 -target 1.6