Fixes #6184 - JEP-411 will deprecate/remove the SecurityManager from … (#9616)

* Fixes #6184 - JEP-411 will deprecate/remove the SecurityManager from the JVM.

Removed usages of `SecurityManager` and `AccessControlller.doPrivileged()`.
In places where they are still necessary, now using reflection via newly introduced `SecurityUtils` class.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2023-04-06 10:59:02 +02:00 committed by GitHub
parent 795315f6ff
commit 2c61011de1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 257 additions and 168 deletions

View File

@ -23,6 +23,7 @@ import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import org.eclipse.jetty.util.security.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -60,9 +61,7 @@ public class DefaultAuthConfigFactory extends AuthConfigFactory
@Override
public String registerConfigProvider(String className, Map properties, String layer, String appContext, String description)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("registerAuthConfigProvider"));
checkPermission("registerAuthConfigProvider");
String key = getKey(layer, appContext);
AuthConfigProvider configProvider = createConfigProvider(className, properties);
@ -76,9 +75,7 @@ public class DefaultAuthConfigFactory extends AuthConfigFactory
@Override
public String registerConfigProvider(AuthConfigProvider provider, String layer, String appContext, String description)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("registerAuthConfigProvider"));
checkPermission("registerAuthConfigProvider");
String key = getKey(layer, appContext);
DefaultRegistrationContext context = new DefaultRegistrationContext(provider, layer, appContext, description, false);
@ -91,9 +88,7 @@ public class DefaultAuthConfigFactory extends AuthConfigFactory
@Override
public boolean removeRegistration(String registrationID)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("removeAuthRegistration"));
checkPermission("removeAuthRegistration");
DefaultRegistrationContext registrationContext = _registrations.remove(registrationID);
if (registrationContext == null)
@ -106,9 +101,7 @@ public class DefaultAuthConfigFactory extends AuthConfigFactory
@Override
public String[] detachListener(RegistrationListener listener, String layer, String appContext)
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("detachAuthListener"));
checkPermission("detachAuthListener");
List<String> registrationIds = new ArrayList<>();
for (DefaultRegistrationContext registration : _registrations.values())
@ -145,13 +138,16 @@ public class DefaultAuthConfigFactory extends AuthConfigFactory
@Override
public void refresh()
{
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new AuthPermission("refreshAuth"));
checkPermission("refreshAuth");
// TODO: maybe we should re-construct providers created from classname.
}
private static void checkPermission(String permission)
{
SecurityUtils.checkPermission(new AuthPermission(permission));
}
private static String getKey(String layer, String appContext)
{
return layer + "/" + appContext;

View File

@ -20,8 +20,6 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -87,6 +85,7 @@ import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.security.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -219,7 +218,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private int _maxFormKeys = Integer.getInteger(MAX_FORM_KEYS_KEY, DEFAULT_MAX_FORM_KEYS);
private int _maxFormContentSize = Integer.getInteger(MAX_FORM_CONTENT_SIZE_KEY, DEFAULT_MAX_FORM_CONTENT_SIZE);
private boolean _compactPath = false;
private boolean _usingSecurityManager = System.getSecurityManager() != null;
private boolean _usingSecurityManager = getSecurityManager() != null;
private final List<EventListener> _programmaticListeners = new CopyOnWriteArrayList<>();
private final List<ServletContextListener> _servletContextListeners = new CopyOnWriteArrayList<>();
@ -326,7 +325,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
public void setUsingSecurityManager(boolean usingSecurityManager)
{
if (usingSecurityManager && System.getSecurityManager() == null)
if (usingSecurityManager && getSecurityManager() == null)
throw new IllegalStateException("No security manager");
_usingSecurityManager = usingSecurityManager;
}
@ -2114,6 +2113,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
_aliasChecks.clear();
}
private static Object getSecurityManager()
{
return SecurityUtils.getSecurityManager();
}
/**
* Context.
* <p>
@ -2561,11 +2565,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
// check to see if the classloader of the caller is the same as the context
// classloader, or a parent of it, as required by the javadoc specification.
// Wrap in a PrivilegedAction so that only Jetty code will require the
// "createSecurityManager" permission, not also application code that calls this method.
Caller caller = AccessController.doPrivileged((PrivilegedAction<Caller>)Caller::new);
ClassLoader callerLoader = caller.getCallerClassLoader(2);
ClassLoader callerLoader = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.getCallerClass()
.getClassLoader();
while (callerLoader != null)
{
if (callerLoader == _classLoader)
@ -2573,7 +2575,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
else
callerLoader = callerLoader.getParent();
}
System.getSecurityManager().checkPermission(new RuntimePermission("getClassLoader"));
SecurityUtils.checkPermission(new RuntimePermission("getClassLoader"));
return _classLoader;
}
}
@ -3103,17 +3105,4 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
*/
void exitScope(Context context, Request request);
}
private static class Caller extends SecurityManager
{
public ClassLoader getCallerClassLoader(int depth)
{
if (depth < 0)
return null;
Class<?>[] classContext = getClassContext();
if (classContext.length <= depth)
return null;
return classContext[depth].getClassLoader();
}
}
}

