Skip to content

Commit

Permalink
[ELF] Add the --shuffle-sections option
Browse files Browse the repository at this point in the history
If the --shuffle-sections is given, mold now randomize the output by
shuffling input sections randomly. This feature is compatible with lld's
--shuffle-sections=SEED option introduced in
https://reviews.llvm.org/D74791.

This feature is useful when you want to equalize the conditions of
benchmarks. That is, some particular memory layout can produce a very
good benchmark number due to hardware-level cache hit rate or something
like that. Therefore, even if you get a good benchmark number after
changing code, there's a chance that that's caused by the layout change
and not by the new code itself. With --shuffle-sections, you can isolate
that.

The other use case I can think of is to enhance security. If you build
your program as a position-independent executable, the kernel
automatically enables ASLR (Address Space Layout Randomization), but ASLR
only shift the entire program image in memory by some random offset;
Relative offsets between sections remain the same. If you compile programs
from source, by using --shuffle-sections, you can make the offsets
unpredictable to attackers.
  • Loading branch information
rui314 committed Feb 12, 2022
1 parent 61eb265 commit 7e91897
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 0 deletions.
5 changes: 5 additions & 0 deletions elf/cmdline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ static const char helpmsg[] = R"(
--rpath-link DIR Ignored
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
--shared, --Bshareable Create a share library
--shuffle-sections[=SEED] Randomize the output by shuffling input sections
--sort-common Ignored
--sort-section Ignored
--spare-dynamic-tags NUMBER Reserve give number of tags in .dynamic section
Expand Down Expand Up @@ -496,6 +497,10 @@ void parse_nonpositional_args(Context<E> &ctx,
ctx.arg.demangle = false;
} else if (read_flag(args, "default-symver")) {
ctx.arg.default_symver = true;
} else if (read_flag(args, "shuffle-sections")) {
ctx.arg.shuffle_sections = 0;
} else if (read_arg(ctx, args, arg, "shuffle-sections")) {
ctx.arg.shuffle_sections = parse_number(ctx, "shuffle-sections", arg);
} else if (read_arg(ctx, args, arg, "y") ||
read_arg(ctx, args, arg, "trace-symbol")) {
ctx.arg.trace_symbol.push_back(arg);
Expand Down
4 changes: 4 additions & 0 deletions elf/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,10 @@ static int elf_main(int argc, char **argv) {
// a special rule. Sort them.
sort_init_fini(ctx);

// Handle --shuffle-sections
if (ctx.arg.shuffle_sections)
shuffle_sections(ctx);

// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
Expand Down
2 changes: 2 additions & 0 deletions elf/mold.h
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ template <typename E> void print_dependencies_full(Context<E> &);
template <typename E> void write_repro_file(Context<E> &);
template <typename E> void check_duplicate_symbols(Context<E> &);
template <typename E> void sort_init_fini(Context<E> &);
template <typename E> void shuffle_sections(Context<E> &);
template <typename E> std::vector<Chunk<E> *>
collect_output_sections(Context<E> &);
template <typename E> void compute_section_sizes(Context<E> &);
Expand Down Expand Up @@ -1488,6 +1489,7 @@ struct Context {
i64 spare_dynamic_tags = 5;
i64 thread_count = 0;
std::optional<GlobPattern> unique;
std::optional<i64> shuffle_sections;
std::string Map;
std::string chroot;
std::string directory;
Expand Down
44 changes: 44 additions & 0 deletions elf/passes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <functional>
#include <map>
#include <optional>
#include <random>
#include <regex>
#include <tbb/parallel_for_each.h>
#include <tbb/partitioner.h>
Expand Down Expand Up @@ -567,6 +568,48 @@ void sort_init_fini(Context<E> &ctx) {
}
}

template <typename T>
static void shuffle(std::vector<T> &vec, u64 seed) {
if (vec.empty())
return;

// Xorshift random number generator. We use this RNG because it is
// measurably faster than MT19937.
auto rand = [&]() {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
return seed;
};

// The Fisher-Yates shuffling algorithm.
//
// We don't want to use std::shuffle for build reproducibility. That is,
// std::shuffle's implementation is not guaranteed to be the same across
// platform, so even though the result is guaranteed to be randomly
// shuffled, the exact order may be different across implementations.
//
// We are not using std::uniform_int_distribution for the same reason.
for (i64 i = 0; i < vec.size() - 1; i++)
std::swap(vec[i], vec[i + rand() % (vec.size() - i)]);
}

template <typename E>
void shuffle_sections(Context<E> &ctx) {
Timer t(ctx, "shuffle_sections");

u64 seed = *ctx.arg.shuffle_sections;
if (seed == 0)
seed = std::random_device()();

tbb::parallel_for_each(ctx.output_sections,
[&](std::unique_ptr<OutputSection<E>> &osec) {
if (osec->name != ".init" && osec->name != ".fini" &&
osec->name != ".init_array" && osec->name != ".fini_array")
shuffle(osec->members, seed + hash_string(osec->name));
});
}

template <typename E>
std::vector<Chunk<E> *> collect_output_sections(Context<E> &ctx) {
std::vector<Chunk<E> *> vec;
Expand Down Expand Up @@ -1288,6 +1331,7 @@ void compress_debug_sections(Context<E> &ctx) {
template void write_repro_file(Context<E> &); \
template void check_duplicate_symbols(Context<E> &); \
template void sort_init_fini(Context<E> &); \
template void shuffle_sections(Context<E> &); \
template std::vector<Chunk<E> *> collect_output_sections(Context<E> &); \
template void compute_section_sizes(Context<E> &); \
template void claim_unresolved_symbols(Context<E> &); \
Expand Down
39 changes: 39 additions & 0 deletions test/elf/shuffle-sections.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
export LANG=
set -e
CC="${CC:-cc}"
CXX="${CXX:-c++}"
testname=$(basename "$0" .sh)
echo -n "Testing $testname ... "
cd "$(dirname "$0")"/../..
mold="$(pwd)/mold"
t=out/test/elf/$testname
mkdir -p $t

cat <<EOF | $CC -o $t/a.o -c -xc -
#include <stdio.h>
int main() {
printf("Hello world\n");
}
EOF

$CC -B. -o $t/exe1 $t/a.o
$t/exe1 | grep -q 'Hello world'

$CC -B. -o $t/exe2 $t/a.o -Wl,-shuffle-sections
$t/exe2 | grep -q 'Hello world'

$CC -B. -o $t/exe3 $t/a.o -Wl,-shuffle-sections=100
$t/exe3 | grep -q 'Hello world'

$CC -B. -o $t/exe4 $t/a.o -Wl,-shuffle-sections=100
$t/exe4 | grep -q 'Hello world'

diff $t/exe3 $t/exe4

$CC -B. -o $t/exe5 $t/a.o -Wl,-shuffle-sections=101
$t/exe5 | grep -q 'Hello world'

! diff $t/exe3 $t/exe4 >& /dev/null || false

echo OK

0 comments on commit 7e91897

Please sign in to comment.