From 85d3bee36c026a70580075092ed85ac517369e8e Mon Sep 17 00:00:00 2001 From: Marco Boeck Date: Wed, 31 Aug 2016 10:15:22 +0200 Subject: [PATCH] Merge branch 'release/7.2.2' --- gradle.properties | 2 +- src/main/java/com/rapidminer/Process.java | 18 ++++- .../rapidminer/operator/ExecutionUnit.java | 24 ++++++- .../security/PluginSandboxPolicy.java | 49 ++++++++++++- .../internal/ParameterServiceRegistry.java | 9 +++ .../internal/ProcessFlowFilterRegistry.java | 72 +++++++++++++++++++ .../com/rapidminer/tools/plugin/Plugin.java | 24 ++++++- 7 files changed, 190 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/rapidminer/studio/internal/ProcessFlowFilterRegistry.java diff --git a/gradle.properties b/gradle.properties index 4461dc1ff..395ff25f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=7.2.1 +version=7.2.2 group=com.rapidminer.studio diff --git a/src/main/java/com/rapidminer/Process.java b/src/main/java/com/rapidminer/Process.java index 30654681f..cc485c674 100644 --- a/src/main/java/com/rapidminer/Process.java +++ b/src/main/java/com/rapidminer/Process.java @@ -86,6 +86,7 @@ import com.rapidminer.repository.RepositoryException; import com.rapidminer.repository.RepositoryLocation; import com.rapidminer.repository.RepositoryManager; +import com.rapidminer.studio.internal.ProcessFlowFilterRegistry; import com.rapidminer.tools.AbstractObservable; import com.rapidminer.tools.LogService; import com.rapidminer.tools.LoggingHandler; @@ -713,6 +714,9 @@ public boolean shouldPause() { /** * Add a new {@link ProcessFlowFilter} to this process. The filter will be called directly * before and after each operator. Refer to {@link ProcessFlowFilter} for more information. + *

+ * If the given filter instance is already registered, it will not be added a second time. + *

* * @param filter * the filter instance to add @@ -721,7 +725,9 @@ public void addProcessFlowFilter(ProcessFlowFilter filter) { if (filter == null) { throw new IllegalArgumentException("filter must not be null!"); } - processFlowFilters.add(filter); + if (!processFlowFilters.contains(filter)) { + processFlowFilters.add(filter); + } } /** @@ -801,6 +807,10 @@ public void fireProcessFlowAfterOperator(Operator previousOperator, Operator nex * process instance */ public void copyProcessFlowListenersToOtherProcess(Process otherProcess) { + if (otherProcess == null) { + throw new IllegalArgumentException("otherProcess must not be null!"); + } + synchronized (processFlowFilters) { for (ProcessFlowFilter filter : processFlowFilters) { otherProcess.addProcessFlowFilter(filter); @@ -1097,6 +1107,12 @@ public final IOContainer run(final IOContainer input, final int logVerbosity, fi */ public final IOContainer run(final IOContainer input, int logVerbosity, final Map macroMap, final boolean storeOutput) throws OperatorException { + // make sure the process flow filter is registered + ProcessFlowFilter filter = ProcessFlowFilterRegistry.INSTANCE.getProcessFlowFilter(); + if (filter != null) { + addProcessFlowFilter(filter); + } + // make sure licensing constraints are not violated // iterate over all operators in the process for (Operator op : rootOperator.getAllInnerOperators()) { diff --git a/src/main/java/com/rapidminer/operator/ExecutionUnit.java b/src/main/java/com/rapidminer/operator/ExecutionUnit.java index 9f237f477..3c9b9619d 100644 --- a/src/main/java/com/rapidminer/operator/ExecutionUnit.java +++ b/src/main/java/com/rapidminer/operator/ExecutionUnit.java @@ -18,6 +18,9 @@ */ package com.rapidminer.operator; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -39,6 +42,7 @@ import com.rapidminer.Process; import com.rapidminer.operator.execution.UnitExecutionFactory; +import com.rapidminer.operator.execution.UnitExecutor; import com.rapidminer.operator.ports.InputPort; import com.rapidminer.operator.ports.InputPorts; import com.rapidminer.operator.ports.OutputPort; @@ -795,7 +799,25 @@ protected List createProcessTreeList(int indent, String selfPrefix, Stri /** Executes the inner operators. */ public void execute() throws OperatorException { - UnitExecutionFactory.getInstance().getExecutor(this).execute(this); + UnitExecutor executor = UnitExecutionFactory.getInstance().getExecutor(this); + // check only the callstack of nested operators, otherwise execution units of + // unsigned extensions might not be able to execute trusted operators + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + + @Override + public Void run() throws OperatorException { + + executor.execute(ExecutionUnit.this); + return null; + } + }); + } catch (PrivilegedActionException e) { + // e.getException() should be an instance of OperatorException, + // as only checked exceptions will be wrapped in a + // PrivilegedActionException. + throw (OperatorException) e.getException(); + } } /** Frees memory used by inner sinks. */ diff --git a/src/main/java/com/rapidminer/security/PluginSandboxPolicy.java b/src/main/java/com/rapidminer/security/PluginSandboxPolicy.java index 7299f903e..11b8ce9dd 100644 --- a/src/main/java/com/rapidminer/security/PluginSandboxPolicy.java +++ b/src/main/java/com/rapidminer/security/PluginSandboxPolicy.java @@ -45,6 +45,9 @@ */ public final class PluginSandboxPolicy extends Policy { + /** Internal permission for {@link RuntimePermission}s */ + public static final String RAPIDMINER_INTERNAL_PERMISSION = "accessClassInPackage.rapidminer.internal"; + /** The key pair algorithm for our signed extensions */ private static final String KEY_ALGORITHM = "RSA"; @@ -54,9 +57,20 @@ public final class PluginSandboxPolicy extends Policy { + "anxWScOfVW6yDxEjgEHJvMiMzZkGNklYC3ULBCkHfIrih5hO83k5FileuUWDNO4BrLrawmjo9AmYksPVOMmd4/DtDpnehpLy0hQtjBJsz61h" + "AGVDnPGpvbsW0rjFAjE4fR5+4RwUNo+SsD/44Jc8bui5seVH5vZuTj02XokybGR4BikrqvJZ4rHe4OGowl8uIr9sEN/+0eIJXQIDAQAB"; + /** + * the system property which can be set to {@code true} to enforce plugin sandboxing even on + * SNAPSHOT versions + */ + private static final String PROPERTY_SECURITY_ENFORCED = "com.rapidminer.security.enforce"; + /** Our public key used to verify the certificates */ private static PublicKey key; + /** + * if {@code true}, plugin sandboxing is enforced even on SNAPSHOT versions + */ + private static volatile Boolean enforced; + static { try { KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM); @@ -125,7 +139,8 @@ private static boolean isUnsignedPlugin(ProtectionDomain domain) { return true; } // special case for SNAPSHOT version: grant all permissions for all extensions - if (RapidMiner.getVersion().isSnapshot()) { + // unless security is enforced via system property + if (RapidMiner.getVersion().isSnapshot() && !isSecurityEnforced()) { return false; } if (domain.getCodeSource().getCertificates() == null) { @@ -200,11 +215,12 @@ private static PermissionCollection createUnsignedPermissions(final PluginClassL @Override public Void run() { String userHome = System.getProperty("user.home"); - String tempDir = System.getProperty("java.io.tempdir"); + String tmpDir = System.getProperty("java.io.tmpdir"); String pluginKey = loader.getPluginKey(); // delete access to the general temp directory - permissions.add(new FilePermission(tempDir + "/-", "read, write, delete")); + permissions.add(new FilePermission(tmpDir, "read, write")); + permissions.add(new FilePermission(tmpDir + "/-", "read, write, delete")); // extensions can only delete files in their own subfolder of the // .RapidMiner/extensions/workspace folder @@ -325,4 +341,31 @@ private static void verifyCertificates(Certificate[] certificates) throws Genera throw lastException; } } + + /** + * Checks whether the system property {@value #PROPERTY_SECURITY_ENFORCED} is set to + * {@code true}. This property is used to enable the full plugin sandbox security even on + * SNAPSHOT versions. + * + * @return {@code true} if the system property is set to 'true', {@code false} otherwise + */ + private static boolean isSecurityEnforced() { + // no need to synchronize, if this is entered multiple times it's fine + if (enforced == null) { + enforced = AccessController.doPrivileged(new PrivilegedAction() { + + @Override + public Boolean run() { + return Boolean.parseBoolean(System.getProperty(PROPERTY_SECURITY_ENFORCED)); + } + }); + + if (enforced) { + LogService.getRoot().log(Level.INFO, + "Plugin sandboxing enforced via '" + PROPERTY_SECURITY_ENFORCED + "' property."); + } + } + + return enforced; + } } diff --git a/src/main/java/com/rapidminer/studio/internal/ParameterServiceRegistry.java b/src/main/java/com/rapidminer/studio/internal/ParameterServiceRegistry.java index 652b848f5..347707bed 100644 --- a/src/main/java/com/rapidminer/studio/internal/ParameterServiceRegistry.java +++ b/src/main/java/com/rapidminer/studio/internal/ParameterServiceRegistry.java @@ -18,6 +18,9 @@ */ package com.rapidminer.studio.internal; +import java.security.AccessController; + +import com.rapidminer.security.PluginSandboxPolicy; import com.rapidminer.tools.ParameterService; @@ -41,8 +44,14 @@ public enum ParameterServiceRegistry { * * @param provider * the provider to register + * @throws SecurityException + * if caller does not have {@link RuntimePermission} for + * {@code accessClassInPackage.rapidminer.internal} */ public void register(ParameterServiceProvider provider) { + if (System.getSecurityManager() != null) { + AccessController.checkPermission(new RuntimePermission(PluginSandboxPolicy.RAPIDMINER_INTERNAL_PERMISSION)); + } if (provider == null) { throw new IllegalArgumentException("Provider cannot be null"); } diff --git a/src/main/java/com/rapidminer/studio/internal/ProcessFlowFilterRegistry.java b/src/main/java/com/rapidminer/studio/internal/ProcessFlowFilterRegistry.java new file mode 100644 index 000000000..5f2621595 --- /dev/null +++ b/src/main/java/com/rapidminer/studio/internal/ProcessFlowFilterRegistry.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2001-2016 by RapidMiner and the contributors + * + * Complete list of developers available at our web site: + * + * http://rapidminer.com + * + * This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU Affero General Public License as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * This program 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License along with this program. + * If not, see http://www.gnu.org/licenses/. + */ +package com.rapidminer.studio.internal; + +import java.security.AccessController; + +import com.rapidminer.operator.execution.ProcessFlowFilter; +import com.rapidminer.security.PluginSandboxPolicy; + + +/** + * Registry for a {@link ProcessFlowFilter}. + * + * @author Marcel Michel + * @since 7.2.2 + */ +public enum ProcessFlowFilterRegistry { + + INSTANCE; + + private ProcessFlowFilter filter; + + /** + * Registers the filter. + * + * Note: Only one registration is allowed. All following request will result in an + * {@link IllegalStateException}. + * + * @param filter + * the filter to register + * @throws SecurityException + * if caller does not have {@link RuntimePermission} for + * {@code accessClassInPackage.rapidminer.internal} + */ + public void register(ProcessFlowFilter filter) { + if (System.getSecurityManager() != null) { + AccessController.checkPermission(new RuntimePermission(PluginSandboxPolicy.RAPIDMINER_INTERNAL_PERMISSION)); + } + if (filter == null) { + throw new IllegalArgumentException("Filter cannot be null"); + } + if (this.filter != null) { + throw new IllegalStateException("Filter already defined"); + } + this.filter = filter; + } + + /** + * Getter for the registered {@link ProcessFlowFilter}. + * + * @return The the registered filter or {@code null} + */ + public ProcessFlowFilter getProcessFlowFilter() { + return filter; + } +} diff --git a/src/main/java/com/rapidminer/tools/plugin/Plugin.java b/src/main/java/com/rapidminer/tools/plugin/Plugin.java index b9a86b37c..75e27dd4c 100644 --- a/src/main/java/com/rapidminer/tools/plugin/Plugin.java +++ b/src/main/java/com/rapidminer/tools/plugin/Plugin.java @@ -37,6 +37,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -239,6 +240,13 @@ public ClassLoader run() throws Exception { return p1.getName().compareTo(p2.getName()); }; + /** + * An ordered collection of plugins sorted by the dependency order. IMPORTANT: This collection + * does not respect failures during initialization, so it might contain more plugins than + * {@link #ALL_PLUGINS}. + */ + private static final Collection PLUGIN_INITIALIZATION_ORDER = new ArrayList<>(); + /** An ordered set of all plugins sorted lexically based on the plugin name. */ private static final Collection ALL_PLUGINS = new TreeSet<>(PLUGIN_COMPARATOR); @@ -1002,6 +1010,9 @@ public static void finalizePluginLoading() { // then we have one more extension that is initialized, next round might find // more found = true; + + // remember the initialization order globally + PLUGIN_INITIALIZATION_ORDER.add(plugin); } recordLoadingTime(plugin.getExtensionId(), start); } @@ -1088,9 +1099,18 @@ public static void initPluginTests() { private static void callPluginInitMethods(String methodName, Class[] arguments, Object[] argumentValues, boolean useOriginalJarClassLoader) { - List plugins = new LinkedList<>(getAllPlugins()); + for (Plugin plugin : PLUGIN_INITIALIZATION_ORDER) { + if (!ALL_PLUGINS.contains(plugin)) { + // plugin may be removed in the meantime, + // so skip the initialization + continue; + } + if (!plugin.checkDependencies(plugin, ALL_PLUGINS)) { + getAllPlugins().remove(plugin); + INCOMPATIBLE_PLUGINS.add(plugin); + continue; + } - for (Plugin plugin : plugins) { long start = System.currentTimeMillis(); if (!plugin.callInitMethod(methodName, arguments, argumentValues, useOriginalJarClassLoader)) { getAllPlugins().remove(plugin);