Java: Add notes about the security manager

This commit is contained in:
Florian Weimer 2013-07-04 18:51:45 +02:00
parent 458170e759
commit b871c9eec0
10 changed files with 555 additions and 0 deletions

View file

@ -6,5 +6,6 @@
<xi:include href="Language.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
<xi:include href="LowLevel.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
<xi:include href="SecurityManager.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
</chapter>

View file

@ -0,0 +1,292 @@
<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
]>
<section id="sect-Defensive_Coding-Java-SecurityManager">
<title>Interacting with the security manager</title>
<para>
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 <emphasis>security
manager</emphasis> draws a line between fully trusted, partially
trusted and untrusted code.
</para>
<para>
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
<literal>java.io.FileOutputStream</literal>.) Instead, critical
functionality is protected by <emphasis>stack
inspection</emphasis>: 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.
</para>
<para>
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 (<xref
linkend="sect-Defensive_Coding-Java-SecurityManager-Privileged"/>).
</para>
<para>
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).
</para>
<section id="sect-Defensive_Coding-Java-SecurityManager-Compatible">
<title>Security manager compatibility</title>
<para>
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.
</para>
<itemizedlist>
<listitem>
<para>
When retrieving system properties using
<function>System.getProperty(String)</function> or similar
methods, catch <literal>SecurityException</literal>
exceptions and treat the property as unset.
</para>
</listitem>
<listitem>
<para>
Avoid unnecessary file system or network access.
</para>
</listitem>
<listitem>
<para>
Avoid explicit class loading. Access to a suitable class
loader might not be available when executing as untrusted
code.
</para>
</listitem>
</itemizedlist>
<para>
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 <xref
linkend="sect-Defensive_Coding-Java-SecurityManager-Privileged"/>.
</para>
</section>
<section id="sect-Defensive_Coding-Java-SecurityManager-Activate">
<title>Activating the security manager</title>
<para>
The usual command to launch a Java application,
<command>java</command>, 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 <xref
linkend="sect-Defensive_Coding-Java-SecurityManager-Unprivileged"/>).
</para>
<para>
The <option>-Djava.security.manager</option> 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 <xref
linkend="ex-Defensive_Coding-Java-SecurityManager-GrantAll"/>
has been saved in a file <filename>grant-all.policy</filename>,
this policy can be activated using the option
<option>-Djava.security.policy=grant-all.policy</option> (in
addition to the <option>-Djava.security.manager</option>
option).
</para>
<example id="ex-Defensive_Coding-Java-SecurityManager-GrantAll">
<title>Most permissve OpenJDK policy file</title>
<programlisting>
grant {
permission java.security.AllPermission;
};
</programlisting>
</example>
<para>
With this most permissive policy, the security manager is still
active, and explicit requests to drop privileges will be
honored.
</para>
</section>
<section id="sect-Defensive_Coding-Java-SecurityManager-Unprivileged">
<title>Reducing trust in code</title>
<para>
<xref linkend="ex-Defensive_Coding-Java-SecurityManager-Unprivileged"/>
shows how to run a piece code of with reduced privileges.
</para>
<example id="ex-Defensive_Coding-Java-SecurityManager-Unprivileged">
<title>Using the security manager to run code with reduced
privileges</title>
<xi:include href="snippets/SecurityManager-Unprivileged.xml"
xmlns:xi="http://www.w3.org/2001/XInclude" />
</example>
<para>
The example above does not add any additional permissions to the
<literal>permissions</literal> object. If such permissions are
necessary, code like the following (which grants read permission
on all files in the current directory) can be used:
</para>
<informalexample>
<xi:include href="snippets/SecurityManager-CurrentDirectory.xml"
xmlns:xi="http://www.w3.org/2001/XInclude" />
</informalexample>
<important>
<para>
Calls to the
<function>java.security.AccessController.doPrivileged()</function>
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
<function>doPrivileged()</function> has returned, even to
objects created by the code which ran with reduced privileges.
(This applies to object finalization in particular.)
</para>
<para>
The example code above does not prevent the called code from
calling the
<function>java.security.AccessController.doPrivileged()</function>
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.
</para>
<para>
The <literal>context</literal> argument in <xref
linkend="ex-Defensive_Coding-Java-SecurityManager-Unprivileged"/>
is extremely important—otherwise, this code would increase
privileges instead of reducing them.
</para>
</important>
<para>
For activating the security manager, see <xref
linkend="sect-Defensive_Coding-Java-SecurityManager-Activate"/>.
Unfortunately, this affects the virtual machine as a whole, so
it is not possible to do this from a library.
</para>
</section>
<section id="sect-Defensive_Coding-Java-SecurityManager-Privileged">
<title>Re-gaining privileges</title>
<para>
Ordinarily, when trusted code is called from untrusted code, it
loses its privileges (because of the untrusted stack frames
visible to stack inspection). The
<function>java.security.AccessController.doPrivileged()</function>
family of methods provides a controlled backdoor from untrusted
to trusted code.
</para>
<important>
<para>
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.
</para>
</important>
<para>
In essence, the <function>doPrivileged()</function> methods
cause the stack inspection to end at their call site. Untrusted
code further down the call stack becomes invisible to security
checks.
</para>
<para>
The following operations are common and safe to perform with
elevated privileges.
</para>
<itemizedlist>
<listitem>
<para>
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.)
</para>
</listitem>
<listitem>
<para>
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.
</para>
</listitem>
<listitem>
<para>
Accessing network resources under a fixed address, name or
URL, derived from a system property or configuration file,
information leaks not withstanding.
</para>
</listitem>
</itemizedlist>
<para>
<xref linkend="ex-Defensive_Coding-Java-SecurityManager-Privileged"/>
shows how to request additional privileges.
</para>
<example id="ex-Defensive_Coding-Java-SecurityManager-Privileged">
<title>Using the security manager to run code with increased
privileges</title>
<xi:include href="snippets/SecurityManager-Privileged.xml"
xmlns:xi="http://www.w3.org/2001/XInclude" />
</example>
<para>
Obviously, this only works if the class containing the call to
<function>doPrivileged()</function> is marked trusted (usually
because it is loaded from a trusted class loader).
</para>
<para>
When writing code that runs with elevated privileges, make sure
that you follow the rules below.
</para>
<itemizedlist>
<listitem>
<para>
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.
</para>
</listitem>
<listitem>
<para>
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.
</para>
</listitem>
<listitem>
<para>
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.
</para>
</listitem>
</itemizedlist>
<para>
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 <xref
linkend="ex-Defensive_Coding-Java-SecurityManager-Callback"/>.
(In this example, it would be much better to move the callback
invocation out of the privileged code section, of course.)
</para>
<example id="ex-Defensive_Coding-Java-SecurityManager-Callback">
<title>Restoring privileges when invoking callbacks</title>
<xi:include href="snippets/SecurityManager-Callback.xml"
xmlns:xi="http://www.w3.org/2001/XInclude" />
</example>
</section>
</section>

