diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 4d391c40e10..4bf6cab4819 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -138,6 +138,9 @@ Other to use MethodHandles and work without extra security privileges. (Uwe Schindler, Robert Muir) +* LUCENE-6921: Fix SPIClassIterator#isParentClassLoader to don't + require extra permissions. (Uwe Schindler) + ======================= Lucene 5.4.0 ======================= New Features diff --git a/lucene/core/src/java/org/apache/lucene/util/SPIClassIterator.java b/lucene/core/src/java/org/apache/lucene/util/SPIClassIterator.java index e30a9ee7b63..fdc4f9ac914 100644 --- a/lucene/core/src/java/org/apache/lucene/util/SPIClassIterator.java +++ b/lucene/core/src/java/org/apache/lucene/util/SPIClassIterator.java @@ -23,12 +23,15 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.Locale; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.ServiceConfigurationError; /** @@ -47,35 +50,51 @@ public final class SPIClassIterator implements Iterator> { private final Enumeration profilesEnum; private Iterator linesIterator; + /** Creates a new SPI iterator to lookup services of type {@code clazz} using the context classloader. */ public static SPIClassIterator get(Class clazz) { return new SPIClassIterator<>(clazz, Thread.currentThread().getContextClassLoader()); } + /** Creates a new SPI iterator to lookup services of type {@code clazz} using the given classloader. */ public static SPIClassIterator get(Class clazz, ClassLoader loader) { return new SPIClassIterator<>(clazz, loader); } - /** Utility method to check if some class loader is a (grand-)parent of or the same as another one. - * This means the child will be able to load all classes from the parent, too. */ - public static boolean isParentClassLoader(final ClassLoader parent, ClassLoader child) { - while (child != null) { - if (child == parent) { - return true; - } - child = child.getParent(); + /** + * Utility method to check if some class loader is a (grand-)parent of or the same as another one. + * This means the child will be able to load all classes from the parent, too. + *

+ * If Lucene's codebase doesn't have enough permissions to do the check, {@code false} is returned. + */ + public static boolean isParentClassLoader(final ClassLoader parent, final ClassLoader child) { + if (parent == child) { + return true; // don't try to use AccessController for performance } - return false; + return AccessController.doPrivileged((PrivilegedAction) () -> { + try { + ClassLoader cl = child; + while (cl != null) { + if (cl == parent) { + return true; + } + cl = cl.getParent(); + } + return false; + } catch (SecurityException se) { + return false; + } + }); } private SPIClassIterator(Class clazz, ClassLoader loader) { - this.clazz = clazz; + this.clazz = Objects.requireNonNull(clazz, "clazz"); + this.loader = Objects.requireNonNull(loader, "loader"); try { final String fullName = META_INF_SERVICES + clazz.getName(); - this.profilesEnum = (loader == null) ? ClassLoader.getSystemResources(fullName) : loader.getResources(fullName); + this.profilesEnum = loader.getResources(fullName); } catch (IOException ioe) { throw new ServiceConfigurationError("Error loading SPI profiles for type " + clazz.getName() + " from classpath", ioe); } - this.loader = (loader == null) ? ClassLoader.getSystemClassLoader() : loader; this.linesIterator = Collections.emptySet().iterator(); } diff --git a/lucene/core/src/test/org/apache/lucene/util/TestSPIClassIterator.java b/lucene/core/src/test/org/apache/lucene/util/TestSPIClassIterator.java new file mode 100644 index 00000000000..ac57b970342 --- /dev/null +++ b/lucene/core/src/test/org/apache/lucene/util/TestSPIClassIterator.java @@ -0,0 +1,38 @@ +package org.apache.lucene.util; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.URL; +import java.net.URLClassLoader; + +public class TestSPIClassIterator extends LuceneTestCase { + + public void testParentChild() throws Exception { + final ClassLoader parent = getClass().getClassLoader(); + final ClassLoader child = URLClassLoader.newInstance(new URL[0], parent); + assertTrue(checkNoPerms(parent, parent)); + assertTrue(checkNoPerms(child, child)); + assertTrue(checkNoPerms(parent, child)); + assertFalse(checkNoPerms(child, parent)); + } + + private boolean checkNoPerms(ClassLoader parent, ClassLoader child) throws Exception { + return runWithRestrictedPermissions(() -> SPIClassIterator.isParentClassLoader(parent, child)); + } + +}