Bug #20601
closedConfiguration flags are not properly propagated to assembler
Description
Looking into #18061, one of the issues is that the .S files are not processed with the correct flags. For example to have the CET enabled, the -fcf-protection should be used to preprocess the coroutine/amd64/Context.S.
First I thought there is something wrong on Fedora side, therefore I have proposed to export the ASFLAGS 1. However, as it turns out, $(ASFLAGS) are used by GNU make default rule and passed to $(AS). And indeed, Ruby had historically override of this rule, but it does not do this anymore since:
https://github.com/ruby/ruby/commit/091422388e943de1e67ace6faac3d71ed08c14d2
https://github.com/ruby/ruby/commit/42575570a908aac979a80b89266804c4c688dd7c
As can be seen, while previously $(AS) was used to process the .s file, it was replaced by the compiler. This however means that the .S files are not preprocessed with the $(CFLAGS), which contains -fcf-protection.
Updated by vo.x (Vit Ondruch) over 1 year ago
This might be the medicine:
$ git diff
diff --git a/template/Makefile.in b/template/Makefile.in
index 2d232d7925..31e5ae5edb 100644
--- a/template/Makefile.in
+++ b/template/Makefile.in
@@ -449,7 +449,7 @@ $(srcdir)/enc/jis/props.h: enc/jis/props.kwd
.$(ASMEXT).$(OBJEXT):
@$(ECHO) assembling $<
- $(Q) $(CC) $(ASFLAGS) -DSYMBOL_PREFIX=$(SYMBOL_PREFIX) -o $@ -c $<
+ $(Q) $(CC) $(ASFLAGS) -DSYMBOL_PREFIX=$(SYMBOL_PREFIX) $(CFLAGS) $(XCFLAGS) $(CPPFLAGS) $(COUTFLAG)$@ -o $@ -c $<
.c.$(ASMEXT):
@$(ECHO) translating $<
But I am not entirely convinced, if leaving the $(ASFLAGS) around is of any use.
Updated by kjtsanaktsidis (KJ Tsanaktsidis) over 1 year ago
I think we should do what we did for the pac-ret stuff, that Florian recommended, right? Run tests against CFLAGS in configure.ac, and then define special macros?
https://github.com/ruby/ruby/blob/93b19d56de64fdee790a96ddf96fcd08d889ac93/configure.ac#L1010-L1061
I'll have a bash at doing this tonight on my x86_64 machine.
Updated by vo.x (Vit Ondruch) over 1 year ago
Is something like that really needed? From my POV, the issue is that originally, there were .s files processed by Assembler, therefore the $(ASFLAGS) were needed. But now we have .S files pre-processed by compiler:
https://github.com/ruby/ruby/commit/e64f71f812324d098bed12ed68c2bc1d6e780c90
https://github.com/ruby/ruby/commit/42575570a908aac979a80b89266804c4c688dd7c
and therefore the same (or possibly extended) set of flags as for other .c files should be used. My understanding is that we should rather drop the $(ASFLAGS) usage.
Updated by kjtsanaktsidis (KJ Tsanaktsidis) over 1 year ago
ยท Edited
OK, I poked at this a bit tonight. I have quite a few thoughts but they're kind of disorganised.
The short version of this is: I think you're right; we should pass the full set of $CFLAGS $CPPFLAGS when calling $(CC) on a .S file, and in fact I would go a step furthre and delete $ASFLAGS in configure.ac and Makefile.in entirely. It's late now but I'll try and put up a PR for this tomorrow or Friday.
The long verison...
Detecting control flow protection with configure tests¶
So I started on a PR to deal with -fcf-protection in intel context.S in the same way we deal with -mbranch-protection in arm context.S: https://github.com/ruby/ruby/compare/master...KJTsanaktsidis:ruby:ktsanaktsidis/fcf_prot.
This works, but it doesn't generate the build notes in the object file to declare that it has control flow protection. I was about to go and do that (in the same way we do it for ARM here https://github.com/ruby/ruby/blob/9aa62bda46bf7f9de91a95c31fa09dafd23def37/coroutine/arm64/Context.S#L109) but then I realised that the <cet.h> header being included already does this! It has something like this in it on my system
#ifdef __CET__
.pushsection ".note.gnu.property", "a"
// Lots of stuff ...
#endif
So including this header automatically emits the build notes, but only if the CET macro is defined, which will only be if gcc context.S is invoked as gcc -fcf-protection=full context.S instead. We could replicate the property stuff like we do for pac-ret, but it seems pretty silly to copy stuff out of the system headers like this.
If we do what you propose and invoke gcc as an assembler with $CFLAGS, this whole thing sorts itself out (and we can drastically simplify how we handle arm64 pac-ret too!)
Rant about GNU Make conventions¶
The conventions here are all kind of messed up. GCC (and compatible compiler drivers) support...
- Passing options related to C compilation directly to the compiler (e.g.
-fno-omit-frame-pointer). These get passed to thecc1compiler process (I think) - Passing options related to the preprocesser via
-Wp,(e.g. you can spell-DFOO=1as-Wp,-DFOO=1if you wanted to). These get passed tocpppreprocessor - Passing options related to the assembler via
-Wa,(e.g.-Wa,--generate-missing-build-notes=yes). These get passed to thegasassembler - Passing options related to the linker via
-Wl,(e.g.-Wl,-z,now). These get passed to theldlinker
The GNU make conventions however are that...
- C files are turned into Object files by running
$(CC) $(CPPFLAGS) $(CFLAGS) -c $<.-
$(CC)is usuallygccor some compatible compiler driver. -
$CFLAGSobviously should contain the C-compiler related flags. -
$CPPFLAGSshould contain flags related to the preprocessor. They could be spelled with the-Wp,form, but the common preprocessor flags (-I,-U,-D, etc) are not required to be and usually aren't. - Internally, there is both a compilation step (C to assembly) and an assembling step (assembly to object), even though I guess gcc doesn't actually generate a textual form of the assembly unless you asked for it. You might want to provide some options to this inline assembler; you can achieve this by passing e.g.
-Wa,--defsym,foo=bartogcc. However, according to the make conventions, you'd put that in$CFLAGS(since that's what gets passed to gcc).
-
- Assembler files are turned into Object by running
$(AS) $(ASFLAGS) $<.-
$(AS)is usuallygasor some actual assembler - NOT a compiler driver -
$ASFLAGStherefore has to be options spelled as they would be passed togas; e.g.--defsym foo=barinstead of-Wa,--defsym,foo=bar
-
- Object files are turned into an executable by running
$(CC) $(LDFLAGS) $^- Because
$LDFLAGSare passed togcc, they need to be spelled in the-Wl,-z,nowformat as GCC expects them, not in the-z nowformat thatldexpects them
- Because
I guess this is all really kind of inconsistent:
-
$CFLAGSare passed to gcc (makes sense) -
$ASFLAGSas passed to gas (makes sense) -
$LDFLAGSare passed to gcc, not ld (???) -
$CPPFLAGSare passed to gcc, not cpp (???)
But what you're saying is that Ruby does not assemble with $(AS) $(ASFLAGS), but rather by calling $(CC) $(ASFLAGS). That would require that $ASFLAGS be spelt as -Wa,..., not as bare options like gas (and a "vanilla" GNU make setup) would expect! I think this is one of the reasons we should delete ASFLAGS - they're not passed to the assembler in the way that they "conventionally" should be, but rather more in a manner analagous to how LDFLAGS is handled.
Updated by kjtsanaktsidis (KJ Tsanaktsidis) over 1 year ago
Just one more thing: I was worried for a moment that trying to assemble with $CFLAGS would cause problems if you had some C-language specific things in cflags, but actually it seems to work fine - even if you have something like -std=gnu11 or some such.
Updated by vo.x (Vit Ondruch) over 1 year ago
kjtsanaktsidis (KJ Tsanaktsidis) wrote in #note-4:
and in fact I would go a step furthre and delete
$ASFLAGSinconfigure.acandMakefile.inentirely. It's late now but I'll try and put up a PR for this tomorrow or Friday.
I totally support removal of $ASFLAGS. I mulled over the idea, but I was not sure. But your analysis makes sense and provides the justification.
but then I realised that the <cet.h> header being included already does this!
Correct. That is also my understanding.
If we do what you propose and invoke gcc as an assembler with $CFLAGS, this whole thing sorts itself out (and we can drastically simplify how we handle arm64 pac-ret too!)
๐
Updated by Anonymous over 1 year ago
- Status changed from Open to Closed
Applied in changeset git|b18701a7ae0a71c339906ef0db4910fb43645b45.
Remove $(ASFLAGS) from build system and assemble with $(CFLAGS) instead
We already assemble our assembly files using the $(CC) compiler driver,
rather than the actual $(AS) assembler. This means that
- The C preprocessor gets run on the assembly file
- It's valid to pass gcc-style flags to it, like e.g.
-mbranch-protection or -fcf-protection - If you do so, the relevant preprocessor macros like CET get set
- If you really wanted to pass assembler flags, you would need to do
that using -Wa,... anyway
So I think it makes sense to pass "$(XCFLAGS) $(CFLAGS) $(CPPFLAGS)" to
gcc/clang/etc when assembling, rather than passing $(ASFLAGS) (since
the flags are not actually passed to as, but cc!).
The side effect of this is that if there are mitigation flags like
-fcf-protection in $CFLAGS, then the relevant macros like CET will
be defined when assembling the files.
[Bug #20601]