-
Notifications
You must be signed in to change notification settings - Fork 14.5k
Add pointer field protection feature. #133538
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: users/pcc/spr/main.add-pointer-field-protection-feature
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -365,6 +365,17 @@ class LangOptionsBase { | |
BKey | ||
}; | ||
|
||
enum class PointerFieldProtectionKind { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I like this being solely a global decision - it makes custom allocators much harder, and it makes it hard for allocators that have different policies, e.g struct ImportantStruct {
void *operator new(size_t) { /*tagged allocator*/ }
};
struct BoringStruct {
...
};
struct SomeOtherStruct {
ImportantStruct *important; // should be tagged pointer
BoringString *lessImportant; // should be a non tagged pointer
}; This would again require a struct attribute to indicate the pointer policy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that allowing this level of customization should be implemented as part of the separate opt-in solution (e.g. it may be a property of the qualifier). It is generally a reasonable assumption that operator new is the same for all types. In the unlikely case that it isn't, we aren't really doing anything wrong by using the malloc-derived decision here. It just means that we lose some entropy from the type or the pointer tag. |
||
/// Pointer field protection disabled | ||
None, | ||
/// Pointer field protection enabled, allocator does not tag heap | ||
/// allocations. | ||
Untagged, | ||
/// Pointer field protection enabled, allocator is expected to tag heap | ||
/// allocations. | ||
Tagged, | ||
}; | ||
|
||
enum class ThreadModelKind { | ||
/// POSIX Threads. | ||
POSIX, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -542,6 +542,7 @@ TYPE_TRAIT_2(__is_pointer_interconvertible_base_of, IsPointerInterconvertibleBas | |
|
||
// Clang-only C++ Type Traits | ||
TYPE_TRAIT_1(__is_trivially_relocatable, IsTriviallyRelocatable, KEYCXX) | ||
TYPE_TRAIT_1(__has_non_relocatable_fields, HasNonRelocatableFields, KEYCXX) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not like this -- there are existing functions for handling whether an object is trivially copying, relocatable, etc that should just be updated to correctly report the traits of impacted types. See the various Sema functions querying In fact a lot of the behavior you're wanting in this feature is essentially covered by the PointerAuthQualfier. It would perhaps be reasonable to generalize that to something akin to SecurePointerQualifier that could be either a PAQ or PFP qualifier. Then most of the current semantic queries made to support pointer auth could just be a single shared implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like it either. As mentioned in the main comment, I hope to get rid of this when the P2786 implementation is finalized. Sharing the qualifiers with PAuth ABI would be a good idea for the separate opt-in solution. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you misunderstand my intent (I tried to suggest in one the comments or the global comment): What I'm suggesting is that the qualifier would be implicitly added at the point of declaration, the overarching behavior you're after would be achieved that way. From your other comment about how you determine "C" strictness (just keying off standard layout) that might be challenging as it means you can't really finalize the types of fields until the struct definition is completed, and I don't know if there's anything else in C++ that behaves that way (@cor3ntin might have an idea). However I'm also not sure how reasonable it is to exclude standard layout types from this protection - what exactly is the rationale for that? (if the reason is a belief that substituting C++ libraries is fine but C libraries is not, I suspect that's not actually very sound, and silently ignoring some structs is similarly a hazard - it can result in "trivial" refactoring invisibly reducing security guarantees). Regarding P2786: the exact same issues apply to pointer authentication today - trying to solve them separately and outside of the existing triviality queries and operations is not the correct approach. I'm ok with the logic being distinct from pointer auth, though I would regret that as I believe the shared semantics warrant a shared implementation, but there is no reason for them to be distinct from any of the type queries. If you want the types to be trivially relocatable - which is not a requirement, it's a performance optimization - then ensure the existing queries and operations handle that correctly. Otherwise you run the risk of other code in clang using the existing trivial behavior queries, and expecting them to be correct, when they are not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No, I understood that part. Please refer to the main comment where I explained why that does not work.
Please refer to the main reply.
While it is true that a refactoring has the potential to remove protection from a pointer, the same is also true for adding protection to a pointer (usually code is modified by adding things to objects, and the rules of the language are such that adding something to an object can usually only make it non-standard-layout or non-trivially-copyable). See also "statistical perspective" from the main reply.
The results of the triviality queries are unchanged because PFP does not change the triviality from a language rule perspective. If a type was trivially copyable without PFP, it remains trivially copyable with PFP (i.e. the program is still allowed to memcpy the object, because the pointers are not address discriminated). If a type was trivially relocatable without PFP, it remains trivially relocatable with PFP (i.e. std::trivially_relocate still works because of changes that I will make (not uploaded yet) to __builtin_trivially_relocate in CGBuiltin.cpp). Divergence from the standard C++ triviality rules creates an incompatible dialect of C++, which I think we should avoid. The copying inhibitions I added in clang/lib/CodeGen are for cases such as those where the object is not trivially copyable according to the language rules but Clang figures out that it can memcpy the object anyway without causing an observable violation of the rules. In other words, Clang is already going outside of the existing triviality queries for optimization purposes, and I am adjusting the conditions under which it does so to account for PFP.
On trivial relocatability, see the main reply. Code in Clang that uses the standards-defined triviality queries will continue to work. Non-standard-defined queries such as isMemcpyEquivalentSpecialMember and isMemcpyableField need to be adjusted, as previously mentioned. It's possible that someone could add a new non-standard query that breaks PFP (and could also break PAuth ABI because it also uses address discriminated pointers) but that's a bug like any other, which our traditional solution for is to rely on testing to flush out any regressions. The patch series I posted for review has been tested as described in the RFC and the remaining test failures have been assessed to be likely to be bugs in the programs being compiled, so we have a reasonable level of confidence in the implementation. |
||
TYPE_TRAIT_1(__is_trivially_equality_comparable, IsTriviallyEqualityComparable, KEYCXX) | ||
TYPE_TRAIT_1(__is_bounded_array, IsBoundedArray, KEYCXX) | ||
TYPE_TRAIT_1(__is_unbounded_array, IsUnboundedArray, KEYCXX) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2957,6 +2957,12 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti" | |
NegFlag<SetFalse, [], [CC1Option], "Do not omit">, | ||
BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>; | ||
|
||
def experimental_pointer_field_protection_EQ : Joined<["-"], "fexperimental-pointer-field-protection=">, Group<f_Group>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, not super sure that I like this being a global compile time setting, but maybe it makes sense to be. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (See other reply.) |
||
Visibility<[ClangOption, CC1Option]>, | ||
Values<"none,untagged,tagged">, NormalizedValuesScope<"LangOptions::PointerFieldProtectionKind">, | ||
NormalizedValues<["None", "Untagged", "Tagged"]>, | ||
MarshallingInfoEnum<LangOpts<"PointerFieldProtection">, "None">; | ||
|
||
def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">, | ||
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>, | ||
HelpText<"C++ ABI to use. This will override the target C++ ABI.">; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This an ABI break so I don't think it can reasonably an on by default for all structs - we can already see annotations in libc++, and they would be needed on every single struct field.
We can imagine a new platform where this would be the platform ABI, but for every other case it is functionally unusable.
I would recommend attributes to permit opt in and opt out on a per-struct basis, and CLI flags to select the default behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are numerous circumstances where the C++ ABI is distinct from the platform ABI and is allowed to change. For example, most Chromium-based web browsers ship with a statically linked libc++, and the same is true for many Android apps and server-side software at Google. In the intended usage model, all of libc++ would be built with a
-fexperimental-pointer-field-protection=
flag consistent with the rest of the program.The libc++abi opt-outs that I added are only for opting out pointer fields of RTTI structs that are generated by the compiler. In principle, we could teach the compiler to sign those pointer fields which would let us remove the opt-outs, but there is a lower ROI for subjecting those fields to PFP because the RTTI structs are not typically dynamically allocated.
We may consider adding an attribute to allow opting in in the case where the flag is not passed. But I think we should do that in a followup. We would need to carefully consider how that would interact with the other aspects of the opt-in solution, such as the pointer qualifiers.