-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[lld/ELF] Add --override-section-flags flag #109454
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
base: main
Are you sure you want to change the base?
Conversation
The motivation is protection against heap-spraying attacks: Put security-critical variables (think IPC-related) into a special section that's usually mapped read-only, but which application code temporarily manually remaps as writeable while the variables in there are written to. That way, these variables can't be written to during heap spraying. The section these variables are in should be mapped read-only at program start. Since the variables in the section aren't constant, source-level techniques like making the variables `const` don't work: That way, LLVM assumes they are constant for optimization purposes. On Windows, this can be achieved, using both link.exe and lld-link.exe by putting the variables in a special section and passing `/SECTION:mysect,R` to the linker to tell it to mark that section as read-only. It can then be remapped as writeable at runtime. On Mac, ld64 and ld64.lld have a `-segprot MYSEG max init` flag that can pick different max and initial segment protections. After llvm#107269, it's possible to use this flag with at least ld64.lld as `-segprot MYSEG rw r` to achieve the desired effect. For ELF, with gcc it's possible to do a bobby tables attack on gcc's section attribute like so: unsigned int __attribute__((section(".myVarSection,\"a\",@progbits #"))) myVar; This gets translated into assembly without any escaping and results in: .section .myVarSection,"a",@progbits #,"aw",@progbits This allows us to smuggle through custom section flags. But while it's funny and it does work, this: * is very hacky * probably needs an arch-dependent comment character at the end * doesn't work with clang. With clang, it used to be possible to instead do __asm__(".section protected_memory, \"a\"\n\t"); constinit __attribute__((section("protected_memory"))) and the merging used to work -- but llvm#50551 "broke" this and this no works. So this patch adds an --override-section-flags flag to LLD that allows changing the flags of a section at link time, just as you can in lld/COFF (and link.exe) and in lld/MachO. See the PR adding this commit for a complete example. It's technically also possible to achieve this by using a linker script, but while linker scripts are common for embedded and kernel work, they are rare for userland programs. People don't know them well, and you'd have to write a > 100 LoC linker script per arch. Also, LLD has historically added targeted flags instead of requiring users to reach for linker scripts, and this follows that tradition. (The PR adding this commit also has a proof-of-concept for this approach.)
@llvm/pr-subscribers-lld-elf @llvm/pr-subscribers-lld Author: Nico Weber (nico) ChangesThe motivation is protection against heap-spraying attacks: Put security-critical variables (think IPC-related) into a special section that's usually mapped read-only, but which application code temporarily manually remaps as writeable while the variables in there are written to. That way, these variables can't be written to during heap spraying. The section these variables are in should be mapped read-only at program start. Since the variables in the section aren't constant, source-level techniques like making the variables On Windows, this can be achieved, using both link.exe and lld-link.exe by putting the variables in a special section and passing On Mac, ld64 and ld64.lld have a For ELF, with gcc it's possible to do a bobby tables attack on gcc's section attribute like so:
This gets translated into assembly without any escaping and results in:
This allows us to smuggle through custom section flags. But while it's funny and it does work, this:
With clang, it used to be possible to instead do
and the merging used to work -- but #50551 "broke" this and this no works. So this patch adds an --override-section-flags flag to LLD that allows changing the flags of a section at link time, just as you can in lld/COFF (and link.exe) and in lld/MachO. See the PR adding this commit for a complete example. It's technically also possible to achieve this by using a linker script, but while linker scripts are common for embedded and kernel work, they are rare for userland programs. People don't know them well, and you'd have to write a > 100 LoC linker script per arch. Also, LLD has historically added targeted flags instead of requiring users to reach for linker scripts, and this follows that tradition. (The PR adding this commit also has a proof-of-concept for this approach.) Full diff: https://github.com/llvm/llvm-project/pull/109454.diff 5 Files Affected:
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 7cae8677ef5ce1..c123b5c23afcf7 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -309,6 +309,8 @@ struct Config {
bool optEL = false;
bool optimizeBBJumps;
bool optRemarksWithHotness;
+ llvm::SmallVector<std::tuple<llvm::GlobPattern, uint32_t>, 0>
+ overrideSectionFlags;
bool picThunk;
bool pie;
bool printGcSections;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index e25db0e4951275..c7de9eb0f90875 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1611,6 +1611,35 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
}
}
+ for (opt::Arg *arg : args.filtered(OPT_override_section_flags)) {
+ SmallVector<StringRef, 0> fields;
+ StringRef(arg->getValue()).split(fields, '=');
+ if (fields.size() != 2) {
+ error(arg->getSpelling() +
+ ": parse error, no '=' found in --override-section-flags arg");
+ continue;
+ }
+
+ uint32_t flags = 0;
+ for (char c : fields[1]) {
+ if (c == 'a')
+ flags |= SHF_ALLOC;
+ else if (c == 'w')
+ flags |= SHF_WRITE;
+ else if (c == 'x')
+ flags |= SHF_EXECINSTR;
+ else
+ error(arg->getSpelling() + ": flags do not match [awx]+");
+ }
+
+ if (Expected<GlobPattern> pat = GlobPattern::create(fields[0])) {
+ config->overrideSectionFlags.emplace_back(std::move(*pat), flags);
+ } else {
+ error(arg->getSpelling() + ": " + toString(pat.takeError()));
+ continue;
+ }
+ }
+
for (opt::Arg *arg : args.filtered(OPT_z)) {
std::pair<StringRef, StringRef> option =
StringRef(arg->getValue()).split('=');
diff --git a/lld/ELF/InputSection.cpp b/lld/ELF/InputSection.cpp
index 9601e6b3250cc0..085221ac5dcf43 100644
--- a/lld/ELF/InputSection.cpp
+++ b/lld/ELF/InputSection.cpp
@@ -47,13 +47,21 @@ static ArrayRef<uint8_t> getSectionContents(ObjFile<ELFT> &file,
return check(file.getObj().getSectionContents(hdr));
}
+uint32_t effectiveSectionFlags(uint32_t flags, StringRef name) {
+ for (auto &[glob, overriddenFlags] : config->overrideSectionFlags) {
+ if (glob.match(name))
+ return overriddenFlags;
+ }
+ return flags;
+}
+
InputSectionBase::InputSectionBase(InputFile *file, uint64_t flags,
uint32_t type, uint64_t entsize,
uint32_t link, uint32_t info,
uint32_t addralign, ArrayRef<uint8_t> data,
StringRef name, Kind sectionKind)
- : SectionBase(sectionKind, name, flags, entsize, addralign, type, info,
- link),
+ : SectionBase(sectionKind, name, effectiveSectionFlags(flags, name),
+ entsize, addralign, type, info, link),
file(file), content_(data.data()), size(data.size()) {
// In order to reduce memory allocation, we assume that mergeable
// sections are smaller than 4 GiB, which is not an unreasonable
diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td
index c80c4017d3512c..cb89fece4d9560 100644
--- a/lld/ELF/Options.td
+++ b/lld/ELF/Options.td
@@ -353,6 +353,10 @@ def omagic: FF<"omagic">, MetaVarName<"<magic>">,
defm orphan_handling:
Eq<"orphan-handling", "Control how orphan sections are handled when linker script used">;
+defm override_section_flags:
+ EEq<"override-section-flags", "Override section flags">,
+ MetaVarName<"<section-glob>=[awx]+">;
+
defm pack_dyn_relocs:
EEq<"pack-dyn-relocs", "Pack dynamic relocations in the given format">,
MetaVarName<"[none,android,relr,android+relr]">;
diff --git a/lld/test/ELF/override-section-flags.s b/lld/test/ELF/override-section-flags.s
new file mode 100644
index 00000000000000..937bb788c63d86
--- /dev/null
+++ b/lld/test/ELF/override-section-flags.s
@@ -0,0 +1,38 @@
+# REQUIRES: x86
+
+# RUN: rm -rf %t && mkdir %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t/a.o
+
+# RUN: ld.lld -pie %t/a.o -o %t/out \
+# RUN: --override-section-flags 'foo0=' \
+# RUN: --override-section-flags 'foo1=a' \
+# RUN: --override-section-flags 'foo2=ax' \
+# RUN: --override-section-flags 'foo3=aw' \
+# RUN: --override-section-flags 'foo4=awx'
+
+# RUN: llvm-readelf --sections --segments %t/out | FileCheck %s
+
+# CHECK-DAG: foo0 PROGBITS {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}}
+# CHECK-DAG: foo1 PROGBITS {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} A {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}}
+# CHECK-DAG: foo2 PROGBITS {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} AX {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}}
+# CHECK-DAG: foo3 PROGBITS {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} WA {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}}
+# CHECK-DAG: foo4 PROGBITS {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}} WAX {{[0-9a-f]+}} {{[0-9a-f]+}} {{[0-9a-f]+}}
+
+
+.globl _start
+_start:
+
+.section foo0,"aw"
+.space 8
+
+.section foo1,"aw"
+.space 8
+
+.section foo2,"aw"
+.space 8
+
+.section foo3,"ax"
+.space 8
+
+.section foo4,"a"
+.space 8
|
Here's the promised example of using this:
(In
) One effect of this is that .mydata ends up in a phrd with .rela.dyn .rela.plt .rodata etc, and when temporarily mapping the first page of .mydata writeable, parts of those other readonly sections temporarily become writeable too. This can be worked around by adding an object file first on the link line that has |
Here's the promised proof-of-concept with a linker script. The only way I found to set section flags is by using custom
But segments look right:
This works, but making it production-quality requires a much longer and more complicated linker script, and per triple. On the other hand, this PR (plus a follow-up |
My initial reaction is that this seems like a low-level solution that could work, but could interact in unforeseen ways with other features as it could break some assumptions that some sections have particular flags. This may be resolvable with a tighter specification of what the option does and any restriction on what it applies to. For example can the section have dynamic relocations like .data.rel.ro if writeable is removed? My intuition is that this might be worth a RFC to see if there are alternative solutions that are perhaps a bit more specific. To make sure I understand, the desire is to have an area of the program that is mapped read-only by the dynamic-linker, ideally aligned on page boundaries. The application, presumably using some kind of linker generated This is kind of like .data.rel.ro which is writeable at static link time, but the dynamic loader marks read-only post dynamic relocation. This sounds like it is almost exactly what you want, with the exception that you probably don't want to make the genuinely RELRO sections temporarily writeable. Perhaps there's a way of making this work by using .data.rel.ro? For example if there were a page aligned sub-section of .data.rel.ro? The dynamic linker would make the RELRO segment read-only. Could the application temporarily map the sub-section of .data.rel.ro as read-write. If we want to pursue the option I'd like to know (in the description) what the semantics of the option are. For example:
|
Right.
When the code wants to write to a variable in such protected memory, it does something like:
Kinda? We want to be able to write to the variable at any time during the program's execution. The idea is for variables in this section to be regular variables, but since pages in this section are readonly most of the time, they're protected from heap spraying attacks (…most of the time).
Currently 'a', 'w', 'x' as described in the help text :)
Might be nice for a follow-up.
You mean in a linker script? Or is it possible to change PHDRS without a linker script? (If not, I'd say just error out when seeing both this flag and a linker script, at least for starters.)
Can you say more about this? In general, I'm trying to go for "simplest thing that could work" here. If there's desire to make this work in more cases in the future to address additional use cases, we can always tweak it later :) (…and make it restrictive enough in this first version to not paint ourselves in a corner) |
Apologies for taking so long to reply, I didn't catch the github notification.
My thought was that extending Possibly some subset of
Yes I mean PHDRS as a linker script.
The linker will only permit dynamic relocations in writeable segments, at least not without If the data in these sections are just constants then they won't need dynamic relocations. However if any of them are initialised to an address, this will need a dynamic relocation if the address is imported from a shared object, or if the program is position independent.
I think that the option could work for this use case. My concern is that the option is so general that I can see people finding it, picking it up and thinking that's just what I need, then finding that it either crashes the linker or doesn't do what they want it to do, which then provokes questions/issues etc. Anyway this is just my opinion. Maybe worth asking MaskRay for his thoughts? |
The motivation is protection against heap-spraying attacks: Put security-critical variables (think IPC-related) into a special section that's usually mapped read-only, but which application code temporarily manually remaps as writeable while the variables in there are written to. That way, these variables can't be written to during heap spraying.
The section these variables are in should be mapped read-only at program start.
Since the variables in the section aren't constant, source-level techniques like making the variables
const
don't work: That way, LLVM assumes they are constant for optimization purposes.On Windows, this can be achieved, using both link.exe and lld-link.exe by putting the variables in a special section and passing
/SECTION:mysect,R
to the linker to tell it to mark that section as read-only. It can then be remapped as writeable at runtime.On Mac, ld64 and ld64.lld have a
-segprot MYSEG max init
flag that can pick different max and initial segment protections. After #107269, it's possible to use this flag with at least ld64.lld as-segprot MYSEG rw r
to achieve the desired effect.For ELF, with gcc it's possible to do a bobby tables attack on gcc's section attribute like so:
This gets translated into assembly without any escaping and results in:
This allows us to smuggle through custom section flags. But while it's funny and it does work, this:
With clang, it used to be possible to instead do
and the merging used to work -- but #50551 "broke" this and this no works.
So this patch adds an --override-section-flags flag to LLD that allows changing the flags of a section at link time, just as you can in lld/COFF (and link.exe) and in lld/MachO. See the PR adding this commit for a complete example.
It's technically also possible to achieve this by using a linker script, but while linker scripts are common for embedded and kernel work, they are rare for userland programs. People don't know them well, and you'd have to write a > 100 LoC linker script per arch. Also, LLD has historically added targeted flags instead of requiring users to reach for linker scripts, and this follows that tradition. (The PR adding this commit also has a proof-of-concept for this approach.)