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) {