Skip to content

[llvm-objdump][ELF] Ensure offset to verdaux entry array does not go past size #115284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

antoniofrighetto
Copy link
Contributor

Validate vd_aux while parsing Elf_Verdef structure.

Fixes: #86611.

@llvmbot
Copy link
Member

llvmbot commented Nov 7, 2024

@llvm/pr-subscribers-llvm-binary-utilities

@llvm/pr-subscribers-objectyaml

Author: Antonio Frighetto (antoniofrighetto)

Changes

Validate vd_aux while parsing Elf_Verdef structure.

Fixes: #86611.


Full diff: https://github.com/llvm/llvm-project/pull/115284.diff

5 Files Affected:

  • (modified) llvm/include/llvm/ObjectYAML/ELFYAML.h (+1)
  • (modified) llvm/lib/ObjectYAML/ELFEmitter.cpp (+6-6)
  • (modified) llvm/lib/ObjectYAML/ELFYAML.cpp (+1)
  • (added) llvm/test/tools/llvm-objdump/ELF/invalid-verdef.test (+29)
  • (modified) llvm/tools/llvm-objdump/ELFDump.cpp (+13-4)
diff --git a/llvm/include/llvm/ObjectYAML/ELFYAML.h b/llvm/include/llvm/ObjectYAML/ELFYAML.h
index 8f045d6383623b..5383037582124c 100644
--- a/llvm/include/llvm/ObjectYAML/ELFYAML.h
+++ b/llvm/include/llvm/ObjectYAML/ELFYAML.h
@@ -582,6 +582,7 @@ struct VerdefEntry {
   std::optional<uint16_t> Flags;
   std::optional<uint16_t> VersionNdx;
   std::optional<uint32_t> Hash;
+  std::optional<uint16_t> AuxVOffset;
   std::vector<StringRef> VerNames;
 };
 
diff --git a/llvm/lib/ObjectYAML/ELFEmitter.cpp b/llvm/lib/ObjectYAML/ELFEmitter.cpp
index fc234581a45a70..a379a3e0d4a596 100644
--- a/llvm/lib/ObjectYAML/ELFEmitter.cpp
+++ b/llvm/lib/ObjectYAML/ELFEmitter.cpp
@@ -1655,7 +1655,7 @@ void ELFState<ELFT>::writeSectionContent(Elf_Shdr &SHeader,
     VerDef.vd_flags = E.Flags.value_or(0);
     VerDef.vd_ndx = E.VersionNdx.value_or(0);
     VerDef.vd_hash = E.Hash.value_or(0);
-    VerDef.vd_aux = sizeof(Elf_Verdef);
+    VerDef.vd_aux = E.AuxVOffset.value_or(sizeof(Elf_Verdef));
     VerDef.vd_cnt = E.VerNames.size();
     if (I == Section.Entries->size() - 1)
       VerDef.vd_next = 0;
@@ -1665,13 +1665,13 @@ void ELFState<ELFT>::writeSectionContent(Elf_Shdr &SHeader,
     CBA.write((const char *)&VerDef, sizeof(Elf_Verdef));
 
     for (size_t J = 0; J < E.VerNames.size(); ++J, ++AuxCnt) {
-      Elf_Verdaux VernAux;
-      VernAux.vda_name = DotDynstr.getOffset(E.VerNames[J]);
+      Elf_Verdaux VerdAux;
+      VerdAux.vda_name = DotDynstr.getOffset(E.VerNames[J]);
       if (J == E.VerNames.size() - 1)
-        VernAux.vda_next = 0;
+        VerdAux.vda_next = 0;
       else
-        VernAux.vda_next = sizeof(Elf_Verdaux);
-      CBA.write((const char *)&VernAux, sizeof(Elf_Verdaux));
+        VerdAux.vda_next = sizeof(Elf_Verdaux);
+      CBA.write((const char *)&VerdAux, sizeof(Elf_Verdaux));
     }
   }
 
diff --git a/llvm/lib/ObjectYAML/ELFYAML.cpp b/llvm/lib/ObjectYAML/ELFYAML.cpp
index e97248cbcf5682..821f48723b35e5 100644
--- a/llvm/lib/ObjectYAML/ELFYAML.cpp
+++ b/llvm/lib/ObjectYAML/ELFYAML.cpp
@@ -1921,6 +1921,7 @@ void MappingTraits<ELFYAML::VerdefEntry>::mapping(IO &IO,
   IO.mapOptional("Flags", E.Flags);
   IO.mapOptional("VersionNdx", E.VersionNdx);
   IO.mapOptional("Hash", E.Hash);
+  IO.mapOptional("AuxVOffset", E.AuxVOffset);
   IO.mapRequired("Names", E.VerNames);
 }
 
diff --git a/llvm/test/tools/llvm-objdump/ELF/invalid-verdef.test b/llvm/test/tools/llvm-objdump/ELF/invalid-verdef.test
new file mode 100644
index 00000000000000..abf9a2829f248c
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/ELF/invalid-verdef.test
@@ -0,0 +1,29 @@
+# RUN: yaml2obj %s -o %t
+# RUN: llvm-objdump -p %t 2>&1 | FileCheck --check-prefix=BROKEN-VERDEF -DFILE=%t %s
+
+# BROKEN-VERDEF: Version definitions:
+# BROKEN-VERDEF-NEXT: warning: '[[FILE]]': out-of-bound while parsing verdaux entries, corrupted verdef section
+
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_DYN
+  Machine:         EM_X86_64
+  Entry:           0x0000000000001000
+Sections:
+  - Name:            .gnu.version_d
+    Type:            SHT_GNU_verdef
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x0000000000000230
+    AddressAlign:    0x0000000000000004
+    Entries:
+      - Version:         1
+        Flags:           44
+        VersionNdx:      0
+        Hash:            12345
+        AuxVOffset:      0x45
+        Names:
+          - VERSION_1
+DynamicSymbols: []
+...
diff --git a/llvm/tools/llvm-objdump/ELFDump.cpp b/llvm/tools/llvm-objdump/ELFDump.cpp
index 5ac13495662faf..3c5d11ef8ba751 100644
--- a/llvm/tools/llvm-objdump/ELFDump.cpp
+++ b/llvm/tools/llvm-objdump/ELFDump.cpp
@@ -39,6 +39,9 @@ template <typename ELFT> class ELFDumper : public Dumper {
   void printProgramHeaders();
   void printSymbolVersion();
   void printSymbolVersionDependency(const typename ELFT::Shdr &Sec);
+  void printSymbolVersionDefinition(const typename ELFT::Shdr &Shdr,
+                                    ArrayRef<uint8_t> Contents,
+                                    StringRef StrTab);
 };
 } // namespace
 
