Skip to content

Commit 7297390

Browse files
authored
Add entitlement checks for java.lang.ClassLoader (#119027)
This commit adds an entitlement for creating a classloader.
1 parent 0292905 commit 7297390

File tree

10 files changed

+174
-155
lines changed

10 files changed

+174
-155
lines changed

libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ public interface EntitlementChecker {
2626

2727
void check$java_lang_Runtime$halt(Class<?> callerClass, Runtime runtime, int status);
2828

29+
// ClassLoader ctor
30+
void check$java_lang_ClassLoader$(Class<?> callerClass);
31+
32+
void check$java_lang_ClassLoader$(Class<?> callerClass, ClassLoader parent);
33+
34+
void check$java_lang_ClassLoader$(Class<?> callerClass, String name, ClassLoader parent);
35+
36+
// SecureClassLoader ctor
37+
void check$java_security_SecureClassLoader$(Class<?> callerClass);
38+
39+
void check$java_security_SecureClassLoader$(Class<?> callerClass, ClassLoader parent);
40+
41+
void check$java_security_SecureClassLoader$(Class<?> callerClass, String name, ClassLoader parent);
42+
2943
// URLClassLoader constructors
3044
void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls);
3145

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.entitlement.instrumentation.Transformer;
2121
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
2222
import org.elasticsearch.entitlement.runtime.policy.CreateClassLoaderEntitlement;
23+
import org.elasticsearch.entitlement.runtime.policy.Entitlement;
2324
import org.elasticsearch.entitlement.runtime.policy.ExitVMEntitlement;
2425
import org.elasticsearch.entitlement.runtime.policy.Policy;
2526
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
@@ -93,9 +94,17 @@ private static PolicyManager createPolicyManager() throws IOException {
9394
// TODO(ES-10031): Decide what goes in the elasticsearch default policy and extend it
9495
var serverPolicy = new Policy(
9596
"server",
96-
List.of(new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement())))
97+
List.of(
98+
new Scope("org.elasticsearch.base", List.of(new CreateClassLoaderEntitlement())),
99+
new Scope("org.elasticsearch.xcontent", List.of(new CreateClassLoaderEntitlement())),
100+
new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement()))
101+
)
97102
);
98-
return new PolicyManager(serverPolicy, pluginPolicies, EntitlementBootstrap.bootstrapArgs().pluginResolver(), ENTITLEMENTS_MODULE);
103+
// agents run without a module, so this is a special hack for the apm agent
104+
// this should be removed once https://github.com/elastic/elasticsearch/issues/109335 is completed
105+
List<Entitlement> agentEntitlements = List.of(new CreateClassLoaderEntitlement());
106+
var resolver = EntitlementBootstrap.bootstrapArgs().pluginResolver();
107+
return new PolicyManager(serverPolicy, agentEntitlements, pluginPolicies, resolver, ENTITLEMENTS_MODULE);
99108
}
100109

