University of Virginia, Department of Computer Science
CS551: Security and Privacy on the Internet, Fall 2000

sun-jdk-1.1.7/javasrc/src/share/sun/sun/applet/AppletSecurity.java

/*
 * @(#)AppletSecurity.java      1.76 98/07/01
 *
 * Copyright 1995-1998 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 * 
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

package sun.applet;

import java.io.File;
import java.io.IOException;
import java.io.FileDescriptor;
import java.net.URL;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Hashtable;
import java.security.*;

import sun.security.provider.*;

/**
 * This class defines an applet security policy
 *
 * @version     1.76, 07/01/98
 */
public
class AppletSecurity extends SecurityManager {

    private static boolean debug = false;

    final static int NETWORK_NONE = 1;
    final static int NETWORK_HOST = 2;
    final static int NETWORK_UNRESTRICTED = 3;

    private final static int PRIVELEGED_PORT = 1024;

    boolean initACL;
    String readACL[];
    String writeACL[];
    int networkMode;

    // where we look for identities
    IdentityScope scope;

    // local cache for network-loaded classes
    Hashtable loadedClasses;


    /**
     * Construct and initialize.
     */
    public AppletSecurity() {
        reset();
    }

    /**
     * Reset from Properties
     */
    public void reset() {
        String str = System.getProperty("appletviewer.security.mode");
        if (str == null) {
            str = "host";
        }

        if (str.equals("unrestricted")) {
            networkMode = NETWORK_UNRESTRICTED;
        } else if (str.equals("none")) {
            networkMode = NETWORK_NONE;
        } else {
            networkMode = NETWORK_HOST;
        }

        // see if the system scope is one we know.
        // XXX: DO NOT REMOVE THIS IN 1.1.X. This call indirectly
        // initializes java.security.Security, as well as the 
        // IdentityDatbase *before* any applets are loaded.
        IdentityScope scope = IdentityScope.getSystemScope();
        if (scope instanceof IdentityDatabase) {
            this.scope = (IdentityDatabase)scope;
            debug("installing " + scope + " as the scope for signers.");
        } else {
            debug("no signer scope found.");        
        }

        loadedClasses = new Hashtable();
    }

    /**
     * True if called directly from an applet.
     */
    boolean fromApplet() {
        return classLoaderDepth() == 1;
    }

