Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions llvm/include/llvm/Object/ELF.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,8 @@ class ELFFile {
Elf_Note_Iterator notes_begin(const Elf_Phdr &Phdr, Error &Err) const {
assert(Phdr.p_type == ELF::PT_NOTE && "Phdr is not of type PT_NOTE");
ErrorAsOutParameter ErrAsOutParam(Err);
if (Phdr.p_offset + Phdr.p_filesz > getBufSize()) {
if (Phdr.p_offset + Phdr.p_filesz > getBufSize() ||
Phdr.p_offset + Phdr.p_filesz < Phdr.p_offset) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since these are independent of your wider change, they should be their own PR, with appropriate unit testing, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This serves as an overflow check, and the unit test would fail without it. Do you think I should create a separate PR to merge this check first?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, and I see you've put a PR up for it.

Err =
createError("invalid offset (0x" + Twine::utohexstr(Phdr.p_offset) +
") or size (0x" + Twine::utohexstr(Phdr.p_filesz) + ")");
Expand Down Expand Up @@ -435,7 +436,8 @@ class ELFFile {
Elf_Note_Iterator notes_begin(const Elf_Shdr &Shdr, Error &Err) const {
assert(Shdr.sh_type == ELF::SHT_NOTE && "Shdr is not of type SHT_NOTE");
ErrorAsOutParameter ErrAsOutParam(Err);
if (Shdr.sh_offset + Shdr.sh_size > getBufSize()) {
if (Shdr.sh_offset + Shdr.sh_size > getBufSize() ||
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above.

Shdr.sh_offset + Shdr.sh_size < Shdr.sh_offset) {
Err =
createError("invalid offset (0x" + Twine::utohexstr(Shdr.sh_offset) +
") or size (0x" + Twine::utohexstr(Shdr.sh_size) + ")");
Expand Down
26 changes: 20 additions & 6 deletions llvm/lib/Object/BuildID.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ using namespace llvm::object;
namespace {

template <typename ELFT> BuildIDRef getBuildID(const ELFFile<ELFT> &Obj) {
auto findBuildID = [&Obj](const auto &ShdrOrPhdr,
uint64_t Alignment) -> std::optional<BuildIDRef> {
Error Err = Error::success();
for (auto N : Obj.notes(ShdrOrPhdr, Err))
if (N.getType() == ELF::NT_GNU_BUILD_ID &&
N.getName() == ELF::ELF_NOTE_GNU)
return N.getDesc(Alignment);
consumeError(std::move(Err));
return std::nullopt;
};

auto Sections = cantFail(Obj.sections());
for (const auto &S : Sections) {
if (S.sh_type != ELF::SHT_NOTE)
continue;
if (std::optional<BuildIDRef> ShdrRes = findBuildID(S, S.sh_addralign))
return ShdrRes.value();
}
auto PhdrsOrErr = Obj.program_headers();
if (!PhdrsOrErr) {
consumeError(PhdrsOrErr.takeError());
Expand All @@ -32,12 +50,8 @@ template <typename ELFT> BuildIDRef getBuildID(const ELFFile<ELFT> &Obj) {
for (const auto &P : *PhdrsOrErr) {
if (P.p_type != ELF::PT_NOTE)
continue;
Error Err = Error::success();
for (auto N : Obj.notes(P, Err))
if (N.getType() == ELF::NT_GNU_BUILD_ID &&
N.getName() == ELF::ELF_NOTE_GNU)
return N.getDesc(P.p_align);
consumeError(std::move(Err));
if (std::optional<BuildIDRef> PhdrRes = findBuildID(P, P.p_align))
return PhdrRes.value();
}
return {};
}
Expand Down
1 change: 1 addition & 0 deletions llvm/test/DebugInfo/symbolize-build-id.test
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Sections:
Type: SHT_NOTE
Flags: [ SHF_ALLOC ]
Content: 040000000800000003000000474e5500abb50d82b6bdc861
AddressAlign: 4
ProgramHeaders:
- Type: PT_NOTE
Flags: [ PF_R ]
Expand Down
117 changes: 117 additions & 0 deletions llvm/unittests/Object/BuildIDTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//===- BuildIDTest.cpp - Tests for getBuildID ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm/Object/BuildID.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Testing/Support/Error.h"

#include "gtest/gtest.h"

using namespace llvm;
using namespace llvm::object;

template <class ELFT>
static Expected<ELFObjectFile<ELFT>> toBinary(SmallVectorImpl<char> &Storage,
StringRef Yaml) {
raw_svector_ostream OS(Storage);
yaml::Input YIn(Yaml);
if (!yaml::convertYAML(YIn, OS, [](const Twine &Msg) {}))
return createStringError(std::errc::invalid_argument,
"unable to convert YAML");
return ELFObjectFile<ELFT>::create(MemoryBufferRef(OS.str(), "dummyELF"));
}

static StringRef getInvalidNoteELF(bool WithShdr) {
static std::string WithSection(R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Machine: EM_X86_64
ProgramHeaders:
- Type: PT_NOTE
FileSize: 0xffffffffffffff88
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure I follow why this is 0xffffffffffffff88 and not 0xffffffffffffffff like it was before (or indeed some other value that better tests the boundary limits).

Copy link
Contributor Author

@cabbaken cabbaken Sep 22, 2025

Choose a reason for hiding this comment

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

In ELF.h, we calculate Phdr.p_offset + Phdr.p_filesz. With p_offset = 0x78, adding it to 0xffffffffffffff88 causes an overflow (wrapping around to 0).
In this case, it effectively works as a boundary value.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not what I had in mind, since I was referring to the original non-overflow checks (i.e. Phdr.p_offset + Phdr.p_filesz > getBufSize() and its section header equivalent). In other words, show that if Phdr.p_offset + Phdr.p_filesz == getBufSize() + 1, we get the error case (arguably there should be a separate test case where Phdr.p_offset + Phdr.p_filesz == getBufSize()). The same applies for the section header case.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, I should add that the current case is a good way of testing the overflow boundary case in the other PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I should add that the current case is a good way of testing the overflow boundary case in the other PR.

Yes, the boundary value is intended for the overflow check.
In this PR, I’ll remove the unrelated parts so it focuses on its main subject.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that I add the boundary check in another PR, should I remove the boundary value here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please refer back to this comment above. I think that should mostly answer your question. It's enough to only check one of the paths there in this PR, because what you're after is test the behaviour when an error is encountered in the underlying code.

FirstSec: .note.gnu.build-id
LastSec: .note.gnu.build-id
Sections:
- Name: .note.gnu.build-id
Type: SHT_NOTE
AddressAlign: 0x04
Notes:
- Name: "GNU"
Desc: "abb50d82b6bdc861"
Type: 3
)");
static std::string WithoutSection(WithSection + R"(
- Type: SectionHeaderTable
NoHeaders: true
)");
if (WithShdr)
return WithSection;
return WithoutSection;
}

// The BuildID can be looked up from a section header, if there is no program header.
TEST(BuildIDTest, InvalidPhdrFileSizeWithShdrs) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, getInvalidNoteELF(true));
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
BuildIDRef BuildID = getBuildID(&ElfOrErr.get());
EXPECT_EQ(
StringRef(reinterpret_cast<const char *>(BuildID.data()), BuildID.size()),
"\xAB\xB5\x0D\x82\xB6\xBD\xC8\x61");
}

// The code handles a malformed program header that points at data outside the file.
TEST(BuildIDTest, InvalidPhdrFileSizeNoShdrs) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr =
toBinary<ELF64LE>(Storage, getInvalidNoteELF(false));
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
BuildIDRef BuildID = getBuildID(&ElfOrErr.get());
EXPECT_EQ(
StringRef(reinterpret_cast<const char *>(BuildID.data()), BuildID.size()),
"");
}

// the code handles a malformed section header that points at data outside the file.
TEST(BuildIDTest, InvalidSectionHeader) {
SmallString<0> Storage;
Expected<ELFObjectFile<ELF64LE>> ElfOrErr = toBinary<ELF64LE>(Storage, R"(
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_EXEC
Machine: EM_X86_64
ProgramHeaders:
- Type: PT_NOTE
FirstSec: .note.gnu.build-id
LastSec: .note.gnu.build-id
Sections:
- Name: .note.gnu.build-id
Type: SHT_NOTE
AddressAlign: 0x04
ShOffset: 0xffffffffffffff88
Notes:
- Name: "GNU"
Desc: "abb50d82b6bdc861"
Type: 3
)");
ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded());
BuildIDRef BuildID = getBuildID(&ElfOrErr.get());
EXPECT_EQ(
StringRef(reinterpret_cast<const char *>(BuildID.data()), BuildID.size()),
"\xAB\xB5\x0D\x82\xB6\xBD\xC8\x61");
}
1 change: 1 addition & 0 deletions llvm/unittests/Object/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set(LLVM_LINK_COMPONENTS

add_llvm_unittest(ObjectTests
ArchiveTest.cpp
BuildIDTest.cpp
COFFObjectFileTest.cpp
DXContainerTest.cpp
ELFObjectFileTest.cpp
Expand Down
Loading