View file

@ -0,0 +1,40 @@
<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE programlisting PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
]>
<!-- Automatically generated file. Do not edit. -->
<programlisting language="Java">
interface Callback&#60;T&#62; {
T call(boolean flag);
}
class CallbackInvoker&#60;T&#62; {
private final AccessControlContext context;
Callback&#60;T&#62; callback;
CallbackInvoker(Callback&#60;T&#62; callback) {
context = AccessController.getContext();
this.callback = callback;
}
public T invoke() {
// Obtain increased privileges.
return AccessController.doPrivileged(new PrivilegedAction&#60;T&#62;() {
@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&#60;T&#62;() {
@Override
public T run() {
return callback.call(flag);
}
}, context);
}
});
}
}
</programlisting>

View file

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE programlisting PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
]>
<!-- Automatically generated file. Do not edit. -->
<programlisting language="Java">
permissions.add(new FilePermission(
System.getProperty("user.dir") + "/-", "read"));
</programlisting>

View file

@ -0,0 +1,19 @@
<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE programlisting PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
]>
<!-- Automatically generated file. Do not edit. -->
<programlisting language="Java">
// This is expected to fail.
try {
System.out.println(System.getProperty("user.home"));
} catch (SecurityException e) {
e.printStackTrace(System.err);
}
AccessController.doPrivileged(new PrivilegedAction&#60;Void&#62;() {
public Void run() {
// This should work.
System.out.println(System.getProperty("user.home"));
return null;
}
});
</programlisting>

View file

@ -0,0 +1,28 @@
<?xml version='1.0' encoding='utf-8' ?>
<!DOCTYPE programlisting PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
]>
<!-- Automatically generated file. Do not edit. -->
<programlisting language="Java">
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&#60;Void&#62;() {
@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);
</programlisting>

View file

@ -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<Void>() {
@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<Void>() {
public Void run() {
// This should work.
System.out.println(System.getProperty("user.home"));
return null;
}
});
//-
return null;
}
}, context);
}
//+ Java SecurityManager-Callback
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);
}
});
}
}
//-
}

View file

@ -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<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);
//-
}
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<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);
}
}

View file

@ -0,0 +1,5 @@
// Used by the JavaSecurityManagerUnprivileged example.
grant {
permission java.security.AllPermission;
};

View file

@ -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