Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Q&A


May99: Java Q&A

Lou is a freelance programmer and writer. He can be contacted at [email protected] compuserve.com.


JDK 1.2 Changes Everything


I recently worked on a Java project that raised a complex question: How do you run classes written by unknown people as part of your Java 1.1 application, without putting your system (or your sanity) at risk? Since this project involved a programming contest open to the public, there was every likelihood I'd encounter code written by people with dishonorable intentions. Furthermore, my code would have to provide a set of methods that the untrusted classes can call for services unique to the game. This meant that during the execution of my program, control would bounce back and forth between trusted and untrusted classes. Clearly, I needed a way to put suspect code in the tightest possible security box. Two things quickly became apparent: First, that I needed to use a customized SecurityManager, and second, that even using that mechanism wouldn't meet all my needs.

Java's SecurityManager architecture is like many other parts of the language -- it's streamlined yet surprisingly powerful. The basic idea is that whenever any code tries to do certain interesting things, like read from or write to disk files, Java calls a method in the currently installed SecurityManager particular to that action to see if it should be allowed. If the method determines that the action is safe, it simply does nothing and returns; otherwise, it throws a SecurityException. SecurityManager has a few dozen methods, tailored to different tasks that can be security checked. (This is the basic mechanism used to create the "sandbox" that Java uses to control applets.) When you're running an application, however, by default there is no SecurityManager installed, so all activities are allowed and the entire system is at the mercy of the program's whims, subject to whatever restrictions the operating system places on any normal program.

The currently installed security manager is a JVM-wide entity, and it isn't applied based on which thread or class is initiating an operation. If you create and install a custom SecurityManager that simply disallows everything, then you'll likely trigger unwanted exceptions, so a more sophisticated approach is needed. Another relevant detail is that you can install only one SecurityManager. Any attempt to replace or remove a security manager throws an exception. While this guarantees your custom security manager won't be overridden, it also means you're stuck with it for the life of your program.

Since my code had to be fully trusted and have free reign of the system, this architecture created a problem. I needed a way to provide fine-grained security control, based on which class was responsible for the operation being security checked. My first solution was simple, obvious, efficient -- and ugly. I created a mutableBoolean class, which wrapped a single, publicly accessible Boolean variable named enabled, and passed this via reference to my security manager's constructor, which stored the reference (not the Boolean itself). In each of the security manager's methods, I simply check the value of enabled: True means strict security is in place, so throw an exception, while False means all bets are off, let the code do what it wants. This worked fine, but it created a code maintenance burden, because I had to set the Boolean just before passing control to the untrusted code, and reset it whenever I got control back. This also required me to flip the Boolean at the entry and exit points of each of those callback methods, and make sure that the Boolean was in the proper state at all times when other, unrelated parts of my program got control. As if all that weren't bad enough, it also created a slight security risk. Listings One through Five present the classes needed to run this switchable security manager, including callback support for the untrusted classes. The comment in SMdemo (Listing Two) provides instructions on running both versions of the programs.

Security, Take 2

My second attempt at improved security proved the old programmer axiom "write it twice and throw the first one away" still holds true. For my purposes, the most intriguing method in Java's SecurityManager is classDepth(), which returns an integer telling you where the most recent occurrence of any method from a specified class is on the call stack. This let me create a security manager that used two lists of names (one of the trusted classes and one of the untrusted classes) and let the security manager dynamically check the call stack and see which group the call ultimately came from. Oddly enough, the security manager doesn't need a list of all classes in the program: The trusted class list only has to include those classes that directly call methods in untrusted code (plus those, in the case of callback methods, that are directly called by untrusted code), and the untrusted list only needs the names of classes directly called by trusted code.

Two examples might help illustrate how this works. My custom security manager, called "smartSM," knows about only two classes: T1 is trusted, and U1 is not. When T1 calls a method in U1, U1 then calls a method in U2, which in turn calls a method in U3. U3's method does something that requires a security check, so the JVM calls the appropriate method in my security manager. When it examines the stack, the security manager finds that U1 is higher on the stack than is T1 (it knows nothing of U2, U3, and other classes, so it ignores them), and it disallows the operation.

Callbacks are handled similarly. Say that T1 calls U1, and U1 then invokes a callback method in T1. The T1 method wants to write to a disk log file, which brings the security manager into the act. It checks the stack and finds that T1 is higher on the stack (thanks to the callback method that U1 called) than is U1, so it lets the operation proceed. Again, as long as the security manager knows about all classes that directly call or are called across the trusted/untrusted divide, this technique will implement the desired security policy, and I don't have to load down my code with all that Boolean flipping.

