From: Kevin Enderby Date: Thu, 20 Jul 2017 23:08:41 +0000 (+0000) Subject: Add error handling to the dyld compact export entries in libObject. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ed0a9e113bcf6a1fe0c4333602e765a02037c52c;p=llvm Add error handling to the dyld compact export entries in libObject. lld needs a matching change for this will be my next commit. Expect it to fail build until that matching commit is picked up by the bots. Like the changes in r296527 for dyld bind entires and the changes in r298883 for lazy bind, weak bind and rebase entries the export entries are the last of the dyld compact info to have error handling added. This follows the model of iterators that can fail that Lang Hanes designed when fixing the problem for bad archives r275316 (or r275361). So that iterating through the exports now terminates if there is an error and returns an llvm::Error with an error message in all cases for malformed input. This change provides the plumbing for the error handling, all the needed testing of error conditions and test cases for all of the unique error messages. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@308690 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/llvm/Object/MachO.h b/include/llvm/Object/MachO.h index 2c3c89d1054..f2b273c82a9 100644 --- a/include/llvm/Object/MachO.h +++ b/include/llvm/Object/MachO.h @@ -66,11 +66,13 @@ using dice_iterator = content_iterator; /// ExportEntry encapsulates the current-state-of-the-walk used when doing a /// non-recursive walk of the trie data structure. This allows you to iterate /// across all exported symbols using: -/// for (const llvm::object::ExportEntry &AnExport : Obj->exports()) { +/// Error Err; +/// for (const llvm::object::ExportEntry &AnExport : Obj->exports(&Err)) { /// } +/// if (Err) { report error ... class ExportEntry { public: - ExportEntry(ArrayRef Trie); + ExportEntry(Error *Err, const MachOObjectFile *O, ArrayRef Trie); StringRef name() const; uint64_t flags() const; @@ -88,7 +90,7 @@ private: void moveToFirst(); void moveToEnd(); - uint64_t readULEB128(const uint8_t *&p); + uint64_t readULEB128(const uint8_t *&p, const char **error); void pushDownUntilBottom(); void pushNode(uint64_t Offset); @@ -107,12 +109,19 @@ private: unsigned ParentStringLength = 0; bool IsExportNode = false; }; + using NodeList = SmallVector; + using node_iterator = NodeList::const_iterator; + Error *E; + const MachOObjectFile *O; ArrayRef Trie; SmallString<256> CumulativeString; - SmallVector Stack; - bool Malformed = false; + NodeList Stack; bool Done = false; + + iterator_range nodes() const { + return make_range(Stack.begin(), Stack.end()); + } }; using export_iterator = content_iterator; @@ -356,10 +365,14 @@ public: iterator_range load_commands() const; /// For use iterating over all exported symbols. - iterator_range exports() const; + iterator_range exports(Error &Err, + const MachOObjectFile *O) const; /// For use examining a trie not in a MachOObjectFile. - static iterator_range exports(ArrayRef Trie); + static iterator_range exports(Error &Err, + ArrayRef Trie, + const MachOObjectFile *O = + nullptr); /// For use iterating over all rebase table entries. iterator_range rebaseTable(Error &Err); diff --git a/lib/Object/MachOObjectFile.cpp b/lib/Object/MachOObjectFile.cpp index 2e4da9f15aa..8b85049ea63 100644 --- a/lib/Object/MachOObjectFile.cpp +++ b/lib/Object/MachOObjectFile.cpp @@ -2607,10 +2607,14 @@ dice_iterator MachOObjectFile::end_dices() const { return dice_iterator(DiceRef(DRI, this)); } -ExportEntry::ExportEntry(ArrayRef T) : Trie(T) {} +ExportEntry::ExportEntry(Error *E, const MachOObjectFile *O, + ArrayRef T) : E(E), O(O), Trie(T) {} void ExportEntry::moveToFirst() { + ErrorAsOutParameter ErrAsOutParam(E); pushNode(0); + if (*E) + return; pushDownUntilBottom(); } @@ -2637,14 +2641,12 @@ bool ExportEntry::operator==(const ExportEntry &Other) const { return true; } -uint64_t ExportEntry::readULEB128(const uint8_t *&Ptr) { +uint64_t ExportEntry::readULEB128(const uint8_t *&Ptr, const char **error) { unsigned Count; - uint64_t Result = decodeULEB128(Ptr, &Count); + uint64_t Result = decodeULEB128(Ptr, &Count, Trie.end(), error); Ptr += Count; - if (Ptr > Trie.end()) { + if (Ptr > Trie.end()) Ptr = Trie.end(); - Malformed = true; - } return Result; } @@ -2679,22 +2681,119 @@ ExportEntry::NodeState::NodeState(const uint8_t *Ptr) : Start(Ptr), Current(Ptr) {} void ExportEntry::pushNode(uint64_t offset) { + ErrorAsOutParameter ErrAsOutParam(E); const uint8_t *Ptr = Trie.begin() + offset; NodeState State(Ptr); - uint64_t ExportInfoSize = readULEB128(State.Current); + const char *error; + uint64_t ExportInfoSize = readULEB128(State.Current, &error); + if (error) { + *E = malformedError("export info size " + Twine(error) + " in export trie " + "data at node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } State.IsExportNode = (ExportInfoSize != 0); const uint8_t* Children = State.Current + ExportInfoSize; + if (Children > Trie.end()) { + *E = malformedError("export info size: 0x" + utohexstr(ExportInfoSize) + + " in export trie data at node: 0x" + utohexstr(offset) + + " too big and extends past end of trie data"); + moveToEnd(); + return; + } if (State.IsExportNode) { - State.Flags = readULEB128(State.Current); + const uint8_t *ExportStart = State.Current; + State.Flags = readULEB128(State.Current, &error); + if (error) { + *E = malformedError("flags " + Twine(error) + " in export trie data at " + "node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } + uint64_t Kind = State.Flags & MachO::EXPORT_SYMBOL_FLAGS_KIND_MASK; + if (State.Flags != 0 && + (Kind != MachO::EXPORT_SYMBOL_FLAGS_KIND_REGULAR && + Kind != MachO::EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE && + Kind != MachO::EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL)) { + *E = malformedError("unsupported exported symbol kind: " + + Twine((int)Kind) + " in flags: 0x" + utohexstr(State.Flags) + + " in export trie data at node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } if (State.Flags & MachO::EXPORT_SYMBOL_FLAGS_REEXPORT) { State.Address = 0; - State.Other = readULEB128(State.Current); // dylib ordinal + State.Other = readULEB128(State.Current, &error); // dylib ordinal + if (error) { + *E = malformedError("dylib ordinal of re-export " + Twine(error) + + " in export trie data at node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } + if (O != nullptr) { + if (State.Other > O->getLibraryCount()) { + *E = malformedError("bad library ordinal: " + Twine((int)State.Other) + + " (max " + Twine((int)O->getLibraryCount()) + ") in export " + "trie data at node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } + } State.ImportName = reinterpret_cast(State.Current); + if (*State.ImportName == '\0') { + State.Current++; + } else { + const uint8_t *End = State.Current + 1; + if (End >= Trie.end()) { + *E = malformedError("import name of re-export in export trie data at " + "node: 0x" + utohexstr(offset) + " starts past end of trie " + "data"); + moveToEnd(); + return; + } + while(*End != '\0' && End < Trie.end()) + End++; + if (*End != '\0') { + *E = malformedError("import name of re-export in export trie data at " + "node: 0x" + utohexstr(offset) + " extends past end of trie " + "data"); + moveToEnd(); + return; + } + State.Current = End + 1; + } } else { - State.Address = readULEB128(State.Current); - if (State.Flags & MachO::EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) - State.Other = readULEB128(State.Current); + State.Address = readULEB128(State.Current, &error); + if (error) { + *E = malformedError("address " + Twine(error) + " in export trie data " + "at node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } + if (State.Flags & MachO::EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER) { + State.Other = readULEB128(State.Current, &error); + if (error) { + *E = malformedError("resolver of stub and resolver " + Twine(error) + + " in export trie data at node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } + } } + if(ExportStart + ExportInfoSize != State.Current) { + *E = malformedError("inconsistant export info size: 0x" + + utohexstr(ExportInfoSize) + " where actual size was: 0x" + + utohexstr(State.Current - ExportStart) + " in export trie data " + "at node: 0x" + utohexstr(offset)); + moveToEnd(); + return; + } + } + if (Children + 1 >= Trie.end()) { + *E = malformedError("byte for count of childern in export trie data at " + "node: 0x" + utohexstr(offset) + " extends past end of trie data"); + moveToEnd(); + return; } State.ChildCount = *Children; State.Current = Children + 1; @@ -2704,21 +2803,50 @@ void ExportEntry::pushNode(uint64_t offset) { } void ExportEntry::pushDownUntilBottom() { + ErrorAsOutParameter ErrAsOutParam(E); + const char *error; while (Stack.back().NextChildIndex < Stack.back().ChildCount) { NodeState &Top = Stack.back(); CumulativeString.resize(Top.ParentStringLength); - for (;*Top.Current != 0; Top.Current++) { + for (;*Top.Current != 0 && Top.Current < Trie.end(); Top.Current++) { char C = *Top.Current; CumulativeString.push_back(C); } + if (Top.Current >= Trie.end()) { + *E = malformedError("edge sub-string in export trie data at node: 0x" + + utohexstr(Top.Start - Trie.begin()) + " for child #" + + Twine((int)Top.NextChildIndex) + " extends past end of trie data"); + moveToEnd(); + return; + } Top.Current += 1; - uint64_t childNodeIndex = readULEB128(Top.Current); + uint64_t childNodeIndex = readULEB128(Top.Current, &error); + if (error) { + *E = malformedError("child node offset " + Twine(error) + + " in export trie data at node: 0x" + + utohexstr(Top.Start - Trie.begin())); + moveToEnd(); + return; + } + for (const NodeState &node : nodes()) { + if (node.Start == Trie.begin() + childNodeIndex){ + *E = malformedError("loop in childern in export trie data at node: 0x" + + utohexstr(Top.Start - Trie.begin()) + " back to node: 0x" + + utohexstr(childNodeIndex)); + moveToEnd(); + return; + } + } Top.NextChildIndex += 1; pushNode(childNodeIndex); + if (*E) + return; } if (!Stack.back().IsExportNode) { - Malformed = true; + *E = malformedError("node is not an export node in export trie data at " + "node: 0x" + utohexstr(Stack.back().Start - Trie.begin())); moveToEnd(); + return; } } @@ -2738,8 +2866,10 @@ void ExportEntry::pushDownUntilBottom() { // stack ivar. If there is no more ways down, it pops up one and tries to go // down a sibling path until a childless node is reached. void ExportEntry::moveNext() { - if (Stack.empty() || !Stack.back().IsExportNode) { - Malformed = true; + assert(!Stack.empty() && "ExportEntry::moveNext() with empty node stack"); + if (!Stack.back().IsExportNode) { + *E = malformedError("node is not an export node in export trie data at " + "node: 0x" + utohexstr(Stack.back().Start - Trie.begin())); moveToEnd(); return; } @@ -2764,21 +2894,23 @@ void ExportEntry::moveNext() { } iterator_range -MachOObjectFile::exports(ArrayRef Trie) { - ExportEntry Start(Trie); +MachOObjectFile::exports(Error &E, ArrayRef Trie, + const MachOObjectFile *O) { + ExportEntry Start(&E, O, Trie); if (Trie.empty()) Start.moveToEnd(); else Start.moveToFirst(); - ExportEntry Finish(Trie); + ExportEntry Finish(&E, O, Trie); Finish.moveToEnd(); return make_range(export_iterator(Start), export_iterator(Finish)); } -iterator_range MachOObjectFile::exports() const { - return exports(getDyldInfoExportsTrie()); +iterator_range MachOObjectFile::exports(Error &Err, + const MachOObjectFile *O) const { + return exports(Err, getDyldInfoExportsTrie(), O); } MachORebaseEntry::MachORebaseEntry(Error *E, const MachOObjectFile *O, diff --git a/test/tools/llvm-objdump/Inputs/macho-inconsistant-export b/test/tools/llvm-objdump/Inputs/macho-inconsistant-export new file mode 100755 index 00000000000..da137800a8d Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-inconsistant-export differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-bad-export-info-malformed-uleb128 b/test/tools/llvm-objdump/Inputs/macho-trie-bad-export-info-malformed-uleb128 new file mode 100755 index 00000000000..57ae7bd6c07 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-bad-export-info-malformed-uleb128 differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-bad-export-info-malformed-uleb128_too_big b/test/tools/llvm-objdump/Inputs/macho-trie-bad-export-info-malformed-uleb128_too_big new file mode 100755 index 00000000000..06e005d209d Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-bad-export-info-malformed-uleb128_too_big differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-bad-kind b/test/tools/llvm-objdump/Inputs/macho-trie-bad-kind new file mode 100755 index 00000000000..809d0be2820 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-bad-kind differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-bad-library-ordinal b/test/tools/llvm-objdump/Inputs/macho-trie-bad-library-ordinal new file mode 100755 index 00000000000..cbe8c2c62d7 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-bad-library-ordinal differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-children-count-byte b/test/tools/llvm-objdump/Inputs/macho-trie-children-count-byte new file mode 100755 index 00000000000..63d389b6586 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-children-count-byte differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-edge-string-end b/test/tools/llvm-objdump/Inputs/macho-trie-edge-string-end new file mode 100755 index 00000000000..573bf8ea710 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-edge-string-end differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-export-info-size-too-big b/test/tools/llvm-objdump/Inputs/macho-trie-export-info-size-too-big new file mode 100755 index 00000000000..e41f314355f Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-export-info-size-too-big differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-import-name-end b/test/tools/llvm-objdump/Inputs/macho-trie-import-name-end new file mode 100755 index 00000000000..f1d8f67ade0 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-import-name-end differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-import-name-start b/test/tools/llvm-objdump/Inputs/macho-trie-import-name-start new file mode 100755 index 00000000000..4f7e93c4fc0 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-import-name-start differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-node-loop b/test/tools/llvm-objdump/Inputs/macho-trie-node-loop new file mode 100755 index 00000000000..b94dfa2610e Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-node-loop differ diff --git a/test/tools/llvm-objdump/Inputs/macho-trie-not-export-node b/test/tools/llvm-objdump/Inputs/macho-trie-not-export-node new file mode 100755 index 00000000000..38882762cf0 Binary files /dev/null and b/test/tools/llvm-objdump/Inputs/macho-trie-not-export-node differ diff --git a/test/tools/llvm-objdump/macho-bad-trie.test b/test/tools/llvm-objdump/macho-bad-trie.test new file mode 100644 index 00000000000..5309123454c --- /dev/null +++ b/test/tools/llvm-objdump/macho-bad-trie.test @@ -0,0 +1,35 @@ +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-bad-kind 2>&1 | FileCheck -check-prefix BAD_KIND %s +BAD_KIND: macho-trie-bad-kind': truncated or malformed object (unsupported exported symbol kind: 3 in flags: 0x13 in export trie data at node: 0x53) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-bad-export-info-malformed-uleb128 2>&1 | FileCheck -check-prefix MALFORMED_ULEB128 %s +MALFORMED_ULEB128: macho-trie-bad-export-info-malformed-uleb128': truncated or malformed object (export info size malformed uleb128, extends past end in export trie data at node: 0x5A) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-bad-export-info-malformed-uleb128_too_big 2>&1 | FileCheck -check-prefix MALFORMED_ULEB128_TOO_BIG %s +MALFORMED_ULEB128_TOO_BIG: macho-trie-bad-export-info-malformed-uleb128_too_big': truncated or malformed object (export info size uleb128 too big for uint64 in export trie data at node: 0x5A) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-export-info-size-too-big 2>&1 | FileCheck -check-prefix EXPORT_INFO_SIZE_TOO_BIG %s +EXPORT_INFO_SIZE_TOO_BIG: macho-trie-export-info-size-too-big': truncated or malformed object (export info size: 0x1234 in export trie data at node: 0x33 too big and extends past end of trie data) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-children-count-byte 2>&1 | FileCheck -check-prefix CHILDREN_COUNT_BYTE %s +CHILDREN_COUNT_BYTE: macho-trie-children-count-byte': truncated or malformed object (byte for count of childern in export trie data at node: 0x5 extends past end of trie data) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-import-name-start 2>&1 | FileCheck -check-prefix IMPORT_NAME_START %s +IMPORT_NAME_START: macho-trie-import-name-start': truncated or malformed object (import name of re-export in export trie data at node: 0x33 starts past end of trie data) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-import-name-end 2>&1 | FileCheck -check-prefix IMPORT_NAME_END %s +IMPORT_NAME_END: macho-trie-import-name-end': truncated or malformed object (import name of re-export in export trie data at node: 0x33 extends past end of trie data) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-edge-string-end 2>&1 | FileCheck -check-prefix EDGE_STRING_END %s +EDGE_STRING_END: macho-trie-edge-string-end': truncated or malformed object (edge sub-string in export trie data at node: 0x42 for child #0 extends past end of trie data) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-not-export-node 2>&1 | FileCheck -check-prefix NOT_EXPORT_NODE %s +NOT_EXPORT_NODE: macho-trie-not-export-node': truncated or malformed object (node is not an export node in export trie data at node: 0x5A) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-node-loop 2>&1 | FileCheck -check-prefix LOOP_OF_CHILDERN %s +LOOP_OF_CHILDERN: macho-trie-node-loop': truncated or malformed object (loop in childern in export trie data at node: 0x42 back to node: 0x5) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-trie-bad-library-ordinal 2>&1 | FileCheck -check-prefix BAD_LIBRARY_ORDINAL %s +BAD_LIBRARY_ORDINAL: macho-trie-bad-library-ordinal': truncated or malformed object (bad library ordinal: 69 (max 3) in export trie data at node: 0x33) + +RUN: not llvm-objdump -macho -exports-trie %p/Inputs/macho-inconsistant-export 2>&1 | FileCheck -check-prefix INCONSISTANT_EXPORT_SIZE %s +INCONSISTANT_EXPORT_SIZE: macho-inconsistant-export': truncated or malformed object (inconsistant export info size: 0x9 where actual size was: 0x5 in export trie data at node: 0x53) diff --git a/tools/llvm-nm/llvm-nm.cpp b/tools/llvm-nm/llvm-nm.cpp index ea47891250f..e6378a74306 100644 --- a/tools/llvm-nm/llvm-nm.cpp +++ b/tools/llvm-nm/llvm-nm.cpp @@ -1226,7 +1226,9 @@ dumpSymbolNamesFromObject(SymbolicFile &Obj, bool printName, if (DyldInfoOnly || AddDyldInfo || HFlags & MachO::MH_NLIST_OUTOFSYNC_WITH_DYLDINFO) { unsigned ExportsAdded = 0; - for (const llvm::object::ExportEntry &Entry : MachO->exports()) { + Error Err = Error::success(); + for (const llvm::object::ExportEntry &Entry : MachO->exports(Err, + MachO)) { bool found = false; bool ReExport = false; if (!DyldInfoOnly) { @@ -1362,6 +1364,8 @@ dumpSymbolNamesFromObject(SymbolicFile &Obj, bool printName, } } } + if (Err) + error(std::move(Err), MachO->getFileName()); // Set the symbol names and indirect names for the added symbols. if (ExportsAdded) { EOS.flush(); diff --git a/tools/llvm-objdump/MachODump.cpp b/tools/llvm-objdump/MachODump.cpp index 3bab94d681a..31a3f66b1d3 100644 --- a/tools/llvm-objdump/MachODump.cpp +++ b/tools/llvm-objdump/MachODump.cpp @@ -9402,7 +9402,8 @@ void llvm::printMachOExportsTrie(const object::MachOObjectFile *Obj) { } } } - for (const llvm::object::ExportEntry &Entry : Obj->exports()) { + Error Err = Error::success(); + for (const llvm::object::ExportEntry &Entry : Obj->exports(Err, Obj)) { uint64_t Flags = Entry.flags(); bool ReExport = (Flags & MachO::EXPORT_SYMBOL_FLAGS_REEXPORT); bool WeakDef = (Flags & MachO::EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION); @@ -9455,6 +9456,8 @@ void llvm::printMachOExportsTrie(const object::MachOObjectFile *Obj) { } outs() << "\n"; } + if (Err) + report_error(Obj->getFileName(), std::move(Err)); } //===----------------------------------------------------------------------===//