From aac2cf7e9bd9b97a8a74234e7ffd52a0f0bada5f Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Wed, 2 Mar 2022 14:41:00 -0500 Subject: [PATCH] GP-1770 Fix Sevenzip native library initialization When multiple ghidra processes on the same host (and share a temporary directory) start-up, the built-in logic in the Sevenzip JBinding library would always overwrite the previous native library file, causing the java vm's that have already linked with it to core dump when trying to execute Sevenzip code. See https://github.com/borisbrodski/sevenzipjbinding/issues/50 for bug report to the upstream developer. This change pre-extracts the native libraries in a gradle build task and places them in the ghidra directory structure, allowing the native libraries to be referred to and loaded without any extra work at runtime. --- Ghidra/Features/FileFormats/build.gradle | 37 +++++ .../sevenzip/SevenZipCustomInitializer.java | 138 ++++++++++++++++++ .../sevenzip/SevenZipFileSystemFactory.java | 3 +- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java diff --git a/Ghidra/Features/FileFormats/build.gradle b/Ghidra/Features/FileFormats/build.gradle index 0b0681b2fe..5a91b1a922 100644 --- a/Ghidra/Features/FileFormats/build.gradle +++ b/Ghidra/Features/FileFormats/build.gradle @@ -48,3 +48,40 @@ dependencies { // include code from src/test/slow in Base testImplementation project(path: ':Base', configuration: 'integrationTestArtifacts') } + +// *********************************************************************************************** +// Sevenzip native library extract task +// *********************************************************************************************** +// The following extracts native libraries from upstream's sevenzipjbinding allPlatform jar +// and places the uncompressed native libraries into a ghidra folder so that they can be used to +// initialize the sevenzipjbinding library without needing to extract the native libraries at run time. +// This is necessary due to bugs in upstream's initSevenZipFromPlatformJAR() that can cause core +// dumps in java processes that have previously loaded the native library. See comments +// at the top of ghidra.file.formats.sevenzip.SevenZipCustomInitializer. +// This gradle task can be removed when SevenZipCustomInitializer is no longer needed. +String getSevenZipJarPath() { + List libs = getExternalRuntimeDependencies(project); + for(String lib: libs) { + if (lib.contains("sevenzipjbinding-all-platforms")) { + return lib; + } + } + return null +} + +task extractSevenZipNativeLibs(type: Copy) { + String jarPath = getSevenZipJarPath(); + from zipTree(jarPath) + include "Linux-amd64/*.so" + include "Windows-amd64/*.dll" + include "Mac-x86_64/*.dylib" + exclude "META-INF" + exclude "Linux-i386" + exclude "Windows-x86" + into ("build/data/sevenzipnativelibs") +} + +rootProject.prepDev.dependsOn extractSevenZipNativeLibs +jar.dependsOn extractSevenZipNativeLibs +// end of sevenzip native library extract task code + diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java new file mode 100644 index 0000000000..4effc00bce --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipCustomInitializer.java @@ -0,0 +1,138 @@ +/* ### + * IP: GHIDRA + * + * Licensed 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. + */ +package ghidra.file.formats.sevenzip; + +import java.util.*; +import java.util.stream.Collectors; + +import java.io.*; + +import ghidra.framework.Application; +import net.sf.sevenzipjbinding.SevenZip; +import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; + +/** + * + * Custom logic to initialize Sevenzip's native libraries that have been placed into + * Ghidra's module data directory by a gradle build task (see extractSevenZipNativeLibs in build.gradle). + *

+ * It is necessary to use this method instead of Sevenzip.initSevenZipFromPlatformJAR() + * because it has a logic error when extracting the native libraries and always overwrites + * the existing libraries, causing other java processes that have already loaded those + * libraries to core-dump. + *

+ * See https://github.com/borisbrodski/sevenzipjbinding/issues/50 + *

