Skip to content

Commit 50be8ef

Browse files
committed
Custom getCallerClass in entitlement bridge
1 parent a4d7297 commit 50be8ef

File tree

5 files changed

+84
-18
lines changed

5 files changed

+84
-18
lines changed

libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,8 @@
3333
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
3434
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
3535
import static org.objectweb.asm.Opcodes.ACC_STATIC;
36-
import static org.objectweb.asm.Opcodes.GETSTATIC;
3736
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
3837
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
39-
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
4038

4139
public class InstrumenterImpl implements Instrumenter {
4240
private static final Logger logger = LogManager.getLogger(InstrumenterImpl.class);
@@ -240,22 +238,9 @@ private void pushCallerClass() {
240238
false
241239
);
242240
} else {
243-
mv.visitFieldInsn(
244-
GETSTATIC,
245-
Type.getInternalName(StackWalker.Option.class),
246-
"RETAIN_CLASS_REFERENCE",
247-
Type.getDescriptor(StackWalker.Option.class)
248-
);
249241
mv.visitMethodInsn(
250242
INVOKESTATIC,
251-
Type.getInternalName(StackWalker.class),
252-
"getInstance",
253-
Type.getMethodDescriptor(Type.getType(StackWalker.class), Type.getType(StackWalker.Option.class)),
254-
false
255-
);
256-
mv.visitMethodInsn(
257-
INVOKEVIRTUAL,
258-
Type.getInternalName(StackWalker.class),
243+
"org/elasticsearch/entitlement/bridge/Util",
259244
"getCallerClass",
260245
Type.getMethodDescriptor(Type.getType(Class.class)),
261246
false

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class EntitlementCheckerHandle {
1818
* This is how the bytecodes injected by our instrumentation access the {@link EntitlementChecker}
1919
* so they can call the appropriate check method.
2020
*/
21+
@SuppressWarnings("unused")
2122
public static EntitlementChecker instance() {
2223
return Holder.instance;
2324
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
import java.util.Optional;
13+
14+
import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
15+
16+
public class Util {
17+
/**
18+
* A special value representing the case where a method <em>has no caller</em>.
19+
* This can occur if it's called directly from the JVM.
20+
*
21+
* @see StackWalker#getCallerClass()
22+
*/
23+
public static final Class<?> NO_CLASS = new Object() {
24+
}.getClass();
25+
26+
/**
27+
* Why would we write this instead of using {@link StackWalker#getCallerClass()}?
28+
* Because that method throws {@link IllegalCallerException} if called from the "outermost frame",
29+
* which includes at least some cases of a method called from a native frame.
30+
*
31+
* @return the class that called the method which called this; or {@link #NO_CLASS} from the outermost frame.
32+
*/
33+
@SuppressWarnings("unused") // Called reflectively from InstrumenterImpl
34+
public static Class<?> getCallerClass() {
35+
Optional<Class<?>> callerClassIfAny = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
36+
.walk(
37+
frames -> frames.skip(2) // Skip this method and its caller
38+
.findFirst()
39+
.map(StackWalker.StackFrame::getDeclaringClass)
40+
);
41+
return callerClassIfAny.orElse(NO_CLASS);
42+
}
43+
44+
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import static java.util.stream.Collectors.toUnmodifiableMap;
5858
import static java.util.zip.ZipFile.OPEN_DELETE;
5959
import static java.util.zip.ZipFile.OPEN_READ;
60+
import static org.elasticsearch.entitlement.bridge.Util.NO_CLASS;
6061

6162
public class PolicyManager {
6263
/**
@@ -712,8 +713,6 @@ Class<?> requestingClass(Class<?> callerClass) {
712713

713714
/**
714715
* Given a stream of {@link StackFrame}s, identify the one whose entitlements should be checked.
715-
*
716-
* @throws NullPointerException if the requesting module is {@code null}
717716
*/
718717
Optional<StackFrame> findRequestingFrame(Stream<StackFrame> frames) {
719718
return frames.filter(f -> f.getDeclaringClass().getModule() != entitlementsModule) // ignore entitlements library
@@ -732,6 +731,10 @@ private static boolean isTriviallyAllowed(Class<?> requestingClass) {
732731
generalLogger.debug("Entitlement trivially allowed: no caller frames outside the entitlement library");
733732
return true;
734733
}
734+
if (requestingClass == NO_CLASS) {
735+
generalLogger.debug("Entitlement trivially allowed from outermost frame");
736+
return true;
737+
}
735738
if (SYSTEM_LAYER_MODULES.contains(requestingClass.getModule())) {
736739
generalLogger.debug("Entitlement trivially allowed from system module [{}]", requestingClass.getModule().getName());
737740
return true;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
import org.elasticsearch.test.ESTestCase;
13+
14+
import static org.elasticsearch.entitlement.bridge.UtilTests.MockSensitiveClass.mockSensitiveMethod;
15+
16+
@ESTestCase.WithoutSecurityManager
17+
public class UtilTests extends ESTestCase {
18+
19+
public void testCallerClass() {
20+
assertEquals(UtilTests.class, mockSensitiveMethod());
21+
}
22+
23+
/**
24+
* A separate class so the stack walk can discern the sensitive method's own class
25+
* from that of its caller.
26+
*/
27+
static class MockSensitiveClass {
28+
public static Class<?> mockSensitiveMethod() {
29+
return Util.getCallerClass();
30+
}
31+
}
32+
33+
}

0 commit comments

Comments
 (0)