Strutures of function pointers are a good surface area for attacks. We
should therefore randomize them unless explicitly told not to.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
clang/lib/Sema/SemaDecl.cpp | ||
---|---|---|
18070 | Is this allowed by C standard? |
clang/lib/Sema/SemaDecl.cpp | ||
---|---|---|
18070 | Randomizing the layout of a structure of pointers or just randomizing a structure in general? There is a major restriction to randomizing structures. The programmer must use designated initializers when initializing rather than relying upon a default ordering. |
The main user of the feature is the Linux kernel. Does it still work with the change? If so, LG.
If not, some fixes may be needed which may possibly postpone the deployment of this Clang feature?
Last, it'd be good to know whether the GCC plugin randomize_layout_plugin.c does this similar thing.
While I agree with the security aspects of this in principle, it is not a conforming behavior in C and it runs significant risk of breaking existing code such that it introduces new security issues. This behavior needs to be opt-in (either through the existing attribute or through a feature flag of some kind).
It also points out that we really, really should have a diagnostic for structure initialization using positional initialization instead of designated initialization if the structure is randomized. (I think that case should be an error, perhaps with an exception if the initialization is empty {}.)
While I agree with the security aspects of this in principle, it is not a conforming behavior in C and it runs significant risk of breaking existing code such that it introduces new security issues.
I agree strongly. This could happily can do more harm than good ("the road to hell is paved with good intentions"). Please leave it opt-in or introduce "modes" for this feature like -frandomize-struct=func-pointers | -frandomize-struct=xxx | -frandomize-struct=all.
[Some context for this patch: the GCC plugin does this already. Our initial implementation of randstruct skipped that part.]
Could you explain a bit more why it's not considered conforming behavior in C? The entire feature is definitely unusual and introduces some caveats (the structure initialization being just one of them).
Here's an article on this feature and why randomizing structs of function pointers is Considered Good(tm): https://lwn.net/Articles/722293/
I'm okay with a flag, but will need to iterate on the name and such. There is the way to opt-out via the no_randomize_layout attribute. But that means this feature is on by default, which I think is where you concerns lie...
The Linux kernel already uses some options which deviate from standard C: __attribute__((__gnu_inline__)), -ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang, -fno-delete-null-pointer-checks
If the feature is to emulate GCC_PLUGIN_STRUCTLEAK in kernel security/Kconfig.hardening and the GCC plugin already does something similar, I am fine with this patch.
(My main concern is the lack of testing from the base patch.)
Does this not lead to non-deterministic/non-reproducible builds?
I do not understand why this feature must be inflicted onto everyone.
It seems that the feature fixes the randomization seed in the option -frandomize-layout-seed=, so it is deterministic. That said, it currently has a possible Windows vs non-Windows host difference, probably related to MSVC's std::mt19937.
My concern is related to the lack of -frandomize-layout-seed-file= testing in the base patch.
It's deterministic based on the randomization seed.
I do not understand why this feature must be inflicted onto everyone.
A developer must mark structures with the randomize_layout attribute and supply a randomization seed for the randstruct feature to be enabled. It's only when a randomization seed is supplied that it'll randomize structures consisting of only function pointers (which happens a lot in Linux). And you're able to opt-out of such a thing.
Given:
typedef void (*func_ptr)(void); struct S { func_ptr f1, f2, f3; }; void func(void); struct S s = {func, 0, func};
C requires that s.f1 and s.f3 point to func and s.f2 is a null pointer, but if you automatically randomize the layout of that structure as in this patch, this strictly conforming code will break.
However, I had forgotten that the base feature *requires* the user to pass a randomization seed via a flag in addition to requiring the attribute (thank you for bringing that back to my attention). Because this feature requires a feature flag to enable it, this behavior *is* a conforming extension (the user has to take an action to get the new behavior). That said, I'm still not convinced we want to do this automagically for users -- it's *really* easy for that flag to be set in a makefile somewhere and the user has no idea that their (non-designated) initialization is now a security vulnerability. If we had error diagnostics when the user is about to shoot their foot off, I'd be more comfortable with the automatic hardening behavior.
Because this feature requires a feature flag to enable it, this behavior *is* a conforming extension (the user has to take an action to get the new behavior).
Well, then we should have proper C test (ok if linux only or so) to show case behaviour of this patch with and without that feature flag.
Looking at this unit test, I thought that would get this feature with simple "clang source.c" invocation.
If we had error diagnostics when the user is about to shoot their foot off, I'd be more comfortable with the automatic hardening behavior.
This feature IMHO should be enabled with simple easy to understand flag, not silently and automatically do something which may break code or create surprises in debuggers.
We should definitely emit an error if a user is trying to use a default initializer for a structure that's up for randomization. It's something that affects the whole feature, not just structs of function pointers. Let me work on that. But otherwise are you okay with this patch?
A developer has to go out of their way to indicate that they want to use randstruct. The attribute has no effect unless they take an extra action to enable it (i.e. passing one of the seed flags). I agree with Aaron though that we need to error out if the developer uses default initialization for a randomized structure.
if the developer uses default initialization for a randomized structure.
Yeah, I suspect that @aaron.ballman @lebedev.ri @xbolva00 missed that this just extends the newly added opt-in -frandomize-layout-seed= (https://reviews.llvm.org/D121556). Why a developer would want the latter but not the former, when the existing GCC plugin being used by the kernel doesn't differentiate is somewhat a tangential feature request that doesn't need to be two distinct things at the moment for existing consumers.
We should definitely emit an error if a user is trying to use a default initializer for a structure that's up for randomization. It's something that affects the whole feature, not just structs of function pointers.
Agree though. Perhaps land that first, then revisit this?
Agreed.
Let me work on that. But otherwise are you okay with this patch?
Yes, I'm okay with the direction of this patch. However, because this still automatically randomizes some structure layouts once the user passes the flag, I think the diagnostic above should perhaps land first unless there's some strong reason not to. (To be honest, I probably should have insisted on it in the first patch, but this one feels significantly more dangerous because it involves function pointers specifically and is more of an "automatic" feature than the first patch.)
It was definitely an oversight on my part as well. This patch will be going in llvm 15, not 14, so there's probably not a huge rush to get this in first. I'll focus on the error and have a patch out soon-ish.
I tested this (with D123958), and it appears to be working as intended. These two fptrs are the first and second listed, and are happily randomized:
[ 0.000000] LSM: offsetof(struct security_hook_heads, ptrace_access_check): 816 [ 0.000000] LSM: offsetof(struct security_hook_heads, ptrace_traceme): 512
Is this allowed by C standard?