+ * This class (and the tasks in build.gradle) can be removed if/when upstream + * fixes the problem with native libraries always being over-written during initialization (and + * thereby causing earlier-loaded java vms to core dump) and also does not produce errors when + * multiple vms are simultaneously started. + */ +public class SevenZipCustomInitializer { + + /** + * Call this before using any SevenzipJBinding classes. Calling multiple times + * is okay. + *

+ * Most likely cause of failure is running on an unsupported platform. + *

+ * + * @throws SevenZipNativeInitializationException + */ + public static synchronized void initSevenZip() throws SevenZipNativeInitializationException { + if (SevenZip.isInitializedSuccessfully()) { + return; + } + + try { + String platform = SevenZip.getPlatformBestMatch(); + + // This depends on the sevenzip native libraries being extracted from the sevenzip jar + // and being placed in the data/sevenzipnativelibs/ directory by a gradle task. + // Sevenzip's platform designator will be used to pick the appropriate native library, + // for example "/data/sevenzipnativelibs/Linux-amd64/lib7-Zip-JBinding.so". + File libDir = Application.getModuleDataSubDirectory("sevenzipnativelibs/" + platform) + .getFile(false); + + Properties properties = loadProperties(platform); + + // libName -> hash. hash not used at the moment + Map nativeLibraryInfo = getNativeLibraryInfo(properties); + List libFiles = nativeLibraryInfo.keySet() + .stream() + .map(libName -> new File(libDir, libName)) + .collect(Collectors.toList()); + loadNativeLibraries(libFiles); + SevenZip.initLoadedLibraries(); + } + catch (IOException e) { + throw new SevenZipNativeInitializationException("Error initializing SevenzipJbinding", + e); + } + } + + private static Properties loadProperties(String platform) + throws SevenZipNativeInitializationException, IOException { + // prop file contains lib.##.name and lib.##.hash values + String propFilename = "/" + platform + "/sevenzipjbinding-lib.properties"; + try (InputStream propFileStream = SevenZip.class.getResourceAsStream(propFilename)) { + if (propFileStream == null) { + throw new IOException("Error loading property file stream " + propFilename); + } + + Properties properties = new Properties(); + properties.load(propFileStream); + return properties; + } + } + + private static Map getNativeLibraryInfo(Properties properties) + throws IOException { + // LinkedHashMap to preserve order + LinkedHashMap libraryInfo = new LinkedHashMap<>(); + int libNum = 1; + String libName; + while ((libName = properties.getProperty(String.format("lib.%d.name", libNum))) != null) { + String libHash = properties.getProperty(String.format("lib.%d.hash", libNum)); + if (libHash == null) { + throw new IOException( + "Missing library hash value in property file for library lib." + libNum + + ".name=" + libName); + } + libraryInfo.put(libName, libHash); + libNum++; + } + if (libraryInfo.isEmpty()) { + throw new IOException("Missing library hash values in property file"); + } + return libraryInfo; + } + + private static void loadNativeLibraries(List libFiles) + throws SevenZipNativeInitializationException { + // Load native libraries in reverse order (per the logic in upstream's initialization code) + for (int i = libFiles.size() - 1; i >= 0; i--) { + File libFile = libFiles.get(i); + try { + System.load(libFile.getPath()); + } + catch (Throwable t) { + throw new SevenZipNativeInitializationException( + "Error loading native library: " + libFile, t); + } + } + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java index 9a8b950fc0..86b7d7782b 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/sevenzip/SevenZipFileSystemFactory.java @@ -27,7 +27,6 @@ import ghidra.formats.gfilesystem.factory.GFileSystemProbeBytesOnly; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -import net.sf.sevenzipjbinding.SevenZip; import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; public class SevenZipFileSystemFactory @@ -90,7 +89,7 @@ public class SevenZipFileSystemFactory */ public static boolean initNativeLibraries() { try { - SevenZip.initSevenZipFromPlatformJAR(); // calling this multiple times is ok + SevenZipCustomInitializer.initSevenZip(); return true; } catch (SevenZipNativeInitializationException e) {