From 34afe864bc19bfb0ba1fc039a8dc39b9f0bc88bc Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Wed, 27 May 2026 08:13:20 -0400 Subject: [PATCH] GP-6875: Check Mach-O headear for invalid number of load commands --- .../app/util/bin/format/macho/MachHeader.java | 46 ++++++++++++++----- .../ghidra/app/util/opinion/MachoLoader.java | 23 ++++++---- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java index f31d772ef9..9d0b7c3c9b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/MachHeader.java @@ -51,6 +51,8 @@ public class MachHeader implements StructConverter { private long _machHeaderStartIndex = 0; private boolean _parsed = false; + private static final int MAX_LOAD_COMMANDS = 32_768; + /** * Returns true if the specified ByteProvider starts with a Mach header magic signature. * @@ -74,7 +76,7 @@ public class MachHeader implements StructConverter { * * @param provider the ByteProvider * @throws IOException if an I/O error occurs while reading from the ByteProvider - * @throws MachException if an invalid MachHeader is detected + * @throws MachException if an invalid header is detected */ public MachHeader(ByteProvider provider) throws IOException, MachException { this(provider, 0); @@ -88,7 +90,7 @@ public class MachHeader implements StructConverter { * @param machHeaderStartIndexInProvider the index into the ByteProvider where the MachHeader * begins * @throws IOException if an I/O error occurs while reading from the ByteProvider - * @throws MachException if an invalid MachHeader is detected + * @throws MachException if an invalid header is detected */ public MachHeader(ByteProvider provider, long machHeaderStartIndexInProvider) throws IOException, MachException { @@ -96,17 +98,17 @@ public class MachHeader implements StructConverter { } /** - * Creatse a new {@link MachHeader}. Assumes the MachHeader starts at index + * Creates a new {@link MachHeader}. Assumes the MachHeader starts at index * machHeaderStartIndexInProvider in the ByteProvider. * * @param provider the ByteProvider * @param machHeaderStartIndexInProvider the index into the ByteProvider where the MachHeader * begins. - * @param isRemainingMachoRelativeToStartIndex true if the rest of the macho uses relative - * indexin (this is common in UBI and kernel cache files); otherwise, false if the rest of the + * @param isRemainingMachoRelativeToStartIndex true if the rest of the Mach-O uses relative + * indexes (this is common in UBI and kernel cache files); otherwise, false if the rest of the * file uses absolute indexing from 0 (this is common in DYLD cache files) * @throws IOException if an I/O error occurs while reading from the ByteProvider - * @throws MachException if an invalid MachHeader is detected + * @throws MachException if an invalid header is detected */ public MachHeader(ByteProvider provider, long machHeaderStartIndexInProvider, boolean isRemainingMachoRelativeToStartIndex) throws IOException, MachException { @@ -144,7 +146,7 @@ public class MachHeader implements StructConverter { * * @return This {@link MachHeader}, for convenience * @throws IOException If there was an IO-related error - * @throws MachException if the load command is invalid + * @throws MachException if a problem was detected with the load commands */ public MachHeader parse() throws IOException, MachException { return parse(null); @@ -157,13 +159,15 @@ public class MachHeader implements StructConverter { * if a split DYLD cache is not being used. * @return This {@link MachHeader}, for convenience * @throws IOException If there was an IO-related error - * @throws MachException if the load command is invalid + * @throws MachException if a problem was detected with the load commands */ public MachHeader parse(SplitDyldCache splitDyldCache) throws IOException, MachException { if (_parsed) { return this; } + validateNumLoadCommands(); + // We must parse segment load commands first, so find and store their indexes separately long currentIndex = _commandIndex; List segmentIndexes = new ArrayList<>(); @@ -198,8 +202,11 @@ public class MachHeader implements StructConverter { * * @return A {@link List} of this {@link MachHeader}'s {@link SegmentCommand segments} * @throws IOException If there was an IO-related error + * @throws MachException if a problem was detected with the load commands */ - public List parseSegments() throws IOException { + public List parseSegments() throws IOException, MachException { + validateNumLoadCommands(); + List segments = new ArrayList<>(); _reader.setPointerIndex(_commandIndex); for (int i = 0; i < nCmds; ++i) { @@ -223,8 +230,11 @@ public class MachHeader implements StructConverter { * @return A {@link List} of this {@link MachHeader}'s * {@link DynamicLibraryCommand reexport load commands} * @throws IOException If there was an IO-related error + * @throws MachException if a problem was detected with the load commands */ - public List parseReexports() throws IOException { + public List parseReexports() throws IOException, MachException { + validateNumLoadCommands(); + List cmds = new ArrayList<>(); _reader.setPointerIndex(_commandIndex); for (int i = 0; i < nCmds; ++i) { @@ -248,9 +258,12 @@ public class MachHeader implements StructConverter { * @param loadCommandType The type of {@link LoadCommand} to check for * @return True if this {@link MachHeader} contains the given {@link LoadCommand} type * @throws IOException If there was an IO-related error + * @throws MachException if a problem was detected with the load commands * @see LoadCommandTypes */ - public boolean parseAndCheck(int loadCommandType) throws IOException { + public boolean parseAndCheck(int loadCommandType) throws IOException, MachException { + validateNumLoadCommands(); + _reader.setPointerIndex(_commandIndex); for (int i = 0; i < nCmds; ++i) { int type = _reader.peekNextInt(); @@ -434,6 +447,17 @@ public class MachHeader implements StructConverter { return getDescription(); } + /** + * Validates the specified number of load commands + * + * @throws MachException if this {@link MachHeader} has an invalid number of load commands + */ + private void validateNumLoadCommands() throws MachException { + if (nCmds > MAX_LOAD_COMMANDS || nCmds < 0) { + throw new MachException("Invalid number of load commands (%d)".formatted(nCmds)); + } + } + /** * Sanitizes invalid segment/section names so they can be used as memory blocks and program tree * modules. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java index 64d0e82715..1ca4ad0332 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java @@ -535,16 +535,21 @@ public class MachoLoader extends AbstractLibrarySupportLoader { * @throws IOException if an IO-related error occurred */ private String detectCompilerName(MachHeader machHeader) throws IOException { - List sectionNames = machHeader.parseSegments() - .stream() - .flatMap(seg -> seg.getSections().stream()) - .map(section -> section.getSectionName()) - .toList(); - if (SwiftUtils.isSwift(sectionNames)) { - return SwiftUtils.SWIFT_COMPILER; + try { + List sectionNames = machHeader.parseSegments() + .stream() + .flatMap(seg -> seg.getSections().stream()) + .map(section -> section.getSectionName()) + .toList(); + if (SwiftUtils.isSwift(sectionNames)) { + return SwiftUtils.SWIFT_COMPILER; + } + if (GoRttiMapper.hasGolangSections(sectionNames)) { + return GoConstants.GOLANG_CSPEC_NAME; + } } - if (GoRttiMapper.hasGolangSections(sectionNames)) { - return GoConstants.GOLANG_CSPEC_NAME; + catch (MachException e) { + // fall thru } return null; }