Because of the nature of my application, I'm imposing a much stricter security model than the one Sun and third parties normally illustrate in their examples. My techniques don't allow system code to pass the security checks -- even if they've been called most recently by untrusted code. If you can't live with those restrictions, you have to use a more complex and fragile solution in Java 1.1 (which entails checking the absolute stack positions of untrusted classes, not relative positions, as does my code), or use the new security enhancements in Java 1.2. See the text box "JDK 1.2 Changes Everything" for details.

The classes to run the "smart" version of the program are in Listing Two (SMdemo) and the files smartSM, trustedSmart, and untrusted2 (all three available electronically; see "Resource Center," page 5).

The return() is in the Mail

An entirely different issue involves untrusted code that simply never returns control. The solution is to make my application create a separate thread for the untrusted code, start it, and then use the System.sleep() method to wait for the maximum time I'm willing to let the untrusted code run for each invocation. When the main thread wakes up, it checks via the untrusted thread's isAlive() method to see if it's still running. If so, then I use the System.exit() method to end the program. (I would have preferred a less drastic way to handle this situation, but Thread.suspend() doesn't always work. In my testing with the JDK 1.1.6 under Windows 95, I found that suspend() and stop() wouldn't immediately stop the errant thread, and wouldn't stop it at all if it was in an infinite loop. Also, the stop(), suspend(), resume(), and destroy() methods of Thread are all deprecated as of JDK 1.2, so I wanted to avoid them. (See http://java.sun .com/products/jdk/1.2/docs/guide/misc/ threadPrimitiveDeprecation.html for an explanation of this decision.) This is where the security exposure I mentioned earlier in the switchableSM security manager comes into play. If the main thread wakes up and finds the untrusted code is still running, it has to relax security before it can do certain things like write to a log file. This can create a slim but real exposure if the untrusted code manages to try something in that sliver of time between when the security is disabled and the program is terminated. (A reliable Thread.suspend() would plug this gap nicely, but that's not available.)

As it turns out, I still wasn't done. It seems that some undesirable actions were still available to the untrusted code, like getting the thread for the main program and calling its sleep() method. The solution was simply to create a new ThreadGroup for the untrusted thread's invocation, and then create the thread in it. (ThreadGroups are roughly analogous to directories in a file system, in that they form a hierarchy of nodes that contain threads and other thread groups.) This, plus the smart security manager, prevents the untrusted code from accessing other threads.

Conclusion

While this "smart SecurityManager" technique solved my problem and is reasonably easy to maintain, Sun has put programmers in a bind. The Java 1.1 security features are simple but limited for more general-purpose situations, and their use often results in difficult to maintain code because of their reliance on absolute call stack depth. The JDK 1.2 security features are far more comprehensive, but introduce a new level of complexity. On top of that, they're still in a state of flux. I've tested this code on both JDK 1.1.6 and beta 4 of JDK 1.2, with identical results.

I would like to see something similar to my smartSM become part of the security architecture in 1.2, possibly as an officially supported helper method on top of the current architecture. Java needs a reliable way for programmers to specify classname-based security, without having to wrestle with the complexity of the new architecture.

DDJ

Listing One

public class mutableBoolean
{ public boolean enabled; }

Back to Article

Listing Two

// SMdemo: SecurityManager demo program. See the comment
// in main() before running this program.

import java.lang.*;
import java.io.*;
import java.util.*;

class SMdemo
{
  static public void main(String[] args)
  {
    // Only enable one of the following two lines!
    doSmartTest();
    // doSwitchableTest();
  }
  ///////////////////////////////////////////////////////////////////
  static void doSmartTest()
  {
    String[] untrusted_class_names = { "untrusted1" };
    String[] trusted_class_names = { "trustedSmart" };

    smartSM sm = new smartSM(untrusted_class_names,
      trusted_class_names);

    AddDumpLine("SMdemo: About to install custom SM.");
    System.setSecurityManager(sm);
    AddDumpLine("SMdemo: Just installed custom SM, about to run test.");

    trustedSmart tSmart = new trustedSmart();
    tSmart.doTest();

    AddDumpLine("SMdemo: Just returned from trustedSmart; about to end.");
  }
  ///////////////////////////////////////////////////////////////////
  static void doSwitchableTest()
  {
  AddDumpLine("SMdemo: About to run test.");
  trustedSwitchable tSwitchable = new trustedSwitchable();
  tSwitchable.doTest();
  AddDumpLine("SMdemo: Just returned from trustedSwitchable; about to end.");
  }
  ///////////////////////////////////////////////////////////////////
  static private void AddDumpLine(String s)
  {
    try
    {
      RandomAccessFile ofile =
        new RandomAccessFile("SMdemo_dump.txt","rw");
      ofile.seek(ofile.length());
      ofile.writeBytes(s);
      ofile.writeBytes("\r\n");
      ofile.close();
    }
    catch (IOException e)
    {
      System.err.println(e);
      return;
    }
  }
}

Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.