blob: babb04da60ef24b3f49ab9fca83f5a464745789d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans;
import java.awt.AWTPermission;
import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LoggingPermission;
import org.openide.util.Lookup;
import org.openide.util.WeakSet;
/** NetBeans security manager implementation.
* @author Ales Novak, Jesse Glick
*/
public class TopSecurityManager extends SecurityManager {
private static final boolean check = !Boolean.getBoolean("netbeans.security.nocheck"); // NOI18N
private static final Logger LOG = Logger.getLogger(TopSecurityManager.class.getName());
private Permission allPermission;
/* JVMPI sometimes deadlocks sync getForeignClassLoader
and Class.forName
*/
private static final Class<?> classLoaderClass = ClassLoader.class;
private static final Class<?> URLClass = URL.class;
private static final Class<?> runtimePermissionClass = RuntimePermission.class;
private static final Class<?> accessControllerClass = AccessController.class;
private static final Class<?> awtPermissionClass = AWTPermission.class;
private static SecurityManager fsSecManager;
private static final List<SecurityManager> delegates = new ArrayList<SecurityManager>();
/** Register a delegate security manager that can handle some checks for us.
* Currently only checkExit and checkTopLevelWindow are supported.
* @param sm the delegate to register
* @throws SecurityException without RuntimePermission "TopSecurityManager.register"
*/
public static void register(SecurityManager sm) throws SecurityException {
/* if (check) {
try {
AccessController.checkPermission(new RuntimePermission("TopSecurityManager.register")); // NOI18N
} catch (SecurityException se) {
// Something is probably wrong; debug it better.
ProtectionDomain pd = sm.getClass().getProtectionDomain();
CodeSource cs = pd.getCodeSource();
System.err.println("Code source of attempted secman: " + (cs != null ? cs.getLocation().toExternalForm() : "<none>")); // NOI18N
System.err.println("Its permissions: " + pd); // NOI18N
throw se;
}
}
*/
synchronized (delegates) {
if (delegates.contains(sm)) throw new SecurityException();
delegates.add(sm);
if (fsSecManager == null) {
for (Lookup.Item<SecurityManager> item : Lookup.getDefault().lookupResult(SecurityManager.class).allItems()) {
if (item != null && "org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager".equals(item.getId())) {//NOI18N
fsSecManager = item.getInstance();
break;
}
}
assert fsSecManager != null;
}
}
}
/** Unregister a delegate security manager.
* @param sm the delegate to unregister
* @throws SecurityException without RuntimePermission "TopSecurityManager.unregister"
*/
public static void unregister(SecurityManager sm) throws SecurityException {
/* if (check) {
AccessController.checkPermission(new RuntimePermission("TopSecurityManager.unregister")); // NOI18N
}
*/
synchronized (delegates) {
if (!delegates.contains(sm)) throw new SecurityException();
delegates.remove(sm);
}
}
/**
* constructs new TopSecurityManager
*/
public TopSecurityManager () {
allPermission = new AllPermission();
}
public @Override void checkExit(int status) throws SecurityException {
if (! check) {
return;
}
synchronized (delegates) {
Iterator<SecurityManager> it = delegates.iterator();
while (it.hasNext()) {
it.next().checkExit(status);
}
}
PrivilegedCheck.checkExit(status, this);
}
SecurityManager getSecurityManager() {
if (fsSecManager == null) {
synchronized (delegates) {
return fsSecManager;
}
}
return fsSecManager;
}
private void notifyDelete(String file) {
SecurityManager s = getSecurityManager();
if (s != null) {
s.checkDelete(file);
}
}
private void notifyRead(String file) {
SecurityManager s = getSecurityManager();
if (s != null) {
s.checkRead(file);
}
}
private void notifyWrite(String file) {
SecurityManager s = getSecurityManager();
if (s != null) {
s.checkWrite(file);
}
}
static boolean officialExit = false;
static Class<?>[] getStack() {
SecurityManager s = System.getSecurityManager();
TopSecurityManager t;
if (s instanceof TopSecurityManager) {
t = (TopSecurityManager)s;
} else {
t = new TopSecurityManager();
}
return t.getClassContext();
}
/** Can be called from core classes to exit the system.
* Direct calls to System.exit will not be honored, for safety.
* @param status the status code to exit with
* @see "#20751"
*/
public static void exit(int status) {
if (officialExit) {
return; // already inside a shutdown hook
}
officialExit = true;
System.exit(status);
}
final void checkExitImpl(int status, AccessControlContext acc) throws SecurityException {
if (!officialExit) {
throw new ExitSecurityException("Illegal attempt to exit early"); // NOI18N
}
super.checkExit(status);
}
@SuppressWarnings("deprecation")
public boolean checkTopLevelWindow(Object window) {
return checkTopLevelWindow(new AWTPermission("showWindowWithoutWarningBanner"), window); // NOI18N
}
private boolean checkTopLevelWindow(Permission windowPermission, Object window) {
synchronized (delegates) {
for (SecurityManager sm : delegates) {
sm.checkPermission(windowPermission, window);
}
}
return true;
}
/* XXX probably unnecessary:
// Hack against permissions of Launcher$AppLoader.
public void checkPackageAccess(String pckg) {
if (pckg == null) return;
if (pckg.startsWith("sun.")) { // NOI18N
if (inClazz("sun.misc.Launcher") || inClazz("java.lang.Class")) { // NOI18N
return;
}
}
super.checkPackageAccess(pckg);
}
private boolean inClazz(String s) {
Class[] classes = getClassContext();
int i = 0;
for (; (i < classes.length) && (classes[i] == TopSecurityManager.class); i++);
if (i == classes.length) {
return false;
}
return classes[i].getName().startsWith(s);
}
*/
/** Performance - all props accessible */
public @Override final void checkPropertyAccess(String x) {
if ("netbeans.debug.exceptions".equals(x)) { // NOI18N
// Get rid of this old system property.
for (Class<?> c : getClassContext()) {
if (c != TopSecurityManager.class &&
c != System.class &&
c != Boolean.class) {
String n = c.getName();
synchronized (warnedClassesNDE) {
if (warnedClassesNDE.add(n)) {
LOG.log(Level.WARNING, "use of system property netbeans.debug.exceptions has been obsoleted in favor of java.util.logging.Logger at {0}", findCallStackLine(n));
}
}
break;
}
}
}
if ("netbeans.home".equals(x) || "netbeans.user".equals(x)) { // NOI18N
// Control access to this system property.
for (Class<?> c : getClassContext()) {
if (c != TopSecurityManager.class &&
c != System.class &&
c != Boolean.class) {
String n = c.getName();
boolean log;
synchronized (warnedClassesNH) {
log = warnedClassesNH.add(n);
}
if (log) {
LOG.log(Level.WARNING, "use of system property {0} has been obsoleted in favor of InstalledFileLocator/Places at {1}", new Object[] {x, findCallStackLine(n)});
}
break;
}
}
}
}
private static String findCallStackLine(String callerClazz) {
for (StackTraceElement line : Thread.currentThread().getStackTrace()) {
if (line.getClassName().equals(callerClazz)) {
return line.toString();
}
}
return callerClazz;
}
private final Set<String> warnedClassesNDE = new HashSet<String>(25);
private static final Set<String> warnedClassesNH = new HashSet<String>(25);
static {
// XXX cleaner would be to use @SuppressWarnings, but that has Retention(SOURCE), and not all these can use org.netbeans.api.annotations.common
warnedClassesNH.add("org.openide.modules.Places");
warnedClassesNH.add("org.netbeans.MainImpl"); // NOI18N
warnedClassesNH.add("org.netbeans.MainImpl$BootClassLoader");
warnedClassesNH.add("org.netbeans.CLIHandler");
warnedClassesNH.add("org.netbeans.Stamps"); // NOI18N
warnedClassesNH.add("org.netbeans.Clusters"); // NOI18N
warnedClassesNH.add("org.netbeans.core.startup.InstalledFileLocatorImpl"); // NOI18N
warnedClassesNH.add("org.netbeans.core.startup.CLIOptions");
warnedClassesNH.add("org.netbeans.core.startup.preferences.RelPaths");
warnedClassesNH.add("org.netbeans.core.startup.layers.BinaryFS");
warnedClassesNH.add("org.netbeans.modules.netbinox.NetbinoxFactory");
warnedClassesNH.add("org.netbeans.updater.UpdateTracking"); // NOI18N
warnedClassesNH.add("org.netbeans.core.ui.ProductInformationPanel"); // #47429; NOI18N
warnedClassesNH.add("org.netbeans.lib.uihandler.LogFormatter");
warnedClassesNH.add("org.netbeans.modules.project.libraries.LibrariesStorage");
warnedClassesNH.add("org.netbeans.modules.j2ee.sun.ide.j2ee.PluginProperties"); // AS bundle is not in any cluster
warnedClassesNH.add("org.netbeans.modules.apisupport.project.universe.NbPlatform"); // defaultPlatformLocation
}
/* ----------------- private methods ------------- */
/**
* The method is empty. This is not "secure", but on the other hand,
* it reduces performance penalty of startup about 10%
*/
public @Override void checkRead(String file) {
notifyRead(file);
}
public @Override void checkRead(FileDescriptor fd) {
}
public @Override void checkWrite(FileDescriptor fd) {
}
/** The method has awful performance in super class */
public @Override void checkDelete(String file) {
notifyDelete(file);
try {
checkPermission(allPermission);
return;
} catch (SecurityException e) {
super.checkDelete(file);
}
}
/** The method has awful performance in super class */
public @Override void checkWrite(String file) {
notifyWrite(file);
try {
checkPermission(allPermission);
return;
} catch (SecurityException e) {
super.checkWrite(file);
}
}
/** Checks connect */
public @Override void checkConnect(String host, int port) {
if (! check) {
return;
}
try {
checkPermission(allPermission);
return;
} catch (SecurityException e) {
}
try {
super.checkConnect(host, port);
return;
} catch (SecurityException e) {
}
PrivilegedCheck.checkConnect(host, port, this);
}
final void checkConnectImpl(String host, int port) {
Class<?> insecure = getInsecureClass();
if (insecure != null) {
URL ctx = getClassURL(insecure);
if (ctx != null) {
try {
String fromHost = ctx.getHost();
InetAddress ia2 = InetAddress.getByName(host);
InetAddress ia3 = InetAddress.getByName(fromHost);
if (ia2.equals(ia3)) {
return;
}
} catch (UnknownHostException e) { // ignore
e.printStackTrace();
}
}
throw new SecurityException();
}
}
public @Override void checkConnect(String s, int port, Object context) {
checkConnect(s, port);
}
private final Set<Class<?>> warnedSunMisc = new WeakSet<>();
private final Set<String> callerWhiteList = createCallerWhiteList();
@SuppressWarnings("deprecation")
public void checkMemberAccess(Class<?> clazz, int which) {
final String n = clazz.getName();
if (n.startsWith("sun.misc")) { // NOI18N
Class<?> caller = null;
Class<?>[] arr = getClassContext();
for (int i = 0; i < arr.length; i++) {
if (arr[i] == TopSecurityManager.class) {
continue;
}
if (arr[i] != Class.class) {
caller = arr[i];
break;
}
}
StringBuilder msg = new StringBuilder();
msg.append("Dangerous reflection access to ").append(n).append(" by ").append(caller).append(" detected!");
if (caller != null && caller.getProtectionDomain() != null) {
CodeSource cs = caller.getProtectionDomain().getCodeSource();
msg.append("\ncode location: ").append(cs == null ? "null" : cs.getLocation());
}
if (caller != null && isDangerous(caller.getName(), n)) {
throw new SecurityException(msg.toString());
}
Level l;
if (caller != null && callerWhiteList.contains(caller.getName())) {
l = Level.FINEST;
} else {
l = Level.FINE;
assert (l = Level.INFO) != null;
}
if (!warnedSunMisc.add(caller)) {
LOG.log(l, msg.toString());
return;
}
Exception ex = new Exception(msg.toString()); // NOI18N
LOG.log(l, null, ex);
}
}
/**
* Create list of safe callers for {@link #checkMemberAccess(Class, int)}.
*/
private static Set<String> createCallerWhiteList() {
Set<String> wl = new HashSet<String>();
wl.add("org.netbeans.core.output2.FileMapStorage"); //NOI18N
wl.add("com.sun.tools.javac.util.CloseableURLClassLoader"); //NOI18N
wl.add("java.lang.Thread$1"); //NOI18N
wl.add("org.clank.support.NativeMemory"); //NOI18N
wl.add("org.apache.lucene.store.MMapDirectory$1"); //NOI18N
wl.add("org.apache.lucene.util.Constants"); //#217037
wl.add("org.apache.lucene.util.RamUsageEstimator");//#217037
wl.add("com.google.gson.internal.UnsafeAllocator"); //#219464 //NOI18N
wl.add("org.netbeans.modules.web.jspparser_ext.WebAppParseSupport$ParserClassLoader"); //#218690 // NOI18N
return wl;
}
private static boolean isDangerous(String caller, String accessTo) {
if ("com.sun.istack.tools.ProtectedTask".equals(caller)) { // NOI18N
if ("sun.misc.ClassLoaderUtil".equals(accessTo)) { // NOI18N
// calling ClassLoaderUtil is allowed
return false;
}
return true;
}
return false;
}
public @Override void checkPermission(Permission perm) {
// assert checkLogger(perm); //#178013 & JDK bug 1694855
checkSetSecurityManager(perm);
//
// part of makeSwingUseSpecialClipboard that makes it work on
// JDK 1.5
//
if (awtPermissionClass.isInstance(perm)) {
if ("accessClipboard".equals (perm.getName ())) { // NOI18N
ThreadLocal<Object> t;
synchronized (TopSecurityManager.class) {
t = CLIPBOARD_FORBIDDEN;
}
if (t == null) {
return;
}
if (t.get () != null) {
t.set (this);
throw new SecurityException ();
} else {
checkWhetherAccessedFromSwingTransfer ();
}
}
if ("showWindowWithoutWarningBanner".equals(perm.getName())) { // NOI18N
checkTopLevelWindow(perm, null);
}
}
return;
}
public @Override void checkPermission(Permission perm, Object context) {
// assert checkLogger(perm); //#178013 & JDK bug 1694855
checkSetSecurityManager(perm);
return;
}
private boolean checkLogger(Permission perm) {
//Do not allow foreign code to replace NetBeans logger with its own
//(particularly java.util.logging.FileLogger, which will deadlock)
//see http://netbeans.org/bugzilla/show_bug.cgi?id=178013
if (LoggingPermission.class.isInstance(perm)) {
//This code will run every time a logger is created; if this
//proves too performance-degrading, replace the assertion test
//with a system property so that mysterious logger-related deadlocks
//can still be done, but leave it off by default
Throwable t = new Exception().fillInStackTrace();
for (StackTraceElement e : t.getStackTrace()) {
//Currently no other reliable way to determine that the call
//is to reset the logging infrastructure, not just create
//a logger - see JDK bug 1694855
if ("java.util.logging.LogManager".equals(e.getClassName()) && "reset".equals(e.getMethodName())) { //NOI18N
SecurityException se = new SecurityException("Illegal attempt to reset system logger"); //NOI18N
throw se;
}
if ("java.util.logging.LogManager".equals(e.getClassName()) && "readConfiguration".equals(e.getMethodName())) { //NOI18N
SecurityException se = new SecurityException("Illegal attempt to replace system logger configuration"); //NOI18N
throw se;
}
}
}
return true;
}
public static void install() {
try {
System.setSecurityManager(new TopSecurityManager());
} catch (SecurityException ex) {
LOG.log(Level.WARNING, "Cannot associated own security manager"); // NOI18N
LOG.log(Level.INFO, "Cannot associated own security manager", ex); // NOI18N
}
}
static void uninstall() {
System.setSecurityManager(null);
}
/** Prohibits to set another SecurityManager */
private void checkSetSecurityManager(Permission perm) {
if (runtimePermissionClass.isInstance(perm)) {
if (perm.getName().equals("setSecurityManager")) { // NOI18N - hardcoded in java.lang
if (!check) {
return;
}
Class<?>[] arr = getClassContext();
boolean seenJava = false;
for (int i = 0; i < arr.length; i++) {
if (arr[i].getName().equals("org.netbeans.TopSecurityManager")) { // NOI18N
if (seenJava) {
// if the change of security manager is called from my own
// class or the class loaded by other classloader, then it is likely ok
return;
} else {
continue;
}
}
if (arr[i] != System.class) {
// if there is a non-java class on stack, skip and throw exception
break;
}
seenJava = true;
}
throw new SecurityException();
}
}
}
//
// public void checkMemberAccess(Class clazz, int which) {
// if ((which == java.lang.reflect.Member.PUBLIC) ||
// javax.swing.text.JTextComponent.class.isAssignableFrom(clazz)) {
// return;
// } else {
// super.checkMemberAccess(clazz, which);
// }
// }
//
private Class<?> getInsecureClass() {
Class<?>[] ctx = getClassContext();
boolean firstACClass = false;
LOOP: for (int i = 0; i < ctx.length; i++) {
if (ctx[i] == accessControllerClass) {
// privileged action is on the stack before an untrusted class loader
// #3950
if (firstACClass) {
return null;
} else {
firstACClass = true;
continue LOOP;
}
} else if (ctx[i].getClassLoader() != null) {
if (isSecureClass(ctx[i])) {
if (classLoaderClass.isAssignableFrom(ctx[i])) {
return null;
} else {
// OK process next one
continue LOOP;
}
}
return ctx[i];
} else if (classLoaderClass.isAssignableFrom(ctx[i])) { // cloader == null
return null; // foreign classloader wants to do work...
}
}
return null;
}
/** Checks if the class is loaded through the nbfs URL */
static boolean isSecureClass(final Class<?> clazz) {
URL source = getClassURL(clazz);
if (source != null) {
return isSecureProtocol(source.getProtocol());
} else {
return true;
}
}
/** @return a protocol through which was the class loaded (file://...) or null
*/
static URL getClassURL(Class<?> clazz) {
java.security.CodeSource cs = clazz.getProtectionDomain().getCodeSource();
if (cs != null) {
URL url = cs.getLocation();
return url;
} else { // PROXY CLASS?
return null;
}
}
static Field getUrlField(Class<?> clazz) {
if (urlField == null) {
try {
Field[] fds = clazz.getDeclaredFields();
for (int i = 0; i < fds.length; i++) {
if (fds[i].getType() == URLClass) {
fds[i].setAccessible(true);
urlField = fds[i];
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return urlField;
}
private static Field urlField;
/** @return Boolean.TRUE iff the string is a safe protocol (file, nbfs, ...) */
static boolean isSecureProtocol(String protocol) {
if (protocol.equals("http") || // NOI18N
protocol.equals("ftp") || // NOI18N
protocol.equals("rmi")) { // NOI18N
return false;
} else {
return true;
}
}
// Workaround for bug
//
// http://developer.java.sun.com/developer/bugParade/bugs/4818143.html
//
// sun.awt.datatransfer.ClipboardTransferable.getClipboardData() can hang
// for very long time (maxlong == eternity). We tries to avoid the hang by
// access the system clipboard from a separate thread. If the hang happens
// the thread will wait for the system clipboard forever but not the whole
// IDE. See also NbClipboard
private static ThreadLocal<Object> CLIPBOARD_FORBIDDEN;
/** Convinces Swing components that they should use special clipboard
* and not Toolkit.getSystemClipboard.
*
* @param clip clipboard to use
*/
public static void makeSwingUseSpecialClipboard (java.awt.datatransfer.Clipboard clip) {
try {
synchronized (TopSecurityManager.class) {
if (! (System.getSecurityManager() instanceof TopSecurityManager)) {
LOG.warning("Our manager has to be active: " + System.getSecurityManager());
return;
} // NOI18N
if (CLIPBOARD_FORBIDDEN != null) {
return;
}
CLIPBOARD_FORBIDDEN = new ThreadLocal<Object>();
CLIPBOARD_FORBIDDEN.set (clip);
}
javax.swing.JComponent source = new javax.swing.JPanel ();
javax.swing.TransferHandler.getPasteAction ().actionPerformed (
new java.awt.event.ActionEvent (source, 0, "")
);
javax.swing.TransferHandler.getCopyAction ().actionPerformed (
new java.awt.event.ActionEvent (source, 0, "")
);
javax.swing.TransferHandler.getCutAction ().actionPerformed (
new java.awt.event.ActionEvent (source, 0, "")
);
Object forb = CLIPBOARD_FORBIDDEN.get ();
CLIPBOARD_FORBIDDEN.set(null);
if (! (forb instanceof TopSecurityManager) ) {
System.err.println("Cannot install our clipboard to swing components, TopSecurityManager is not the security manager: " + forb); // NOI18N
return;
}
Class<?> appContextClass = ClassLoader.getSystemClassLoader().loadClass("sun.awt.AppContext"); // NOI18N
Method getAppContext = appContextClass.getMethod ("getAppContext"); // NOI18N
Object appContext = getAppContext.invoke (null, new Object[0]);
Class<?> actionClass = javax.swing.TransferHandler.getCopyAction ().getClass ();
java.lang.reflect.Field sandboxKeyField = actionClass.getDeclaredField ("SandboxClipboardKey"); // NOI18N
sandboxKeyField.setAccessible (true);
Object value = sandboxKeyField.get (null);
Method put = appContextClass.getMethod ("put", Object.class, Object.class); // NOI18N
put.invoke (appContext, new Object[] { value, clip });
} catch (ThreadDeath ex) {
throw ex;
} catch (Throwable t) {
t.printStackTrace();
} finally {
if (CLIPBOARD_FORBIDDEN != null) {
CLIPBOARD_FORBIDDEN.set (null);
}
}
}
/** Throws exception if accessed from javax.swing.TransferHandler class
*/
private void checkWhetherAccessedFromSwingTransfer () throws SecurityException {
boolean throwExc = false;
for (Class<?> c : getClassContext()) {
if (c.getName().equals("org.netbeans.editor.BaseCaret")) { // NOI18N
return;
}
if (c.getName().equals("javax.swing.TransferHandler$TransferAction")) { // NOI18N
throwExc = true;
}
}
if (throwExc) {
throw new SecurityException("All swing access to clipboard should be redirected to ExClipboard"); // NOI18N
}
}
private static final class PrivilegedCheck implements PrivilegedExceptionAction<Object> {
int action;
TopSecurityManager tsm;
// exit
int status;
AccessControlContext acc;
// connect
String host;
int port;
public PrivilegedCheck(int action, TopSecurityManager tsm) {
this.action = action;
this.tsm = tsm;
if (action == 0) {
acc = AccessController.getContext();
}
}
public Object run() throws Exception {
switch (action) {
case 0 :
tsm.checkExitImpl(status, acc);
break;
case 1 :
tsm.checkConnectImpl(host, port);
break;
default :
}
return null;
}
static void checkExit(int status, TopSecurityManager tsm) {
PrivilegedCheck pea = new PrivilegedCheck(0, tsm);
pea.status = status;
check(pea);
}
static void checkConnect(String host, int port, TopSecurityManager tsm) {
PrivilegedCheck pea = new PrivilegedCheck(1, tsm);
pea.host = host;
pea.port = port;
check(pea);
}
private static void check(PrivilegedCheck action) {
try {
AccessController.doPrivileged(action);
} catch (PrivilegedActionException e) {
Exception orig = e.getException();
if (orig instanceof RuntimeException) {
throw ((RuntimeException) orig);
}
orig.printStackTrace();
}
}
}
}