    /**
     * This method takes a set of signers and returns true if 
     * this set of signers implies that a class is trusted.
     * In this implementation, it returns true if any of the
     * signers is a SystemIdentity which is trusted.
     */
    protected boolean assessTrust(Object[] signers) {
        /* Remind: do we want to actually look into the scope here? */

        for (int i = 0; i < signers.length; i++) {

            if (signers[i] instanceof SystemIdentity) {
                SystemIdentity sysid = (SystemIdentity)signers[i];
                if (sysid.isTrusted()) {
                    return true;
                }

            } else if (signers[i] instanceof SystemSigner) {
                SystemSigner sysid = (SystemSigner)signers[i];
                if (sysid.isTrusted()) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * True if called indirectly from an untrusted applet.
     */
    boolean inApplet() {
        return inClassLoader();
    }

    /**
     * The only variable that currently affects whether an applet can
     * perform certain operations is the host it came from.
     */
    public Object getSecurityContext() {
        ClassLoader loader = currentClassLoader();

        if (loader == null) {
            return null;
        } else if (loader instanceof AppletClassLoader) {
            AppletClassLoader appletLoader = (AppletClassLoader)loader;
            return appletLoader.base;
        } else {
            throw new AppletSecurityException("getsecuritycontext.unknown");
        }
    }

    /**
     * Override getInCheck() and make it synchronized.
     * This is to plug a hole explained in bug: 4052456
     * InetAddress reveals IP addresses of machines behind firewall
     */
    public synchronized boolean getInCheck() {
        return super.getInCheck();
    }

    /**
     * Applets are not allowed to create class loaders, or even execute any
     * of ClassLoader's methods. The name of this method should be changed to
     * checkClassLoaderOperation or somesuch.
     */
    public synchronized void checkCreateClassLoader() {
        if (classLoaderDepth() == 2) {
            throw new AppletSecurityException("checkcreateclassloader");
        }
    }

    /**
     * Returns true if this threadgroup is in the applet's own thread
     * group. This will return false if there is no current class
     * loader.
     */
    protected boolean inThreadGroup(ThreadGroup g) {
        ClassLoader loader = currentClassLoader();
        
        /* If this class wasn't loaded by an AppletClassLoader, we have
           not eay of telling, for now. */

        if (loader instanceof AppletClassLoader) {

            AppletClassLoader appletLoader = (AppletClassLoader)loader;
            ThreadGroup appletGroup = appletLoader.getThreadGroup();
            
            return appletGroup.parentOf(g);
        }
        return false;
    }

    /**
     * Returns true of the threadgroup of thread is in the applet's
     * own threadgroup.
     */
    protected boolean inThreadGroup(Thread thread) {
        return inThreadGroup(thread.getThreadGroup());
    }

    /**
     * Applets are not allowed to manipulate threads outside
     * applet thread groups.
     */
    public synchronized void checkAccess(Thread t) {
        if (classLoaderDepth()==3 && (! inThreadGroup(t))) {
            throw new AppletSecurityException("checkaccess.thread");
        }
    }

    /**
     * Applets are not allowed to manipulate thread groups outside
     * applet thread groups.
     */
    public synchronized void checkAccess(ThreadGroup g) {
        if (classLoaderDepth() == 4 && (! inThreadGroup(g))) {
                throw new AppletSecurityException("checkaccess.threadgroup", g.toString());

        }
    }

    /**
     * Applets are not allowed to exit the VM.
     */
    public synchronized void checkExit(int status) {
        if (inApplet()) {
            throw new AppletSecurityException("checkexit", String.valueOf(status));
        }
    }

    /**
     * Applets are not allowed to fork processes.
     */
    public synchronized void checkExec(String cmd){
        if (inApplet()) {
            throw new AppletSecurityException("checkexec", cmd);
        }
    }

    /**
     * Applets are not allowed to link dynamic libraries.
     */
    public synchronized void checkLink(String lib){
        switch (classLoaderDepth()) {
          case 2: // Runtime.load
          case 3: // System.loadLibrary
            throw new AppletSecurityException("checklink", lib);
          default:
            break;
        }
    }

    /**
     * Applets are not allowed to access the entire system properties
     * list, only properties explicitly labeled as accessible to applets.
     */
    public synchronized void checkPropertiesAccess() {
        if (classLoaderDepth() == 2) {
            throw new AppletSecurityException("checkpropsaccess");
        }
    }

    /**
     * Applets can access the system property named by key
     * only if its twin key.applet property is set to true.
     * For example, the property java.home can be read by
     * applets only if java.home.applet is true.
     */
    public synchronized void checkPropertyAccess(String key) {
        if (classLoaderDepth() == 2) {
            String prop = System.getProperty(key + ".applet");
            boolean allow = new Boolean(prop).booleanValue();
            if (allow) {
                return;
            } else {
                throw new AppletSecurityException("checkpropsaccess.key", prop);
            }
        }
    }

    /**
     * Parse an ACL. Deals with "~" and "+"
     */
    void parseACL(Vector v, String path, String defaultPath) {
        String sep = System.getProperty("path.separator");
        StringTokenizer t = new StringTokenizer(path, sep);

        while (t.hasMoreTokens()) {
            String dir = t.nextToken();
            if (dir.startsWith("~")) {
                v.addElement(System.getProperty("user.home") + 
                             dir.substring(1));
            } else if (dir.equals("+")) {
                if (defaultPath != null) {
                    parseACL(v, defaultPath, null);
                }
            } else {
                v.addElement(dir);
            }
        }
    }

    /**
     * Parse an ACL.
     */
    String[] parseACL(String path, String defaultPath) {
        if (path == null) {
            return new String[0];
        }
        if (path.equals("*")) {
            return null;
        }
        Vector v = new Vector();
        parseACL(v, path, defaultPath);

        String acl[] = new String[v.size()];
        v.copyInto(acl);
        return acl;
    }

    /**
     * Initialize ACLs. Called only once.
     */
    void initializeACLs() {
        readACL = parseACL(System.getProperty("acl.read"), 
                           System.getProperty("acl.read.default"));
        writeACL = parseACL(System.getProperty("acl.write"), 
                            System.getProperty("acl.write.default"));
        initACL = true;
    }

    /**
     * Check if an applet can read a particular file.
     */
    public synchronized void checkRead(String file) {
        ClassLoader loader = currentClassLoader();

        /* If no class loader, it's a system class. */
        if (loader == null) {
            return;
        }

        /* If not an AppletClassLoader, we don't know what to do */
        if (! (loader instanceof AppletClassLoader)) {
            throw new AppletSecurityException("checkread.unknown", file);
        }
        AppletClassLoader appletLoader = (AppletClassLoader)loader;
        checkRead(file, appletLoader.base);
    }

    public synchronized void checkRead(String file, URL base) {
        if (base != null) {
            if (!initACL) {
                initializeACLs();
            }
            if (readACL == null) {
                return;
            }
            
            String realPath = null;
            try {
                realPath = (new File(file)).getCanonicalPath();
            } catch (IOException e) {
                throw new AppletSecurityException("checkread.exception1", e.getMessage(), file);
            }

            for (int i = readACL.length ; i-- > 0 ;) {
                if (realPath.startsWith(readACL[i])) {
                    return;
                }
            }
            // if the applet is loaded from a file URL, allow reading
            // in that directory
            String host = base.getHost();
            if (base.getProtocol().equals("file") &&
                (host == null || host.equals("") || host.equals("~") ||
                host.equals("localhost"))) {

                String dir = null;
                try {
                    dir = (new File(base.getFile()).getCanonicalPath());
                } catch (IOException e) { // shouldn't happen
                    throw new AppletSecurityException("checkread.exception2", e.toString());
                }
                if (realPath.startsWith(dir)) {
                    return;
                }
            }
            
            throw new AppletSecurityException("checkread", file, realPath);
        }
    }

    /**
     * Checks to see if the current context or the indicated context are
     * both allowed to read the given file name.
     * @param file the system dependent file name
     * @param context the alternate execution context which must also
     * be checked
     * @exception  SecurityException If the file is not found.
     */
    public void checkRead(String file, Object context) {
        checkRead(file);
        if (context != null) {
            checkRead(file, (URL) context);
        }
    }

    /**
     * Check if an applet can write a particular file.
     */
    public synchronized void checkWrite(String file) {
        if (inApplet()) {
            if (!initACL) {
                initializeACLs();
            }
            if (writeACL == null) {
                return;
            }

            String realPath = null;
            try {
                realPath = (new File(file)).getCanonicalPath();
            } catch (IOException e) {
                throw new AppletSecurityException("checkwrite.exception", e.getMessage(), file);
            }

            for (int i = writeACL.length ; i-- > 0 ;) {
                if (realPath.startsWith(writeACL[i])) {
                    return;
                }
            }
            throw new AppletSecurityException("checkwrite", file, realPath);
        }
    }

    /**
     * Check if an applet can delete a particular file.
     */
    public synchronized void checkDelete(String file) {
        checkWrite(file);
    }

    /**
     * Applets are not allowed to open file descriptors unless
     * it is done through a socket, in which case other access
     * restrictions still apply.
     */
    public synchronized void checkRead(FileDescriptor fd) {
        if ((inApplet() && !inClass("java.net.SocketInputStream"))
            || (!fd.valid()) ) {
            throw new AppletSecurityException("checkread.fd");
        }
    }

    /**
     * Applets are not allowed to open file descriptors unless
     * it is done through a socket, in which case other access
     * restrictions still apply.
     */
    public synchronized void checkWrite(FileDescriptor fd) {
        if ( (inApplet() && !inClass("java.net.SocketOutputStream")) 
             || (!fd.valid()) ) {
            throw new AppletSecurityException("checkwrite.fd");

        }
    }

    /**
     * Applets can only listen on unpriveleged ports > 1024
     * A port of 0 denotes an ephemeral system-assigned port
     * Which will be outside this range.  Note that java sockets
     * take an int and ports are really a u_short, but range
     * checking is done in ServerSocket & DatagramSocket, so the port policy
     * cannot be subverted by ints that wrap around to an illegal u_short.
     */
    public synchronized void checkListen(int port) {
        if (inApplet() && port > 0 && port < PRIVELEGED_PORT) {
            throw new AppletSecurityException("checklisten", String.valueOf(port));

        } 
    }

    /**
     * Applets can accept connectionions on unpriveleged ports, from
     * any hosts they can also connect to (typically host-of-origin
     * only, depending on the network security setting).
     */
    public synchronized void checkAccept(String host, int port) {
        if (inApplet() && port < PRIVELEGED_PORT) {
            throw new AppletSecurityException("checkaccept", host, String.valueOf(port));
        }
        checkConnect(host, port);
    }

    /**
     * Check if an applet can connect to the given host:port.
     */
    public synchronized void checkConnect(String host, int port) {
        ClassLoader loader = currentClassLoader();
        if (loader == null) {
            // Not called from an applet, so it is ok
            return;
        }

        // REMIND: This is only appropriate for our protocol handlers.
        int depth = classDepth("sun.net.www.http.HttpClient");
        if (depth > 1) {
            // Called through our http protocol handler
            return;
        }
        if (loader instanceof AppletClassLoader) {
            AppletClassLoader appletLoader = (AppletClassLoader)loader;
            checkConnect(appletLoader.base.getHost(), host);
        } else {
            throw new AppletSecurityException("checkconnect.unknown");
        }
    }

    /**
     * Checks to see if the applet and the indicated execution context
     * are both allowed to connect to the indicated host and port.
     */
    public void checkConnect(String host, int port, Object context) {
        checkConnect(host, port);
        if (context != null) {
            checkConnect(((URL) context).getHost(), host);
        }
    }

    public synchronized void checkConnect(String fromHost, String toHost, 
                                          boolean trustP) {
        if (fromHost == null) {
            return;
        }

        switch (networkMode) {
          case NETWORK_NONE:
            throw new AppletSecurityException("checkconnect.networknone", fromHost, toHost);


          case NETWORK_HOST:
            /*
             * The policy here is as follows:
             *
             * - if the strings match, and we know the IP address for it
             * we allow the connection. The calling code downstream will
             * substitute the IP in their request to the proxy if needed.
             * - if the strings don't match, and we can get the IP of
             * both hosts then
             *   - if the IPs match, we allow the connection
             *   - if they don't we throw an exception
             * - if the string match works and we don't know the IP address
             * then we consult the trustProxy property, and if that is true,
             * we allow the connection.
             * set inCheck so InetAddress knows it doesn't have to
             * check security.
             */
            try {
                inCheck = true;
                InetAddress toHostAddr, fromHostAddr;
                if (!fromHost.equals(toHost)) {
                    try {
                        // the only time we allow non-matching strings
                        // is when IPs and the IPs match.
                        toHostAddr = InetAddress.getByName(toHost);
                        fromHostAddr = InetAddress.getByName(fromHost);
                        
                        if (fromHostAddr.equals(toHostAddr)) {
                            return;
                        } else {
                            throw new AppletSecurityException("checkconnect.networkhost1", toHost, fromHost);

                        }
                    } catch (UnknownHostException e) {
                        throw new AppletSecurityException("checkconnect.networkhost2", toHost, fromHost);

                    }  
                } else {
                    try {
                        toHostAddr = InetAddress.getByName(toHost);
                        
                        // strings match: if we have IP, we're homefree, 
                        // otherwise we check the properties.
                        return;
                        // getBoolean really defaults to false.         
                    } catch (UnknownHostException e) {
                        if (trustP) {
                            return;
                        } else {
                            throw new AppletSecurityException("checkconnect.networkhost3", toHost);
                        }
                    }
                }
            } finally {
                inCheck = false;
            }

          case NETWORK_UNRESTRICTED:
            return;
        }
        throw new AppletSecurityException("checkconnect", fromHost, toHost);
    }

        

    /**
     * Check if an applet from a host can connect to another
     * host. This usually means that you need to determine whether
     * the hosts are inside or outside the firewall. For now applets
     * can only access the host they came from.
     */
    public synchronized void checkConnect(String fromHost, String toHost) {
        checkConnect(fromHost, toHost, Boolean.getBoolean("trustProxy"));
    }

    /**
     * Check if applet is allowed to use (join/leave/send/receive)
     * IP multicast.
     */
    public void checkMulticast(InetAddress maddr) {
        if (inApplet()) {
            throw new AppletSecurityException("checkMulticast");
        }
    }
        
    /**
     * Check if applet is allowed to use (join/leave/send/receive)
     * IP multicast.
     */
    public void checkMulticast(InetAddress maddr, byte ttl) {
        if (inApplet()) {
            throw new AppletSecurityException("checkMulticast");
        }
    }
        
    /**
     * Checks to see if top-level windows can be created by the caller.
     */
    public synchronized boolean checkTopLevelWindow(Object window) {
        if (inClassLoader()) {
            /* XXX: this used to return depth > 3. However, this lets */
            /* some applets create frames without warning strings. */
            return false;
        }
        return true;
    }

    /**
     * Check if an applet can access a package.
     */
    public synchronized void checkPackageAccess(String pkg) {

        if (!inClassLoader())
            return;
        int i = pkg.indexOf('.');

        while (i > 0) {
            String subpkg = pkg.substring(0,i);
            if (Boolean.getBoolean("package.restrict.access." + subpkg)) {
                throw new AppletSecurityException("checkpackageaccess", pkg);
            }
            i = pkg.indexOf('.',i+1);
        }
    }

    /**
     * Check if an applet can define classes in a package.
     */
    public synchronized void checkPackageDefinition(String pkg) {

        if (!inClassLoader())
            return;
        int i = pkg.indexOf('.');

        while (i > 0) {
            String subpkg = pkg.substring(0,i);
            if (Boolean.getBoolean("package.restrict.definition." + subpkg)) {
                throw new AppletSecurityException("checkpackagedefinition", pkg);
            }
            i = pkg.indexOf('.',i+1);
        }
    }


    /**
     * Check if an applet can set a networking-related object factory.
     */
    public synchronized void checkSetFactory() {
        if (inApplet() && !inClass("sun.net.www.MimeTable")) {
            throw new AppletSecurityException("cannotsetfactory");
        }
    }

    /**
     * Check if client is allowed to reflective access to a member or
     * a set of members for the specified class.  Once initial access
     * is granted, the reflected members can be queried for
     * identifying information, but can only be used
     * (via get, set, invoke, or newInstance) with standard Java
     * language access control.
     *
     * 

The policy is to deny untrusted clients access to * declared members of classes other than those loaded * via the same class loader. All other accesses are granted. * * XXX: Should VerifyClassAccess here? Should Class.forName do it? */ public void checkMemberAccess(Class clazz, int which) { if (which != java.lang.reflect.Member.PUBLIC) { ClassLoader currentLoader = currentClassLoader(); if (currentLoader != null && (classLoaderDepth() <= 3)) { /* Client is an untrusted class loaded by currentLoader */ if (currentLoader != clazz.getClassLoader()) { throw new AppletSecurityException("checkmemberaccess"); } } } } /** * Checks to see if an applet can initiate a print job request. */ public void checkPrintJobAccess() { if (inApplet()) { throw new AppletSecurityException("checkgetprintjob"); } } /** * Checks to see if an applet can get System Clipboard access. */ public void checkSystemClipboardAccess() { if (inApplet()) { throw new AppletSecurityException("checksystemclipboardaccess"); } } /** * Checks to see if an applet can get EventQueue access. */ public void checkAwtEventQueueAccess() { if (inApplet()) { throw new AppletSecurityException("checkawteventqueueaccess"); } } /** * Checks to see if an applet can perform a given operation. */ public void checkSecurityAccess(String action) { if (inApplet()) { throw new AppletSecurityException("checksecurityaccess", action); } } /** * Returns the thread group of the applet. We consult the classloader * if there is one. */ public ThreadGroup getThreadGroup() { /* First we check if any classloaded thing is on the stack. */ ClassLoader loader = currentClassLoader(); if (loader != null && (loader instanceof AppletClassLoader)) { AppletClassLoader appletLoader = (AppletClassLoader)loader; return appletLoader.getThreadGroup(); } else { return super.getThreadGroup(); } } public void debug(String s) { if (debug) { System.err.println(s); } } }


CS 655 University of Virginia
Department of Computer Science
CS 551: Security and Privacy on the Internet
David Evans
evans@virginia.edu