Check hidden frames in entitlements (#127877)

Entitlements do a stack walk to find the calling class. When method
refences are used in a lambda, the frame ends up hidden in the stack
walk. In the case of using a method reference with
AccessController.doPrivileged, the call looks like it is the jdk itself,
so the call is trivially allowed. This commit adds hidden frames to the
stack walk so that the lambda frame created for the method reference is
included. Several internal packages are then necessary to filter out of
the stack.
This commit is contained in:
Ryan Ernst 2025-05-08 16:59:03 -07:00 committed by GitHub
parent a3700ff598
commit ab690ba23f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 20 additions and 2 deletions

View file

@ -0,0 +1,5 @@
pr: 127877
summary: Check hidden frames in entitlements
area: Infra/Core
type: bug
issues: []

View file

@ -10,8 +10,10 @@
package org.elasticsearch.entitlement.bridge;
import java.util.Optional;
import java.util.Set;
import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
import static java.lang.StackWalker.Option.SHOW_HIDDEN_FRAMES;
public class Util {
/**
@ -23,6 +25,8 @@ public class Util {
public static final Class<?> NO_CLASS = new Object() {
}.getClass();
private static final Set<String> skipInternalPackages = Set.of("java.lang.invoke", "java.lang.reflect", "jdk.internal.reflect");
/**
* Why would we write this instead of using {@link StackWalker#getCallerClass()}?
* Because that method throws {@link IllegalCallerException} if called from the "outermost frame",
@ -32,9 +36,10 @@ public class Util {
*/
@SuppressWarnings("unused") // Called reflectively from InstrumenterImpl
public static Class<?> getCallerClass() {
Optional<Class<?>> callerClassIfAny = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
Optional<Class<?>> callerClassIfAny = StackWalker.getInstance(Set.of(RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES))
.walk(
frames -> frames.skip(2) // Skip this method and its caller
.filter(frame -> skipInternalPackages.contains(frame.getDeclaringClass().getPackageName()) == false)
.findFirst()
.map(StackWalker.StackFrame::getDeclaringClass)
);

View file

@ -182,7 +182,9 @@ public record FilesEntitlement(List<FileData> filesData) implements Entitlement
case "config" -> BaseDir.CONFIG;
case "data" -> BaseDir.DATA;
case "home" -> BaseDir.USER_HOME;
// NOTE: shared_repo is _not_ accessible to policy files, only internally
// it would be nice to limit this to just ES modules, but we don't have a way to plumb that through to here
// however, we still don't document in the error case below that shared_repo is valid
case "shared_repo" -> BaseDir.SHARED_REPO;
default -> throw new PolicyValidationException(
"invalid relative directory: " + baseDir + ", valid values: [config, data, home]"
);

View file

@ -1,2 +1,8 @@
org.elasticsearch.repository.url:
- outbound_network
- files:
- relative_path: .
relative_to: shared_repo
mode: read
org.apache.httpcomponents.httpclient:
- outbound_network # for URLHttpClient