Add explicit message when noexec prevents library loading.

Motivation:

Docker's `--tmpfs` flag mounts the temp volume with `noexec` by default,
resulting in an UnsatisfiedLinkError.  While this is good security
practice, it is a surprising failure from a seemingly innocuous flag.

Modifications:

Add a best-effort attempt in `NativeLibraryLoader` to detect when temp
files beng loaded cannot be executed even when execution permissions
are set, often because the `noexec` flag is set on the volume.

Requires numerous additional exclusions to the Animal Sniffer config
for Java7 POSIX permissions manipulation.

Result:

Fixes [#6678].
This commit is contained in:
Michael K. Werle 2017-05-05 10:44:23 -05:00 committed by Scott Mitchell
parent 1f0d47dee7
commit ed5fcbb773
2 changed files with 64 additions and 1 deletions

View File

@ -18,8 +18,8 @@ package io.netty.util.internal;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.Closeable;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -27,10 +27,14 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
/**
* Helper class to load JNI resources.
@ -227,6 +231,20 @@ public final class NativeLibraryLoader {
out = null;
loadLibrary(loader, tmpFile.getPath(), true);
} catch (UnsatisfiedLinkError e) {
try {
if (tmpFile != null && tmpFile.isFile() && tmpFile.canRead() &&
!NoexecVolumeDetector.canExecuteExecutable(tmpFile)) {
logger.info("{} exists but cannot be executed even when execute permissions set; " +
"check volume for \"noexec\" flag; use -Dio.netty.native.workdir=[path] " +
"to set native working directory separately.",
tmpFile.getPath());
}
} catch (Throwable t) {
logger.debug("Error checking if {} is on a file store mounted with noexec", tmpFile, t);
}
// Re-throw to fail the load
throw e;
} catch (Exception e) {
throw (UnsatisfiedLinkError) new UnsatisfiedLinkError(
"could not load a native library: " + name).initCause(e);
@ -371,4 +389,42 @@ public final class NativeLibraryLoader {
private NativeLibraryLoader() {
// Utility
}
private static final class NoexecVolumeDetector {
private static boolean canExecuteExecutable(File file) throws IOException {
if (PlatformDependent.javaVersion() < 7) {
// Pre-JDK7, the Java API did not directly support POSIX permissions; instead of implementing a custom
// work-around, assume true, which disables the check.
return true;
}
// If we can already execute, there is nothing to do.
if (file.canExecute()) {
return true;
}
// On volumes, with noexec set, even files with the executable POSIX permissions will fail to execute.
// The File#canExecute() method honors this behavior, probaby via parsing the noexec flag when initializing
// the UnixFileStore, though the flag is not exposed via a public API. To find out if library is being
// loaded off a volume with noexec, confirm or add executalbe permissions, then check File#canExecute().
Set<PosixFilePermission> existingFilePermissions = Files.getPosixFilePermissions(file.toPath());
Set<PosixFilePermission> executePermissions = EnumSet.of(PosixFilePermission.OWNER_EXECUTE,
PosixFilePermission.GROUP_EXECUTE,
PosixFilePermission.OTHERS_EXECUTE);
if (existingFilePermissions.containsAll(executePermissions)) {
return false;
}
Set<PosixFilePermission> newPermissions = EnumSet.copyOf(existingFilePermissions);
newPermissions.addAll(executePermissions);
Files.setPosixFilePermissions(file.toPath(), newPermissions);
return file.canExecute();
}
private NoexecVolumeDetector() {
// Utility
}
}
}

View File

@ -709,6 +709,13 @@
<!-- Resolver -->
<ignore>java.net.InetAddress</ignore>
<!-- NoexecVolumeDetector -->
<ignore>java.nio.file.attribute.PosixFilePermission</ignore>
<ignore>java.nio.file.Files</ignore>
<ignore>java.nio.file.LinkOption</ignore>
<ignore>java.nio.file.Path</ignore>
<ignore>java.io.File</ignore>
</ignores>
</configuration>
<executions>