@@ -380,9 +383,9 @@ void ELFDumper<ELFT>::printSymbolVersionDependency(
 }
 
 template <class ELFT>
-static void printSymbolVersionDefinition(const typename ELFT::Shdr &Shdr,
-                                         ArrayRef<uint8_t> Contents,
-                                         StringRef StrTab) {
+void ELFDumper<ELFT>::printSymbolVersionDefinition(
+    const typename ELFT::Shdr &Shdr, ArrayRef<uint8_t> Contents,
+    StringRef StrTab) {
   outs() << "\nVersion definitions:\n";
 
   const uint8_t *Buf = Contents.data();
@@ -398,6 +401,12 @@ static void printSymbolVersionDefinition(const typename ELFT::Shdr &Shdr,
            << format("0x%08" PRIx32 " ", (uint32_t)Verdef->vd_hash);
 
     const uint8_t *BufAux = Buf + Verdef->vd_aux;
+    if (BufAux > Contents.end()) {
+      reportWarning("out-of-bound while parsing verdaux entries, corrupted "
+                    "verdef section",
+                    Obj.getFileName());
+      break;
+    }
     uint16_t VerdauxIndex = 0;
     while (BufAux) {
       auto *Verdaux = reinterpret_cast<const typename ELFT::Verdaux *>(BufAux);
@@ -430,7 +439,7 @@ template <class ELFT> void ELFDumper<ELFT>::printSymbolVersion() {
     if (Shdr.sh_type == ELF::SHT_GNU_verneed)
       printSymbolVersionDependency(Shdr);
     else
-      printSymbolVersionDefinition<ELFT>(Shdr, Contents, StrTab);
+      printSymbolVersionDefinition(Shdr, Contents, StrTab);
   }
 }
 

@antoniofrighetto
Copy link
Contributor Author

Change in ObjectYAML can be pre-committed separately, if it looks good.

Copy link
Collaborator

@jh7370 jh7370 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be worth splitting the yaml2obj part into a separate PR. It should include tests too for yaml2obj (and possibly obj2yaml, if it can be used to generate YAML for such a corrupt object).

Comment on lines 1668 to 1674
Elf_Verdaux VerdAux;
VerdAux.vda_name = DotDynstr.getOffset(E.VerNames[J]);
if (J == E.VerNames.size() - 1)
VernAux.vda_next = 0;
VerdAux.vda_next = 0;
else
VernAux.vda_next = sizeof(Elf_Verdaux);
CBA.write((const char *)&VernAux, sizeof(Elf_Verdaux));
VerdAux.vda_next = sizeof(Elf_Verdaux);
CBA.write((const char *)&VerdAux, sizeof(Elf_Verdaux));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is unrelated, could you put it in a separate commit, please?

@@ -1921,6 +1921,7 @@ void MappingTraits<ELFYAML::VerdefEntry>::mapping(IO &IO,
IO.mapOptional("Flags", E.Flags);
IO.mapOptional("VersionNdx", E.VersionNdx);
IO.mapOptional("Hash", E.Hash);
IO.mapOptional("AuxVOffset", E.AuxVOffset);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand it correctly, all this does is set the value - it doesn't actually cause the data to be written to do a different offset. Is that correct?

Assuming that's the case, I'd suggest naming it after the spec name of vd_aux, e.g. VDAux. This is to match the similar fields like SHName and SHOffset for sections, whose only impact is to modify the section header (in contrast with the Name and Offset fields etc).

Flags: 44
VersionNdx: 0
Hash: 12345
AuxVOffset: 0x45
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put a comment somewhere around here, or possibly at the start of the YAML, highlighting how this field is invalid.

Comment on lines 405 to 407
reportWarning("out-of-bound while parsing verdaux entries, corrupted "
"verdef section",
Obj.getFileName());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rewrite this error to include as much context as possible. In particular, take a look at how llvm-readobj and llvm-objdump describe section names, and include the actual offset and expected offset.

I'd consider something like "vd_aux value xxx in section xxx points past the end of the section at offset xxx", where "xxx" should be replaced with appropriate strings/numbers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with 'vd_aux value %val in section verdef points past end of the section, corrupted section,' as I don't think it’s that relevant to provide the exact byte count by which the aux entry went out of bounds. I added 'corrupted section' consistently with the other warnings.

@antoniofrighetto
Copy link
Contributor Author

@jh7370, I rebased this to the other PR, but commits have been split up. Should have reviewed everything, feel free to let me know if it makes sense.

# RUN: yaml2obj %s -o %t
# RUN: llvm-objdump -p %t 2>&1 | FileCheck --check-prefix=BROKEN-VERDEF -DFILE=%t %s

# Ensure we emit a warning when vd_aux offset field is invalid.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

##

return createStringError(errc::invalid_argument,
"vd_aux value " + Twine(Verdef->vd_aux) +
" in section verdef points past end of the "
"section, corrupted section");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

, corrupted section reads strange

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased for both.

@antoniofrighetto antoniofrighetto force-pushed the feature/fix-elfdump-oob branch 2 times, most recently from ab4c234 to 5f36ef3 Compare November 8, 2024 17:49
…past size

Validate `vd_aux` while parsing `Elf_Verdef` structure.

Fixes: llvm#86611.
@antoniofrighetto
Copy link
Contributor Author

Rebased it to main.

Copy link
Collaborator

@jh7370 jh7370 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't force push to active reviews. It's against LLVM review policy, there's actual no need to, it makes it harder to review (because you can't view changes since the previous review) and leads to "commit not found" style errors when clicking on links.

Comment on lines +405 to +407
reportWarning("corrupted section: vd_aux value " + Twine(Verdef->vd_aux) +
" in section verdef points past end of the section",
Obj.getFileName());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with 'vd_aux value %val in section verdef points past end of the section, corrupted section,' as I don't think it’s that relevant to provide the exact byte count by which the aux entry went out of bounds. I added 'corrupted section' consistently with the other warnings.

I just searched for the string "corrupted section" and haven't found any relevant reference. What other warnings are you trying to be consistent with?

Please use the describe method (in Object\ELF.h) in place of "in section verdef" to be consistent with other recent messages added/changed in llvm-readelf.

The idea of including the section size is that it could be the size that is malformed, not the vd_aux value (for whatever reason). ELFDumper.cpp in llvm-readelf has a number of examples of this sort of message pattern (search for "past the end"). There may be others too.

- Version: 1
Flags: 0
VersionNdx: 0
VDAux: 69
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still could do with a comment saying how it's invalid (i.e. what it should be).

@MaskRay
Copy link
Member

MaskRay commented Feb 23, 2025

Obsoleted by #128434 . We should reuse llvm-readobj -V code and possibly reuse some llvm-readobj tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

llvm-objdump: corrupt ELF file can crash llvm-objdump in printSymbolVersionDefinition()
4 participants