Merge remote-tracking branch 'origin/Ghidra_12.2'

This commit is contained in:
Ryan Kurtz
2026-09-23 08:20:57 -04:00
23 changed files with 498 additions and 220 deletions

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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 : "") + "]";
}
}

View File

@@ -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);
}
}

View File

@@ -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<ExternalDebugInfo> 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.
*

View File

@@ -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<ExternalDebugInfo> fromProgram(Program program) {
List<ExternalDebugInfo> 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;
}
}

View File

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

View File

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

View File

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

View File

@@ -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.
* <p>
* A debug-link (filename+crc) specified debug file must exist in the binary's original import
* directory and must have a matching crc.
* <p>
* 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;
}

View File

@@ -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<ExternalDebugInfo> 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;
}

View File

@@ -102,7 +102,7 @@ public class ArrayValuesFieldFactory extends FieldFactory {
List<FieldElement> 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 =

View File

@@ -172,7 +172,7 @@ public class FSRL {
String params = path.substring(paramStart + 1);
path = path.substring(0, paramStart);
Map<String, String> 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<String, String> 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;

View File

@@ -35,6 +35,8 @@ import ghidra.util.Msg;
public class FileSystemIndexHelper<METADATATYPE> {
private static final int MAX_SYMLINK_RECURSE_DEPTH = 10;
public static final int MAX_FILEENTRY_COUNT = 500_000;
private FileData<METADATATYPE> rootDir;
static class FileData<METADATATYPE> {
@@ -336,9 +338,10 @@ public class FileSystemIndexHelper<METADATATYPE> {
* @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<METADATATYPE> {
* @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<METADATATYPE> fileData =
doStoreFile(filename, parent, fileIndex, isDirectory, length, null, metadata);
return fileData.file;
@@ -397,9 +401,10 @@ public class FileSystemIndexHelper<METADATATYPE> {
* @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<METADATATYPE> {
* @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<METADATATYPE> fileData =
doStoreFile(filename, parent, fileIndex, false, length, symlinkPath, metadata);
@@ -458,7 +464,13 @@ public class FileSystemIndexHelper<METADATATYPE> {
}
private FileData<METADATATYPE> 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)) {

View File

@@ -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<Object> fsih =
new FileSystemIndexHelper<Object>(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<GFile> 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;
}
}
}

View File

@@ -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());

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;

View File

@@ -187,7 +187,7 @@ public class ZipFileSystem extends AbstractFileSystem<ZipArchiveEntry> {
}, 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();

View File

@@ -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