Skip to content

Commit 5411b93

Browse files
authored
Entitlements tools: public callers finder (#116257)
* WIP: Tool to find all public caller from a starting list of (JDK) methods. * Add public-callers-finder tool, extract common stuff to common module * Adjustments to visibility/functions and classes and modules to print out * Spotless * Missing gradle configuration * Add details in README as requested in PR * Update ASM version * Including protected methods
1 parent 1bad1cf commit 5411b93

File tree

11 files changed

+603
-50
lines changed

11 files changed

+603
-50
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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.tools;
11+
12+
import java.util.Arrays;
13+
import java.util.EnumSet;
14+
import java.util.stream.Collectors;
15+
16+
public enum ExternalAccess {
17+
PUBLIC_CLASS,
18+
PUBLIC_METHOD,
19+
PROTECTED_METHOD;
20+
21+
private static final String DELIMITER = ":";
22+
23+
public static String toString(EnumSet<ExternalAccess> externalAccesses) {
24+
return externalAccesses.stream().map(Enum::toString).collect(Collectors.joining(DELIMITER));
25+
}
26+
27+
public static EnumSet<ExternalAccess> fromPermissions(
28+
boolean packageExported,
29+
boolean publicClass,
30+
boolean publicMethod,
31+
boolean protectedMethod
32+
) {
33+
if (publicMethod && protectedMethod) {
34+
throw new IllegalArgumentException();
35+
}
36+
37+
EnumSet<ExternalAccess> externalAccesses = EnumSet.noneOf(ExternalAccess.class);
38+
if (publicMethod) {
39+
externalAccesses.add(ExternalAccess.PUBLIC_METHOD);
40+
} else if (protectedMethod) {
41+
externalAccesses.add(ExternalAccess.PROTECTED_METHOD);
42+
}
43+
44+
if (packageExported && publicClass) {
45+
externalAccesses.add(ExternalAccess.PUBLIC_CLASS);
46+
}
47+
return externalAccesses;
48+
}
49+
50+
public static boolean isExternallyAccessible(EnumSet<ExternalAccess> access) {
51+
return access.contains(ExternalAccess.PUBLIC_CLASS)
52+
&& (access.contains(ExternalAccess.PUBLIC_METHOD) || access.contains(ExternalAccess.PROTECTED_METHOD));
53+
}
54+
55+
public static EnumSet<ExternalAccess> fromString(String accessAsString) {
56+
if ("PUBLIC".equals(accessAsString)) {
57+
return EnumSet.of(ExternalAccess.PUBLIC_CLASS, ExternalAccess.PUBLIC_METHOD);
58+
}
59+
if ("PUBLIC-METHOD".equals(accessAsString)) {
60+
return EnumSet.of(ExternalAccess.PUBLIC_METHOD);
61+
}
62+
if ("PRIVATE".equals(accessAsString)) {
63+
return EnumSet.noneOf(ExternalAccess.class);
64+
}
65+
66+
return EnumSet.copyOf(Arrays.stream(accessAsString.split(DELIMITER)).map(ExternalAccess::valueOf).toList());
67+
}
68+
}

libs/entitlement/tools/common/src/main/java/org/elasticsearch/entitlement/tools/Utils.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,28 @@
1111

1212
import java.io.IOException;
1313
import java.lang.module.ModuleDescriptor;
14+
import java.net.URI;
1415
import java.nio.file.FileSystem;
16+
import java.nio.file.FileSystems;
1517
import java.nio.file.Files;
18+
import java.nio.file.Path;
1619
import java.util.HashMap;
20+
import java.util.List;
1721
import java.util.Map;
1822
import java.util.Set;
1923
import java.util.stream.Collectors;
2024

2125
public class Utils {
2226

23-
public static Map<String, Set<String>> findModuleExports(FileSystem fs) throws IOException {
27+
private static final Set<String> EXCLUDED_MODULES = Set.of(
28+
"java.desktop",
29+
"jdk.jartool",
30+
"jdk.jdi",
31+
"java.security.jgss",
32+
"jdk.jshell"
33+
);
34+
35+
private static Map<String, Set<String>> findModuleExports(FileSystem fs) throws IOException {
2436
var modulesExports = new HashMap<String, Set<String>>();
2537
try (var stream = Files.walk(fs.getPath("modules"))) {
2638
stream.filter(p -> p.getFileName().toString().equals("module-info.class")).forEach(x -> {
@@ -42,4 +54,27 @@ public static Map<String, Set<String>> findModuleExports(FileSystem fs) throws I
4254
return modulesExports;
4355
}
4456

57+
public interface JdkModuleConsumer {
58+
void accept(String moduleName, List<Path> moduleClasses, Set<String> moduleExports);
59+
}
60+
61+
public static void walkJdkModules(JdkModuleConsumer c) throws IOException {
62+
63+
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
64+
65+
var moduleExports = Utils.findModuleExports(fs);
66+
67+
try (var stream = Files.walk(fs.getPath("modules"))) {
68+
var modules = stream.filter(x -> x.toString().endsWith(".class"))
69+
.collect(Collectors.groupingBy(x -> x.subpath(1, 2).toString()));
70+
71+
for (var kv : modules.entrySet()) {
72+
var moduleName = kv.getKey();
73+
if (Utils.EXCLUDED_MODULES.contains(moduleName) == false) {
74+
var thisModuleExports = moduleExports.get(moduleName);
75+
c.accept(moduleName, kv.getValue(), thisModuleExports);
76+
}
77+
}
78+
}
79+
}
4580
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
This tool scans the JDK on which it is running. It takes a list of methods (compatible with the output of the `securitymanager-scanner` tool), and looks for the "public surface" of these methods (i.e. any class/method accessible from regular Java code that calls into the original list, directly or transitively).
2+
3+
It acts basically as a recursive "Find Usages" in Intellij, stopping at the first fully accessible point (public method on a public class).
4+
The tool scans every method in every class inside the same java module; e.g.
5+
if you have a private method `File#normalizedList`, it will scan `java.base` to find
6+
public methods like `File#list(String)`, `File#list(FilenameFilter, String)` and
7+
`File#listFiles(File)`.
8+
9+
The tool considers implemented interfaces (directly); e.g. if we're looking at a
10+
method `C.m`, where `C implements I`, it will look for calls to `I.m`. It will
11+
also consider (indirectly) calls to `S.m` (where `S` is a supertype of `C`), as
12+
it treats calls to `super` in `S.m` as regular calls (e.g. `example() -> S.m() -> C.m()`).
13+
14+
15+
In order to run the tool, use:
16+
```shell
17+
./gradlew :libs:entitlement:tools:public-callers-finder:run <input-file> [<bubble-up-from-public>]
18+
```
19+
Where `input-file` is a CSV file (columns separated by `TAB`) that contains the following columns:
20+
Module name
21+
1. unused
22+
2. unused
23+
3. unused
24+
4. Fully qualified class name (ASM style, with `/` separators)
25+
5. Method name
26+
6. Method descriptor (ASM signature)
27+
7. Visibility (PUBLIC/PUBLIC-METHOD/PRIVATE)
28+
29+
And `bubble-up-from-public` is a boolean (`true|false`) indicating if the code should stop at the first public method (`false`: default, recommended) or continue to find usages recursively even after reaching the "public surface".
30+
31+
The output of the tool is another CSV file, with one line for each entry-point, columns separated by `TAB`
32+
33+
1. Module name
34+
2. File name (from source root)
35+
3. Line number
36+
4. Fully qualified class name (ASM style, with `/` separators)
37+
5. Method name
38+
6. Method descriptor (ASM signature)
39+
7. Visibility (PUBLIC/PUBLIC-METHOD/PRIVATE)
40+
8. Original caller Module name
41+
9. Original caller Class name (ASM style, with `/` separators)
42+
10. Original caller Method name
43+
11. Original caller Visibility
44+
45+
Examples:
46+
```
47+
java.base DeleteOnExitHook.java 50 java/io/DeleteOnExitHook$1 run ()V PUBLIC java.base java/io/File delete PUBLIC
48+
java.base ZipFile.java 254 java/util/zip/ZipFile <init> (Ljava/io/File;ILjava/nio/charset/Charset;)V PUBLIC java.base java/io/File delete PUBLIC
49+
java.logging FileHandler.java 279 java/util/logging/FileHandler <init> ()V PUBLIC java.base java/io/File delete PUBLIC
50+
```
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
plugins {
2+
id 'application'
3+
}
4+
5+
apply plugin: 'elasticsearch.build'
6+
apply plugin: 'elasticsearch.publish'
7+
8+
tasks.named("dependencyLicenses").configure {
9+
mapping from: /asm-.*/, to: 'asm'
10+
}
11+
12+
group = 'org.elasticsearch.entitlement.tools'
13+
14+
ext {
15+
javaMainClass = "org.elasticsearch.entitlement.tools.publiccallersfinder.Main"
16+
}
17+
18+
application {
19+
mainClass.set(javaMainClass)
20+
applicationDefaultJvmArgs = [
21+
'--add-exports', 'java.base/sun.security.util=ALL-UNNAMED',
22+
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
23+
'--add-opens', 'java.base/java.net=ALL-UNNAMED',
24+
'--add-opens', 'java.base/java.net.spi=ALL-UNNAMED',
25+
'--add-opens', 'java.base/java.util.concurrent=ALL-UNNAMED',
26+
'--add-opens', 'java.base/javax.crypto=ALL-UNNAMED',
27+
'--add-opens', 'java.base/javax.security.auth=ALL-UNNAMED',
28+
'--add-opens', 'java.base/jdk.internal.logger=ALL-UNNAMED',
29+
'--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED',
30+
'--add-opens', 'jdk.management.jfr/jdk.management.jfr=ALL-UNNAMED',
31+
'--add-opens', 'java.logging/java.util.logging=ALL-UNNAMED',
32+
'--add-opens', 'java.logging/sun.util.logging.internal=ALL-UNNAMED',
33+
'--add-opens', 'java.naming/javax.naming.ldap.spi=ALL-UNNAMED',
34+
'--add-opens', 'java.rmi/sun.rmi.runtime=ALL-UNNAMED',
35+
'--add-opens', 'jdk.dynalink/jdk.dynalink=ALL-UNNAMED',
36+
'--add-opens', 'jdk.dynalink/jdk.dynalink.linker=ALL-UNNAMED',
37+
'--add-opens', 'java.desktop/sun.awt=ALL-UNNAMED',
38+
'--add-opens', 'java.sql.rowset/javax.sql.rowset.spi=ALL-UNNAMED',
39+
'--add-opens', 'java.sql/java.sql=ALL-UNNAMED',
40+
'--add-opens', 'java.xml.crypto/com.sun.org.apache.xml.internal.security.utils=ALL-UNNAMED'
41+
]
42+
}
43+
44+
repositories {
45+
mavenCentral()
46+
}
47+
48+
dependencies {
49+
compileOnly(project(':libs:core'))
50+
implementation 'org.ow2.asm:asm:9.7.1'
51+
implementation 'org.ow2.asm:asm-util:9.7.1'
52+
implementation(project(':libs:entitlement:tools:common'))
53+
}
54+
55+
tasks.named('forbiddenApisMain').configure {
56+
replaceSignatureFiles 'jdk-signatures'
57+
}
58+
59+
tasks.named("thirdPartyAudit").configure {
60+
ignoreMissingClasses()
61+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright (c) 2012 France Télécom
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions
6+
are met:
7+
1. Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
2. Redistributions in binary form must reproduce the above copyright
10+
notice, this list of conditions and the following disclaimer in the
11+
documentation and/or other materials provided with the distribution.
12+
3. Neither the name of the copyright holders nor the names of its
13+
contributors may be used to endorse or promote products derived from
14+
this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26+
THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)