Skip to content

ELF: Introduce --randomize-section-padding option. #117653

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

Merged
merged 7 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions lld/ELF/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ struct Config {
bool relrPackDynRelocs = false;
llvm::DenseSet<llvm::StringRef> saveTempsArgs;
llvm::SmallVector<std::pair<llvm::GlobPattern, uint32_t>, 0> shuffleSections;
std::optional<uint64_t> shufflePadding;
bool singleRoRx;
bool shared;
bool symbolic;
Expand Down
2 changes: 2 additions & 0 deletions lld/ELF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,8 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
ctx.arg.searchPaths = args::getStrings(args, OPT_library_path);
ctx.arg.sectionStartMap = getSectionStartMap(ctx, args);
ctx.arg.shared = args.hasArg(OPT_shared);
if (args.hasArg(OPT_shuffle_padding))
ctx.arg.shufflePadding = args::getInteger(args, OPT_shuffle_padding, 0);
ctx.arg.singleRoRx = !args.hasFlag(OPT_rosegment, OPT_no_rosegment, true);
ctx.arg.soName = args.getLastArgValue(OPT_soname);
ctx.arg.sortSection = getSortSection(ctx, args);
Expand Down
3 changes: 3 additions & 0 deletions lld/ELF/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ defm section_start: Eq<"section-start", "Set address of section">,

def shared: F<"shared">, HelpText<"Build a shared object">;

def shuffle_padding: JJ<"shuffle-padding=">,
HelpText<"Randomly insert padding between input sections using given seed">;

defm soname: Eq<"soname", "Set DT_SONAME">;

defm sort_section:
Expand Down
4 changes: 2 additions & 2 deletions lld/ELF/OutputSections.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,14 @@ class OutputSection final : public SectionBase {
void sortInitFini();
void sortCtorsDtors();

std::array<uint8_t, 4> getFiller(Ctx &);

// Used for implementation of --compress-debug-sections and
// --compress-sections.
CompressedData compressed;

private:
SmallVector<InputSection *, 0> storage;

std::array<uint8_t, 4> getFiller(Ctx &);
};

struct OutputDesc final : SectionCommand {
Expand Down
15 changes: 15 additions & 0 deletions lld/ELF/SyntheticSections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2753,6 +2753,21 @@ RelroPaddingSection::RelroPaddingSection(Ctx &ctx)
: SyntheticSection(ctx, ".relro_padding", SHT_NOBITS, SHF_ALLOC | SHF_WRITE,
1) {}

ShufflePaddingSection::ShufflePaddingSection(Ctx &ctx, uint64_t size,
OutputSection *parent)
: SyntheticSection(ctx, ".shuffle_padding", SHF_ALLOC, SHT_PROGBITS, 1),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do these flags behave themselves in the case of .bss and .data?

I'd expect if there is at least one other .data section the combined OutputSection flags would be SHF_WRITE. Could be interesting if there's no existing .data, although I expect there won't be any of these section inserted in that case.

Would the SHT_PROGBITs padding sections in .bss result in the combined .bss Output Section being output as SHT_PROGBITS? This might not be ideal if benchmarking program startup, although shouldn't make a difference to the program runtime.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new sections are added after the output section flags are resolved, so I don't think the values here matter. I just picked values arbitrarily. The new tests I added show that the correct section flags are preserved.

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK I see. Could possibly use parent->flags, and parent->type instead. I don't think it will make a difference at the moment, but may be a bit less surprising see.

size(size) {
this->parent = parent;
}

void ShufflePaddingSection::writeTo(uint8_t *buf) {
std::array<uint8_t, 4> filler = getParent()->getFiller(ctx);
uint8_t *end = buf + size;
for (; buf + 4 <= end; buf += 4)
memcpy(buf, &filler[0], 4);
memcpy(buf, &filler[0], end - buf);
}

// The string hash function for .gdb_index.
static uint32_t computeGdbHash(StringRef s) {
uint32_t h = 0;
Expand Down
9 changes: 9 additions & 0 deletions lld/ELF/SyntheticSections.h
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,15 @@ class RelroPaddingSection final : public SyntheticSection {
void writeTo(uint8_t *buf) override {}
};

class ShufflePaddingSection final : public SyntheticSection {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be better to call this a PaddingSection? While it can be used to implement shuffle padding, there isn't anything in here that's random. I guess the fixed SHT_PROGBITS would need to be changed if this were to be a completely generic padding section.

Just thinking of possible future uses for this type of section.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can always rename the section if it turns out to be needed for another purpose. For now let's name it after its only user so that it's clear what it's used for.

uint64_t size;

public:
ShufflePaddingSection(Ctx &ctx, uint64_t size, OutputSection *parent);
size_t getSize() const override { return size; }
void writeTo(uint8_t *buf) override;
};

// Used by the merged DWARF32 .debug_names (a per-module index). If we
// move to DWARF64, most of this data will need to be re-sized.
class DebugNamesBaseSection : public SyntheticSection {
Expand Down
35 changes: 35 additions & 0 deletions lld/ELF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,38 @@ static void finalizeSynthetic(Ctx &ctx, SyntheticSection *sec) {
}
}

static bool canInsertPadding(OutputSection *sec) {
StringRef s = sec->name;
return s == ".bss" || s == ".data" || s == ".data.rel.ro" || s == ".rodata" ||
s.starts_with(".text");
}

static void shufflePadding(Ctx &ctx) {
std::mt19937 g(*ctx.arg.shufflePadding);
PhdrEntry *curPtLoad = nullptr;
for (OutputSection *os : ctx.outputSections) {
if (!canInsertPadding(os))
continue;
for (SectionCommand *bc : os->commands) {
if (auto *isd = dyn_cast<InputSectionDescription>(bc)) {
SmallVector<InputSection *, 0> tmp;
if (os->ptLoad != curPtLoad) {
tmp.push_back(
make<ShufflePaddingSection>(ctx, g() % ctx.arg.maxPageSize, os));
curPtLoad = os->ptLoad;
}
for (InputSection *isec : isd->sections) {
if (g() < (1 << 28))
Copy link
Member

Choose a reason for hiding this comment

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

1<<28 needs a comment. This should be configurable by the command line option.

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 added a comment and made this a bit less magical (use modulus instead). I'm not sure we need to make it configurable from the start. In the future, if/when we make it configurable, we can just make 1 in 16 the default.

tmp.push_back(
make<ShufflePaddingSection>(ctx, isec->addralign, os));
tmp.push_back(isec);
}
isd->sections = std::move(tmp);
}
}
}
}

// We need to generate and finalize the content that depends on the address of
// InputSections. As the generation of the content may also alter InputSection
// addresses we must converge to a fixed point. We do that here. See the comment
Expand All @@ -1470,6 +1502,9 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
if (ctx.arg.emachine == EM_HEXAGON)
hexagonTLSSymbolUpdate(ctx);

if (ctx.arg.shufflePadding)
shufflePadding(ctx);

uint32_t pass = 0, assignPasses = 0;
for (;;) {
bool changed = ctx.target->needsThunks
Expand Down
9 changes: 9 additions & 0 deletions lld/docs/ld.lld.1
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,15 @@ were concatenated in the order they appeared on the command line.
Set address of section.
.It Fl -shared , Fl -Bsharable
Build a shared object.
.It Fl -shuffle-padding Ns = Ns Ar seed
Randomly insert padding between input sections using the given seed.
Padding is inserted into output sections with names matching the following patterns:
.Cm .bss ,
.Cm .data ,
.Cm .data.rel.ro ,
.Cm .rodata
and
.Cm .text* .
.It Fl -shuffle-sections Ns = Ns Ar seed
Shuffle matched sections using the given seed before mapping them to the output sections.
If -1, reverse the section order. If 0, use a random seed.
Expand Down
23 changes: 23 additions & 0 deletions lld/test/ELF/shuffle-padding-bss.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o

## --shuffle-padding= inserts segment offset padding and pre-section padding,
## and does not affect .bss flags.
# RUN: ld.lld --shuffle-padding=1 %t.o -o %t.out
# RUN: llvm-readelf -sS %t.out | FileCheck --check-prefix=HEADER %s
# HEADER: .bss NOBITS 0000000000202580 000580 000f90 00 WA 0 0 1
# HEADER: 1: 000000000020350c 0 NOTYPE LOCAL DEFAULT 2 a
# HEADER: 2: 000000000020350e 0 NOTYPE LOCAL DEFAULT 2 b
# HEADER: 3: 000000000020350f 0 NOTYPE LOCAL DEFAULT 2 c

.section .bss.a,"a",@nobits
a:
.zero 1

.section .bss.b,"a",@nobits
b:
.zero 1

.section .bss.c,"a",@nobits
c:
.zero 1
36 changes: 36 additions & 0 deletions lld/test/ELF/shuffle-padding-data.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o

# RUN: ld.lld %t.o -o %t.out
# RUN: llvm-readelf -x .data %t.out | FileCheck %s
# CHECK: Hex dump of section '.data':
# CHECK-NEXT: 0x00202158 010203

## --shuffle-padding= inserts segment offset padding and pre-section padding.
# RUN: ld.lld --shuffle-padding=1 %t.o -o %t.out
# RUN: llvm-readelf -x .data %t.out | FileCheck --check-prefix=PAD1 %s
# PAD1: Hex dump of section '.data':
# PAD1: 0x00203500 00000000 00000000 00000000 01000203

## --shuffle-padding= does not affect .rodata flags.
# RUN: llvm-readelf -S %t.out | FileCheck --check-prefix=HEADER %s
# HEADER: .data PROGBITS 0000000000202580 000580 000f90 00 WA 0 0 1

## Size of segment offset padding and location of pre-section padding is
## dependent on the seed.
# RUN: ld.lld --shuffle-padding=2 %t.o -o %t.out
# RUN: llvm-readelf -x .data %t.out | FileCheck --check-prefix=PAD2 %s
# PAD2: Hex dump of section '.data':
# PAD2: 0x002037e0 00000000 00000000 00000000 00010203

.section .data.a,"aw",@progbits
a:
.byte 1

.section .data.b,"aw",@progbits
b:
.byte 2

.section .data.c,"aw",@progbits
c:
.byte 3
36 changes: 36 additions & 0 deletions lld/test/ELF/shuffle-padding-rodata.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# REQUIRES: x86
Copy link
Member

Choose a reason for hiding this comment

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

bss/data/rodata can be merged into one test.

It's important to test an output section with multiple InputSectionDescription.

By default .rodata .text .data are in different PT_LOAD segments. The os->ptLoad != curPtLoad condition isn't covered by tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I moved this into a single test and used the fact that bss and data are in the same PT_LOAD to cover os->ptLoad != curPtLoad .

# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o

# RUN: ld.lld %t.o -o %t.out
# RUN: llvm-readelf -x .rodata %t.out | FileCheck %s
# CHECK: Hex dump of section '.rodata':
# CHECK-NEXT: 0x00200120 010203

## --shuffle-padding= inserts segment offset padding and pre-section padding.
# RUN: ld.lld --shuffle-padding=1 %t.o -o %t.out
# RUN: llvm-readelf -x .rodata %t.out | FileCheck --check-prefix=PAD1 %s
# PAD1: Hex dump of section '.rodata':
# PAD1: 0x00200540 00000000 00010203

## --shuffle-padding= does not affect .rodata flags.
# RUN: llvm-readelf -S %t.out | FileCheck --check-prefix=HEADER %s
# HEADER: .rodata PROGBITS 0000000000200120 000120 000428 00 A 0 0 1

## Size of segment offset padding and location of pre-section padding is
## dependent on the seed.
# RUN: ld.lld --shuffle-padding=2 %t.o -o %t.out
# RUN: llvm-readelf -x .rodata %t.out | FileCheck --check-prefix=PAD2 %s
# PAD2: Hex dump of section '.rodata':
# PAD2: 0x00200dc0 00000000 00000000 01000203

.section .rodata.a,"a",@progbits
a:
.byte 1

.section .rodata.b,"a",@progbits
b:
.byte 2

.section .rodata.c,"a",@progbits
c:
.byte 3
26 changes: 26 additions & 0 deletions lld/test/ELF/shuffle-padding-text.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o

# RUN: ld.lld %t.o -o %t.out
# RUN: llvm-readelf -x .text %t.out | FileCheck %s
# CHECK: Hex dump of section '.text':
# CHECK-NEXT: 0x00201120 010203

## --shuffle-padding= inserts segment offset padding and pre-section padding.
# RUN: ld.lld --shuffle-padding=1 %t.o -o %t.out
# RUN: llvm-readelf -x .text %t.out | FileCheck --check-prefix=PAD1 %s
# PAD1: Hex dump of section '.text':
# PAD1: 0x00201540 cccccccc cccccccc 0102cc03

## --shuffle-padding= does not affect .text flags.
# RUN: llvm-readelf -S %t.out | FileCheck --check-prefix=HEADER %s
# HEADER: .text PROGBITS 0000000000201120 000120 00042c 00 AX 0 0 4

.section .text.a,"ax"
.byte 1

.section .text.b,"ax"
.byte 2

.section .text.c,"ax"
.byte 3