101110
private static Map<String, Policy> createPluginPolicies(Collection<EntitlementBootstrap.PluginData> pluginData) throws IOException {
@@ -120,12 +129,12 @@ private static Policy loadPluginPolicy(Path pluginRoot, boolean isModular, Strin
120129

121130
// TODO: should this check actually be part of the parser?
122131
for (Scope scope : policy.scopes) {
123-
if (moduleNames.contains(scope.name) == false) {
132+
if (moduleNames.contains(scope.moduleName) == false) {
124133
throw new IllegalStateException(
125134
Strings.format(
126135
"Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy file [%s]",
127136
pluginName,
128-
scope.name,
137+
scope.moduleName,
129138
String.join(", ", moduleNames),
130139
policyFile
131140
)

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* The trampoline module loads this object via SPI.
2828
*/
2929
public class ElasticsearchEntitlementChecker implements EntitlementChecker {
30+
3031
private final PolicyManager policyManager;
3132

3233
public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
@@ -43,6 +44,36 @@ public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
4344
policyManager.checkExitVM(callerClass);
4445
}
4546

47+
@Override
48+
public void check$java_lang_ClassLoader$(Class<?> callerClass) {
49+
policyManager.checkCreateClassLoader(callerClass);
50+
}
51+
52+
@Override
53+
public void check$java_lang_ClassLoader$(Class<?> callerClass, ClassLoader parent) {
54+
policyManager.checkCreateClassLoader(callerClass);
55+
}
56+
57+
@Override
58+
public void check$java_lang_ClassLoader$(Class<?> callerClass, String name, ClassLoader parent) {
59+
policyManager.checkCreateClassLoader(callerClass);
60+
}
61+
62+
@Override
63+
public void check$java_security_SecureClassLoader$(Class<?> callerClass) {
64+
policyManager.checkCreateClassLoader(callerClass);
65+
}
66+
67+
@Override
68+
public void check$java_security_SecureClassLoader$(Class<?> callerClass, ClassLoader parent) {
69+
policyManager.checkCreateClassLoader(callerClass);
70+
}
71+
72+
@Override
73+
public void check$java_security_SecureClassLoader$(Class<?> callerClass, String name, ClassLoader parent) {
74+
policyManager.checkCreateClassLoader(callerClass);
75+
}
76+
4677
@Override
4778
public void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls) {
4879
policyManager.checkCreateClassLoader(callerClass);

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java

Lines changed: 64 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,31 @@
1717
import java.lang.StackWalker.StackFrame;
1818
import java.lang.module.ModuleFinder;
1919
import java.lang.module.ModuleReference;
20-
import java.util.ArrayList;
21-
import java.util.HashMap;
22-
import java.util.IdentityHashMap;
2320
import java.util.List;
2421
import java.util.Map;
25-
import java.util.Objects;
2622
import java.util.Optional;
2723
import java.util.Set;
24+
import java.util.concurrent.ConcurrentHashMap;
2825
import java.util.function.Function;
2926
import java.util.stream.Collectors;
3027
import java.util.stream.Stream;
3128

3229
import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
3330
import static java.util.Objects.requireNonNull;
31+
import static java.util.stream.Collectors.groupingBy;
3432

3533
public class PolicyManager {
3634
private static final Logger logger = LogManager.getLogger(PolicyManager.class);
3735

38-
static class ModuleEntitlements {
39-
public static final ModuleEntitlements NONE = new ModuleEntitlements(List.of());
40-
private final IdentityHashMap<Class<? extends Entitlement>, List<Entitlement>> entitlementsByType;
36+
record ModuleEntitlements(Map<Class<? extends Entitlement>, List<Entitlement>> entitlementsByType) {
37+
public static final ModuleEntitlements NONE = new ModuleEntitlements(Map.of());
4138

42-
ModuleEntitlements(List<Entitlement> entitlements) {
43-
this.entitlementsByType = entitlements.stream()
44-
.collect(Collectors.toMap(Entitlement::getClass, e -> new ArrayList<>(List.of(e)), (a, b) -> {
45-
a.addAll(b);
46-
return a;
47-
}, IdentityHashMap::new));
39+
ModuleEntitlements {
40+
entitlementsByType = Map.copyOf(entitlementsByType);
41+
}
42+
43+
public static ModuleEntitlements from(List<Entitlement> entitlements) {
44+
return new ModuleEntitlements(entitlements.stream().collect(groupingBy(Entitlement::getClass)));
4845
}
4946

5047
public boolean hasEntitlement(Class<? extends Entitlement> entitlementClass) {
@@ -56,9 +53,10 @@ public <E extends Entitlement> Stream<E> getEntitlements(Class<E> entitlementCla
5653
}
5754
}
5855

59-
final Map<Module, ModuleEntitlements> moduleEntitlementsMap = new HashMap<>();
56+
final Map<Module, ModuleEntitlements> moduleEntitlementsMap = new ConcurrentHashMap<>();
6057

6158
protected final Map<String, List<Entitlement>> serverEntitlements;
59+
protected final List<Entitlement> agentEntitlements;
6260
protected final Map<String, Map<String, List<Entitlement>>> pluginsEntitlements;
6361
private final Function<Class<?>, String> pluginResolver;
6462

@@ -85,12 +83,14 @@ private static Set<Module> findSystemModules() {
8583
private final Module entitlementsModule;
8684

8785
public PolicyManager(
88-
Policy defaultPolicy,
86+
Policy serverPolicy,
87+
List<Entitlement> agentEntitlements,
8988
Map<String, Policy> pluginPolicies,
9089
Function<Class<?>, String> pluginResolver,
9190
Module entitlementsModule
9291
) {
93-
this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(defaultPolicy));
92+
this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy));
93+
this.agentEntitlements = agentEntitlements;
9494
this.pluginsEntitlements = requireNonNull(pluginPolicies).entrySet()
9595
.stream()
9696
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue())));
@@ -99,15 +99,15 @@ public PolicyManager(
9999
}
100100

101101
private static Map<String, List<Entitlement>> buildScopeEntitlementsMap(Policy policy) {
102-
return policy.scopes.stream().collect(Collectors.toUnmodifiableMap(scope -> scope.name, scope -> scope.entitlements));
102+
return policy.scopes.stream().collect(Collectors.toUnmodifiableMap(scope -> scope.moduleName, scope -> scope.entitlements));
103103
}
104104

105105
public void checkStartProcess(Class<?> callerClass) {
106106
neverEntitled(callerClass, "start process");
107107
}
108108

109109
private void neverEntitled(Class<?> callerClass, String operationDescription) {
110-
var requestingModule = requestingModule(callerClass);
110+
var requestingModule = requestingClass(callerClass);
111111
if (isTriviallyAllowed(requestingModule)) {
112112
return;
113113
}
@@ -139,49 +139,45 @@ public void checkSetGlobalHttpsConnectionProperties(Class<?> callerClass) {
139139
}
140140

141141
private void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass) {
142-
var requestingModule = requestingModule(callerClass);
143-
if (isTriviallyAllowed(requestingModule)) {
142+
var requestingClass = requestingClass(callerClass);
143+
if (isTriviallyAllowed(requestingClass)) {
144144
return;
145145
}
146146

147-
ModuleEntitlements entitlements = getEntitlementsOrThrow(callerClass, requestingModule);
147+
ModuleEntitlements entitlements = getEntitlements(requestingClass);
148148
if (entitlements.hasEntitlement(entitlementClass)) {
149149
logger.debug(
150150
() -> Strings.format(
151-
"Entitled: caller [%s], module [%s], type [%s]",
152-
callerClass,
153-
requestingModule.getName(),
151+
"Entitled: class [%s], module [%s], entitlement [%s]",
152+
requestingClass,
153+
requestingClass.getModule().getName(),
154154
entitlementClass.getSimpleName()
155155
)
156156
);
157157
return;
158158
}
159159
throw new NotEntitledException(
160160
Strings.format(
161-
"Missing entitlement: caller [%s], module [%s], type [%s]",
162-
callerClass,
163-
requestingModule.getName(),
161+
"Missing entitlement: class [%s], module [%s], entitlement [%s]",
162+
requestingClass,
163+
requestingClass.getModule().getName(),
164164
entitlementClass.getSimpleName()
165165
)
166166
);
167167
}
168168

169-
ModuleEntitlements getEntitlementsOrThrow(Class<?> callerClass, Module requestingModule) {
170-
ModuleEntitlements cachedEntitlement = moduleEntitlementsMap.get(requestingModule);
171-
if (cachedEntitlement != null) {
172-
if (cachedEntitlement == ModuleEntitlements.NONE) {
173-
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, requestingModule) + "[CACHED]");
174-
}
175-
return cachedEntitlement;
176-
}
169+
ModuleEntitlements getEntitlements(Class<?> requestingClass) {
170+
return moduleEntitlementsMap.computeIfAbsent(requestingClass.getModule(), m -> computeEntitlements(requestingClass));
171+
}
177172

173+
private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
174+
Module requestingModule = requestingClass.getModule();
178175
if (isServerModule(requestingModule)) {
179-
var scopeName = requestingModule.getName();
180-
return getModuleEntitlementsOrThrow(callerClass, requestingModule, serverEntitlements, scopeName);
176+
return getModuleScopeEntitlements(requestingClass, serverEntitlements, requestingModule.getName());
181177
}
182178

183179
// plugins
184-
var pluginName = pluginResolver.apply(callerClass);
180+
var pluginName = pluginResolver.apply(requestingClass);
185181
if (pluginName != null) {
186182
var pluginEntitlements = pluginsEntitlements.get(pluginName);
187183
if (pluginEntitlements != null) {
@@ -191,60 +187,53 @@ ModuleEntitlements getEntitlementsOrThrow(Class<?> callerClass, Module requestin
191187
} else {
192188
scopeName = requestingModule.getName();
193189
}
194-
return getModuleEntitlementsOrThrow(callerClass, requestingModule, pluginEntitlements, scopeName);
190+
return getModuleScopeEntitlements(requestingClass, pluginEntitlements, scopeName);
195191
}
196192
}
197193

198-
moduleEntitlementsMap.put(requestingModule, ModuleEntitlements.NONE);
199-
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, requestingModule));
200-
}
194+
if (requestingModule.isNamed() == false) {
195+
// agents are the only thing running non-modular
196+
return ModuleEntitlements.from(agentEntitlements);
197+
}
201198

202-
private static String buildModuleNoPolicyMessage(Class<?> callerClass, Module requestingModule) {
203-
return Strings.format("Missing entitlement policy: caller [%s], module [%s]", callerClass, requestingModule.getName());
199+
logger.warn("No applicable entitlement policy for class [{}]", requestingClass.getName());
200+
return ModuleEntitlements.NONE;
204201
}
205202

206-
private ModuleEntitlements getModuleEntitlementsOrThrow(
203+
private ModuleEntitlements getModuleScopeEntitlements(
207204
Class<?> callerClass,
208-
Module module,
209205
Map<String, List<Entitlement>> scopeEntitlements,
210206
String moduleName
211207
) {
212208
var entitlements = scopeEntitlements.get(moduleName);
213209
if (entitlements == null) {
214-
// Module without entitlements - remember we don't have any
215-
moduleEntitlementsMap.put(module, ModuleEntitlements.NONE);
216-
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, module));
210+
logger.warn("No applicable entitlement policy for module [{}], class [{}]", moduleName, callerClass);
211+
return ModuleEntitlements.NONE;
217212
}
218-
// We have a policy for this module
219-
var classEntitlements = new ModuleEntitlements(entitlements);
220-
moduleEntitlementsMap.put(module, classEntitlements);
221-
return classEntitlements;
213+
return ModuleEntitlements.from(entitlements);
222214
}
223215

224216
private static boolean isServerModule(Module requestingModule) {
225217
return requestingModule.isNamed() && requestingModule.getLayer() == ModuleLayer.boot();
226218
}
227219

228220
/**
229-
* Walks the stack to determine which module's entitlements should be checked.
221+
* Walks the stack to determine which class should be checked for entitlements.
230222
*
231-
* @param callerClass when non-null will be used if its module is suitable;
223+
* @param callerClass when non-null will be returned;
232224
* this is a fast-path check that can avoid the stack walk
233225
* in cases where the caller class is available.
234-
* @return the requesting module, or {@code null} if the entire call stack
226+
* @return the requesting class, or {@code null} if the entire call stack
235227
* comes from the entitlement library itself.
236228
*/
237-
Module requestingModule(Class<?> callerClass) {
229+
Class<?> requestingClass(Class<?> callerClass) {
238230
if (callerClass != null) {
239-
var callerModule = callerClass.getModule();
240-
if (callerModule != null && entitlementsModule.equals(callerModule) == false) {
241-
// fast path
242-
return callerModule;
243-
}
231+
// fast path
232+
return callerClass;
244233
}
245-
Optional<Module> module = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
246-
.walk(frames -> findRequestingModule(frames.map(StackFrame::getDeclaringClass)));
247-
return module.orElse(null);
234+
Optional<Class<?>> result = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
235+
.walk(frames -> findRequestingClass(frames.map(StackFrame::getDeclaringClass)));
236+
return result.orElse(null);
248237
}
249238

250239
/**
@@ -253,33 +242,25 @@ Module requestingModule(Class<?> callerClass) {
253242
*
254243
* @throws NullPointerException if the requesting module is {@code null}
255244
*/
256-
Optional<Module> findRequestingModule(Stream<Class<?>> classes) {
257-
return classes.map(Objects::requireNonNull)
258-
.map(PolicyManager::moduleOf)
259-
.filter(m -> m != entitlementsModule) // Ignore the entitlements library itself entirely
260-
.skip(1) // Skip the sensitive method itself
245+
Optional<Class<?>> findRequestingClass(Stream<Class<?>> classes) {
246+
return classes.filter(c -> c.getModule() != entitlementsModule) // Ignore the entitlements library
247+
.skip(1) // Skip the sensitive caller method
261248
.findFirst();
262249
}
263250

264-
private static Module moduleOf(Class<?> c) {
265-
var result = c.getModule();
266-
if (result == null) {
267-
throw new NullPointerException("Entitlements system does not support non-modular class [" + c.getName() + "]");
268-
} else {
269-
return result;
270-
}
271-
}
272-
273-
private static boolean isTriviallyAllowed(Module requestingModule) {
251+
/**
252+
* @return true if permission is granted regardless of the entitlement
253+
*/
254+
private static boolean isTriviallyAllowed(Class<?> requestingClass) {
274255
if (logger.isTraceEnabled()) {
275256
logger.trace("Stack trace for upcoming trivially-allowed check", new Exception());
276257
}
277-
if (requestingModule == null) {
258+
if (requestingClass == null) {
278259
logger.debug("Entitlement trivially allowed: no caller frames outside the entitlement library");
279260
return true;
280261
}
281-
if (systemModules.contains(requestingModule)) {
282-
logger.debug("Entitlement trivially allowed from system module [{}]", requestingModule.getName());
262+
if (systemModules.contains(requestingClass.getModule())) {
263+
logger.debug("Entitlement trivially allowed from system module [{}]", requestingClass.getModule().getName());
283264
return true;
284265
}
285266
logger.trace("Entitlement not trivially allowed");

0 commit comments

Comments
 (0)