From d817b073885ff86ee9606df80fe71662624e8874 Mon Sep 17 00:00:00 2001 From: Matthew Duggan Date: Tue, 22 Sep 2026 20:43:04 +1000 Subject: [PATCH 1/5] Fix display of integer arrays In e0aae02, the `++` was dropped from the index reference in this loop, but that means the first item on each row of an integer array is incorrectly repeated. Restore the ++ so the array index is incremented across the line. --- .../ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java index 886556e985..8b32135e93 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ArrayValuesFieldFactory.java @@ -102,7 +102,7 @@ public class ArrayValuesFieldFactory extends FieldFactory { List elements = new ArrayList<>(); for (int i = 0; i < itemCount; i++) { - Data child = parent.getComponent(index); + Data child = parent.getComponent(index++); boolean isLastItem = isLastLine && (i == itemCount - 1); String value = getDisplayValue(child, !isLastItem); AttributedString as = From b81eb22750ab8ba7022c8b218d388e82b5115c4e Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:53:15 +0000 Subject: [PATCH 2/5] GP-7232 limit squashfs inode table count --- .../ghidra/file/formats/squashfs/SquashInodeTable.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/squashfs/SquashInodeTable.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/squashfs/SquashInodeTable.java index 8a53c36119..ba62e4c312 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/squashfs/SquashInodeTable.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/squashfs/SquashInodeTable.java @@ -4,9 +4,9 @@ * 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. @@ -23,6 +23,7 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; public class SquashInodeTable { + private static final int MAX_SANE_INODE_COUNT = 500_000; // An array of inodes indexed by their inode number private final SquashInode[] inodes; @@ -43,6 +44,9 @@ public class SquashInodeTable { */ public SquashInodeTable(BinaryReader reader, SquashSuperBlock superBlock, TaskMonitor monitor) throws IOException, CancelledException { + if (superBlock.getInodeCount() > MAX_SANE_INODE_COUNT) { + throw new IOException("Inode count large: " + superBlock.getInodeCount()); + } // Read from the start of the inode table reader.setPointerIndex(superBlock.getInodeTableStart()); @@ -52,7 +56,7 @@ public class SquashInodeTable { decompressInodeTable(reader, superBlock.getDirectoryTableStart(), superBlock, monitor); // Create inode array. inode count is off by one - inodes = new SquashInode[(int) superBlock.getInodeCount() + 1]; + inodes = new SquashInode[superBlock.getInodeCount() + 1]; // inodes begin indexing at 1, so 0th inode is null inodes[0] = null; From 061ec06a79f4147267f6fc690b0dcf944b2d4957 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:52:54 +0000 Subject: [PATCH 3/5] GP-7239 limit max number of files in a filesystem --- .../gfilesystem/FileSystemIndexHelper.java | 22 +++- .../FileSystemIndexHelperTest.java | 102 ++++++++++++++++++ .../file/formats/zip/ZipFileSystem.java | 2 +- .../java/skeleton/SkeletonFileSystem.java | 3 +- 4 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/FileSystemIndexHelperTest.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java index add4505674..a13dfba94b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FileSystemIndexHelper.java @@ -35,6 +35,8 @@ import ghidra.util.Msg; public class FileSystemIndexHelper { private static final int MAX_SYMLINK_RECURSE_DEPTH = 10; + public static final int MAX_FILEENTRY_COUNT = 500_000; + private FileData rootDir; static class FileData { @@ -336,9 +338,10 @@ public class FileSystemIndexHelper { * @param metadata opaque blob that will be stored and associated with the new * GFile instance * @return new GFile instance + * @throws IOException if error adding files (too many files) */ public synchronized GFile storeFile(String path, long fileIndex, boolean isDirectory, - long length, METADATATYPE metadata) { + long length, METADATATYPE metadata) throws IOException { String[] nameparts = FSUtilities.splitPath(path); if (nameparts.length == 0) { @@ -370,9 +373,10 @@ public class FileSystemIndexHelper { * @param metadata opaque blob that will be stored and associated with the new * GFile instance * @return new GFile instance + * @throws IOException if error adding files (too many files) */ public synchronized GFile storeFileWithParent(String filename, GFile parent, long fileIndex, - boolean isDirectory, long length, METADATATYPE metadata) { + boolean isDirectory, long length, METADATATYPE metadata) throws IOException { FileData fileData = doStoreFile(filename, parent, fileIndex, isDirectory, length, null, metadata); return fileData.file; @@ -397,9 +401,10 @@ public class FileSystemIndexHelper { * @param metadata opaque blob that will be stored and associated with the new * GFile instance * @return new GFile instance + * @throws IOException if error adding files (too many files) */ public synchronized GFile storeSymlink(String path, long fileIndex, String symlinkPath, - long length, METADATATYPE metadata) { + long length, METADATATYPE metadata) throws IOException { String[] nameparts = FSUtilities.splitPath(path); if (nameparts.length == 0) { Msg.warn(this, @@ -434,9 +439,10 @@ public class FileSystemIndexHelper { * @param metadata opaque blob that will be stored and associated with the new * GFile instance * @return new GFile instance + * @throws IOException if error adding files (too many files) */ public synchronized GFile storeSymlinkWithParent(String filename, GFile parent, long fileIndex, - String symlinkPath, long length, METADATATYPE metadata) { + String symlinkPath, long length, METADATATYPE metadata) throws IOException { length = length != 0 ? length : symlinkPath.length(); FileData fileData = doStoreFile(filename, parent, fileIndex, false, length, symlinkPath, metadata); @@ -458,7 +464,13 @@ public class FileSystemIndexHelper { } private FileData doStoreFile(String filename, GFile parent, long fileIndex, - boolean isDirectory, long length, String symlinkPath, METADATATYPE metadata) { + boolean isDirectory, long length, String symlinkPath, METADATATYPE metadata) + throws IOException { + + if (fileToEntryMap.size() > MAX_FILEENTRY_COUNT) { + throw new IOException("Too many file entries: " + fileToEntryMap.size()); + } + parent = (parent == null) ? rootDir.file : parent; long fileNum = (fileIndex != -1) ? fileIndex : fileToEntryMap.size(); if (fileIndexToEntryMap.containsKey(fileNum)) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/FileSystemIndexHelperTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/FileSystemIndexHelperTest.java new file mode 100644 index 0000000000..ec8d8e0c02 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/formats/gfilesystem/FileSystemIndexHelperTest.java @@ -0,0 +1,102 @@ +/* ### + * 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.formats.gfilesystem; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.List; + +import org.junit.Test; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class FileSystemIndexHelperTest { + + @Test + public void testMaxFiles() throws IOException { + FSRLRoot fsFSRL = FSRLRoot.makeRoot("dummy"); + FileSystemIndexHelper fsih = + new FileSystemIndexHelper(new DummyFileSystem(), fsFSRL); + + for (int i = 0; i < FileSystemIndexHelper.MAX_FILEENTRY_COUNT; i++) { + fsih.storeFile("file" + i, -1, false, 1, null); + } + + try { + fsih.storeFile("toomuch", -1, false, 1, null); + fail("Should not get here"); + } + catch (IOException e) { + // good + } + } + + private static class DummyFileSystem implements GFileSystem { + + @Override + public void close() throws IOException { + // empty dummy + + } + + @Override + public GFile lookup(String path) throws IOException { + // empty dummy + return null; + } + + @Override + public boolean isClosed() { + // empty dummy + return false; + } + + @Override + public FileSystemRefManager getRefManager() { + // empty dummy + return null; + } + + @Override + public String getName() { + // empty dummy + return null; + } + + @Override + public List getListing(GFile directory) throws IOException { + // empty dummy + return null; + } + + @Override + public FSRLRoot getFSRL() { + // empty dummy + return null; + } + + @Override + public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) + throws IOException, CancelledException { + // empty dummy + return null; + } + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java index 01a510f194..35cda74f61 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/zip/ZipFileSystem.java @@ -187,7 +187,7 @@ public class ZipFileSystem extends AbstractFileSystem { }, monitor); } - private void indexFiles(TaskMonitor monitor) throws CancelledException { + private void indexFiles(TaskMonitor monitor) throws CancelledException, IOException { int zipIndex = 0; for (ZipArchiveEntry zipEntry : entries) { monitor.checkCancelled(); diff --git a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java index 2bca41437d..3848e201ee 100644 --- a/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java +++ b/GhidraBuild/Skeleton/src/main/java/skeleton/SkeletonFileSystem.java @@ -59,8 +59,9 @@ public class SkeletonFileSystem implements GFileSystem { * Mounts (opens) the file system. * * @param monitor A cancellable task monitor. + * @throws IOException */ - public void mount(TaskMonitor monitor) { + public void mount(TaskMonitor monitor) throws IOException { monitor.setMessage("Opening " + SkeletonFileSystem.class.getSimpleName() + "..."); // Customize how things in the file system are stored. The following should be From 6d874355c61571c801ee35ac4c52985495366d2e Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:14:37 +0000 Subject: [PATCH 4/5] GP-7241 stricter validation of FSRL md5 values --- .../java/ghidra/formats/gfilesystem/FSRL.java | 17 ++++++++++- .../ghidra/formats/gfilesystem/FSRLTest.java | 28 +++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java index dd5d2c3569..0230820c94 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/formats/gfilesystem/FSRL.java @@ -172,7 +172,7 @@ public class FSRL { String params = path.substring(paramStart + 1); path = path.substring(0, paramStart); Map paramMap = getParamMapFromString(params); - md5 = paramMap.get(FSRL.PARAM_MD5); + md5 = getValidatedMD5(paramMap); } FSRLRoot fsRoot = FSRLRoot.nestedFS(containerFile, proto); @@ -200,6 +200,21 @@ public class FSRL { return paramMap; } + private static final int MD5_DIGEST_LEN = 16; + private static String getValidatedMD5(Map params) { + String md5 = params.get(FSRL.PARAM_MD5); + if (md5 != null && md5.length() == MD5_DIGEST_LEN * 2) { + try { + byte[] digestBytes = HexFormat.of().parseHex(md5); + return HexFormat.of().formatHex(digestBytes); + } + catch (IllegalArgumentException e) { + // not a valid hex string + } + } + return null; + } + protected final FSRL parent; protected final String path; private final String md5; diff --git a/Ghidra/Features/Base/src/test/java/ghidra/formats/gfilesystem/FSRLTest.java b/Ghidra/Features/Base/src/test/java/ghidra/formats/gfilesystem/FSRLTest.java index 203ba7688c..235aa927e0 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/formats/gfilesystem/FSRLTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/formats/gfilesystem/FSRLTest.java @@ -103,9 +103,11 @@ public class FSRLTest { @Test public void testStringFormat() throws MalformedURLException { - FSRL fsrl = FSRL.fromString("fsrl://path/filename?MD5=1234|subfsrl://subpath/subfile"); + FSRL fsrl = FSRL.fromString( + "fsrl://path/filename?MD5=00000000000000000000000000000000|subfsrl://subpath/subfile"); - assertEquals("string format bad", "fsrl://path/filename?MD5=1234|subfsrl://subpath/subfile", + assertEquals("string format bad", + "fsrl://path/filename?MD5=00000000000000000000000000000000|subfsrl://subpath/subfile", fsrl.toString()); assertEquals("pretty string format bad", "fsrl://path/filename|subfsrl://subpath/subfile", fsrl.toPrettyString()); @@ -117,11 +119,12 @@ public class FSRLTest { @Test public void testStringFormat2() throws MalformedURLException { - FSRL fsrl = - FSRL.fromString("fsrl://path/filename?MD5=1234|subfsrl://subpath/subfile|sub2://"); + FSRL fsrl = FSRL.fromString( + "fsrl://path/filename?MD5=00000000000000000000000000000000|subfsrl://subpath/subfile|sub2://"); assertEquals("string format bad", - "fsrl://path/filename?MD5=1234|subfsrl://subpath/subfile|sub2://", fsrl.toString()); + "fsrl://path/filename?MD5=00000000000000000000000000000000|subfsrl://subpath/subfile|sub2://", + fsrl.toString()); assertEquals("pretty string format bad", "fsrl://path/filename|subfsrl://subpath/subfile|sub2://", fsrl.toPrettyString()); assertEquals("partial string format bad", "sub2://", fsrl.toStringPart()); @@ -131,11 +134,12 @@ public class FSRLTest { @Test public void testStringFormat3() throws MalformedURLException { - FSRL fsrl = - FSRL.fromString("fsrl:///path/filename?MD5=1234|subfsrl:///subpath/subfile|sub2://"); + FSRL fsrl = FSRL.fromString( + "fsrl:///path/filename?MD5=00000000000000000000000000000000|subfsrl:///subpath/subfile|sub2://"); assertEquals("string format bad", - "fsrl:///path/filename?MD5=1234|subfsrl:///subpath/subfile|sub2://", fsrl.toString()); + "fsrl:///path/filename?MD5=00000000000000000000000000000000|subfsrl:///subpath/subfile|sub2://", + fsrl.toString()); assertEquals("pretty string format bad", "fsrl:///path/filename|subfsrl:///subpath/subfile|sub2://", fsrl.toPrettyString()); assertEquals("partial string format bad", "sub2://", fsrl.toStringPart()); @@ -243,4 +247,12 @@ public class FSRLTest { assertTrue(childFSRL.isDescendantOf(parentFSRL)); } + + @Test + public void testBadMD5s() throws MalformedURLException { + assertNull(FSRL.fromString("fsrl://path/rootfile?MD5").getMD5()); + assertNull(FSRL.fromString("fsrl://path/rootfile?MD5=").getMD5()); + assertNull(FSRL.fromString("fsrl://path/rootfile?MD5=xyz").getMD5()); + assertNull(FSRL.fromString("fsrl://path/rootfile?MD5=AABB").getMD5()); + } } From f822b7399a6fc89f055449987a07da01652c8096 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Mon, 21 Sep 2026 16:47:11 +0000 Subject: [PATCH 5/5] GP-7242 tighten up samedir path validation, no co-mingling debuginfo Split up debug-link and build-id debug info into separate classes to make boundary between them more obvious. When searching for a debug file in the "same dir" search location, ensure its not a symlink. (user never explicitly signaled that they trusted the directory, they only tried to import a file from there.) --- .../DWARFLineInfoSourceMapScript.java | 9 +- .../external/BuildIdDebugFileProvider.java | 13 +- .../dwarf/external/BuildIdDebugInfo.java | 129 ++++++++++++++++ .../dwarf/external/DebugLinkDebugInfo.java | 87 +++++++++++ .../external/ExternalDebugFilesService.java | 18 +++ .../dwarf/external/ExternalDebugInfo.java | 138 ++---------------- .../external/HttpDebugInfoDProvider.java | 8 +- .../external/LocalDirDebugInfoDProvider.java | 16 +- .../external/LocalDirDebugLinkProvider.java | 18 ++- .../external/SameDirDebugInfoProvider.java | 20 ++- .../ExternalDebugFileSectionProvider.java | 8 +- .../BuildIdDebugFileProviderTest.java | 8 +- .../external/HttpDebugInfoDProviderTest.java | 39 ++--- .../LocalDirDebugInfoDProviderTest.java | 19 +-- .../LocalDirDebugLinkProviderTest.java | 2 +- 15 files changed, 332 insertions(+), 200 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugInfo.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/DebugLinkDebugInfo.java diff --git a/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoSourceMapScript.java b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoSourceMapScript.java index dd4304a5a4..f3a29c4667 100644 --- a/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoSourceMapScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/DWARFLineInfoSourceMapScript.java @@ -76,8 +76,7 @@ public class DWARFLineInfoSourceMapScript extends GhidraScript { popup("Unable to get reader for debug line info"); return; } - ExternalDebugInfo extDebugInfo = ExternalDebugInfo.fromProgram(dprog.getGhidraProgram()); - boolean hasBuildId = extDebugInfo != null && extDebugInfo.hasBuildId(); + BuildIdDebugInfo buildId = BuildIdDebugInfo.fromProgram(dprog.getGhidraProgram()); ExternalDebugFilesService edfs = ExternalDebugFilesService.forProgram(dprog.getGhidraProgram()); @@ -108,9 +107,9 @@ public class DWARFLineInfoSourceMapScript extends GhidraScript { SourceFile sFile = new SourceFile(path, type, sfi.md5()); sourceManager.addSourceFile(sFile); sourceFileInfoToSourceFile.put(sfi, sFile); - if (hasBuildId) { - ExternalDebugInfo srcFileDebugInfo = - extDebugInfo.withType(ObjectType.SOURCE, path); + if (buildId != null) { + BuildIdDebugInfo srcFileDebugInfo = + buildId.withType(ObjectType.SOURCE, path); File srcFile = edfs.find(srcFileDebugInfo, monitor); if (srcFile != null) { println("Source file: " + srcFile); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProvider.java index 9ac09b506d..95fd75fdd0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProvider.java @@ -89,14 +89,17 @@ public class BuildIdDebugFileProvider implements DebugFileProvider { @Override public File getFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) throws IOException, CancelledException { - String buildId = debugInfo.getBuildId(); - if (buildId == null || buildId.length() < 4 /* 2 bytes = 4 hex digits */ ) { + if (!(debugInfo instanceof BuildIdDebugInfo buildIdInfo)) { return null; } - File bucketDir = new File(rootDir, buildId.substring(0, 2)); - File file = new File(bucketDir, buildId.substring(2) + ".debug"); + String buildIdStr = buildIdInfo.getBuildIdHexString(); + if (buildIdStr == null || buildIdStr.length() < 4 /* 2 bytes = 4 hex digits */ ) { + return null; + } + File bucketDir = new File(rootDir, buildIdStr.substring(0, 2)); + File file = new File(bucketDir, buildIdStr.substring(2) + ".debug"); if (!rootDir.equals(bucketDir.getParentFile()) || !bucketDir.equals(file.getParentFile())) { - throw new IOException("Bad buildid: " + buildId); + throw new IOException("Bad buildid: " + buildIdStr); } return file.isFile() ? file : null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugInfo.java new file mode 100644 index 0000000000..7d9fce67c4 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugInfo.java @@ -0,0 +1,129 @@ +/* ### + * 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.app.util.bin.format.dwarf.external; + +import java.util.*; + +import ghidra.app.util.bin.format.elf.info.NoteGnuBuildId; +import ghidra.program.model.listing.Program; + +/** + * Represents {@link ExternalDebugInfo} found in a build-id property embedded in an ELF file. + */ +public class BuildIdDebugInfo implements ExternalDebugInfo { + private static final int MIN_BUILDID_HASH_LENGTH = 20; + + public static BuildIdDebugInfo fromProgram(Program program) { + NoteGnuBuildId buildId = NoteGnuBuildId.fromProgram(program); + return buildId != null && buildId.getDescription().length >= MIN_BUILDID_HASH_LENGTH + ? new BuildIdDebugInfo(buildId.getDescription()) + : null; + } + + private final byte[] buildId; + private final ObjectType objectType; + private final String extra; + + /** + * Creates a new {@link BuildIdDebugInfo} from the bytes of its hash digest + * + * @param buildId bytes of the hash digest + */ + public BuildIdDebugInfo(byte[] buildId) { + this(buildId, ObjectType.DEBUGINFO, null); + } + + /** + * Creates a new {@link BuildIdDebugInfo} from the bytes of its hash digest, a specifier for the + * type of the object being pointed to, and an optional path used ObjectType.SOURCE instances. + * + * @param buildId build-id hash digest found in ".note.gnu.build-id" section + * @param objectType {@link ObjectType} specifies what kind of debug file is specified by the + * other info + * @param extra additional information used by {@link ObjectType#SOURCE} + */ + public BuildIdDebugInfo(byte[] buildId, ObjectType objectType, String extra) { + Objects.requireNonNull(buildId); + + this.buildId = buildId; + this.objectType = objectType; + this.extra = extra; + } + + /** + * Return the build-id. + * + * @return build-id hash string + */ + public String getBuildIdHexString() { + return HexFormat.of().formatHex(buildId); + } + + /** + * {@return ObjectType that this build-id specifier points to} + */ + public ObjectType getObjectType() { + return objectType; + } + + /** + * {@return extra info used by ObjectTypes that need it} + */ + public String getExtra() { + return extra; + } + + /** + * {@return a new BuildIdDebugInfo instance, pointing to the same build-id hash but an + * alternate object associated with the base debug file} + * @param newObjectType the new {@link ObjectType} + * @param newExtra extra information used by the ObjectType to find the target file + */ + public BuildIdDebugInfo withType(ObjectType newObjectType, String newExtra) { + return new BuildIdDebugInfo(buildId, newObjectType, newExtra); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(buildId); + result = prime * result + Objects.hash(extra, objectType); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof BuildIdDebugInfo)) { + return false; + } + BuildIdDebugInfo other = (BuildIdDebugInfo) obj; + return Arrays.equals(buildId, other.buildId) && Objects.equals(extra, other.extra) && + objectType == other.objectType; + } + + @Override + public String toString() { + return "BuildIdDebugInfo [" + + (buildId != null ? "buildId=" + Arrays.toString(buildId) + ", " : "") + + (objectType != null ? "objectType=" + objectType + ", " : "") + + (extra != null ? "extra=" + extra : "") + "]"; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/DebugLinkDebugInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/DebugLinkDebugInfo.java new file mode 100644 index 0000000000..75a34cc598 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/DebugLinkDebugInfo.java @@ -0,0 +1,87 @@ +/* ### + * 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.app.util.bin.format.dwarf.external; + +import java.util.Objects; + +import ghidra.app.util.bin.format.elf.info.GnuDebugLink; +import ghidra.program.model.listing.Program; + +/** + * Represents {@link ExternalDebugInfo} found in a debug-link property embedded in an ELF file. + */ +public class DebugLinkDebugInfo implements ExternalDebugInfo { + + public static DebugLinkDebugInfo fromProgram(Program program) { + GnuDebugLink debugLink = GnuDebugLink.fromProgram(program); + return debugLink != null && !debugLink.getFilename().isEmpty() + ? new DebugLinkDebugInfo(debugLink.getFilename(), debugLink.getCrc()) + : null; + } + + private final String filename; + private final int crc; + + /** + * @param filename filename of external debug file + * @param crc crc32 of external debug file + */ + public DebugLinkDebugInfo(String filename, int crc) { + this.filename = filename; + this.crc = crc; + } + + /** + * Return the filename of the external debug file + * + * @return String filename of external debug file + */ + public String getFilename() { + return filename; + } + + /** + * Return the crc of the external debug file. + * + * @return int crc32 of external debug file. + */ + public int getCrc() { + return crc; + } + + @Override + public int hashCode() { + return Objects.hash(Integer.valueOf(crc), filename); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DebugLinkDebugInfo)) { + return false; + } + DebugLinkDebugInfo other = (DebugLinkDebugInfo) obj; + return crc == other.crc && Objects.equals(filename, other.filename); + } + + @Override + public String toString() { + return String.format("DebugLinkDebugInfo [filename=%s, crc=%s]", filename, crc); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugFilesService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugFilesService.java index 882a71874f..b8851beeb7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugFilesService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugFilesService.java @@ -75,6 +75,24 @@ public class ExternalDebugFilesService { providers.add(provider); } + /** + * Searches for the specified external debug file. + * + * @param debugInfos list of information about the external debug file + * @param monitor {@link TaskMonitor} + * @return first found file, or {@code null} if not found + * @throws IOException if error + */ + public File find(List debugInfos, TaskMonitor monitor) throws IOException { + for (ExternalDebugInfo debugInfo : debugInfos) { + File result = find(debugInfo, monitor); + if (result != null) { + return result; + } + } + return null; + } + /** * Searches for the specified external debug file. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugInfo.java index 47a2b92050..049437479b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/ExternalDebugInfo.java @@ -15,10 +15,10 @@ */ package ghidra.app.util.bin.format.dwarf.external; -import ghidra.app.util.bin.format.elf.info.GnuDebugLink; -import ghidra.app.util.bin.format.elf.info.NoteGnuBuildId; +import java.util.ArrayList; +import java.util.List; + import ghidra.program.model.listing.Program; -import ghidra.util.NumericUtilities; /** * Metadata needed to find an ELF/DWARF external debug file, retrieved from an ELF binary's @@ -27,134 +27,28 @@ import ghidra.util.NumericUtilities; * The debuglink can provide a filename and crc of the external debug file, while the build-id * can provide a hash that is converted to a filename that identifies the external debug file. */ -public class ExternalDebugInfo { +public interface ExternalDebugInfo { /** * Create a new {@link ExternalDebugInfo} from information found in the specified program. * * @param program {@link Program} to query - * @return new {@link ExternalDebugInfo} or null if no external debug metadata found in - * program + * @return List of {@link ExternalDebugInfo} instances that were found in the program. */ - public static ExternalDebugInfo fromProgram(Program program) { - GnuDebugLink debugLink = GnuDebugLink.fromProgram(program); - NoteGnuBuildId buildId = NoteGnuBuildId.fromProgram(program); - if (debugLink == null && buildId == null) { - return null; + public static List fromProgram(Program program) { + List results = new ArrayList<>(2); + + BuildIdDebugInfo buildId = BuildIdDebugInfo.fromProgram(program); + if (buildId != null) { + results.add(buildId); } - String filename = debugLink != null ? debugLink.getFilename() : null; - int crc = debugLink != null ? debugLink.getCrc() : 0; - String hash = buildId != null - ? NumericUtilities.convertBytesToString(buildId.getDescription()) - : null; + DebugLinkDebugInfo debugLink = DebugLinkDebugInfo.fromProgram(program); + if (debugLink != null) { + results.add(debugLink); + } - return new ExternalDebugInfo(filename, crc, hash, ObjectType.DEBUGINFO, null); - } - - /** - * {@return a new ExternalDebugInfo instance created using the specified Build-Id value} - * @param buildId hex string - */ - public static ExternalDebugInfo forBuildId(String buildId) { - return new ExternalDebugInfo(null, 0, buildId, ObjectType.DEBUGINFO, null); - } - - /** - * {@return a new ExternalDebugInfo instance created using the specified debuglink values} - * @param debugLinkFilename filename from debuglink section - * @param crc crc32 from debuglink section - */ - public static ExternalDebugInfo forDebugLink(String debugLinkFilename, int crc) { - return new ExternalDebugInfo(debugLinkFilename, crc, null, ObjectType.DEBUGINFO, null); - } - - private final String filename; - private final int crc; - private final String buildId; - private final ObjectType objectType; - private final String extra; - - /** - * Constructor to create an {@link ExternalDebugInfo} instance. - * - * @param filename filename of external debug file, or null - * @param crc crc32 of external debug file, or 0 if no filename - * @param buildId build-id hash digest found in ".note.gnu.build-id" section, or null if - * not present - * @param objectType {@link ObjectType} specifies what kind of debug file is specified by the - * other info - * @param extra additional information used by {@link ObjectType#SOURCE} - */ - public ExternalDebugInfo(String filename, int crc, String buildId, ObjectType objectType, - String extra) { - this.filename = filename; - this.crc = crc; - this.buildId = buildId; - this.objectType = objectType; - this.extra = extra; - } - - /** - * Return true if there is a filename - * - * @return boolean true if filename is available, false if not - */ - public boolean hasDebugLink() { - return filename != null && !filename.isBlank(); - } - - /** - * Return the filename of the external debug file, or null if not specified. - * - * @return String filename of external debug file, or null if not specified - */ - public String getFilename() { - return filename; - } - - /** - * Return the crc of the external debug file. Not valid if filename is missing. - * - * @return int crc32 of external debug file. - */ - public int getCrc() { - return crc; - } - - /** - * Return the build-id. - * - * @return build-id hash string - */ - public String getBuildId() { - return buildId; - } - - /** - * {@return true if buildId is available, false if not} - */ - public boolean hasBuildId() { - return buildId != null && !buildId.isBlank(); - } - - public ObjectType getObjectType() { - return objectType; - } - - public String getExtra() { - return extra; - } - - public ExternalDebugInfo withType(ObjectType newObjectType, String newExtra) { - return new ExternalDebugInfo(extra, crc, buildId, newObjectType, newExtra); - } - - @Override - public String toString() { - return String.format( - "ExternalDebugInfo [filename=%s, crc=%s, hash=%s, objectType=%s, extra=%s]", filename, - crc, buildId, objectType, extra); + return results; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProvider.java index f36d4ed5df..cbe5439d6c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProvider.java @@ -91,13 +91,13 @@ public class HttpDebugInfoDProvider implements DebugStreamProvider { return DebugInfoProviderStatus.UNKNOWN; } - private HttpRequest.Builder request(ExternalDebugInfo id) throws IOException { + private HttpRequest.Builder request(BuildIdDebugInfo id) throws IOException { try { String extra = ""; if (id.getObjectType() == ObjectType.SOURCE) { extra = "/" + Objects.requireNonNullElse(id.getExtra(), ""); } - String requestPath = "buildid/%s/%s%s".formatted(id.getBuildId(), + String requestPath = "buildid/%s/%s%s".formatted(id.getBuildIdHexString(), id.getObjectType().getPathString(), extra); return HttpRequest.newBuilder(serverURI.resolve(requestPath)) .setHeader("User-Agent", GHIDRA_USER_AGENT); @@ -110,14 +110,14 @@ public class HttpDebugInfoDProvider implements DebugStreamProvider { @Override public StreamInfo getStream(ExternalDebugInfo id, TaskMonitor monitor) throws IOException, CancelledException { - if (!id.hasBuildId()) { + if (!(id instanceof BuildIdDebugInfo buildIdInfo)) { return null; } monitor.setIndeterminate(true); monitor.setMessage("Connecting to " + serverURI); - HttpRequest request = request(id).GET().build(); + HttpRequest request = request(buildIdInfo).GET().build(); retryLoop: for (int retryNum = 0; retryNum < maxRetryCount; retryNum++) { if (retryNum > 0) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProvider.java index cc84c104bb..6176b3c17d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProvider.java @@ -154,12 +154,12 @@ public class LocalDirDebugInfoDProvider implements DebugFileStorage { @Override public File getFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) throws IOException, CancelledException { - if (!isValid() || !debugInfo.hasBuildId()) { + if (!isValid() || !(debugInfo instanceof BuildIdDebugInfo buildIdInfo)) { return null; } performInitMaintIfNeeded(); - File f = getCachePath(debugInfo); + File f = getCachePath(buildIdInfo); if (f.isFile()) { f.setLastModified(System.currentTimeMillis()); return f; @@ -175,26 +175,26 @@ public class LocalDirDebugInfoDProvider implements DebugFileStorage { return dir; } - private File getCachePath(ExternalDebugInfo id) throws IOException { + private File getCachePath(BuildIdDebugInfo id) throws IOException { String suffix = ""; if (id.getObjectType() == ObjectType.SOURCE) { suffix = "-" + escapePath(Objects.requireNonNullElse(id.getExtra(), "")); } - return new File(getBuildidDir(id.getBuildId()), + return new File(getBuildidDir(id.getBuildIdHexString()), id.getObjectType().getPathString() + suffix); } @Override - public File putStream(ExternalDebugInfo id, StreamInfo stream, TaskMonitor monitor) + public File putStream(ExternalDebugInfo debugInfo, StreamInfo stream, TaskMonitor monitor) throws IOException, CancelledException { assertValid(); - if (!id.hasBuildId()) { - throw new IOException("Can't store debug file without BuildId value: " + id); + if (!(debugInfo instanceof BuildIdDebugInfo buildId)) { + throw new IOException("Can't store debug file without BuildId value"); } performInitMaintIfNeeded(); - File f = getCachePath(id); + File f = getCachePath(buildId); File tmpF = new File(f.getParentFile(), ".tmp_" + f.getName()); FileUtilities.checkedMkdirs(f.getParentFile()); try (stream; FileOutputStream fos = new FileOutputStream(tmpF)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProvider.java index f0ce100bd1..c412286411 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProvider.java @@ -16,6 +16,7 @@ package ghidra.app.util.bin.format.dwarf.external; import java.io.*; +import java.nio.file.Files; import java.util.zip.CRC32; import ghidra.util.Msg; @@ -24,7 +25,8 @@ import ghidra.util.task.TaskMonitor; import utilities.util.FileUtilities; /** - * Searches for DWARF external debug files specified via a debug-link filename / crc in a directory. + * Searches for DWARF external debug files specified via a debug-link filename / crc + * recursively in a directory. */ public class LocalDirDebugLinkProvider implements DebugFileProvider { @@ -89,10 +91,10 @@ public class LocalDirDebugLinkProvider implements DebugFileProvider { @Override public File getFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) throws CancelledException, IOException { - if (!debugInfo.hasDebugLink() || !isValid()) { + if (!(debugInfo instanceof DebugLinkDebugInfo debugLink) || !isValid()) { return null; } - ensureSafeFilename(debugInfo.getFilename()); + ensureSafeFilename(debugLink.getFilename()); // ensures there are no shenanigans in debugLink filename return findFile(searchDir, debugInfo, monitor); } @@ -105,13 +107,13 @@ public class LocalDirDebugLinkProvider implements DebugFileProvider { File findFile(File dir, ExternalDebugInfo debugInfo, TaskMonitor monitor) throws IOException, CancelledException { - if (!debugInfo.hasDebugLink()) { + if (!(debugInfo instanceof DebugLinkDebugInfo debugLink)) { return null; } - File file = new File(dir, debugInfo.getFilename()); + File file = new File(dir, debugLink.getFilename()); if (file.isFile()) { int fileCRC = calcCRC(file); - if (fileCRC == debugInfo.getCrc()) { + if (fileCRC == debugLink.getCrc()) { return file; // success } Msg.info(this, @@ -119,8 +121,8 @@ public class LocalDirDebugLinkProvider implements DebugFileProvider { .formatted(file, fileCRC)); } File[] subDirs; - if ((subDirs = dir.listFiles(f -> f.isDirectory())) != null) { - // TODO: prevent recursing into symlinks? + if ((subDirs = + dir.listFiles(f -> f.isDirectory() && !Files.isSymbolicLink(f.toPath()))) != null) { for (File subDir : subDirs) { File result = findFile(subDir, debugInfo, monitor); if (result != null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/SameDirDebugInfoProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/SameDirDebugInfoProvider.java index 9f80be847b..3d0ab6d9d5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/SameDirDebugInfoProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/external/SameDirDebugInfoProvider.java @@ -17,6 +17,7 @@ package ghidra.app.util.bin.format.dwarf.external; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import org.apache.commons.io.FilenameUtils; @@ -28,6 +29,12 @@ import utilities.util.FileUtilities; /** * A {@link DebugFileProvider} that only looks in the program's original import directory for * matching debug files. + *

+ * A debug-link (filename+crc) specified debug file must exist in the binary's original import + * directory and must have a matching crc. + *

+ * A build-id (20'ish byte hash) specified debug file must exist in the binary's original import + * directory, named "aabbcc...ff.debug" */ public class SameDirDebugInfoProvider implements DebugFileProvider { @@ -92,13 +99,13 @@ public class SameDirDebugInfoProvider implements DebugFileProvider { @Override public File getFile(ExternalDebugInfo debugInfo, TaskMonitor monitor) throws IOException, CancelledException { - if (debugInfo.hasDebugLink()) { + if (debugInfo instanceof DebugLinkDebugInfo debugLink) { // This differs from the LocalDirDebugLinkProvider in that it does NOT recursively search // for the file - File debugFile = ensureSafeFilename(debugInfo.getFilename()); + File debugFile = ensureSafeFilename(debugLink.getFilename()); if (debugFile.isFile()) { int fileCRC = LocalDirDebugLinkProvider.calcCRC(debugFile); - if (fileCRC == debugInfo.getCrc()) { + if (fileCRC == debugLink.getCrc()) { return debugFile; // success } Msg.info(this, @@ -107,10 +114,10 @@ public class SameDirDebugInfoProvider implements DebugFileProvider { } } - if (debugInfo.hasBuildId()) { + if (debugInfo instanceof BuildIdDebugInfo buildId) { // this probe is a w.a.g for what people might do when co-locating a build-id debug // file with the original binary - File debugFile = ensureSafeFilename(debugInfo.getBuildId() + ".debug"); + File debugFile = ensureSafeFilename(buildId.getBuildIdHexString() + ".debug"); if (debugFile.isFile()) { return debugFile; } @@ -124,6 +131,9 @@ public class SameDirDebugInfoProvider implements DebugFileProvider { if (!progDir.equals(testFile.getParentFile())) { throw new IOException("Unsupported path specified in debug file: " + filename); } + if (Files.isSymbolicLink(testFile.toPath())) { + throw new IOException("Unsupported symlink specified as debug file: " + filename); + } return testFile; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java index 87005d2a0a..6e49fec5e1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/dwarf/sectionprovider/ExternalDebugFileSectionProvider.java @@ -55,14 +55,14 @@ public class ExternalDebugFileSectionProvider extends BaseSectionProvider { public static DWARFSectionProvider createExternalSectionProviderFor(Program program, TaskMonitor monitor) { try { - ExternalDebugInfo extDebugInfo = ExternalDebugInfo.fromProgram(program); - if (extDebugInfo == null) { + List extDebugInfos = ExternalDebugInfo.fromProgram(program); + if (extDebugInfos.isEmpty()) { return null; } Msg.info(ExternalDebugFileSectionProvider.class, - "DWARF external debug information found: " + extDebugInfo); + "DWARF external debug information found: " + extDebugInfos); ExternalDebugFilesService edfs = ExternalDebugFilesService.forProgram(program); - File extDebugFile = edfs.find(extDebugInfo, monitor); + File extDebugFile = edfs.find(extDebugInfos, monitor); if (extDebugFile == null) { return null; } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProviderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProviderTest.java index aa4811d619..77e42885f0 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProviderTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/BuildIdDebugFileProviderTest.java @@ -32,6 +32,7 @@ import utilities.util.FileUtilities; public class BuildIdDebugFileProviderTest extends AbstractGenericTest { private TaskMonitor monitor = TaskMonitor.DUMMY; private File tmpDir; + BuildIdDebugInfo id = new BuildIdDebugInfo(new byte[20] /* all 00's */); @Before public void setUp() throws Exception { @@ -42,14 +43,13 @@ public class BuildIdDebugFileProviderTest extends AbstractGenericTest { public void testGet() throws IOException, CancelledException { BuildIdDebugFileProvider provider = new BuildIdDebugFileProvider(tmpDir); - String buildId = "0000000000000000000000000000000000000000"; - + String buildIdStr = id.getBuildIdHexString(); File f = new File(tmpDir, - "%s/%s.debug".formatted(buildId.substring(0, 2), buildId.substring(2))); + "%s/%s.debug".formatted(buildIdStr.substring(0, 2), buildIdStr.substring(2))); FileUtilities.checkedMkdirs(f.getParentFile()); FileUtilities.writeStringToFile(f, "test1"); - File result = provider.getFile(ExternalDebugInfo.forBuildId(buildId), monitor); + File result = provider.getFile(id, monitor); assertEquals("test1", Files.readString(result.toPath())); assertEquals(5, result.length()); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProviderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProviderTest.java index f09f2d4ecb..a1ec9ccf19 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProviderTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/HttpDebugInfoDProviderTest.java @@ -30,31 +30,32 @@ import com.sun.net.httpserver.HttpServer; import generic.hash.HashUtilities; import generic.test.AbstractGenericTest; import ghidra.app.util.bin.format.dwarf.external.DebugStreamProvider.StreamInfo; +import ghidra.util.NumericUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; public class HttpDebugInfoDProviderTest extends AbstractGenericTest { private TaskMonitor monitor = TaskMonitor.DUMMY; + BuildIdDebugInfo id = new BuildIdDebugInfo(new byte[20] /* all 00's */); + @Test public void testNoConnect() throws IOException, CancelledException { InetSocketAddress unusedAddr = nextLoopbackServerAddr(); HttpDebugInfoDProvider httpProvider = new HttpDebugInfoDProvider(getURI(unusedAddr)); - StreamInfo stream = httpProvider.getStream( - ExternalDebugInfo.forBuildId("0000000000000000000000000000000000000000"), monitor); + StreamInfo stream = httpProvider.getStream(id, monitor); assertNull(stream); } @Test public void testGet() throws IOException, CancelledException { - String buildId = "0000000000000000000000000000000000000000"; HttpServer server = createMockHttpServer(); - server.createContext("/buildid/" + buildId + "/debuginfo", + server.createContext("/buildid/" + id.getBuildIdHexString() + "/debuginfo", createStaticResponseHandler("application/octet-stream", "result1".getBytes())); - server.createContext("/buildid/" + buildId + "/executable", + server.createContext("/buildid/" + id.getBuildIdHexString() + "/executable", createStaticResponseHandler("application/octet-stream", "result2".getBytes())); - server.createContext("/buildid/" + buildId + "/source/usr/include/stdio.h", + server.createContext("/buildid/" + id.getBuildIdHexString() + "/source/usr/include/stdio.h", createStaticResponseHandler("application/octet-stream", "result3".getBytes())); HttpDebugInfoDProvider httpProvider = @@ -62,7 +63,6 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { try { server.start(); - ExternalDebugInfo id = ExternalDebugInfo.forBuildId(buildId); assertStreamResult("result1", httpProvider.getStream(id, monitor)); assertStreamResult("result2", httpProvider.getStream(id.withType(ObjectType.EXECUTABLE, null), monitor)); @@ -80,10 +80,9 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { @Test public void testGetWithRetry() throws IOException, CancelledException { - String buildId = "0000000000000000000000000000000000000000"; HttpServer server = createMockHttpServer(); - server.createContext("/buildid/" + buildId + "/debuginfo", + server.createContext("/buildid/" + id.getBuildIdHexString() + "/debuginfo", wrapHandlerWithRetryError( createStaticResponseHandler("application/octet-stream", "result1".getBytes()), 3, HTTP_INTERNAL_ERROR)); @@ -93,7 +92,6 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { try { server.start(); - ExternalDebugInfo id = ExternalDebugInfo.forBuildId(buildId); assertStreamResult("result1", httpProvider.getStream(id, monitor)); assertEquals(3, httpProvider.getRetriedCount()); } @@ -104,10 +102,9 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { @Test public void testTimeout() throws IOException, CancelledException { - String buildId = "0000000000000000000000000000000000000000"; HttpServer server = createMockHttpServer(); - server.createContext("/buildid/" + buildId + "/debuginfo", wrapHandlerWithDelay( + server.createContext("/buildid/" + id.getBuildIdHexString() + "/debuginfo", wrapHandlerWithDelay( createStaticResponseHandler("application/octet-stream", "result1".getBytes()), 3000)); HttpDebugInfoDProvider httpProvider = @@ -118,9 +115,8 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { server.start(); long startms = System.currentTimeMillis(); - ExternalDebugInfo id = ExternalDebugInfo.forBuildId(buildId); - long elapsed = System.currentTimeMillis() - startms; assertNull(httpProvider.getStream(id, monitor)); + long elapsed = System.currentTimeMillis() - startms; assertTrue("Request took too long", elapsed < (1000 * 2)); // make sure request time was approx same as timeout setting } finally { @@ -130,6 +126,7 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { @Test public void testGetNotFound() throws IOException, CancelledException { + HttpServer server = createMockHttpServer(); HttpDebugInfoDProvider httpProvider = @@ -137,8 +134,6 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { try { server.start(); - ExternalDebugInfo id = - ExternalDebugInfo.forBuildId("0000000000000000000000000000000000000000"); assertNull(httpProvider.getStream(id, monitor)); assertEquals(0, httpProvider.getRetriedCount()); assertEquals(1, httpProvider.getNotFoundCount()); @@ -150,9 +145,9 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { @Test public void testServerError() throws IOException, CancelledException { - String buildId = "0000000000000000000000000000000000000000"; + HttpServer server = createMockHttpServer(); - server.createContext("/buildid/" + buildId + "/debuginfo", + server.createContext("/buildid/" + id.getBuildIdHexString() + "/debuginfo", createStaticResponseHandler(HTTP_INTERNAL_ERROR, "text/plain", "".getBytes())); HttpDebugInfoDProvider httpProvider = @@ -160,8 +155,6 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { try { server.start(); - ExternalDebugInfo id = - ExternalDebugInfo.forBuildId("0000000000000000000000000000000000000000"); assertNull(httpProvider.getStream(id, monitor)); assertEquals(4, httpProvider.getRetriedCount()); assertEquals(0, httpProvider.getNotFoundCount()); @@ -178,10 +171,10 @@ public class HttpDebugInfoDProviderTest extends AbstractGenericTest { // The specified buildId may stop being present at some point of time in the future HttpDebugInfoDProvider httpProvider = new HttpDebugInfoDProvider(URI.create("https://debuginfod.elfutils.org/")); - ExternalDebugInfo id = - ExternalDebugInfo.forBuildId("421e1abd8faf1cb290df755a558377c5d7def3b1"); + BuildIdDebugInfo eu_id = new BuildIdDebugInfo( + NumericUtilities.convertStringToBytes("421e1abd8faf1cb290df755a558377c5d7def3b1")); assertStreamHash("f5894783abae9084e531b8da76bbb2444a688d18", - httpProvider.getStream(id, monitor)); + httpProvider.getStream(eu_id, monitor)); } private void assertStreamResult(String expectedResult, StreamInfo stream) throws IOException { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProviderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProviderTest.java index 380bac2246..5999b8764b 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProviderTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugInfoDProviderTest.java @@ -33,6 +33,8 @@ public class LocalDirDebugInfoDProviderTest extends AbstractGenericTest { private TaskMonitor monitor = TaskMonitor.DUMMY; private File tmpDir; + BuildIdDebugInfo id = new BuildIdDebugInfo(new byte[20] /* all 00's */); + @Before public void setUp() throws Exception { tmpDir = createTempDirectory("debuginfod_provider_test"); @@ -43,9 +45,7 @@ public class LocalDirDebugInfoDProviderTest extends AbstractGenericTest { LocalDirDebugInfoDProvider provider = new LocalDirDebugInfoDProvider(tmpDir); provider.purgeAll(); - String buildId = "0000000000000000000000000000000000000000"; - - File f = new File(tmpDir, buildId + "/debuginfo"); + File f = new File(tmpDir, id.getBuildIdHexString() + "/debuginfo"); FileUtilities.checkedMkdirs(f.getParentFile()); FileUtilities.writeStringToFile(f, "test1"); @@ -70,13 +70,11 @@ public class LocalDirDebugInfoDProviderTest extends AbstractGenericTest { LocalDirDebugInfoDProvider provider = new LocalDirDebugInfoDProvider(tmpDir); provider.purgeAll(); - String buildId = "0000000000000000000000000000000000000000"; - - File f = new File(tmpDir, buildId + "/debuginfo"); + File f = new File(tmpDir, id.getBuildIdHexString() + "/debuginfo"); FileUtilities.checkedMkdirs(f.getParentFile()); FileUtilities.writeStringToFile(f, "test1"); - File result = provider.getFile(ExternalDebugInfo.forBuildId(buildId), monitor); + File result = provider.getFile(id, monitor); assertEquals("debuginfo", result.getName()); assertEquals(5, result.length()); @@ -87,10 +85,9 @@ public class LocalDirDebugInfoDProviderTest extends AbstractGenericTest { LocalDirDebugInfoDProvider provider = new LocalDirDebugInfoDProvider(tmpDir); provider.purgeAll(); - String buildId = "0000000000000000000000000000000000000000"; byte bytes[] = "test".getBytes(); StreamInfo stream = new StreamInfo(new ByteArrayInputStream(bytes), bytes.length); - File f = provider.putStream(ExternalDebugInfo.forBuildId(buildId), stream, monitor); + File f = provider.putStream(id, stream, monitor); assertEquals("debuginfo", f.getName()); assertEquals(bytes.length, f.length()); @@ -104,8 +101,8 @@ public class LocalDirDebugInfoDProviderTest extends AbstractGenericTest { byte bytes[] = "test".getBytes(); StreamInfo stream = new StreamInfo(new ByteArrayInputStream(bytes), bytes.length); try { - File f = provider.putStream(ExternalDebugInfo.forDebugLink("test.debug", 0x11223344), - stream, monitor); + File f = provider.putStream(new DebugLinkDebugInfo("test.debug", 0x11223344), stream, + monitor); fail("Shouldn't get here: " + f); } catch (IOException e) { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProviderTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProviderTest.java index 6e568586d4..cfe64612a1 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProviderTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/bin/format/dwarf/external/LocalDirDebugLinkProviderTest.java @@ -48,7 +48,7 @@ public class LocalDirDebugLinkProviderTest extends AbstractGenericTest { LocalDirDebugLinkProvider provider = new LocalDirDebugLinkProvider(tmpDir); File result = - provider.getFile(ExternalDebugInfo.forDebugLink("debugfile.abc", crc), monitor); + provider.getFile(new DebugLinkDebugInfo("debugfile.abc", crc), monitor); assertEquals("test_debuglink", Files.readString(result.toPath())); }