diff --git a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java index bc687e1b69b..80948d4695c 100644 --- a/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java +++ b/jetty-jaspi/src/main/java/org/eclipse/jetty/security/jaspi/DefaultAuthConfigFactory.java @@ -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 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; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 5816266ab65..ef5cec5f884 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -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 _programmaticListeners = new CopyOnWriteArrayList<>(); private final List _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. *

@@ -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::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(); - } - } } diff --git a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java index dd95a72819d..e51024b03f0 100644 --- a/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java +++ b/jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java @@ -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)() -> + // 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) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java index aa024f549ed..4b53a983c7f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java @@ -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() - { - @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) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java index e983e56e370..be8ce1200cf 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java @@ -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)() -> clazz.getProtectionDomain()); + ProtectionDomain domain = clazz.getProtectionDomain(); if (domain != null) { CodeSource source = domain.getCodeSource(); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/security/SecurityUtils.java b/jetty-util/src/main/java/org/eclipse/jetty/util/security/SecurityUtils.java new file mode 100644 index 00000000000..28849f5648f --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/security/SecurityUtils.java @@ -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; + +/** + *

Collections of utility methods to deal with the scheduled removal + * of the security classes defined by JEP 411.

+ */ +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; + } + } + + /** + *

Checks the given permission, if the {@link #getSecurityManager() security manager} + * is set.

+ * + * @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) + { + } + } + + /** + *

Runs the given action with the calling context restricted + * to just the calling frame, not all the frames in the stack.

+ * + * @param action the action to run + * @return the result of running the action + * @param the type of the result + */ + public static T doPrivileged(PrivilegedAction 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 doPrivileged(MethodHandle doPrivileged, PrivilegedAction action) + { + try + { + return (T)doPrivileged.invoke(action); + } + catch (RuntimeException | Error x) + { + throw x; + } + catch (Throwable x) + { + throw new RuntimeException(x); + } + } + + private SecurityUtils() + { + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/PrivilegedThreadFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/PrivilegedThreadFactory.java index 5c506c7abfc..812fe96f1da 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/PrivilegedThreadFactory.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/PrivilegedThreadFactory.java @@ -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. + *

Convenience {@link Thread} factory that ensure threads are + * created without referencing any web application {@link ClassLoader}.

+ *

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.

+ *

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.

*/ 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. + *

Creates a new {@link Thread} from the given {@link Supplier}, + * without retaining the calling context.

+ * + * @param creator the action that creates the {@link Thread} + * @return a new {@link Thread} without retaining the calling context */ - static T newThread(Supplier newThreadSupplier) + static T newThread(PrivilegedAction creator) + { + return SecurityUtils.doPrivileged(creator); + } + + private PrivilegedThreadFactory() { - return AccessController.doPrivileged(new PrivilegedAction() - { - @Override - public T run() - { - return newThreadSupplier.get(); - } - }); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java index f6981ca6a7b..10aeb59bf3d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java @@ -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 _lifeCycles = new CopyOnWriteArrayList(); + private final List _lifeCycles = new CopyOnWriteArrayList<>(); /** * Default constructor for the singleton diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java index ca48f84d7e5..76e143d5a45 100644 --- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java @@ -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)() -> + 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 objects = new ArrayList<>(args.length); - for (String arg : args) + // For all arguments, parse XMLs + XmlConfiguration last = null; + List 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 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 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 started = new ArrayList<>(objects.size()); - for (Object obj : objects) + // For all objects created by XmlConfigurations, start them if they are lifecycles. + List 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) {