View File

@ -16,8 +16,6 @@ package org.eclipse.jetty.logging;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Locale;
import java.util.Properties;
@ -161,29 +159,26 @@ public class JettyLoggerConfiguration
*/
public JettyLoggerConfiguration load(ClassLoader loader)
{
return AccessController.doPrivileged((PrivilegedAction<JettyLoggerConfiguration>)() ->
// First see if the jetty-logging.properties object exists in the classpath.
// * This is an optional feature used by embedded mode use, and test cases to allow for early
// * configuration of the Log class in situations where access to the System.properties are
// * either too late or just impossible.
load(readProperties(loader, "jetty-logging.properties"));
// Next see if an OS specific jetty-logging.properties object exists in the classpath.
// This really for setting up test specific logging behavior based on OS.
String osName = System.getProperty("os.name");
if (osName != null && osName.length() > 0)
{
// First see if the jetty-logging.properties object exists in the classpath.
// * This is an optional feature used by embedded mode use, and test cases to allow for early
// * configuration of the Log class in situations where access to the System.properties are
// * either too late or just impossible.
load(readProperties(loader, "jetty-logging.properties"));
// NOTE: cannot use jetty-util's StringUtil.replace() as it may initialize logging itself.
osName = osName.toLowerCase(Locale.ENGLISH).replace(' ', '-');
load(readProperties(loader, "jetty-logging-" + osName + ".properties"));
}
// Next see if an OS specific jetty-logging.properties object exists in the classpath.
// This really for setting up test specific logging behavior based on OS.
String osName = System.getProperty("os.name");
if (osName != null && osName.length() > 0)
{
// NOTE: cannot use jetty-util's StringUtil.replace() as it may initialize logging itself.
osName = osName.toLowerCase(Locale.ENGLISH).replace(' ', '-');
load(readProperties(loader, "jetty-logging-" + osName + ".properties"));
}
// Now load the System.properties as-is into the properties,
// these values will override any key conflicts in properties.
load(System.getProperties());
return this;
});
// Now load the System.properties as-is into the properties,
// these values will override any key conflicts in properties.
load(System.getProperties());
return this;
}
public String getString(String key, String defValue)

View File

