package org.autoplot; import java.awt.AWTPermission; import java.io.FilePermission; import java.net.InetAddress; import java.net.NetPermission; import java.net.URLPermission; import java.nio.file.LinkPermission; import java.security.AllPermission; import java.security.Permission; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.PropertyPermission; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.LoggingPermission; import javax.sound.sampled.AudioPermission; import org.autoplot.datasource.AutoplotSettings; import org.autoplot.hapi.HapiDataSource; /** * Security Manager which allows Autoplot access to: * * Imagined attacks which are still possible: * * Presently this just logs access. Level FINER implies that the property * access would be okay, and FINE implied this needs to be studied more. * * @author jbf */ public final class Sandbox { private static final Logger logger = org.das2.util.LoggerManager.getLogger("autoplot.security"); /** * return a security manager which allows: * This is likely to change from the implementation as things develop, so * please review this code if you must know precisely, or perform * experiments until you are satisfied with its operation. * @return */ public static SecurityManager getSandboxManager( ) { boolean linux= System.getProperty("os.name").equals("Linux"); boolean notWindows= !System.getProperty("os.name").equals("Windows"); List readWriteList= new ArrayList<>(); readWriteList.add( AutoplotSettings.settings().resolveProperty( AutoplotSettings.PROP_AUTOPLOTDATA ) ); if ( linux ) { readWriteList.add( System.getProperty("user.home")+"/.java/.userPrefs" ); } if ( notWindows ) { readWriteList.add( "/tmp" ); } String hapiData= HapiDataSource.getHapiCache(); readWriteList.add( hapiData ); ArrayList readOnlyList= new ArrayList<>(readWriteList); String path= System.getProperty("java.class.path"); if ( linux ) { readOnlyList.addAll(Arrays.asList(path.split(":"))); } readOnlyList.add( System.getProperty("java.home") ); if ( linux ) { readOnlyList.add( "/usr/share/fonts/" ); readOnlyList.add( System.getProperty("user.home")+"/.fonts/" ); } readOnlyList.add( "__classpath__"); // files on Jython classpath show this. readOnlyList.add( System.getProperty("user.home")+"/.das2rc" ); final List f_rwOkayHome= Collections.unmodifiableList(readWriteList); final List f_roOkayHome= Collections.unmodifiableList(readOnlyList); Set roBlacklist= new HashSet<>(); if ( notWindows ) { roBlacklist.add( "/etc" ); roBlacklist.add( "/sys" ); roBlacklist.add( "/boot" ); roBlacklist.add( "/proc" ); roBlacklist.add( "/dev" ); } Set f_roBlacklist= Collections.unmodifiableSet(roBlacklist); Set okayProps= new HashSet<>(); okayProps.add( "sun.awt.disablegrab" ); okayProps.add( "java.awt.headless" ); okayProps.add( "useHugeScatter" ); okayProps.add( "rangeChecking" ); okayProps.add( "sun.arch.data.model" ); okayProps.add( "line.separator" ); okayProps.add( "os.name" ); okayProps.add( "os.arch" ); okayProps.add( "user.dir" ); okayProps.add( "user.home" ); okayProps.add( "java.home" ); okayProps.add( "java.version" ); okayProps.add( "proxyHost" ); okayProps.add( "socksProxyHost" ); okayProps.add( "https.proxyHost" ); okayProps.add( "http.agent" ); okayProps.add( "enableReferenceCache" ); okayProps.add( "sun.awt.noerasebackground" ); okayProps.add( "HAPI_DATA" ); okayProps.add( "AUTOPLOT_DATA" ); okayProps.add( "cdawebHttps" ); okayProps.add( "python.cachedir" ); // this one is odd, because Autoplot writes to it, but we probably don't want others to write to it. Set f_okayProps= Collections.unmodifiableSet(okayProps); Set okayPermissions= new HashSet<>(); okayPermissions.add("accessClipboard"); okayPermissions.add("AWTPermission"); okayPermissions.add("suppressAccessChecks"); okayPermissions.add("accessDeclaredMembers"); Set f_okayPermissions= Collections.unmodifiableSet(okayPermissions); Set okaySecurityAccessTargets= new HashSet<>(); okaySecurityAccessTargets.add("putProviderProperty.SunRsaSign"); okaySecurityAccessTargets.add("putProviderProperty.SUN"); okaySecurityAccessTargets.add("putProviderProperty.Sun"); okaySecurityAccessTargets.add("putProviderProperty.XMLD"); Set f_okaySecurityAccessTargets= Collections.unmodifiableSet(okaySecurityAccessTargets); SecurityManager limitedSecurityManager = new SecurityManager() { @Override public ThreadGroup getThreadGroup() { return super.getThreadGroup(); } @Override public void checkSecurityAccess(String target) { for ( String s : f_okaySecurityAccessTargets ) { if ( target.startsWith(s) ) { logger.log(Level.FINER, "checkSecurityAccess({0})", s); return; } }; logger.log(Level.FINE, "checkSecurityAccess({0})", target); } @Override public void checkPackageDefinition(String pkg) { logger.log(Level.FINE, "checkPackageDefinition({0})", pkg); } @Override public void checkPropertyAccess(String key) { if ( f_okayProps.contains(key) ) { logger.log(Level.FINER, "checkPropertyAccess({0}) OK", key); } else { logger.log(Level.FINER, "checkPropertyAccess({0}) (All properties are okay)", key); } } @Override public void checkMulticast(InetAddress maddr) { logger.log(Level.FINE, "checkMulticast({0})", maddr); } @Override public void checkAccept(String host, int port) { logger.log(Level.FINER, "checkAccept({0}, {1})", new Object[]{host, port}); } @Override public void checkListen(int port) { logger.log(Level.FINER, "checkListen({0})", port); } @Override public void checkConnect(String host, int port, Object context) { logger.log(Level.FINER, "checkConnect({0}, {1}, {2}) NET", new Object[]{host, port, context}); } @Override public void checkConnect(String host, int port) { logger.log(Level.FINER, "checkConnect({0}, {1}) NET", new Object[]{host, port}); } /** * return true if the file is within a sandboxed filesystem where * Autoplot can write files as well as read them. * * @param file * @return */ private boolean whitelistFile( String file ) { if ( file.contains("..") ) { return false; } return f_rwOkayHome.stream().anyMatch((s) -> ( file.startsWith(s) )); } /** * return true if the file is within a sandboxed filesystem where * writing is allowed. * @param file * @return */ private boolean readOnlyWhitelistFile( String file ) { if ( file.contains("..") ) { return false; } return f_roOkayHome.stream().anyMatch((s) -> ( file.startsWith(s) )); } /** * return true if the file has been blacklisted for reading, for example * /etc, /boot * @param file * @return */ private boolean readOnlyBlacklistFile( String file ) { if ( file.contains("..") ) { return false; } return f_roBlacklist.stream().anyMatch((s) -> ( file.startsWith(s) )); } @Override public void checkDelete(String file) { if ( whitelistFile(file) ) { logger.log(Level.FINER, "checkDelete({0}) WHITELIST", file ); } else { throw new SecurityException("sandbox disallows delete of "+file); } } @Override public void checkWrite(String file) { if ( whitelistFile(file) ) { logger.log(Level.FINER, "checkWrite({0}) WHITELIST", file); } else { throw new SecurityException("sandbox disallows write of "+file); } } @Override public void checkRead(String file, Object context) { if ( readOnlyWhitelistFile(file) ) { logger.log( Level.FINER, "checkRead({0}, {1}) WHITELIST", new Object[]{file, context}); } else { if ( readOnlyBlacklistFile(file) ) { throw new SecurityException( String.format( "sandbox disallows read from "+ file) ); } else { logger.log(Level.FINER, "checkRead({0}, {1})", new Object[]{file, context}); } logger.log(Level.FINE, "checkRead({0}, {1})", new Object[]{file, context}); } } @Override public void checkRead(String file) { if ( readOnlyWhitelistFile(file) ) { logger.log(Level.FINER, "checkRead({0}) WHITELIST", file); } else { if ( readOnlyBlacklistFile(file) ) { throw new SecurityException( String.format( "checkRead( (%s) BLACKLIST", file) ); } else { logger.log(Level.FINER, "checkRead({0})", file); } } } @Override public void checkPermission(Permission perm, Object context) { logger.log(Level.FINE, "checkPermission( {0}, {1})", new Object[]{perm.getName(), context}); //super.checkPermission(perm); checkPermission( perm ); } @Override public void checkPermission(Permission perm) { if ( perm instanceof PropertyPermission ) { String name= perm.getName(); String actions= perm.getActions(); if ( f_okayProps.contains(name) ) { logger.log(Level.FINER, "checkPermission( PropertyPermission {0} ) OK", new Object[]{name}); } else { if ( "*".equals(name) && "read,write".equals(actions) ) { logger.log(Level.FINER, "checkPermission( PropertyPermission * read,write ) okay for Beans", new Object[]{name}); } else if ( "read".equals(actions) ) { logger.log(Level.FINER, "checkPermission( PropertyPermission read ) OK" ); } else { logger.log(Level.FINE, "checkPermission( PropertyPermission {0} )", new Object[]{name}); } } } else if ( perm instanceof LoggingPermission ) { logger.log(Level.FINER, "checkPermission( LoggingPermission {0} ) OK", new Object[]{perm}); } else if ( perm instanceof AWTPermission ) { logger.log(Level.FINER, "checkPermission( AWTPermission {0} ) OK", new Object[]{perm}); } else if ( perm instanceof URLPermission ) { logger.log(Level.FINER, "checkPermission( URLPermission {0} ) OK", new Object[]{perm}); } else if ( perm instanceof FilePermission ) { String name= perm.getName(); String action= perm.getActions(); if ( "read".equals(action) ) { String file= name; if ( readOnlyWhitelistFile(file) ) { logger.log(Level.FINER, "checkPermission( FilePermission({0}) WHITELIST", file); } else { if ( readOnlyBlacklistFile(file) ) { throw new SecurityException( String.format( "checkPermission( FilePermission(%s) BLACKLIST", file) ); } else { logger.log(Level.FINER, "checkPermission( FilePermission({0})", file); } } } else { super.checkPermission(perm); } } else if ( perm instanceof LinkPermission ) { throw new SecurityException( String.format( "sandbox disallows making filesystem links." ) ); } else if ( perm instanceof RuntimePermission ) { String name= perm.getName(); logger.log(Level.FINER, "checkPermission( RuntimePermission {0} ) OK", new Object[]{name}); } else if ( perm instanceof AllPermission ) { // there's code in the FileSystem codes that detects applet using this. logger.log(Level.FINER, "checkPermission( AllPermission ) FileSystem calls so OK" ); } else if ( perm instanceof NetPermission ) { logger.log(Level.FINER, "checkPermission( NetPermission ) OK, but this should be studied more." ); } else if ( perm instanceof AudioPermission ) { throw new SecurityException( String.format( "checkPermission( AudioPermission(%s) )", perm.getName() ) ); } else { String name= perm.getName(); if ( f_okayPermissions.contains(name) ) { logger.log(Level.FINER, "checkPermission( {0} ) OK", new Object[]{name}); } else { throw new SecurityException( String.format( "unrecognized permission: (%s)", name ) ); } } } }; return limitedSecurityManager; } /** * lock down this thread and all child threads so that they cannot do damage * to the running system. */ public static void enterSandbox( ) { System.setSecurityManager( getSandboxManager() ); } }