Skip to content

Commit

Permalink
[8.x] [Entitlements] Add checks for native libraries restricted metho…
Browse files Browse the repository at this point in the history
…ds (#120775) (#121017)

* [Entitlements] Add checks for native libraries restricted methods (#120775)

* Introducing main21 (does not compile with main23 on the main lib)

* Move foreign API to Java22; fix EntitlementInitialization to work across multiple versions

* [CI] Auto commit changes from spotless

---------

Co-authored-by: elasticsearchmachine <[email protected]>
  • Loading branch information
ldematte and elasticsearchmachine authored Jan 29, 2025
1 parent 98c65ff commit 2171064
Show file tree
Hide file tree
Showing 17 changed files with 513 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ private void addJar(Project project, SourceSet sourceSet, int javaVersion) {
project.getConfigurations().register("java" + javaVersion);
TaskProvider<Jar> jarTask = project.getTasks().register("java" + javaVersion + "Jar", Jar.class, task -> {
task.from(sourceSet.getOutput());
task.getArchiveClassifier().set("java" + javaVersion);
});
project.getArtifacts().add("java" + javaVersion, jarTask);
}
Expand Down
6 changes: 6 additions & 0 deletions libs/entitlement/bridge/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ tasks.named('jar').configure {
if (sourceSets.findByName("main23")) {
from sourceSets.main23.output
}
if (sourceSets.findByName("main21")) {
from sourceSets.main21.output
}
if (sourceSets.findByName("main22")) {
from sourceSets.main22.output
}
}

tasks.withType(CheckForbiddenApisTask).configureEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,11 +411,14 @@ public interface EntitlementChecker {
//
// Load native libraries
//
// Using the list of restricted methods from https://download.java.net/java/early_access/jdk24/docs/api/restricted-list.html
void check$java_lang_Runtime$load(Class<?> callerClass, Runtime that, String filename);

void check$java_lang_Runtime$loadLibrary(Class<?> callerClass, Runtime that, String libname);

void check$java_lang_System$$load(Class<?> callerClass, String filename);

void check$java_lang_System$$loadLibrary(Class<?> callerClass, String libname);

void check$java_lang_ModuleLayer$Controller$enableNativeAccess(Class<?> callerClass, ModuleLayer.Controller that, Module target);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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.entitlement.bridge;

public interface Java21EntitlementChecker extends EntitlementChecker {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.entitlement.bridge;

/**
* Java21 variant of {@link EntitlementChecker} handle holder.
*/
public class Java21EntitlementCheckerHandle {

public static Java21EntitlementChecker instance() {
return Holder.instance;
}

private static class Holder {
private static final Java21EntitlementChecker instance = HandleLoader.load(Java21EntitlementChecker.class);
}

// no construction
private Java21EntitlementCheckerHandle() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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.entitlement.bridge;

import java.lang.foreign.AddressLayout;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.invoke.MethodHandle;
import java.nio.file.Path;
import java.util.function.Consumer;

public interface Java22EntitlementChecker extends Java21EntitlementChecker {
// Sealed implementation of java.lang.foreign.AddressLayout
void check$jdk_internal_foreign_layout_ValueLayouts$OfAddressImpl$withTargetLayout(
Class<?> callerClass,
AddressLayout that,
MemoryLayout memoryLayout
);

// Sealed implementation of java.lang.foreign.Linker
void check$jdk_internal_foreign_abi_AbstractLinker$downcallHandle(
Class<?> callerClass,
Linker that,
FunctionDescriptor function,
Linker.Option... options
);

void check$jdk_internal_foreign_abi_AbstractLinker$downcallHandle(
Class<?> callerClass,
Linker that,
MemorySegment address,
FunctionDescriptor function,
Linker.Option... options
);

void check$jdk_internal_foreign_abi_AbstractLinker$upcallStub(
Class<?> callerClass,
Linker that,
MethodHandle target,
FunctionDescriptor function,
Arena arena,
Linker.Option... options
);

// Sealed implementation for java.lang.foreign.MemorySegment.reinterpret(long)
void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(Class<?> callerClass, MemorySegment that, long newSize);

void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(
Class<?> callerClass,
MemorySegment that,
long newSize,
Arena arena,
Consumer<MemorySegment> cleanup
);

void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(
Class<?> callerClass,
MemorySegment that,
Arena arena,
Consumer<MemorySegment> cleanup
);

void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, String name, Arena arena);

void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, Path path, Arena arena);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.entitlement.bridge;

/**
* Java22 variant of {@link EntitlementChecker} handle holder.
*/
public class Java22EntitlementCheckerHandle {

public static Java22EntitlementChecker instance() {
return Holder.instance;
}

private static class Holder {
private static final Java22EntitlementChecker instance = HandleLoader.load(Java22EntitlementChecker.class);
}

// no construction
private Java22EntitlementCheckerHandle() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@

package org.elasticsearch.entitlement.bridge;

public interface Java23EntitlementChecker extends EntitlementChecker {}
public interface Java23EntitlementChecker extends Java22EntitlementChecker {}
6 changes: 6 additions & 0 deletions libs/entitlement/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ dependencies {
}

// guarding for intellij
if (sourceSets.findByName("main21")) {
main21CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java21')
}
if (sourceSets.findByName("main22")) {
main22CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java22')
}
if (sourceSets.findByName("main23")) {
main23CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java23')
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,21 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
entry("runtime_load", forPlugins(LoadNativeLibrariesCheckActions::runtimeLoad)),
entry("runtime_load_library", forPlugins(LoadNativeLibrariesCheckActions::runtimeLoadLibrary)),
entry("system_load", forPlugins(LoadNativeLibrariesCheckActions::systemLoad)),
entry("system_load_library", forPlugins(LoadNativeLibrariesCheckActions::systemLoadLibrary))
entry("system_load_library", forPlugins(LoadNativeLibrariesCheckActions::systemLoadLibrary)),

entry("enable_native_access", new CheckAction(VersionSpecificNativeChecks::enableNativeAccess, false, 22)),
entry("address_target_layout", new CheckAction(VersionSpecificNativeChecks::addressLayoutWithTargetLayout, false, 22)),
entry("donwncall_handle", new CheckAction(VersionSpecificNativeChecks::linkerDowncallHandle, false, 22)),
entry("donwncall_handle_with_address", new CheckAction(VersionSpecificNativeChecks::linkerDowncallHandleWithAddress, false, 22)),
entry("upcall_stub", new CheckAction(VersionSpecificNativeChecks::linkerUpcallStub, false, 22)),
entry("reinterpret", new CheckAction(VersionSpecificNativeChecks::memorySegmentReinterpret, false, 22)),
entry("reinterpret_cleanup", new CheckAction(VersionSpecificNativeChecks::memorySegmentReinterpretWithCleanup, false, 22)),
entry(
"reinterpret_size_cleanup",
new CheckAction(VersionSpecificNativeChecks::memorySegmentReinterpretWithSizeAndCleanup, false, 22)
),
entry("symbol_lookup_name", new CheckAction(VersionSpecificNativeChecks::symbolLookupWithName, false, 22)),
entry("symbol_lookup_path", new CheckAction(VersionSpecificNativeChecks::symbolLookupWithPath, false, 22))
)
.filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion())
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.entitlement.qa.test;

class VersionSpecificNativeChecks {

static void enableNativeAccess() throws Exception {}

static void addressLayoutWithTargetLayout() {}

static void linkerDowncallHandle() {}

static void linkerDowncallHandleWithAddress() {}

static void linkerUpcallStub() throws NoSuchMethodException {}

static void memorySegmentReinterpret() {}

static void memorySegmentReinterpretWithCleanup() {}

static void memorySegmentReinterpretWithSizeAndCleanup() {}

static void symbolLookupWithPath() {}

static void symbolLookupWithName() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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.entitlement.qa.test;

import org.elasticsearch.entitlement.qa.entitled.EntitledPlugin;

import java.lang.foreign.AddressLayout;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.module.Configuration;
import java.lang.module.ModuleFinder;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;

import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_LONG;

class VersionSpecificNativeChecks {

static void enableNativeAccess() throws Exception {
ModuleLayer parent = ModuleLayer.boot();

var location = EntitledPlugin.class.getProtectionDomain().getCodeSource().getLocation();

// We create a layer for our own module, so we have a controller to try and call enableNativeAccess on it.
// This works in both the modular and non-modular case: the target module has to be present in the new layer, but its entitlements
// and policies do not matter to us: we are checking that the caller is (or isn't) entitled to use enableNativeAccess
Configuration cf = parent.configuration()
.resolve(ModuleFinder.of(Path.of(location.toURI())), ModuleFinder.of(), Set.of("org.elasticsearch.entitlement.qa.entitled"));
var controller = ModuleLayer.defineModulesWithOneLoader(cf, List.of(parent), ClassLoader.getSystemClassLoader());
var targetModule = controller.layer().findModule("org.elasticsearch.entitlement.qa.entitled");

controller.enableNativeAccess(targetModule.get());
}

static void addressLayoutWithTargetLayout() {
AddressLayout addressLayout = ADDRESS.withoutTargetLayout();
addressLayout.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE));
}

static void linkerDowncallHandle() {
Linker linker = Linker.nativeLinker();
linker.downcallHandle(FunctionDescriptor.of(JAVA_LONG, ADDRESS));
}

static void linkerDowncallHandleWithAddress() {
Linker linker = Linker.nativeLinker();
linker.downcallHandle(linker.defaultLookup().find("strlen").get(), FunctionDescriptor.of(JAVA_LONG, ADDRESS));
}

static int callback() {
return 0;
}

static void linkerUpcallStub() throws NoSuchMethodException {
Linker linker = Linker.nativeLinker();

MethodHandle mh = null;
try {
mh = MethodHandles.lookup().findStatic(VersionSpecificNativeChecks.class, "callback", MethodType.methodType(int.class));
} catch (IllegalAccessException e) {
assert false;
}

FunctionDescriptor callbackDescriptor = FunctionDescriptor.of(ValueLayout.JAVA_INT);
linker.upcallStub(mh, callbackDescriptor, Arena.ofAuto());
}

static void memorySegmentReinterpret() {
Arena arena = Arena.ofAuto();
MemorySegment segment = arena.allocate(100);
segment.reinterpret(50);
}

static void memorySegmentReinterpretWithCleanup() {
Arena arena = Arena.ofAuto();
MemorySegment segment = arena.allocate(100);
segment.reinterpret(Arena.ofAuto(), s -> {});
}

static void memorySegmentReinterpretWithSizeAndCleanup() {
Arena arena = Arena.ofAuto();
MemorySegment segment = arena.allocate(100);
segment.reinterpret(50, Arena.ofAuto(), s -> {});
}

static void symbolLookupWithPath() {
try {
SymbolLookup.libraryLookup(Path.of("/foo/bar/libFoo.so"), Arena.ofAuto());
} catch (IllegalArgumentException e) {
// IllegalArgumentException is thrown if path does not point to a valid library (and it does not)
}
}

static void symbolLookupWithName() {
try {
SymbolLookup.libraryLookup("foo", Arena.ofAuto());
} catch (IllegalArgumentException e) {
// IllegalArgumentException is thrown if path does not point to a valid library (and it does not)
}
}
}
Loading

0 comments on commit 2171064

Please sign in to comment.