Skip to content

Commit

Permalink
Do not allow third parties to provide Netty's native libraries (netty…
Browse files Browse the repository at this point in the history
…#11856)

Throw an exception when there are multiple netty-tcnative-** libraries with the same path in the classpath

Motivation:

Currently Netty loads the first resource in the classpath with a given name.
It seems there are [libraries](netty/netty-tcnative#681 (comment)) which provide Netty's native libraries themselves.

Modification:

From now on Netty will look for all resources with the given name and throw an exception if there are more than one.
The user application needs to make sure that there is at most one provider of Netty's native libraties (netty-tcnative-**)

Result:

Fixes netty/netty-tcnative#681.
  • Loading branch information
martin-g authored and 夏无影 committed Jul 8, 2022
1 parent 7883481 commit df40a4e
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;

Expand Down Expand Up @@ -94,6 +96,7 @@ public static void loadFirstAvailable(ClassLoader loader, String... names) {
for (String name : names) {
try {
load(name, loader);
logger.debug("Loaded library with name '{}'", name);
return;
} catch (Throwable t) {
suppressed.add(t);
Expand Down Expand Up @@ -145,22 +148,13 @@ public static void load(String originalName, ClassLoader loader) {
InputStream in = null;
OutputStream out = null;
File tmpFile = null;
URL url;
if (loader == null) {
url = ClassLoader.getSystemResource(path);
} else {
url = loader.getResource(path);
}
URL url = getResource(path, loader);
try {
if (url == null) {
if (PlatformDependent.isOsx()) {
String fileName = path.endsWith(".jnilib") ? NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib" :
NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib";
if (loader == null) {
url = ClassLoader.getSystemResource(fileName);
} else {
url = loader.getResource(fileName);
}
url = getResource(fileName, loader);
if (url == null) {
FileNotFoundException fnf = new FileNotFoundException(fileName);
ThrowableUtil.addSuppressedAndClear(fnf, suppressed);
Expand Down Expand Up @@ -236,6 +230,30 @@ public static void load(String originalName, ClassLoader loader) {
}
}

private static URL getResource(String path, ClassLoader loader) {
final Enumeration<URL> urls;
try {
if (loader == null) {
urls = ClassLoader.getSystemResources(path);
} else {
urls = loader.getResources(path);
}
} catch (IOException iox) {
throw new RuntimeException("An error occurred while getting the resources for " + path, iox);
}

List<URL> urlsList = Collections.list(urls);
int size = urlsList.size();
switch (size) {
case 0:
return null;
case 1:
return urlsList.get(0);
default:
throw new IllegalStateException("Multiple resources found for '" + path + "': " + urlsList);
}
}

static void tryPatchShadedLibraryIdAndSign(File libraryFile, String originalName) {
String newId = new String(generateUniqueId(originalName.length()), CharsetUtil.UTF_8);
if (!tryExec("install_name_tool -id " + newId + " " + libraryFile.getAbsolutePath())) {
Expand Down Expand Up @@ -288,7 +306,7 @@ private static void loadLibrary(final ClassLoader loader, final String name, fin
Throwable suppressed = null;
try {
try {
// Make sure the helper is belong to the target ClassLoader.
// Make sure the helper belongs to the target ClassLoader.
final Class<?> newHelper = tryToLoadClass(loader, NativeLibraryUtil.class);
loadLibraryByHelper(newHelper, name, absolute);
logger.debug("Successfully loaded the library {}", name);
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,31 @@
package io.netty.util.internal;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.function.Executable;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.condition.OS.LINUX;

public class NativeLibraryLoaderTest {
class NativeLibraryLoaderTest {

private static final String OS_ARCH = System.getProperty("os.arch");
private boolean is_x86_64() {
return "x86_64".equals(OS_ARCH) || "amd64".equals(OS_ARCH);
}

@Test
public void testFileNotFound() {
void testFileNotFound() {
try {
NativeLibraryLoader.load(UUID.randomUUID().toString(), NativeLibraryLoaderTest.class.getClassLoader());
fail();
Expand All @@ -39,7 +53,7 @@ public void testFileNotFound() {
}

@Test
public void testFileNotFoundWithNullClassLoader() {
void testFileNotFoundWithNullClassLoader() {
try {
NativeLibraryLoader.load(UUID.randomUUID().toString(), null);
fail();
Expand All @@ -51,6 +65,38 @@ public void testFileNotFoundWithNullClassLoader() {
}
}

@Test
@EnabledOnOs(LINUX)
@EnabledIf("is_x86_64")
void testMultipleResourcesInTheClassLoader() throws MalformedURLException {
URL url1 = new File("src/test/data/NativeLibraryLoader/1").toURI().toURL();
URL url2 = new File("src/test/data/NativeLibraryLoader/2").toURI().toURL();
final URLClassLoader loader = new URLClassLoader(new URL[] {url1, url2});
final String resourceName = "test1";

Exception ise = assertThrows(IllegalStateException.class, new Executable() {
@Override
public void execute() {
NativeLibraryLoader.load(resourceName, loader);
}
});
assertTrue(ise.getMessage()
.contains("Multiple resources found for 'META-INF/native/lib" + resourceName + ".so'"));
}

@Test
@EnabledOnOs(LINUX)
@EnabledIf("is_x86_64")
void testSingleResourceInTheClassLoader() throws MalformedURLException {
URL url1 = new File("src/test/data/NativeLibraryLoader/1").toURI().toURL();
URL url2 = new File("src/test/data/NativeLibraryLoader/2").toURI().toURL();
URLClassLoader loader = new URLClassLoader(new URL[] {url1, url2});
String resourceName = "test2";

NativeLibraryLoader.load(resourceName, loader);
assertTrue(true);
}

@SuppressJava6Requirement(reason = "uses Java 7+ Throwable#getSuppressed but is guarded by version checks")
private static void verifySuppressedException(UnsatisfiedLinkError error,
Class<?> expectedSuppressedExceptionClass) {
Expand Down

0 comments on commit df40a4e

Please sign in to comment.