Remove PrivilegedOperations (#127726)

With the SecurityManager gone, the PrivilegedOperations class is no
longer needed, these operations can be called directly.
This commit is contained in:
Ryan Ernst 2025-05-06 10:50:49 -07:00 committed by GitHub
parent 8bb7dc4058
commit b78ac7c94c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 40 additions and 203 deletions

View file

@ -9,11 +9,11 @@
package org.elasticsearch.core.internal.provider;
import org.elasticsearch.core.IOUtils;
import org.elasticsearch.core.Strings;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.core.internal.provider.EmbeddedImplClassLoader.CompoundEnumeration;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PrivilegedOperations;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
@ -195,13 +195,10 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
Path outerJar = topLevelDir.resolve("impl.jar");
JarUtils.createJarWithEntries(outerJar, jarEntries);
URL[] urls = new URL[] { outerJar.toUri().toURL() };
URLClassLoader parent = URLClassLoader.newInstance(urls, EmbeddedImplClassLoaderTests.class.getClassLoader());
try {
try (URLClassLoader parent = loader(urls)) {
EmbeddedImplClassLoader loader = EmbeddedImplClassLoader.getInstance(parent, "x-foo");
Class<?> c = loader.loadClass("p.FooBar");
return c.getConstructor().newInstance();
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
@ -245,8 +242,7 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
Path outerJar = topLevelDir.resolve("impl.jar");
JarUtils.createJarWithEntriesUTF(outerJar, jarEntries);
URL[] urls = new URL[] { outerJar.toUri().toURL() };
URLClassLoader parent = URLClassLoader.newInstance(urls, EmbeddedImplClassLoaderTests.class.getClassLoader());
try {
try (URLClassLoader parent = loader(urls)) {
EmbeddedImplClassLoader loader = EmbeddedImplClassLoader.getInstance(parent, "res");
// resource in a valid java package dir
URL url = loader.findResource("p/res.txt");
@ -274,8 +270,6 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
hasToString(endsWith("impl.jar!/IMPL-JARS/res/zoo-impl.jar/A-C/res.txt"))
)
);
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
@ -326,9 +320,7 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
containsInAnyOrder("Parent Resource", "Embedded Resource")
);
} finally {
for (URLClassLoader closeable : closeables) {
PrivilegedOperations.closeURLClassLoader(closeable);
}
IOUtils.close(closeables);
}
}
@ -463,9 +455,7 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
assertThat(new String(is.readAllBytes(), UTF_8), is("Hello World" + expectedVersion));
}
} finally {
for (URLClassLoader closeable : closeables) {
PrivilegedOperations.closeURLClassLoader(closeable);
}
IOUtils.close(closeables);
}
}
@ -493,8 +483,7 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
Path outerJar = topLevelDir.resolve("impl.jar");
JarUtils.createJarWithEntriesUTF(outerJar, jarEntries);
URL[] urls = new URL[] { outerJar.toUri().toURL() };
URLClassLoader parent = URLClassLoader.newInstance(urls, EmbeddedImplClassLoaderTests.class.getClassLoader());
try {
try (URLClassLoader parent = loader(urls)) {
embedLoader = EmbeddedImplClassLoader.getInstance(parent, "res");
Class<?> c = embedLoader.loadClass("java.lang.Object");
@ -514,8 +503,6 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
expectThrows(NPE, () -> embedLoader.getResourceAsStream(null));
expectThrows(NPE, () -> embedLoader.resources(null));
expectThrows(NPE, () -> embedLoader.loadClass(null));
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
@ -542,8 +529,7 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
JarUtils.createJarWithEntries(outerJar, jarEntries);
URL[] urls = new URL[] { outerJar.toUri().toURL() };
URLClassLoader parent = URLClassLoader.newInstance(urls, EmbeddedImplClassLoaderTests.class.getClassLoader());
try {
try (URLClassLoader parent = loader(urls)) {
EmbeddedImplClassLoader loader = EmbeddedImplClassLoader.getInstance(parent, "blah");
Class<?> c = loader.loadClass("p.Foo");
Object obj = c.getConstructor().newInstance();
@ -555,8 +541,6 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
expectThrows(CNFE, () -> loader.loadClass("p.Unknown"));
expectThrows(CNFE, () -> loader.loadClass("q.Unknown"));
expectThrows(CNFE, () -> loader.loadClass("r.Unknown"));
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
@ -577,18 +561,20 @@ public class EmbeddedImplClassLoaderTests extends ESTestCase {
Path outerJar = topLevelDir.resolve("impl.jar");
JarUtils.createJarWithEntriesUTF(outerJar, jarEntries);
URL[] urls = new URL[] { outerJar.toUri().toURL() };
URLClassLoader parent = URLClassLoader.newInstance(urls, EmbeddedImplClassLoaderTests.class.getClassLoader());
try {
try (URLClassLoader parent = loader(urls)) {
EmbeddedImplClassLoader loader = EmbeddedImplClassLoader.getInstance(parent, "blah");
var res = Collections.list(loader.getResources("res.txt"));
assertThat(res, hasSize(3));
List<String> l = res.stream().map(EmbeddedImplClassLoaderTests::urlToString).toList();
assertThat(l, containsInAnyOrder("fooRes", "barRes", "bazRes"));
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
private static URLClassLoader loader(URL[] urls) {
return URLClassLoader.newInstance(urls, EmbeddedImplClassLoaderTests.class.getClassLoader());
}
@SuppressForbidden(reason = "file urls")
static String urlToString(URL url) {
try {

View file

@ -10,11 +10,11 @@
package org.elasticsearch.core.internal.provider;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PrivilegedOperations;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
import java.lang.module.ModuleDescriptor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
@ -117,12 +117,8 @@ public class ProviderLocatorTests extends ESTestCase {
Path topLevelDir = createTempDir(getTestName());
Path outerJar = topLevelDir.resolve("impl.jar");
JarUtils.createJarWithEntries(outerJar, jarEntries);
URLClassLoader parent = URLClassLoader.newInstance(
new URL[] { outerJar.toUri().toURL() },
ProviderLocatorTests.class.getClassLoader()
);
try {
try (URLClassLoader parent = loader(outerJar)) {
// test scenario
ProviderLocator<IntSupplier> locator = new ProviderLocator<>("x-foo", IntSupplier.class, parent, "x.foo.impl", Set.of(), true);
IntSupplier impl = locator.get();
@ -139,8 +135,6 @@ public class ProviderLocatorTests extends ESTestCase {
assertThat(md.exports(), containsInAnyOrder(exportsOf("p")));
assertThat(md.opens(), containsInAnyOrder(opensOf("q")));
assertThat(md.packages(), containsInAnyOrder(equalTo("p"), equalTo("q"), equalTo("r")));
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
@ -172,12 +166,8 @@ public class ProviderLocatorTests extends ESTestCase {
Path topLevelDir = createTempDir(getTestName());
Path outerJar = topLevelDir.resolve("impl.jar");
JarUtils.createJarWithEntries(outerJar, jarEntries);
URLClassLoader parent = URLClassLoader.newInstance(
new URL[] { outerJar.toUri().toURL() },
ProviderLocatorTests.class.getClassLoader()
);
try {
try (URLClassLoader parent = loader(outerJar)) {
// test scenario
ProviderLocator<LongSupplier> locator = new ProviderLocator<>("x-foo", LongSupplier.class, parent, "", Set.of(), false);
LongSupplier impl = locator.get();
@ -185,8 +175,6 @@ public class ProviderLocatorTests extends ESTestCase {
assertThat(impl.toString(), equalTo("Hello from FooLongSupplier - non-modular!"));
assertThat(impl.getClass().getName(), equalTo("p.FooLongSupplier"));
assertThat(impl.getClass().getModule().isNamed(), is(false));
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
@ -215,12 +203,7 @@ public class ProviderLocatorTests extends ESTestCase {
Path pb = Files.createDirectories(barRoot.resolve("pb"));
Files.write(pb.resolve("BarIntSupplier.class"), classToBytes.get("pb.BarIntSupplier"));
URLClassLoader parent = URLClassLoader.newInstance(
new URL[] { topLevelDir.toUri().toURL() },
ProviderLocatorTests.class.getClassLoader()
);
try {
try (URLClassLoader parent = loader(topLevelDir)) {
// test scenario
ProviderLocator<IntSupplier> locator = new ProviderLocator<>("y-bar", IntSupplier.class, parent, "", Set.of(), false);
IntSupplier impl = locator.get();
@ -228,8 +211,10 @@ public class ProviderLocatorTests extends ESTestCase {
assertThat(impl.toString(), equalTo("Hello from BarIntSupplier - exploded non-modular!"));
assertThat(impl.getClass().getName(), equalTo("pb.BarIntSupplier"));
assertThat(impl.getClass().getModule().isNamed(), is(false));
} finally {
PrivilegedOperations.closeURLClassLoader(parent);
}
}
private static URLClassLoader loader(Path jar) throws MalformedURLException {
return URLClassLoader.newInstance(new URL[] { jar.toUri().toURL() }, ProviderLocatorTests.class.getClassLoader());
}
}

View file

@ -132,7 +132,7 @@ public class WhitelistLoaderTests extends ESTestCase {
JarUtils.createJarWithEntries(jar, jarEntries);
try (var loader = JarUtils.loadJar(jar)) {
Controller controller = JarUtils.loadModule(jar, loader.classloader(), "m");
Controller controller = JarUtils.loadModule(jar, loader, "m");
Module module = controller.layer().findModule("m").orElseThrow();
Class<?> ownerClass = module.getClassLoader().loadClass("p.TestOwner");

View file

@ -10,7 +10,6 @@
package org.elasticsearch.plugins;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PrivilegedOperations.ClosableURLClassLoader;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
@ -35,7 +34,7 @@ public class ExtensionLoaderTests extends ESTestCase {
int getValue();
}
private ClosableURLClassLoader buildProviderJar(Map<String, CharSequence> sources) throws Exception {
private URLClassLoader buildProviderJar(Map<String, CharSequence> sources) throws Exception {
var classToBytes = InMemoryJavaCompiler.compile(sources);
Map<String, byte[]> jarEntries = new HashMap<>();
@ -55,7 +54,7 @@ public class ExtensionLoaderTests extends ESTestCase {
JarUtils.createJarWithEntries(jar, jarEntries);
URL[] urls = new URL[] { jar.toUri().toURL() };
return new ClosableURLClassLoader(URLClassLoader.newInstance(urls, this.getClass().getClassLoader()));
return URLClassLoader.newInstance(urls, this.getClass().getClassLoader());
}
private String defineProvider(String name, int value) {
@ -79,7 +78,7 @@ public class ExtensionLoaderTests extends ESTestCase {
public void testOneProvider() throws Exception {
Map<String, CharSequence> sources = Map.of("p.FooService", defineProvider("FooService", 1));
try (var loader = buildProviderJar(sources)) {
TestService service = ExtensionLoader.loadSingleton(ServiceLoader.load(TestService.class, loader.classloader()))
TestService service = ExtensionLoader.loadSingleton(ServiceLoader.load(TestService.class, loader))
.orElseThrow(AssertionError::new);
assertThat(service, not(nullValue()));
assertThat(service.getValue(), equalTo(1));
@ -96,7 +95,7 @@ public class ExtensionLoaderTests extends ESTestCase {
try (var loader = buildProviderJar(sources)) {
var e = expectThrows(
IllegalStateException.class,
() -> ExtensionLoader.loadSingleton(ServiceLoader.load(TestService.class, loader.classloader()))
() -> ExtensionLoader.loadSingleton(ServiceLoader.load(TestService.class, loader))
);
assertThat(e.getMessage(), containsString("More than one extension found"));
assertThat(e.getMessage(), containsString("TestService"));

View file

@ -24,7 +24,6 @@ import org.elasticsearch.indices.recovery.plan.RecoveryPlannerService;
import org.elasticsearch.indices.recovery.plan.ShardSnapshotsService;
import org.elasticsearch.ingest.Processor;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PrivilegedOperations;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
@ -233,11 +232,8 @@ public class PluginIntrospectorTests extends ESTestCase {
JarUtils.createJarWithEntries(jar, jarEntries);
URL[] urls = new URL[] { jar.toUri().toURL() };
URLClassLoader loader = URLClassLoader.newInstance(urls, PluginIntrospectorTests.class.getClassLoader());
try {
try (URLClassLoader loader = URLClassLoader.newInstance(urls, PluginIntrospectorTests.class.getClassLoader())) {
assertThat(pluginIntrospector.interfaces(loader.loadClass("r.FooPlugin")), contains("ActionPlugin"));
} finally {
PrivilegedOperations.closeURLClassLoader(loader);
}
}

View file

@ -19,7 +19,6 @@ import org.elasticsearch.logging.Logger;
import org.elasticsearch.nativeaccess.NativeAccessUtil;
import org.elasticsearch.plugin.analysis.CharFilterFactory;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PrivilegedOperations;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
@ -351,13 +350,13 @@ public class PluginsLoaderTests extends ESTestCase {
pluginsLoader.pluginLayers().forEach(lp -> {
if (lp.pluginClassLoader() instanceof URLClassLoader urlClassLoader) {
try {
PrivilegedOperations.closeURLClassLoader(urlClassLoader);
urlClassLoader.close();
} catch (IOException unexpected) {
throw new UncheckedIOException(unexpected);
}
} else if (lp.pluginClassLoader() instanceof UberModuleClassLoader loader) {
try {
PrivilegedOperations.closeURLClassLoader(loader.getInternalLoader());
loader.getInternalLoader().close();
} catch (Exception e) {
throw new RuntimeException(e);
}

View file

@ -24,7 +24,6 @@ import org.elasticsearch.plugins.spi.BarPlugin;
import org.elasticsearch.plugins.spi.BarTestService;
import org.elasticsearch.plugins.spi.TestService;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PrivilegedOperations;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
@ -671,9 +670,11 @@ public class PluginsServiceTests extends ESTestCase {
}
public void testLoadServiceProviders() throws Exception {
try (
URLClassLoader fakeClassLoader = buildTestProviderPlugin("integer");
URLClassLoader fakeClassLoader1 = buildTestProviderPlugin("string");
try {
URLClassLoader fakeClassLoader1 = buildTestProviderPlugin("string")
) {
@SuppressWarnings("unchecked")
Class<? extends Plugin> fakePluginClass = (Class<? extends Plugin>) fakeClassLoader.loadClass("r.FooPlugin");
@SuppressWarnings("unchecked")
@ -699,9 +700,6 @@ public class PluginsServiceTests extends ESTestCase {
providers = service.loadServiceProviders(TestService.class);
assertEquals(0, providers.size());
} finally {
PrivilegedOperations.closeURLClassLoader(fakeClassLoader);
PrivilegedOperations.closeURLClassLoader(fakeClassLoader1);
}
}
@ -877,13 +875,13 @@ public class PluginsServiceTests extends ESTestCase {
for (var lp : pluginService.plugins()) {
if (lp.classLoader() instanceof URLClassLoader urlClassLoader) {
try {
PrivilegedOperations.closeURLClassLoader(urlClassLoader);
urlClassLoader.close();
} catch (IOException unexpected) {
throw new UncheckedIOException(unexpected);
}
} else if (lp.classLoader() instanceof UberModuleClassLoader loader) {
try {
PrivilegedOperations.closeURLClassLoader(loader.getInternalLoader());
loader.getInternalLoader().close();
} catch (Exception e) {
throw new RuntimeException(e);
}

View file

@ -16,11 +16,9 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.Booleans;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.jdk.JarHell;
import org.elasticsearch.test.PrivilegedOperations;
import org.elasticsearch.test.mockito.SecureMockMaker;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
@ -75,13 +73,6 @@ public class BootstrapForTesting {
// init mockito
SecureMockMaker.init();
// init the privileged operation
try {
MethodHandles.publicLookup().ensureInitialized(PrivilegedOperations.class);
} catch (IllegalAccessException unexpected) {
throw new AssertionError(unexpected);
}
// Log ifconfig output before SecurityManager is installed
IfConfig.logIfNecessary();
}

View file

@ -1,113 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.test;
import org.elasticsearch.core.SuppressForbidden;
import java.io.FilePermission;
import java.io.IOException;
import java.net.URLClassLoader;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.DomainCombiner;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Enumeration;
import javax.tools.JavaCompiler;
/**
* A small set of privileged operations that can be executed by unprivileged test code.
* The set of operations is deliberately small, and the permissions narrow.
*/
public final class PrivilegedOperations {
private PrivilegedOperations() {}
public static void closeURLClassLoader(URLClassLoader loader) throws IOException {
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
loader.close();
return null;
}, context, new RuntimePermission("closeClassLoader"));
} catch (PrivilegedActionException pae) {
Exception e = pae.getException();
if (e instanceof IOException ioe) {
throw ioe;
} else {
throw new IOException(e);
}
}
}
public record ClosableURLClassLoader(URLClassLoader classloader) implements AutoCloseable {
@Override
public void close() throws Exception {
closeURLClassLoader(classloader);
}
}
public static Boolean compilationTaskCall(JavaCompiler.CompilationTask compilationTask) {
return AccessController.doPrivileged(
(PrivilegedAction<Boolean>) () -> compilationTask.call(),
context,
new RuntimePermission("createClassLoader"),
new RuntimePermission("closeClassLoader"),
new RuntimePermission("accessSystemModules"),
newAllFilesReadPermission()
);
}
@SuppressForbidden(reason = "need to create file permission")
private static FilePermission newAllFilesReadPermission() {
return new FilePermission("<<ALL FILES>>", "read");
}
// -- security manager related stuff, to facilitate asserting permissions for test operations.
@SuppressWarnings("removal")
private static AccessControlContext getContext() {
ProtectionDomain[] pda = new ProtectionDomain[] {
new ProtectionDomain(new CodeSource(null, (CodeSigner[]) null), new PermissivePermissionCollection()) };
DomainCombiner combiner = (ignoreCurrent, ignoreAssigned) -> pda;
AccessControlContext acc = new AccessControlContext(AccessController.getContext(), combiner);
// getContext must be called with the new acc so that a combined context will be created
return AccessController.doPrivileged((PrivilegedAction<AccessControlContext>) AccessController::getContext, acc);
}
// An all-powerful context for wrapping calls
@SuppressWarnings("removal")
private static final AccessControlContext context = getContext();
// A permissive permission collection - implies all permissions.
private static final class PermissivePermissionCollection extends PermissionCollection {
private PermissivePermissionCollection() {}
@Override
public void add(Permission permission) {}
@Override
public boolean implies(Permission permission) {
return true;
}
@Override
public Enumeration<Permission> elements() {
return null;
}
}
}

View file

@ -9,8 +9,6 @@
package org.elasticsearch.test.compiler;
import org.elasticsearch.test.PrivilegedOperations;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@ -137,7 +135,7 @@ public class InMemoryJavaCompiler {
try (FileManagerWrapper wrapper = new FileManagerWrapper(files)) {
CompilationTask task = getCompilationTask(wrapper, options);
boolean result = PrivilegedOperations.compilationTaskCall(task);
boolean result = task.call();
if (result == false) {
throw new RuntimeException("Could not compile " + sources.entrySet().stream().toList());
}
@ -162,7 +160,7 @@ public class InMemoryJavaCompiler {
try (FileManagerWrapper wrapper = new FileManagerWrapper(file)) {
CompilationTask task = getCompilationTask(wrapper, options);
boolean result = PrivilegedOperations.compilationTaskCall(task);
boolean result = task.call();
if (result == false) {
throw new RuntimeException("Could not compile " + className + " with source code " + sourceCode);
}

View file

@ -9,8 +9,6 @@
package org.elasticsearch.test.jar;
import org.elasticsearch.test.PrivilegedOperations.ClosableURLClassLoader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
@ -101,10 +99,10 @@ public final class JarUtils {
* @param path Path to the jar file to load
* @return A URLClassLoader that will load classes from the jar. It should be closed when no longer needed.
*/
public static ClosableURLClassLoader loadJar(Path path) {
public static URLClassLoader loadJar(Path path) {
try {
URL[] urls = new URL[] { path.toUri().toURL() };
return new ClosableURLClassLoader(URLClassLoader.newInstance(urls, JarUtils.class.getClassLoader()));
return URLClassLoader.newInstance(urls, JarUtils.class.getClassLoader());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}