@ -13,9 +13,6 @@
package org.eclipse.jetty.util;
import java.security.AccessController;
import java.security.PrivilegedAction;
/**
* MemoryUtils provides an abstraction over memory properties and operations.
*/
@ -25,18 +22,11 @@ public class MemoryUtils
static
{
final int defaultValue = 64;
int defaultValue = 64;
int value = defaultValue;
try
{
value = Integer.parseInt(AccessController.doPrivileged(new PrivilegedAction<String>()
{
@Override
public String run()
{
return System.getProperty("org.eclipse.jetty.util.cacheLineBytes", String.valueOf(defaultValue));
}
}));
value = Integer.parseInt(System.getProperty("org.eclipse.jetty.util.cacheLineBytes", String.valueOf(defaultValue)));
}
catch (Exception ignored)
{

View File

@ -26,9 +26,7 @@ import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
@ -671,7 +669,7 @@ public class TypeUtil
{
try
{
ProtectionDomain domain = AccessController.doPrivileged((PrivilegedAction<ProtectionDomain>)() -> clazz.getProtectionDomain());
ProtectionDomain domain = clazz.getProtectionDomain();
if (domain != null)
{
CodeSource source = domain.getCodeSource();

View File

@ -0,0 +1,124 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util.security;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.security.Permission;
import java.security.PrivilegedAction;
/**
* <p>Collections of utility methods to deal with the scheduled removal
* of the security classes defined by <a href="https://openjdk.org/jeps/411">JEP 411</a>.</p>
*/
public class SecurityUtils
{
private static final MethodHandle doPrivileged = lookup();
private static MethodHandle lookup()
{
try
{
// Use reflection to work with Java versions that have and don't have AccessController.
Class<?> klass = ClassLoader.getPlatformClassLoader().loadClass("java.security.AccessController");
MethodHandles.Lookup lookup = MethodHandles.lookup();
return lookup.findStatic(klass, "doPrivileged", MethodType.methodType(Object.class, PrivilegedAction.class));
}
catch (Throwable x)
{
return null;
}
}
/**
* @return the current security manager, if available
*/
public static Object getSecurityManager()
{
try
{
// Use reflection to work with Java versions that have and don't have SecurityManager.
return System.class.getMethod("getSecurityManager").invoke(null);
}
catch (Throwable ignored)
{
return null;
}
}
/**
* <p>Checks the given permission, if the {@link #getSecurityManager() security manager}
* is set.</p>
*
* @param permission the permission to check
* @throws SecurityException if the permission check fails
*/
public static void checkPermission(Permission permission) throws SecurityException
{
Object securityManager = SecurityUtils.getSecurityManager();
if (securityManager == null)
return;
try
{
securityManager.getClass().getMethod("checkPermission")
.invoke(securityManager, permission);
}
catch (SecurityException x)
{
throw x;
}
catch (Throwable ignored)
{
}
}
/**
* <p>Runs the given action with the calling context restricted
* to just the calling frame, not all the frames in the stack.</p>
*
* @param action the action to run
* @return the result of running the action
* @param <T> the type of the result
*/
public static <T> T doPrivileged(PrivilegedAction<T> action)
{
// Keep this method short and inlineable.
MethodHandle methodHandle = doPrivileged;
if (methodHandle == null)
return action.run();
return doPrivileged(methodHandle, action);
}
@SuppressWarnings("unchecked")
private static <T> T doPrivileged(MethodHandle doPrivileged, PrivilegedAction<T> action)
{
try
{
return (T)doPrivileged.invoke(action);
}
catch (RuntimeException | Error x)
{
throw x;
}
catch (Throwable x)
{
throw new RuntimeException(x);
}
}
private SecurityUtils()
{
}
}

View File

@ -13,39 +13,46 @@
package org.eclipse.jetty.util.thread;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.function.Supplier;
import org.eclipse.jetty.util.security.SecurityUtils;
/**
* Convenience class to ensure that a new Thread is created
* inside a privileged block.
*
* This prevents the Thread constructor
* from pinning the caller's context classloader. This happens
* when the Thread constructor takes a snapshot of the current
* calling context - which contains ProtectionDomains that may
* reference the context classloader - and remembers it for the
* lifetime of the Thread.
* <p>Convenience {@link Thread} factory that ensure threads are
* created without referencing any web application {@link ClassLoader}.</p>
* <p>Up to Java 17, the {@code Thread} constructor was taking a
* snapshot of the calling context, which may contain a {@link ProtectionDomain}
* that references a web application {@code ClassLoader}
* (for example if the creation of the {@code Thread} was triggered
* by some operation performed by the web application).
* The {@code Thread} might then be pooled and prevent the
* web application {@code ClassLoader} to be garbage collected
* when the web application is undeployed.
* For this reason, {@code Thread}s must be created in a privileged
* action, which restricts the calling context to just the caller
* frame, not all the frames in the stack.</p>
* <p>Since Java 18 and the removal of the Java security manager
* and related classes by JEP 411, {@code Thread}s do not retain
* the calling context, so there is no need to create them in a
* privileged action.</p>
*/
class PrivilegedThreadFactory
{
/**
* Use a Supplier to make a new thread, calling it within
* a privileged block to prevent classloader pinning.
*
* @param newThreadSupplier a Supplier to create a fresh thread
* @return a new thread, protected from classloader pinning.
* <p>Creates a new {@link Thread} from the given {@link Supplier},
* without retaining the calling context.</p>
*
* @param creator the action that creates the {@link Thread}
* @return a new {@link Thread} without retaining the calling context
*/
static <T extends Thread> T newThread(Supplier<T> newThreadSupplier)
static <T extends Thread> T newThread(PrivilegedAction<T> creator)
{
return SecurityUtils.doPrivileged(creator);
}
private PrivilegedThreadFactory()
{
return AccessController.doPrivileged(new PrivilegedAction<T>()
{
@Override
public T run()
{
return newThreadSupplier.get();
}
});
}
}

View File

@ -31,14 +31,11 @@ import org.slf4j.LoggerFactory;
public class ShutdownThread extends Thread
{
private static final Logger LOG = LoggerFactory.getLogger(ShutdownThread.class);
private static final ShutdownThread _thread = PrivilegedThreadFactory.newThread(() ->
{
return new ShutdownThread();
});
private static final ShutdownThread _thread = PrivilegedThreadFactory.newThread(ShutdownThread::new);
private final AutoLock _lock = new AutoLock();
private boolean _hooked;
private final List<LifeCycle> _lifeCycles = new CopyOnWriteArrayList<LifeCycle>();
private final List<LifeCycle> _lifeCycles = new CopyOnWriteArrayList<>();
/**
* Default constructor for the singleton

View File

@ -30,8 +30,6 @@ import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -493,7 +491,7 @@ public class XmlConfiguration
}
catch (Exception e)
{
LOG.warn("Config error {} at {} in {}", e.toString(), node, _configuration);
LOG.warn("Config error {} at {} in {}", e, node, _configuration);
throw e;
}
}
@ -1815,86 +1813,81 @@ public class XmlConfiguration
{
try
{
AccessController.doPrivileged((PrivilegedExceptionAction<Void>)() ->
Properties properties = new Properties();
properties.putAll(System.getProperties());
// For all arguments, load properties
if (LOG.isDebugEnabled())
LOG.debug("args={}", Arrays.asList(args));
for (String arg : args)
{
Properties properties = new Properties();
properties.putAll(System.getProperties());
// For all arguments, load properties
if (LOG.isDebugEnabled())
LOG.debug("args={}", Arrays.asList(args));
for (String arg : args)
if (arg.indexOf('=') >= 0)
{
if (arg.indexOf('=') >= 0)
int i = arg.indexOf('=');
properties.put(arg.substring(0, i), arg.substring(i + 1));
}
else if (arg.toLowerCase(Locale.ENGLISH).endsWith(".properties"))
{
try (InputStream inputStream = Resource.newResource(arg).getInputStream())
{
int i = arg.indexOf('=');
properties.put(arg.substring(0, i), arg.substring(i + 1));
}
else if (arg.toLowerCase(Locale.ENGLISH).endsWith(".properties"))
{
try (InputStream inputStream = Resource.newResource(arg).getInputStream())
{
properties.load(inputStream);
}
properties.load(inputStream);
}
}
}
// For all arguments, parse XMLs
XmlConfiguration last = null;
List<Object> objects = new ArrayList<>(args.length);
for (String arg : args)
// For all arguments, parse XMLs
XmlConfiguration last = null;
List<Object> objects = new ArrayList<>(args.length);
for (String arg : args)
{
if (!arg.toLowerCase(Locale.ENGLISH).endsWith(".properties") && (arg.indexOf('=') < 0))
{
if (!arg.toLowerCase(Locale.ENGLISH).endsWith(".properties") && (arg.indexOf('=') < 0))
XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(arg));
if (last != null)
configuration.getIdMap().putAll(last.getIdMap());
if (properties.size() > 0)
{
XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(arg));
if (last != null)
configuration.getIdMap().putAll(last.getIdMap());
if (properties.size() > 0)
{
Map<String, String> props = new HashMap<>();
properties.entrySet().stream()
.forEach(objectObjectEntry -> props.put(objectObjectEntry.getKey().toString(),
String.valueOf(objectObjectEntry.getValue())));
configuration.getProperties().putAll(props);
}
Object obj = configuration.configure();
if (obj != null && !objects.contains(obj))
objects.add(obj);
last = configuration;
Map<String, String> props = new HashMap<>();
properties.forEach((key, value) -> props.put(key.toString(),
String.valueOf(value)));
configuration.getProperties().putAll(props);
}
Object obj = configuration.configure();
if (obj != null && !objects.contains(obj))
objects.add(obj);
last = configuration;
}
}
if (LOG.isDebugEnabled())
LOG.debug("objects={}", objects);
if (LOG.isDebugEnabled())
LOG.debug("objects={}", objects);
// For all objects created by XmlConfigurations, start them if they are lifecycles.
List<LifeCycle> started = new ArrayList<>(objects.size());
for (Object obj : objects)
// For all objects created by XmlConfigurations, start them if they are lifecycles.
List<LifeCycle> started = new ArrayList<>(objects.size());
for (Object obj : objects)
{
if (obj instanceof LifeCycle)
{
if (obj instanceof LifeCycle)
LifeCycle lc = (LifeCycle)obj;
if (!lc.isRunning())
{
LifeCycle lc = (LifeCycle)obj;
if (!lc.isRunning())
lc.start();
if (lc.isStarted())
started.add(lc);
else
{
lc.start();
if (lc.isStarted())
started.add(lc);
else
// Failed to start a component, so stop all started components
Collections.reverse(started);
for (LifeCycle slc : started)
{
// Failed to start a component, so stop all started components
Collections.reverse(started);
for (LifeCycle slc : started)
{
slc.stop();
}
break;
slc.stop();
}
break;
}
}
}
return null;
});
}
}
catch (Error | Exception e)
{