[HN Gopher] Hardening mode for the compiler
       ___________________________________________________________________
        
       Hardening mode for the compiler
        
       Author : vitaut
       Score  : 148 points
       Date   : 2025-08-02 02:12 UTC (20 hours ago)
        
 (HTM) web link (discourse.llvm.org)
 (TXT) w3m dump (discourse.llvm.org)
        
       | dilawar wrote:
       | > So this mode needs to set user expectations appropriately: your
       | code breaking between compiler releases is a feature, not a bug.
       | 
       | Good luck. I feel that the C++ community values backward
       | compatibility way too much for this to succeed. Most package
       | maintainers are not going to like it a bit.
        
         | pjmlp wrote:
         | There has been plenty of breakage throughout ISO revisions.
         | 
         | The biggest problem is ABI, in theory that isn't something that
         | standard cares about, in practice all compiler vendors do, thus
         | proposals that break ABI from existing binary libraries tend to
         | be an issue.
         | 
         | Another issue is that WG21 nowadays is full of people without
         | compiler experience, willing to push through their proposals,
         | even without implementations, which then compiler vendors are
         | supposed to suck it up and implement them somehow.
         | 
         | After around C++14 time, it became cool to join WG21 and now
         | the process is completely broken, there are more than 200
         | members.
         | 
         | There is no guidance on an overall vision per se, everyone gets
         | to submit their pet proposal, and then needs to champion it.
         | 
         | Most of these folks aren't that keen into security, hence the
         | kind of baby steps that have been happening.
        
           | dzaima wrote:
           | Compilers at least allow specifying the standard to target,
           | which solves the ISO revision issue. But breaking within the
           | same -std=... setting is quite a bit more annoying, forcing
           | either indefinite patching on otherwise-complete functional
           | codebases, or keeping potentially every compiler version on
           | your system, both of which are pretty terrible options.
        
             | charcircuit wrote:
             | Assuming the code is position independent why can't the
             | linker translate the ABI?
        
               | tempodox wrote:
               | Data sizes, alignment, the way stuff is loaded into
               | registers, all that can change.
        
               | tialaramex wrote:
               | My favourite weird ABI choice is: Who does what for a
               | barrier? The barrier needs _one party_ to do work for a
               | full fence, but which party that should be doesn 't
               | matter... There can be an x86 FENCE over on the store
               | side, or we can put the x86 FENCE on the load. We could
               | do _both_ but that 's pointless, so don't do that.
               | However if we do _neither_ we haven 't built a barrier
               | and now there's probably a horrible bug.
        
               | dzaima wrote:
               | Maybe some things could be translated by a linker, but a
               | linker can't change the size/layout of an in-memory data
               | structure, and there's no info on what to translate from,
               | even if info was added on what to translate to, anyway.
        
             | pjmlp wrote:
             | Breaking within the same std, is something impossible to
             | prevent in compiled languages with enough freedom in build.
             | 
             | Even the C ABI many talk about, most of them don't have any
             | idea of what they are actually talking about.
             | 
             | First of all, it is the OS ABI, in operating systems that
             | happened to be written in C.
             | 
             | Secondly, even C binary libraries have plenty of breakage
             | opportunities within the same std, and compiler.
             | 
             | ABI stability even in languages that kind of promise it, is
             | in reality an half promise.
             | 
             | Bytecode, or some part of the language is guaranteed to be
             | stable, while being tied to a specific version, not all
             | build flags fall under the promise, and not much is
             | promised over the standard library.
             | 
             | Even other good examples that go to great efforts like
             | Java, .NET or Swift, aren't fully ABI safe.
        
               | yjftsjthsd-h wrote:
               | > First of all, it is the OS ABI, in operating systems
               | that happened to be written in C.
               | 
               | It may be per-OS (I wouldn't try linking Linux and NT
               | object files even if they were both compiled from C by
               | GCC with matching versions and everything), but enough
               | details come from C that I think it's fair to call it a C
               | ABI. Like, I can write unix software in pascal, but in
               | order to write to stdout that code is gonna have to
               | convert pascal strings into C strings. OTOH, pascal
               | binaries using pascal libraries can use pascal semantics
               | even on an OS that uses C ABIs.
        
               | pjmlp wrote:
               | Strings is the easy part.
               | 
               | Try to link two binary libraries in Linux, both compiled
               | with GCC, while not using exactly the same compiler
               | flags, or the same data padding, for example things like
               | structures.
               | 
               | Since committee people can explain it even better,
               | 
               | "To Save C, We Must Save ABI"
               | 
               | https://thephd.dev/to-save-c-we-must-save-abi-fixing-c-
               | funct...
        
               | uecker wrote:
               | Sorry, this is nonsense. Binaries link just fine on
               | Linux. If you use a compiler flag that changes the ABI,
               | then you are on your own, of course, but the GCC docu
               | makes it very clear which specific flags those are. There
               | is some corner cases with problems where you get problems
               | if you use different compilers, e.g. atomic alignment (by
               | adopting the broken C++ design into C) and some other
               | corner cases where compilers did things slightly
               | different.
        
               | RossBencina wrote:
               | > e.g. atomic alignment (by adopting the broken C++
               | design into C)
               | 
               | I would like to learn more about that. Do you mean this:
               | 
               | https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65146
        
               | uecker wrote:
               | Things like this resulting in differnt alignment between
               | Clang and GCC on x86_64 for _Atomic struct { char a[3];
               | }; See: https://godbolt.org/z/v5hsjhzj9
               | 
               | The problem is that in C++ these atomics are library
               | types, but in C they are built-in types which should have
               | a clearly specified ABI. But the goal was to make them
               | compatibility with C++ library types, which is a rather
               | stupid idea, which pulls in even more problems.
        
               | pjmlp wrote:
               | Assuming you have control over all binaries.
               | 
               | Exactly because one gets issues with multiple compilers
               | is yet another prof why there isn't such thing as
               | official C ABI.
        
               | uecker wrote:
               | There official platform ABIs. There are not called C ABIs
               | though.
        
               | dzaima wrote:
               | It's certainly not impossible to write code that breaks,
               | or modify a library in an ABI-incompatible way, but ABI
               | stability, at least on Linux, does largely Just Work(tm).
               | A missing older shared library can be quite problematic,
               | but that's largely it.
               | 
               | And while, yes, there are times where ABIs are broken,
               | compiler versions affecting things would add a whole
               | another uncontrollable axis on top of that. I would quite
               | like to avoid a world of "this library can only be used
               | by code compiled with clang-25" as much as possible.
        
               | pjmlp wrote:
               | Works most of the time, probably, isn't really the
               | meaning of stable.
        
               | dzaima wrote:
               | Can't solve the issue of "you just don't have the library
               | (or a specific version thereof) installed".
               | 
               | But you can make it worse by changing "You must have
               | version X of library Y installed" to "You must have
               | version X of library Y compiled by compiler Z installed".
               | 
               | As-is, one can reasonably achieve ABI stability for their
               | C library if they want to; really all it takes is "don't
               | modify exposed types or signatures of exposed functions"
               | and "don't use intmax_t", and currently you can actually
               | break the latter.
        
               | pjmlp wrote:
               | You forgot about binaries compiled with incompatible
               | build or linker flags.
               | 
               | There is a reason why commercial software has several
               | combinations on their SDKs, for their libraries.
               | 
               | Release, debug, multi-threaded, with math emulation, with
               | fast math, specific CPU ISA with and without SIMD, and
               | these are only the most common ones.
        
               | dzaima wrote:
               | Release vs debug shouldn't affect ABI (or, at least, the
               | library author can decide whether it does; all it takes
               | is.. not having `#if DEBUG` in your exposed header files
               | changing types or function signatures).
               | 
               | Multi-threading doesn't affect ABI in any way at all.
               | 
               | fast-math doesn't affect ABI (maybe you mean the setting
               | of FTZ/DAZ? but modern clang & gcc don't do that either,
               | and anyway that breaks everything float in general, ABI
               | itself is one of the few float things that _don 't_
               | immediately break, really).
               | 
               | Presence or absence of SIMD extensions, hard-float, or
               | indeed any other form of extension, also doesn't modify
               | the ABI by itself.
               | 
               | There's a separate -mabi=... that controls hard-float &
               | co, but generally people don't touch that, and those that
               | do, well, have a clear indication of "abi" in "-mabi"
               | that tells them that they're touching something about
               | ABI. (SIMD does have some asterisks on passing around
               | SIMD types, but gcc does give a -Wpsabi warning when
               | using a natively-unsupported SIMD type in a function
               | signature; and this affects only very specialized low-
               | level libraries, and said functions should be marked via
               | an attribute to assume the presence of the necessary
               | intended extension anyway, and probably are header-only
               | and thus unaffected in the first place)
               | 
               | That said, it would probably make sense to have a way to
               | configure -mabi at the function level (if this doesn't
               | already exist).
               | 
               | General CPU ISA is one thing that does inescapably affect
               | ABI of compiled programs; but you can have a stable ABI
               | within one ISA. But yes, there's the wider requirement of
               | "You must have version X of library Y for ISA W
               | installed", but yet "You must have version X of library Y
               | for ISA W compiled by compiler Z installed" is still
               | worse.
        
               | pjmlp wrote:
               | Forgetting that C now has threading capabilities, and
               | some of it can get exposed via ABI?
               | 
               | C89 was long time ago.
               | 
               | We are not talking about what gcc, clang do in their
               | specific implementations, we are talking about C.
               | 
               | All those examples with compiler flags are exactly
               | workarounds around the one true ABI that C doesn't
               | actually have.
        
               | dzaima wrote:
               | I'm of course not saying that C has a single universal
               | ABI. But for any single platform (OS+ISA) where it is
               | possible and meaningful to have shared libraries in the
               | first place, there's a pretty clear single ABI to follow,
               | so the distinction is, for practical purposes, completely
               | meaningless. (ok windows does have some mess, but that's
               | windows for you, it'll achieve a mess in anything)
               | 
               | Still have no clue what you mean by threading; sure,
               | threads exist, even officially so in C11, but still just
               | in completely no way whatsoever affect ABI any more than
               | any other part of the standard library, i.e. "as stable
               | as the stdlib impl is".
        
               | imtringued wrote:
               | This is honestly what pisses me off about the whole ABI
               | thing. The ABI is defined by the OS, not the compiler.
               | The compiler just implements the ABI, but somehow
               | everyone lets their OS be defined by what a particular
               | C/C++ implementation does. This then leads to FFI
               | realistically only being possible by using a C/C++
               | compiler for interfacing, which defeats the point of an
               | OS wide ABI.
        
               | 1718627440 wrote:
               | I would consider the compiler to be part of the OS. You
               | can't use an OS, if there is no way to create programs
               | for it.
        
             | dvtkrlbs wrote:
             | I wish the additional proposak that would add Eust like
             | editions with the cpp moduled were expected. So sad it
             | didnt pass.
        
         | porridgeraisin wrote:
         | I don't like that statement (or that whole paragraph) one bit
         | either. My packages breaking between compiler releases is most
         | definitely a big fat bug.
         | 
         | If bounds checks are going to be added, cool, -fstl-bounds-
         | check. Or -fhardened like GCC. But not by default.
         | 
         | Working existing code is working existing code, I don't care if
         | it looks "suspicious" to some random guy's random compiler
         | feature.
        
           | convolvatron wrote:
           | I'm kind of with you on the coding-style warning flags. it
           | does really bother me that some opinionated person has
           | decided that the way I use parenthesis needs to be punished.
           | 
           | but I totally disagree with your second point. running code
           | often has real problems with race conditions, error handling,
           | unwanted memory reuse, out of bounds pointers, etc. if a new
           | version of the compiler can prove these things for me -
           | that's invaluable.
        
             | porridgeraisin wrote:
             | I too love those features. Just behind an option, so that
             | existing scripts etc still continue to work.
             | 
             | If many of those features are being added and the flags
             | might add up to become a pain, then even a group flag
             | -f-new-safety-features or whatever.
        
         | thefaux wrote:
         | For many, backwards compatibility == long term employment.
        
       | wyldfire wrote:
       | A really good accompaniment to this is Carruth's "C++, bounds
       | checking, performance, and compilers" [1]:
       | 
       | > ... strong belief that bounds checks couldn't realistically be
       | made cheap enough to enable by default. However, so far they are
       | looking very affordable. From the above post, 0.3% for bounds
       | checks in all the standard library types!
       | 
       | There's more to the hardening story than just bounds checks. But
       | it's a big part IMO.
       | 
       | [1] https://chandlerc.blog/posts/2024/11/story-time-bounds-
       | check...
        
         | tempodox wrote:
         | Even if bounds checks were only active in debug builds, that
         | would already be of high value.
        
           | pjmlp wrote:
           | That at least has been covered almost since C++ exists.
           | 
           | First in compiler vendors frameworks, pre C++98, afterwards
           | with build settings.
           | 
           | It is quite telling from existing community culture, that
           | some folks only read their compiler manuals when government
           | knocks on the door.
        
             | tester756 wrote:
             | >It is quite telling from existing community culture, that
             | some folks only read their compiler manuals when government
             | knocks on the door.
             | 
             | What do you want to say?
             | 
             | Is this bad? I think this is desired. Only in c or c++
             | world people act like understanding how compiler internals
             | work (often poorly) is desired
        
               | pjmlp wrote:
               | Where in the world reading a compiler manual means
               | understanding compiler internals?!?
               | 
               | One does not need to understand compiler internals to be
               | aware what build flags are used to turn bounds checking
               | on the standard library.
        
               | throw-qqqqq wrote:
               | I have never known a situation where LESS knowledge about
               | the compiler (flags, options, hell even internal
               | workings) have been better - on the contrary.
        
               | RossBencina wrote:
               | > Only in c or c++ world people act like understanding
               | how compiler internals work (often poorly) is desired
               | 
               | I think this says more about other parts of the developer
               | ecosystem than about C and C++. Understanding how the
               | compilers work (and how CPUs work) is fundamental to
               | software development.
        
           | delta_p_delta_x wrote:
           | > Even if bounds checks were only active in debug builds
           | 
           | In MSVC or Clang, when compiled against the Microsoft C++
           | STL, they already are. So,                 auto x =
           | std::vector{1, 2, 3, 4, 5};       std::println("{}", x[5]);
           | 
           | throws a very specific exception at runtime under debug mode.
           | 
           | In fact on Windows, even the _C_ runtime has debug checks.
           | That 's why there are four options to choose from when
           | linking against the modern UCRT:                 /MT (static
           | linking, no debug)       /MTd (static linking, with debug)
           | /MD (dynamic linking, no debug)       /MDd (dynamic linking,
           | with debug)
           | 
           | For what 'debug in the C runtime' entails, see this comment I
           | made a while ago[1]. As I mentioned, Unix-likes have no
           | equivalent; you get one libc, and if you want to statically
           | link against it, you have to release your own source code
           | because it's GPL. Not sure why people put up with it.
           | 
           | [1]: https://news.ycombinator.com/item?id=40361096
        
             | uecker wrote:
             | Not sure what you mean by "Not sure why people put up with
             | it". Glibc is licensed under LGPL, so you can distribute it
             | proprietary software even with static linking under some
             | conditions. And there also other alternatives.
        
             | fweimer wrote:
             | The GNU equivalent is -D_GLIBCXX_ASSERTIONS: https://gcc.gn
             | u.org/onlinedocs/libstdc++/manual/using_macros...
             | 
             | It mostly impacts templated code, so it's a compiler flag,
             | not a linker flag. Many distributions have been using this
             | flag to build C++ code for quite some time.
             | 
             | (And this concerns GNU libstdc++, not glibc, so different
             | licensing rules apply.)
        
               | delta_p_delta_x wrote:
               | A considerable number of C++ libraries are header-only,
               | so having this macro defined is the onus of the
               | libraries' consumers, rather than just the package
               | managers'.
               | 
               | Also, I mentioned no _libc_ equivalent, and that remains
               | true. Regardless of the libc distribution (glibc, musl,
               | BSD, macOS libSystem), none of them have a debug mode in
               | the vein that Windows UCRT does.
        
           | rwmj wrote:
           | This is why you should have an option (enabled routinely in
           | your CI) to run your tests under valgrind.
        
             | throw-qqqqq wrote:
             | I do this too, but valgrind is slow! In my experience, the
             | runtime increases a factor of 10/20/30x ..
        
               | rwmj wrote:
               | Yes, it's definitely slow. That's why we have an option
               | to run the same tests normally or with valgrind, and
               | mainly use valgrind runs in our CI system _after_ changes
               | have been committed.
        
               | thefaux wrote:
               | I can't help but notice the subtle ways in which git has
               | corrupted our understanding of the word commit.
        
       | another_twist wrote:
       | Maybe an easier way out is to add safe access instructions to
       | LLVM itself. Its an IR after all, it should be possible to do a 3
       | phase update - add instructions to the IR, update the
       | intermediate LLVM generator, then update the targetting backends.
        
       | ajb wrote:
       | In the long term, it might be best to disable the ability to
       | switch off checks using command line flags (which usually means,
       | the whole executable) and only allow it on individual functions.
       | Although the current mechanism to switch them off per function
       | isn't idiot proof either (you need to remember to "#pragma
       | diagnostic pop" after ) - we really need to be able to do it in a
       | function attribute.
        
       | rurban wrote:
       | They should also turn off the C11 Unicode identifier bugs with
       | -fhardened, which enabled homoglyph attacks. There is no plan for
       | C26 to fix this. No unicode identifiers without proper security
       | measures
        
         | rwmj wrote:
         | What is the threat profile here? I don't understand how this
         | would be exploited in the real world. Once you're linking to a
         | library, there are so many ways for the library to exploit your
         | main program (eg. by running arbitrary code in constructors).
        
           | rurban wrote:
           | https://github.com/rurban/libu8ident
           | 
           | Search for homoglyph attacks and the unicode security
           | guidelines for identifiers
        
             | rwmj wrote:
             | OK that is pretty interesting. For the TL;DR crowd, the
             | exploit was:                 if(environment!=ENV_PROD){
             | // bypass authZ checks in DEV         return true;       }
             | 
             | where the '!' is a Unicode homoglyph (U+1C3 "LATIN LETTER
             | ALVEOLAR CLICK") which obviously completely changes the
             | nature of the code.
             | 
             | I'll note that GCC gives a clear warning here ("suggest
             | parentheses around assignment used as truth value"), so as
             | always, turn on -Werror and take warnings seriously!
        
               | quuxplusone wrote:
               | The shown code is JavaScript; it wouldn't compile as C,
               | because "environment[alveolar-click]" was never declared,
               | and C requires declare-before-use. Does the advice to use
               | GCC -Werror still apply to JavaScript? (I'd guess no, but
               | I don't know for sure if I'm missing something.)
        
               | rwmj wrote:
               | It compiles fine as C (using gcc-15.1.1-2.fc43.x86_64).
               | Here's the complete program that I tested before posting
               | the comment above:                 int environment!;
               | int main()       {         if(environment!=0){
               | // bypass authZ checks in DEV           return 0;
               | }         return 1;       }
               | 
               | The output of GCC is:                 $ gcc -Wall test.c
               | test.c: In function 'main':       test.c:4:6: warning:
               | suggest parentheses around assignment used as truth value
               | [-Wparentheses]           4 |   if(environment!=0){
               | |      ^~~~~~~~~~~~
               | 
               | In a real exploit you'd have to be smarter about hiding
               | the variable declaration (maybe in a library or
               | something).
        
       ___________________________________________________________________
       (page generated 2025-08-02 23:02 UTC)