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] 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())); }