Add a new '-z nognustack' option that suppresses emitting PT_GNU_STACK
segment. This segment is not supported at all on NetBSD (stack is
always non-executable), and the option is meant to be used to disable
emitting it.
Details
Diff Detail
Event Timeline
docs/ld.lld.1 | ||
---|---|---|
515 ↗ | (On Diff #181121) | section? |
docs/ld.lld.1 | ||
---|---|---|
515 ↗ | (On Diff #181121) | I think 'segment' is actually the correct term here. |
ELF/Driver.cpp | ||
---|---|---|
874 ↗ | (On Diff #181121) | I would add gnustack vs nognustack - two options, but it's up to maintainers to decide. We can keep it enabled by default.... and disable it in NetBSD's clang driver. |
The absence of PT_GNU_STACK segment makes stack area executable on systems that recognizes PT_GNU_STACK segment. So, I think if -z execstack is specified, we should omit PT_GNU_STACK segment rather than adding it, which I think you guys want. If we do that, it seems -z nognustack is a redundant option. That option name is unfortunate (you don't really mean you want an executable stack area), but that's I think still better than adding an option that is very similar to an existing feature.
If we are going to change the meaning of -z execstack, can we rename the option in lld? Probably to -z gnustack vs -z nognustack, probably there is no other use-case than RWX->RW protection change.
Systems like fuchsia don't need/want it either. FreeBSD&Linux recognize this ELF segment.
Both -z execstack and -z noexecstack are supported by GNU linkers, so we can't simply rename them. We might be able to define aliases to the options. But I'm not sure if we want it if the only reason to do so is aesthetic reason.
For compat with GNU linkers we could disable it by default, and emit GNU stack only on demand.
The current approach to enable GNU STACK is to inline a new segment definition in assembly. It's done this way in llvm projects too.
Actually looking at GNU ld(1) source code, the logic is a little bit more complex and it depends on more aspects like relocations.
Right. In terms of which is the default, lld is not compatible with GNU. lld by default tries to disable executable stack (which is definitely a good thing to do today). GNU linkers tries to do something smarter which doesn't make much sense and more error-prone today. I think changing the default makes sense.
Defining an alias to -z execstack as -z nognustack is a different story. I'm not totally against doing it, and I see that the option name is somewhat confusing, but I'm actually curious if the alias is what you really want. If you pass -z noexecstack to lld, your command line still works for both lld and GNU ld. But if you define -z nognustack and pass that option to lld, your command line is no longer compatible with GNU. You would no longer be able to copy all arguments to lld to run it again with GNU ld by just copy-n-pasting, for example. Are you happy with that?
Shouldn't the logic of emitting/not-emitting be based on findSection(".note.GNU-stack")? @mgorny can you please investigate it?
I understand the concerns, but lld is biased with being a Linux linker. We need to customize it (restore defaults?) for NetBSD.
GNU linkers assume that input object files that work with non-executable stack has a .note.GNU-stack section, and they emit a PT_GNU_STACK segment to mark the stack area non-executable if all input files have that marker section.
If restoring default means we implement that behavior, then lld shouldn't do that. That mechanism is error-prone, and the consequence of an error is disabling a major security measure. To be on the safe side, we simply emit unless it is explicitly to not do that by -z execstack.
If you add -z nognustack as an alias to -z execstack (and changing the behavior of -z execstack to not add the marker segment at all), I think I'm fine with that.
What do you think about this new logic:
- specified -z execstack -> do not emit GNU STACK segment
- specified -z noexecstack -> emit GNU STACK segment
- no option specified -> detect findSection(".note.GNU-stack") (I might miss offhand some details here to be sure if it is reliable)
3.1. detected -> emit GNU STACK segment
3.2. not detected -> do not emit GNU STACK segment
Additionally we will specify explicitly in the clang driver for Linux and FreeBSD -z noexecstack.
If this is not acceptable and we must use GNU extensions for everybody, we will need to specify this ugly -z execstack in the NetBSD driver (once we will get an agreement that we customize the linker through the driver).
@ruiu, what if one of the systems changes defaults (e.g. due to Hardening) and starts defaulting to noexecstack? In that case we'd want `-z execstack' to actually emit PT_GNU_STACK, and I don't think we really are able to 100% detect the default in clang. I'd really see this as a trinary option: emit RW, emit RWX or not emit at all. I don't think it's a good idea to make linker rely on implicit assumptions or hidden guesses that could easily confuse user as to what's happening and why.
Actually, I've just researched a bit and default stack permissions depend on arch in glibc, so assuming it's RWX by default is wrong.
I think I don't like the very idea of using "marker sections" in input object files to change the linker behavior. That's too subtle and seems error-prone to me.
After thinking for a while, I started thinking that the first version of this patch is probably exactly what we want. I had been thinking that -z {no,}execstack and -z {no,}gnustack are four different options, but what we actually want to get is tri-state:
- emit PT_GNU_STACK with RWX permission
- emit PT_GNU_STACK with RW permission
- do not emit PT_GNU_STACK
So we could map them to -z {execstack,noexecstack,nognustack}, respectively, with default set to -z noexecstack. You guys can pass -z nognustack to the linker to tell the linker to not emit it at all. That's exactly this patch does.
Actually after a longer thought, I recommend to hardcode inside lld a check for TargetTriple.isOSNetBSD(). Using -z nognustack isn't portable to other linkers.
ELF/Writer.cpp | ||
---|---|---|
1979 ↗ | (On Diff #181121) | Please go for if (!Config->TargetTriple.isOSNetBSD()) { |
@ruiu, what do you think? The current logic forces some precedence, i.e. if you pass -z nognustack, further -z {no,}execstack are ignored. I suppose we could just force passing -z nognustack as last option clang-wise.
Are you asking about making the flag tri-state? If so, I think it logically makes sense and feels more natural to represent it with two boolean flags. As you said, the last one should always takes precedence over the others among the three flags.
No, I'm suggesting you add execstack, noexecstack and nognustack as a tri-state -z flag. Does this sound good?
Do you mean that of those three options, the last one specified should take precedence?
(and, if yes, could you suggest how to implement it? getZOption() doesn't seem suitable.
This segment is not supported at all on NetBSD (stack is always non-executable), and the option is meant to be used to disable emitting it.
The string .note.GNU-stack takes only a few bytes in .shstrtab and a few for an Elf64_Shdr instance. Are there any tools warning about unknown section .note.GNU-stack on NetBSD?
Overall looks good.
Do you mean that of those three options, the last one specified should take precedence?
Yes.
ELF/Config.h | ||
---|---|---|
191 ↗ | (On Diff #183729) | Members are (roughly) sorted alphabetically, so move this below the last boolean member. |
ELF/Driver.cpp | ||
352 ↗ | (On Diff #183729) | nit: no else after return. |
369 ↗ | (On Diff #183729) | If you have clang-format, please run it on this file. |
ELF/Config.h | ||
---|---|---|
191 ↗ | (On Diff #183729) | I don't really comprehend the sort key here but done ;-). |
ELF/Driver.cpp | ||
369 ↗ | (On Diff #183729) | File or function? Because the former would add some irrelevant changes to the diff, so if at all, I'd rather do it separately. File I've kept unformatted to make the patch easier to read. I've reformatted it now. |
@ruiu, we have some hardware, which bootloader does not support PT_GNU_STACK segments, and would therefore benefit quite a bit from this option. As this does not really depend on NetBSD support, could you please merge this patch in time for 9.0? Thanks!
This patch makes sense to me. It can benefit other OS/environment as well.
lld/ELF/Driver.cpp | ||
---|---|---|
394 | This is obvious. The comment can be removed. |
lld/ELF/Driver.cpp | ||
---|---|---|
394 | Not done. // default can be deleted I think. |
lld/ELF/Driver.cpp | ||
---|---|---|
394 | You didn't give me time to upload updated patch ;-). |
lld/ELF/Driver.cpp | ||
---|---|---|
394 | You had plenty of time updating the patch but you still committed without changing this part... |
lld/ELF/Driver.cpp | ||
---|---|---|
394 | I'm really sorry, I thought I did that. You commented before I actually uploaded the new patch, and no comments afterwards got me confused. |
This is obvious. The comment can be removed.