All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.svm.truffle.tck.PermissionsFeature Maven / Gradle / Ivy

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.svm.truffle.tck;

import static com.oracle.graal.pointsto.reports.ReportUtils.report;

import java.io.FileDescriptor;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.polyglot.io.FileSystem;

import com.oracle.graal.pointsto.BigBang;
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
import com.oracle.graal.pointsto.meta.AnalysisMethod;
import com.oracle.graal.pointsto.meta.AnalysisType;
import com.oracle.graal.pointsto.meta.InvokeInfo;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.option.BundleMember;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.LocatableMultiOptionValue;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.hosted.FeatureImpl;
import com.oracle.svm.hosted.ImageClassLoader;
import com.oracle.svm.hosted.SVMHost;
import com.oracle.svm.hosted.config.ConfigurationParserUtils;
import com.oracle.svm.util.ClassUtil;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.runtime.OptimizedCallTarget;

import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.graph.Node;
import jdk.graal.compiler.graph.NodeInputList;
import jdk.graal.compiler.graph.NodeSourcePosition;
import jdk.graal.compiler.nodes.Invoke;
import jdk.graal.compiler.nodes.PiNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.java.NewInstanceNode;
import jdk.graal.compiler.nodes.spi.TrackedUnsafeAccess;
import jdk.graal.compiler.options.Option;
import jdk.graal.compiler.options.OptionType;
import jdk.vm.ci.common.JVMCIError;
import jdk.vm.ci.meta.ModifiersProvider;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;

