Skip to content

Commit

Permalink
[ELF] Implement -z ibtplt
Browse files Browse the repository at this point in the history
  • Loading branch information
rui314 committed Jan 8, 2022
1 parent 18367e6 commit fbfa01d
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 24 deletions.
2 changes: 1 addition & 1 deletion elf/arch-arm64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ static void write_plt_header(Context<ARM64> &ctx, u8 *buf) {
}

static void write_plt_entry(Context<ARM64> &ctx, u8 *buf, Symbol<ARM64> &sym) {
u8 *ent = buf + ARM64::plt_hdr_size + sym.get_plt_idx(ctx) * ARM64::plt_size;
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;

static const u8 data[] = {
0x10, 0x00, 0x00, 0x90, // adrp x16, .got.plt[n]
Expand Down
2 changes: 1 addition & 1 deletion elf/arch-i386.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ static void write_plt_header(Context<I386> &ctx, u8 *buf) {

static void write_plt_entry(Context<I386> &ctx, u8 *buf, Symbol<I386> &sym,
i64 idx) {
u8 *ent = buf + I386::plt_hdr_size + sym.get_plt_idx(ctx) * I386::plt_size;
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;

if (ctx.arg.pic) {
static const u8 data[] = {
Expand Down
76 changes: 65 additions & 11 deletions elf/arch-x86-64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,53 @@ void GotPltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
buf[sym->get_gotplt_idx(ctx)] = sym->get_plt_addr(ctx) + 6;
}

template <>
void PltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
// The IBTPLT is a security-enhanced version of the regular PLT.
// It uses Intel MPX instructions to protect the control flow integirty.
// IBTPLT is slightly larger than the regular PLT (24 bytes vs 16 bytes
// for each entry).
//
// Note that our IBTPLT instruction sequence is different from the one
// used in GNU ld. GNU's IBTPLT implementation uses two separate
// sections (.plt and .plt.sec) in which one PLT entry takes 32 bytes
// in total.
static void write_ibtplt(Context<X86_64> &ctx) {
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;

// Write PLT header
static const u8 plt0[] = {
0xff, 0x35, 0, 0, 0, 0, // pushq GOTPLT+8(%rip)
0xf2, 0xff, 0x25, 0, 0, 0, 0, // bnd jmpq *GOTPLT+16(%rip)
0x0f, 0x1f, 0x0, // nop
};

memcpy(buf, plt0, sizeof(plt0));
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
*(u32 *)(buf + 9) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 3;

// Write PLT entries
i64 relplt_idx = 0;

static const u8 data[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0xf2, 0xff, 0x25, 0, 0, 0, 0, // bnd jmpq *foo@GOTPLT
0x68, 0, 0, 0, 0, // pushq $index_in_relplt
0xf2, 0xe9, 0, 0, 0, 0, // bnd PLT[0]
0x66, 0x90, // nop
};

for (Symbol<X86_64> *sym : ctx.plt->symbols) {
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 7) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 11;
*(u32 *)(ent + 12) = relplt_idx++;
*(u32 *)(ent + 18) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 22;
}
}

// The regular PLT. Unless `-z ibtplt` is given (which is rare), this
// version will be used.
static void write_plt(Context<X86_64> &ctx) {
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;

// Write PLT header
static const u8 plt0[] = {
Expand All @@ -29,8 +73,8 @@ void PltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
};

memcpy(buf, plt0, sizeof(plt0));
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr + 2;
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - this->shdr.sh_addr + 4;
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;

// Write PLT entries
i64 relplt_idx = 0;
Expand All @@ -41,28 +85,38 @@ void PltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
0xe9, 0, 0, 0, 0, // jmp PLT[0]
};

for (Symbol<X86_64> *sym : symbols) {
u8 *ent = buf + X86_64::plt_hdr_size + sym->get_plt_idx(ctx) * X86_64::plt_size;
for (Symbol<X86_64> *sym : ctx.plt->symbols) {
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
*(u32 *)(ent + 7) = relplt_idx++;
*(u32 *)(ent + 12) = this->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
}
}

template <>
void PltSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
if (ctx.arg.z_ibtplt)
write_ibtplt(ctx);
else
write_plt(ctx);
}

template <>
void PltGotSection<X86_64>::copy_buf(Context<X86_64> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;

// We always write a IBT-enabled PLTGOT. If a processor does not
// support Intel MPX, the BND prefix just behaves as a nop.
static const u8 data[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0x66, 0x90, // nop
0xf2, 0xff, 0x25, 0, 0, 0, 0, // bnd jmp *foo@GOT
0x90, // nop
};

for (Symbol<X86_64> *sym : symbols) {
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
*(u32 *)(ent + 3) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 7;
}
}

Expand Down
19 changes: 19 additions & 0 deletions elf/cmdline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ void parse_nonpositional_args(Context<E> &ctx,
ctx.arg.z_initfirst = true;
} else if (read_z_flag(args, "interpose")) {
ctx.arg.z_interpose = true;
} else if (read_z_flag(args, "ibtplt")) {
ctx.arg.z_ibtplt = true;
} else if (read_z_flag(args, "muldefs")) {
ctx.arg.allow_multiple_definition = true;
} else if (read_z_flag(args, "keep-text-section-prefix")) {
Expand Down Expand Up @@ -899,6 +901,23 @@ void parse_nonpositional_args(Context<E> &ctx,
ctx.arg.default_version = VER_NDX_LAST_RESERVED + 1;
}

switch (E::e_machine) {
case EM_X86_64:
ctx.plt_hdr_size = 16;
ctx.plt_size = ctx.arg.z_ibtplt ? 24 : 16;
break;
case EM_386:
ctx.plt_hdr_size = 16;
ctx.plt_size = 16;
break;
case EM_AARCH64:
ctx.plt_hdr_size = 32;
ctx.plt_size = 16;
break;
default:
unreachable();
}

ctx.arg.undefined.push_back(ctx.arg.entry);

// TLSDESC relocs must be always relaxed for statically-linked
Expand Down
6 changes: 0 additions & 6 deletions elf/elf.h
Original file line number Diff line number Diff line change
Expand Up @@ -875,8 +875,6 @@ struct X86_64 {
static constexpr u32 word_size = 8;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_X86_64;
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr bool is_rel = false;
static constexpr bool is_le = true;
Expand Down Expand Up @@ -912,8 +910,6 @@ struct I386 {
static constexpr u32 word_size = 4;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_386;
static constexpr u32 plt_hdr_size = 16;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 8;
static constexpr bool is_rel = true;
static constexpr bool is_le = true;
Expand Down Expand Up @@ -949,8 +945,6 @@ struct ARM64 {
static constexpr u32 word_size = 8;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_AARCH64;
static constexpr u32 plt_hdr_size = 32;
static constexpr u32 plt_size = 16;
static constexpr u32 pltgot_size = 16;
static constexpr bool is_rel = false;
static constexpr bool is_le = true;
Expand Down
7 changes: 5 additions & 2 deletions elf/mold.h
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ class PltSection : public Chunk<E> {
this->name = ".plt";
this->shdr.sh_type = SHT_PROGBITS;
this->shdr.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
this->shdr.sh_addralign = E::plt_hdr_size;
this->shdr.sh_addralign = 16;
}

void add_symbol(Context<E> &ctx, Symbol<E> *sym);
Expand Down Expand Up @@ -1257,6 +1257,7 @@ struct Context {
bool z_dlopen = true;
bool z_dump = true;
bool z_execstack = false;
bool z_ibtplt = false;
bool z_initfirst = false;
bool z_interpose = false;
bool z_keep_text_section_prefix = false;
Expand Down Expand Up @@ -1311,6 +1312,8 @@ struct Context {
bool llvm_lto = false;

i64 page_size = -1;
i64 plt_hdr_size = -1;
i64 plt_size = -1;

// Symbol table
tbb::concurrent_hash_map<std::string_view, Symbol<E>> symbol_map;
Expand Down Expand Up @@ -1816,7 +1819,7 @@ inline u64 Symbol<E>::get_tlsdesc_addr(Context<E> &ctx) const {
template <typename E>
inline u64 Symbol<E>::get_plt_addr(Context<E> &ctx) const {
if (i32 idx = get_plt_idx(ctx); idx != -1)
return ctx.plt->shdr.sh_addr + E::plt_hdr_size + idx * E::plt_size;
return ctx.plt->shdr.sh_addr + ctx.plt_hdr_size + idx * ctx.plt_size;
return ctx.pltgot->shdr.sh_addr + get_pltgot_idx(ctx) * E::pltgot_size;
}

Expand Down
4 changes: 2 additions & 2 deletions elf/output-chunks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -892,12 +892,12 @@ void PltSection<E>::add_symbol(Context<E> &ctx, Symbol<E> *sym) {
assert(!sym->has_plt(ctx));

if (this->shdr.sh_size == 0) {
this->shdr.sh_size = E::plt_hdr_size;
this->shdr.sh_size = ctx.plt_hdr_size;
ctx.gotplt->shdr.sh_size = E::word_size * 3;
}

sym->set_plt_idx(ctx, symbols.size());
this->shdr.sh_size += E::plt_size;
this->shdr.sh_size += ctx.plt_size;
symbols.push_back(sym);

sym->set_gotplt_idx(ctx, ctx.gotplt->shdr.sh_size / E::word_size);
Expand Down
2 changes: 1 addition & 1 deletion test/elf/pltgot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ EOF

objdump -d -j .plt.got $t/exe > $t/log

grep -Pq '1020:\s+ff 25 da 0f 00 00\s+jmp.*# 2000 <ext2>' $t/log
grep -Pq '1020:.*jmp.*# 2000 <ext2>' $t/log

echo OK
24 changes: 24 additions & 0 deletions test/elf/z-ibtplt.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
export LANG=
set -e
CC="${CC:-cc}"
CXX="${CXX:-c++}"
testname=$(basename -s .sh "$0")
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 ");
puts("world");
}
EOF

$CC -B. -o $t/exe $t/a.o -Wl,-z,ibtplt
$t/exe | grep -q 'Hello world'

echo OK

0 comments on commit fbfa01d

Please sign in to comment.