/**
 * A Truffle TCK {@code Feature} detecting privileged calls done by Truffle language. The
 * {@code PermissionsFeature} finds calls of privileged methods originating in Truffle language. The
 * calls going through {@code Truffle} library, GraalVM SDK or compiler are treated as safe calls
 * and are not reported.
 * 

* To execute the {@code PermissionsFeature} you need to enable it using * {@code --features=com.oracle.svm.truffle.tck.PermissionsFeature} native-image option, specify * report file using {@code -H:TruffleTCKPermissionsReportFile} option and specify the language * packages by {@code -H:TruffleTCKPermissionsLanguagePackages} option. You also need to disable * folding of {@code System.getSecurityManager} using {@code -H:-FoldSecurityManagerGetter} option. */ public class PermissionsFeature implements Feature { private static final String CONFIG = "truffle-language-permissions-config.json"; public static class Options { @Option(help = "Path to file where to store report of Truffle language privilege access.")// public static final HostedOptionKey TruffleTCKPermissionsReportFile = new HostedOptionKey<>(null); @BundleMember(role = BundleMember.Role.Input)// @Option(help = "Comma separated list of exclude files.")// public static final HostedOptionKey TruffleTCKPermissionsExcludeFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @Option(help = "Maximal depth of a stack trace.", type = OptionType.Expert)// public static final HostedOptionKey TruffleTCKPermissionsMaxStackTraceDepth = new HostedOptionKey<>(-1); @Option(help = "Maximum number of erroneous privileged accesses reported.", type = OptionType.Expert)// public static final HostedOptionKey TruffleTCKPermissionsMaxErrors = new HostedOptionKey<>(100); } /** * Predicate to enable substitutions needed by the {@link PermissionsFeature}. */ static final class IsEnabled implements BooleanSupplier { @Override public boolean getAsBoolean() { return ImageSingletons.contains(PermissionsFeature.class); } } /** * List of safe packages. */ private static final Set safePackages; static { safePackages = new HashSet<>(); safePackages.add("org.graalvm."); safePackages.add("com.oracle.graalvm."); safePackages.add("com.oracle.svm.core."); safePackages.add("com.oracle.truffle.api."); safePackages.add("com.oracle.truffle.polyglot."); safePackages.add("com.oracle.truffle.host."); safePackages.add("com.oracle.truffle.nfi."); safePackages.add("com.oracle.truffle.object."); safePackages.add("com.oracle.truffle.runtime."); safePackages.add("com.oracle.truffle.runtime.debug."); safePackages.add("com.oracle.truffle.runtime.jfr."); safePackages.add("com.oracle.truffle.runtime.jfr.impl."); safePackages.add("com.oracle.truffle.runtime.hotspot."); safePackages.add("com.oracle.truffle.runtime.hotspot.libgraal."); safePackages.add("com.oracle.truffle.runtime.enterprise."); safePackages.add("com.oracle.truffle.sandbox.enterprise."); safePackages.add("com.oracle.truffle.polyglot.enterprise."); safePackages.add("com.oracle.truffle.object.enterprise."); safePackages.add("com.oracle.svm.truffle.isolated."); safePackages.add("com.oracle.svm.enterprise.truffle."); } private static final Set systemClassLoaders; static { systemClassLoaders = new HashSet<>(); for (ClassLoader cl = ClassLoader.getSystemClassLoader(); cl != null; cl = cl.getParent()) { systemClassLoaders.add(cl); } } /** * Methods which should not be found. */ private Set deniedMethods = new HashSet<>(); /** * Path to store report into. */ private Path reportFilePath; /** * Methods which are allowed to do privileged calls without being reported. */ private Set whiteList; private Set contextFilters; /** * Classes for reflective accesses which are opaque for permission analysis. */ private InlinedUnsafeMethodNode inlinedUnsafeCall; private Class sunMiscUnsafe; @Override public String getDescription() { return "Detects privileged calls in Truffle languages"; } @Override public void beforeAnalysis(BeforeAnalysisAccess access) { if (SubstrateOptions.FoldSecurityManagerGetter.getValue()) { UserError.abort("%s requires -H:-FoldSecurityManagerGetter option.", ClassUtil.getUnqualifiedName(getClass())); } String reportFile = Options.TruffleTCKPermissionsReportFile.getValue(); if (reportFile == null) { UserError.abort("Path to report file must be given by -H:TruffleTCKPermissionsReportFile option."); } reportFilePath = Paths.get(reportFile); if (ModuleLayer.boot().findModule("jdk.unsupported").isPresent()) { sunMiscUnsafe = access.findClassByName("sun.misc.Unsafe"); } var accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; initializeDeniedMethods(accessImpl); BigBang bb = accessImpl.getBigBang(); contextFilters = new HashSet<>(); Collections.addAll(contextFilters, new SafeInterruptRecognizer(bb), new SafePrivilegedRecognizer(bb), new SafeServiceLoaderRecognizer(bb, accessImpl.getImageClassLoader()), new SafeSetThreadNameRecognizer(bb)); /* * Ensure methods which are either deniedMethods or on the whiteList are never inlined into * methods. These methods are important for identifying violations. */ Set preventInlineBeforeAnalysis = new HashSet<>(); deniedMethods.stream().map(BaseMethodNode::getMethod).forEach(preventInlineBeforeAnalysis::add); whiteList.stream().map(BaseMethodNode::getMethod).forEach(preventInlineBeforeAnalysis::add); contextFilters.stream().map(CallGraphFilter::getInspectedMethods).forEach(preventInlineBeforeAnalysis::addAll); accessImpl.getHostVM().registerNeverInlineTrivialHandler((caller, callee) -> { if (!caller.isOriginalMethod()) { // we only care about tracing original methods return false; } if (preventInlineBeforeAnalysis.contains(callee)) { return true; } // We must maintain the boundary of entering a safe class return !isSafeClass(caller.getDeclaringClass()) && isSafeClass(callee.getDeclaringClass()); }); accessImpl.getHostVM().keepAnalysisGraphs(); } private void initializeDeniedMethods(FeatureImpl.BeforeAnalysisAccessImpl accessImpl) { BigBang bb = accessImpl.getBigBang(); if (sunMiscUnsafe != null) { inlinedUnsafeCall = new InlinedUnsafeMethodNode(bb.getMetaAccess().lookupJavaType(sunMiscUnsafe)); } WhiteListParser parser = new WhiteListParser(accessImpl.getImageClassLoader(), bb); ConfigurationParserUtils.parseAndRegisterConfigurations(parser, accessImpl.getImageClassLoader(), ClassUtil.getUnqualifiedName(getClass()), Options.TruffleTCKPermissionsExcludeFiles, new ResourceAsOptionDecorator(getClass().getPackage().getName().replace('.', '/') + "/resources/jre.json"), CONFIG); whiteList = parser.getLoadedWhiteList(); deniedMethods.addAll(findMethods(bb, SecurityManager.class, (m) -> m.getName().startsWith("check"))); if (sunMiscUnsafe != null) { deniedMethods.addAll(findMethods(bb, sunMiscUnsafe, ModifiersProvider::isPublic)); } // The type of the host Java NIO FileSystem. // The FileSystem obtained from the FileSystem.newDefaultFileSystem() is in the Truffle // package but // can be directly used by a language. We need to include it into deniedMethods. deniedMethods.addAll(findMethods(bb, FileSystem.newDefaultFileSystem().getClass(), ModifiersProvider::isPublic)); // JDK 19 introduced BigInteger.parallelMultiply that uses the ForkJoinPool. // We deny this method but explicitly allow non-parallel multiply (cf. jre.json). deniedMethods.addAll(findMethods(bb, BigInteger.class, (m) -> m.getName().startsWith("parallel"))); if (inlinedUnsafeCall != null) { deniedMethods.add(inlinedUnsafeCall); } } @Override @SuppressWarnings("try") public void afterAnalysis(AfterAnalysisAccess access) { try { Files.deleteIfExists(reportFilePath); } catch (IOException ioe) { throw UserError.abort("Cannot delete existing report file %s.", reportFilePath); } FeatureImpl.AfterAnalysisAccessImpl accessImpl = (FeatureImpl.AfterAnalysisAccessImpl) access; DebugContext debugContext = accessImpl.getDebugContext(); try (DebugContext.Scope s = debugContext.scope(ClassUtil.getUnqualifiedName(getClass()))) { BigBang bb = accessImpl.getBigBang(); Map> cg = callGraph(bb, deniedMethods, debugContext, (SVMHost) bb.getHostVM()); List> report = new ArrayList<>(); int maxStackDepth = Options.TruffleTCKPermissionsMaxStackTraceDepth.getValue(); maxStackDepth = maxStackDepth == -1 ? Integer.MAX_VALUE : maxStackDepth; for (BaseMethodNode deniedMethod : deniedMethods) { if (cg.containsKey(deniedMethod)) { collectViolations(report, deniedMethod, maxStackDepth, Options.TruffleTCKPermissionsMaxErrors.getValue(), cg, contextFilters, new LinkedList<>(), new HashSet<>(), 1, 0); } } if (!report.isEmpty()) { report( "detected privileged calls originated in language packages ", reportFilePath, (pw) -> { StringBuilder builder = new StringBuilder(); for (List callPath : report) { for (BaseMethodNode call : callPath) { builder.append(call.asStackTraceElement()).append('\n'); } builder.append('\n'); } pw.print(builder); }); } } } private static Class loadClassOrFail(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { throw JVMCIError.shouldNotReachHere(e); } } /** * Creates an inverted call graph for methods given by {@code targets} parameter. For each * called method in {@code targets} or transitive caller of {@code targets} the resulting * {@code Map} contains an entry holding all direct callers of the method in the entry value. * * @param bb the {@link BigBang} * @param targets the target methods to build call graph for * @param debugContext the {@link DebugContext} */ private Map> callGraph(BigBang bb, Set targets, DebugContext debugContext, SVMHost hostVM) { Deque todo = new LinkedList<>(); Map> visited = new HashMap<>(); for (AnalysisMethodNode m : findMethods(bb, OptimizedCallTarget.class, (m) -> "profiledPERoot".equals(m.getName()))) { visited.put(m, new HashSet<>()); todo.add(m); } Deque path = new LinkedList<>(); for (AnalysisMethodNode m : todo) { callGraphImpl(m, targets, visited, path, debugContext, hostVM); } return visited; } private void findUnsafeAccesses( BaseMethodNode mNode, Map> visited, SVMHost hostVM) { /* * In this situation it is unnecessary to check for unsafe accesses. */ if (inlinedUnsafeCall == null || isSystemOrSafeClass(mNode)) { return; } StructuredGraph mGraph = hostVM.getAnalysisGraph(mNode.getMethod()); for (Node node : mGraph.getNodes().filter(n -> n instanceof TrackedUnsafeAccess)) { /* * Check the origin of all tracked unsafe accesses. * * We must determine whether the access originates from a safe class. It is possible for * these accesses to be inlined into other methods during the method handle * intrinsification process. */ NodeSourcePosition current = node.getNodeSourcePosition(); boolean foundSystemClass = false; while (current != null) { var declaringClass = OriginalClassProvider.getJavaClass(current.getMethod().getDeclaringClass()); if (!declaringClass.equals(sunMiscUnsafe) && isSystemClass(declaringClass)) { foundSystemClass = true; break; } current = current.getCaller(); } if (!foundSystemClass) { visited.computeIfAbsent(inlinedUnsafeCall, (e) -> new HashSet<>()).add(mNode); return; } } } private boolean callGraphImpl( BaseMethodNode mNode, Set targets, Map> visited, Deque path, DebugContext debugContext, SVMHost hostVM) { AnalysisMethod m = mNode.getMethod(); String mName = getMethodName(m); path.addFirst(mNode); findUnsafeAccesses(mNode, visited, hostVM); try { boolean callPathContainsTarget = false; debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Entered method: %s.", mName); for (InvokeInfo invoke : m.getInvokes()) { for (AnalysisMethod callee : invoke.getOriginalCallees()) { AnalysisMethodNode calleeNode = new AnalysisMethodNode(callee); if (callee.isInvoked() || !isSystemOrSafeClass(calleeNode)) { Set parents = visited.get(calleeNode); String calleeName = getMethodName(callee); debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Callee: %s, new: %b.", calleeName, parents == null); if (parents == null) { parents = new HashSet<>(); visited.put(calleeNode, parents); if (targets.contains(calleeNode)) { parents.add(mNode); callPathContainsTarget = true; continue; } boolean add = callGraphImpl(calleeNode, targets, visited, path, debugContext, hostVM); if (add) { parents.add(mNode); debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Added callee: %s for %s.", calleeName, mName); } callPathContainsTarget |= add; } else if (!isBacktrace(calleeNode, path) || isBackTraceOverLanguageMethod(calleeNode, path)) { parents.add(mNode); debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Added backtrace callee: %s for %s.", calleeName, mName); callPathContainsTarget = true; } else { if (debugContext.isLogEnabled(DebugContext.VERY_DETAILED_LEVEL)) { debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Ignoring backtrace callee: %s for %s.", calleeName, mName); } } } } } debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Exited method: %s.", mName); return callPathContainsTarget; } finally { path.removeFirst(); } } /** * Checks if the method is already included on call path, in other words it's a recursive call. * * @param method the {@link AnalysisMethodNode} to check * @param path the current call path */ private static boolean isBacktrace(AnalysisMethodNode method, Deque path) { return path.contains(method); } /** * Checks if the back call of given method crosses some language method on given call path. If * the back call crosses a language method the call has to be included into the call graph, the * crossed language method is the start method of a violation. Example: P privileged method, L * language method. * *

     * G((A,L),(A,P),(L,C),(C,A),(C,D))
     * 
* * The violation is L->C->A->P * * @param method the method being invoked * @param path the current call path * @return {@code true} if the call of given method crosses some language method. */ private static boolean isBackTraceOverLanguageMethod(AnalysisMethodNode method, Deque path) { if (!isSystemOrSafeClass(method)) { return false; } boolean found = false; for (Iterator it = path.descendingIterator(); it.hasNext();) { BaseMethodNode pe = it.next(); if (method.equals(pe)) { found = true; } else if (found && !isSystemOrSafeClass(pe)) { return true; } } return false; } /** * Collects the calls of privileged methods originated in Truffle language. * * @param report the list to collect violations into * @param mNode currently processed method * @param maxDepth maximal call trace depth * @param maxReports maximal number of reports * @param callGraph call graph obtained from * {@link PermissionsFeature#callGraph(BigBang, Set, DebugContext, SVMHost)} * @param contextFiltersParam filters removing known valid calls * @param currentPath current path from a privileged method in a call graph * @param visited set of already visited methods, these methods are already part of an existing * report or do not lead to language class * @param depth current depth */ private int collectViolations( List> report, BaseMethodNode mNode, int maxDepth, int maxReports, Map> callGraph, Set contextFiltersParam, List currentPath, Set visited, int depth, int initialNumReports) { int numReports = initialNumReports; if (numReports >= maxReports) { return numReports; } if (depth > 1) { // The denied method can be a method from a "safe" class if (isSafeClass(mNode)) { return numReports; } // The denied method can be excluded by a white list if (isExcludedClass(mNode)) { return numReports; } } if (!visited.contains(mNode)) { visited.add(mNode); currentPath.add(mNode); try { Set callers = callGraph.get(mNode); if (depth > maxDepth) { if (!callers.isEmpty()) { numReports = collectViolations(report, callers.iterator().next(), maxDepth, maxReports, callGraph, contextFiltersParam, currentPath, visited, depth + 1, numReports); } } else if (!isSystemOrSafeClass(mNode)) { List callPath = new ArrayList<>(currentPath); report.add(callPath); numReports++; } else { for (BaseMethodNode caller : callers) { if (contextFiltersParam.stream().noneMatch((f) -> f.test(mNode, caller, currentPath))) { numReports = collectViolations(report, caller, maxDepth, maxReports, callGraph, contextFiltersParam, currentPath, visited, depth + 1, numReports); } } } } finally { BaseMethodNode last = currentPath.removeLast(); assert last == mNode; } } return numReports; } private static boolean isSystemOrSafeClass(BaseMethodNode methodNode) { return isSystemClass(methodNode) || isSafeClass(methodNode); } /** * Tests if method represented by given {@link BaseMethodNode} is loaded by a system * {@link ClassLoader}. * * @param methodNode the {@link BaseMethodNode} to check */ private static boolean isSystemClass(BaseMethodNode methodNode) { return isSystemClass(methodNode.getOwner().getJavaClass()); } private static boolean isSystemClass(Class clz) { if (clz == null) { return false; } return clz.getClassLoader() == null || systemClassLoaders.contains(clz.getClassLoader()); } /** * Tests if the given {@link AnalysisMethod} is from Truffle library, GraalVM SDK or compiler * package. * * @param method the {@link AnalysisMethod} to check */ private static boolean isSafeClass(BaseMethodNode method) { return isSafeClass(method.getOwner()); } private static boolean isSafeClass(AnalysisType type) { return isClassInPackage(type.toJavaName(), safePackages); } /** * Tests if a class of given name transitively belongs to some package given by {@code packages} * parameter. * * @param javaName the {@link AnalysisMethod} to check * @param packages the list of packages */ private static boolean isClassInPackage(String javaName, Collection packages) { for (String pkg : packages) { if (javaName.startsWith(pkg)) { return true; } } return false; } /** * Tests if the given {@link BaseMethodNode} is excluded by white list. * * @param methodNode the {@link BaseMethodNode} to check */ private boolean isExcludedClass(BaseMethodNode methodNode) { return whiteList.contains(methodNode); } /** * Finds methods declared in {@code owner} class using {@code filter} predicate. * * @param bb the {@link BigBang} * @param owner the class which methods should be listed * @param filter the predicate filtering methods declared in {@code owner} * @return the methods accepted by {@code filter} * @throws IllegalStateException if owner cannot be resolved */ private static Set findMethods(BigBang bb, Class owner, Predicate filter) { AnalysisType clazz = bb.getMetaAccess().lookupJavaType(owner); if (clazz == null) { throw new IllegalStateException("Cannot resolve " + owner.getName() + "."); } return findMethods(bb, clazz, filter); } /** * Finds methods declared in {@code owner} {@link AnalysisType} using {@code filter} predicate. * * @param bb the {@link BigBang} * @param owner the {@link AnalysisType} which methods should be listed * @param filter the predicate filtering methods declared in {@code owner} * @return the methods accepted by {@code filter} */ static Set findMethods(BigBang bb, AnalysisType owner, Predicate filter) { return findImpl(bb, owner.getWrapped().getDeclaredMethods(false), filter); } /** * Finds constructors declared in {@code owner} {@link AnalysisType} using {@code filter} * predicate. * * @param bb the {@link BigBang} * @param owner the {@link AnalysisType} which constructors should be listed * @param filter the predicate filtering constructors declared in {@code owner} * @return the constructors accepted by {@code filter} */ static Set findConstructors(BigBang bb, AnalysisType owner, Predicate filter) { return findImpl(bb, owner.getWrapped().getDeclaredConstructors(false), filter); } private static Set findImpl(BigBang bb, ResolvedJavaMethod[] methods, Predicate filter) { Set result = new HashSet<>(); for (ResolvedJavaMethod m : methods) { if (filter.test(m)) { result.add(new AnalysisMethodNode(bb.getUniverse().lookup(m))); } } return result; } /** * Returns a method name in the format: {@code ownerFQN.name(parameters)}. * * @param method to create a name for */ private static String getMethodName(AnalysisMethod method) { return method.format("%H.%n(%p)"); } /** * Filter to filter out known valid calls, included by points to analysis, from the report. */ private interface CallGraphFilter { /** * @return whether this methodNode should not be considered a violation */ boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, List trace); Collection getInspectedMethods(); } /** * Filters out {@link Thread#interrupt()} calls done on {@link Thread#currentThread()}. */ private static final class SafeInterruptRecognizer implements CallGraphFilter { private final SVMHost hostVM; private final AnalysisMethodNode threadInterrupt; private final AnalysisMethod threadCurrentThread; SafeInterruptRecognizer(BigBang bb) { this.hostVM = (SVMHost) bb.getHostVM(); Set methods = findMethods(bb, Thread.class, (m) -> m.getName().equals("interrupt")); if (methods.size() != 1) { throw new IllegalStateException("Failed to lookup Thread.interrupt()."); } threadInterrupt = methods.iterator().next(); methods = findMethods(bb, Thread.class, (m) -> m.getName().equals("currentThread")); if (methods.size() != 1) { throw new IllegalStateException("Failed to lookup Thread.currentThread()."); } threadCurrentThread = methods.iterator().next().getMethod(); } @Override public boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, List trace) { Boolean res = null; if (threadInterrupt.equals(methodNode)) { AnalysisMethod caller = callerNode.getMethod(); StructuredGraph graph = hostVM.getAnalysisGraph(caller); for (Invoke invoke : graph.getInvokes()) { if (threadInterrupt.getMethod().equals(invoke.callTarget().targetMethod())) { boolean vote = false; ValueNode node = invoke.getReceiver(); if (node instanceof PiNode) { node = ((PiNode) node).getOriginalNode(); if (node instanceof Invoke) { boolean isCurrentThread = threadCurrentThread.equals(((Invoke) node).callTarget().targetMethod()); vote = res == null ? isCurrentThread : (res && isCurrentThread); } } res = vote; } } } return res != null && res; } @Override public Collection getInspectedMethods() { return Set.of(threadInterrupt.getMethod(), threadCurrentThread); } } /** * Filters out {@code AccessController#doPrivileged} done by JRE. */ private static final class SafePrivilegedRecognizer implements CallGraphFilter { private final SVMHost hostVM; private final Set doPrivileged; SafePrivilegedRecognizer(BigBang bb) { this.hostVM = (SVMHost) bb.getHostVM(); this.doPrivileged = findMethods(bb, java.security.AccessController.class, (m) -> m.getName().equals("doPrivileged") || m.getName().equals("doPrivilegedWithCombiner")); } @Override public boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, List trace) { if (!doPrivileged.contains(methodNode)) { return false; } if (isSystemOrSafeClass(callerNode)) { return true; } AnalysisMethod method = methodNode.getMethod(); AnalysisMethod caller = callerNode.getMethod(); StructuredGraph graph = hostVM.getAnalysisGraph(caller); for (Invoke invoke : graph.getInvokes()) { if (method.equals(invoke.callTarget().targetMethod())) { NodeInputList args = invoke.callTarget().arguments(); if (args.isEmpty()) { return false; } ValueNode arg0 = args.get(0); if (!(arg0 instanceof NewInstanceNode)) { return false; } ResolvedJavaType newType = ((NewInstanceNode) arg0).instanceClass(); ResolvedJavaMethod methodCalledByAccessController = findPrivilegedEntryPoint(method, trace); if (newType == null || methodCalledByAccessController == null) { return false; } if (newType.equals(methodCalledByAccessController.getDeclaringClass())) { return false; } } } return true; } /** * Finds an entry point to {@code PrivilegedAction} called by {@code doPrivilegedMethod}. */ private static ResolvedJavaMethod findPrivilegedEntryPoint(ResolvedJavaMethod doPrivilegedMethod, List trace) { ResolvedJavaMethod ep = null; for (BaseMethodNode mNode : trace) { AnalysisMethod m = mNode.getMethod(); if (doPrivilegedMethod.equals(m)) { return ep; } ep = m; } return null; } @Override public Collection getInspectedMethods() { return doPrivileged.stream().map(AnalysisMethodNode::getMethod).toList(); } } private static final class SafeServiceLoaderRecognizer implements CallGraphFilter { private final AnalysisMethodNode providerImplGet; private final ImageClassLoader imageClassLoader; SafeServiceLoaderRecognizer(BigBang bb, ImageClassLoader imageClassLoader) { AnalysisType serviceLoaderIterator = bb.getMetaAccess().lookupJavaType(loadClassOrFail("java.util.ServiceLoader$ProviderImpl")); Set methods = findMethods(bb, serviceLoaderIterator, (m) -> m.getName().equals("get")); if (methods.size() != 1) { throw new IllegalStateException("Failed to lookup ServiceLoader$ProviderImpl.get()."); } this.providerImplGet = methods.iterator().next(); this.imageClassLoader = imageClassLoader; } @Override public boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, List trace) { if (providerImplGet.equals(methodNode)) { ResolvedJavaType instantiatedType = findInstantiatedType(trace); return instantiatedType != null && !isRegisteredInServiceLoader(instantiatedType); } return false; } /** * Finds last constructor invocation. */ private static ResolvedJavaType findInstantiatedType(List trace) { ResolvedJavaType res = null; for (BaseMethodNode mNode : trace) { AnalysisMethod m = mNode.getMethod(); if (m != null && "".equals(m.getName())) { res = m.getDeclaringClass(); } } return res; } /** * Finds if the given type may be instantiated by ServiceLoader. */ private boolean isRegisteredInServiceLoader(ResolvedJavaType type) { String resource = String.format("META-INF/services/%s", type.toClassName()); if (imageClassLoader.getClassLoader().getResource(resource) != null) { return true; } for (ResolvedJavaType ifc : type.getInterfaces()) { if (isRegisteredInServiceLoader(ifc)) { return true; } } ResolvedJavaType superClz = type.getSuperclass(); if (superClz != null) { return isRegisteredInServiceLoader(superClz); } return false; } @Override public Collection getInspectedMethods() { return Set.of(providerImplGet.getMethod()); } } private static final class SafeSetThreadNameRecognizer implements CallGraphFilter { private final SVMHost hostVM; private final AnalysisMethodNode threadSetName; private final Set envCreateThread; private final Set envCreateSystemThread; SafeSetThreadNameRecognizer(BigBang bb) { hostVM = (SVMHost) bb.getHostVM(); Set methods = findMethods(bb, Thread.class, (m) -> m.getName().equals("setName")); if (methods.size() != 1) { throw new IllegalStateException("Failed to lookup Thread.setName()."); } threadSetName = methods.iterator().next(); envCreateThread = findMethods(bb, TruffleLanguage.Env.class, (m) -> m.getName().equals("createThread")).stream().map(AnalysisMethodNode::getMethod).collect(Collectors.toSet()); if (envCreateThread.isEmpty()) { throw new IllegalStateException("Failed to lookup TruffleLanguage.Env.createThread()."); } envCreateSystemThread = findMethods(bb, TruffleLanguage.Env.class, (m) -> m.getName().equals("createSystemThread")).stream().map(AnalysisMethodNode::getMethod).collect(Collectors.toSet()); if (envCreateSystemThread.isEmpty()) { throw new IllegalStateException("Failed to lookup TruffleLanguage.Env.createSystemThread()."); } } @Override public boolean test(BaseMethodNode methodNode, BaseMethodNode callerNode, List trace) { if (!threadSetName.equals(methodNode)) { return false; } AnalysisMethod caller = callerNode.getMethod(); StructuredGraph graph = hostVM.getAnalysisGraph(caller); Boolean res = null; AnalysisMethod method = methodNode.getMethod(); for (Invoke invoke : graph.getInvokes()) { if (method.equals(invoke.callTarget().targetMethod())) { NodeInputList args = invoke.callTarget().arguments(); ValueNode arg0 = args.get(0); boolean isTruffleThread = false; if (arg0 instanceof PiNode) { arg0 = ((PiNode) arg0).getOriginalNode(); if (arg0 instanceof Invoke) { ResolvedJavaMethod target = ((Invoke) arg0).callTarget().targetMethod(); isTruffleThread = envCreateThread.contains(target) || envCreateSystemThread.contains(target); } } res = res == null ? isTruffleThread : (res && isTruffleThread); } } return res != null && res; } @Override public Collection getInspectedMethods() { Set set = new HashSet<>(envCreateThread); set.addAll(envCreateSystemThread); set.add(threadSetName.getMethod()); return set; } } /** * Options facade for a resource containing the JRE white list. */ private static final class ResourceAsOptionDecorator extends HostedOptionKey { ResourceAsOptionDecorator(String defaultValue) { super(LocatableMultiOptionValue.Strings.buildWithDefaults(defaultValue)); } } abstract static class BaseMethodNode { abstract StackTraceElement asStackTraceElement(); abstract AnalysisType getOwner(); abstract AnalysisMethod getMethod(); } private static final class InlinedUnsafeMethodNode extends BaseMethodNode { private final AnalysisType unsafe; InlinedUnsafeMethodNode(AnalysisType unsafe) { this.unsafe = unsafe; } @Override StackTraceElement asStackTraceElement() { return new StackTraceElement(unsafe.toJavaName(), "", unsafe.getSourceFileName(), -1); } @Override AnalysisType getOwner() { return unsafe; } @Override AnalysisMethod getMethod() { return null; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } InlinedUnsafeMethodNode other = (InlinedUnsafeMethodNode) o; return unsafe.equals(other.unsafe); } @Override public int hashCode() { return Objects.hash(unsafe); } @Override public String toString() { return String.format("%s[unsafe=%s]", ClassUtil.getUnqualifiedName(getClass()), unsafe); } } static final class AnalysisMethodNode extends BaseMethodNode { private final AnalysisMethod method; AnalysisMethodNode(AnalysisMethod method) { this.method = Objects.requireNonNull(method); } @Override StackTraceElement asStackTraceElement() { return method.asStackTraceElement(0); } @Override AnalysisType getOwner() { return method.getDeclaringClass(); } @Override public AnalysisMethod getMethod() { return method; } @Override public int hashCode() { return Objects.hash(method); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } AnalysisMethodNode other = (AnalysisMethodNode) o; return method.equals(other.method); } @Override public String toString() { return String.format("%s[method=%s]", ClassUtil.getUnqualifiedName(getClass()), method); } } } @TargetClass(value = java.lang.SecurityManager.class, onlyWith = PermissionsFeature.IsEnabled.class) final class Target_java_lang_SecurityManager { @Substitute @SuppressWarnings("unused") private void checkSecurityAccess(String target) { } @Substitute private void checkSetFactory() { } @Substitute @SuppressWarnings("unused") private void checkPackageDefinition(String pkg) { } @Substitute @SuppressWarnings("unused") private void checkPackageAccess(String pkg) { } @Substitute private void checkPrintJobAccess() { } @Substitute @SuppressWarnings("unused") private void checkPropertyAccess(String key) { } @Substitute private void checkPropertiesAccess() { } @Substitute @SuppressWarnings("unused") private void checkMulticast(InetAddress maddr) { } @Substitute @SuppressWarnings("unused") private void checkAccept(String host, int port) { } @Substitute @SuppressWarnings("unused") private void checkListen(int port) { } @Substitute @SuppressWarnings("unused") private void checkConnect(String host, int port, Object context) { } @Substitute @SuppressWarnings("unused") private void checkConnect(String host, int port) { } @Substitute @SuppressWarnings("unused") private void checkDelete(String file) { } @Substitute @SuppressWarnings("unused") private void checkWrite(String file) { } @Substitute @SuppressWarnings("unused") private void checkWrite(FileDescriptor fd) { } @Substitute @SuppressWarnings("unused") private void checkRead(String file, Object context) { } @Substitute @SuppressWarnings("unused") private void checkRead(String file) { } @Substitute @SuppressWarnings("unused") private void checkRead(FileDescriptor fd) { } @Substitute @SuppressWarnings("unused") private void checkLink(String lib) { } @Substitute @SuppressWarnings("unused") private void checkExec(String cmd) { } @Substitute @SuppressWarnings("unused") private void checkExit(int status) { } @Substitute @SuppressWarnings("unused") private void checkAccess(ThreadGroup g) { } @Substitute @SuppressWarnings("unused") private void checkAccess(Thread t) { } @Substitute private void checkCreateClassLoader() { } @Substitute @SuppressWarnings("unused") private void checkPermission(Permission perm, Object context) { } @Substitute @SuppressWarnings("unused") private void checkPermission(Permission perm) { } } final class SecurityManagerHolder { @SuppressWarnings("deprecation") // SecurityManager deprecated since 17. static final SecurityManager SECURITY_MANAGER = new SecurityManager(); } @TargetClass(value = java.lang.System.class, onlyWith = PermissionsFeature.IsEnabled.class) final class Target_java_lang_System { @Substitute private static SecurityManager getSecurityManager() { return SecurityManagerHolder.SECURITY_MANAGER; } } final class LoggerFinderHolder { static final System.LoggerFinder LOGGER_FINDER = System.LoggerFinder.getLoggerFinder(); } @TargetClass(value = java.lang.System.LoggerFinder.class, onlyWith = PermissionsFeature.IsEnabled.class) final class Target_java_lang_System_LoggerFinder { @Substitute private static System.LoggerFinder getLoggerFinder() { return LoggerFinderHolder.LOGGER_FINDER; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy