https://github.com/kumarak created https://github.com/llvm/llvm-project/pull/215629
Add a function attribute that requests function-boundary zeroization: at every supported exit, the function's stack frame and the caller-observable register state are cleared. It targets code that holds key material or plaintext in locals, where whatever the function leaves behind in its frame or in registers stays readable to whatever runs on that storage next. "On return" is shorthand for supported exits; clearing is also emitted on explicit exception cleanup paths where the target's exception model provides them. The closest existing attribute is zero_call_used_regs, which this follows for subject list and documentation layout. It is a separate attribute rather than a tenth mode of that one for two reasons: it covers stack memory as well as registers, and it is a single request rather than a choice of how much hardening to apply. The attribute takes no arguments and applies to functions only, with misuse diagnosed as an error rather than an ignored attribute. SimpleHandler covers Sema, so no handler is needed in SemaDeclAttr.cpp. This patch is parse, diagnose and document only, so the attribute currently has no effect on codegen. Lowering to IR function attributes is a follow-up, as are diagnostics for attribute combinations that would defeat the boundary (always_inline, musttail) and for targets that cannot honour the request. The documentation states the scope limits explicitly: the guarantee covers the annotated function's own frame and the machine state at its exits, is not inductive across the call graph, does not reach memory below the stack pointer, and does not survive leaving the frame by a route that never runs its exit code. Part of #1. Closes #2. >From 5a3ceb1006b6888989d5beb89f81be6cb38dde4f Mon Sep 17 00:00:00 2001 From: AkshayK <[email protected]> Date: Tue, 11 Aug 2026 12:15:42 -0400 Subject: [PATCH] [Clang] Add zeroize_on_return function attribute Add a function attribute that requests function-boundary zeroization: at every supported exit, the function's stack frame and the caller-observable register state are cleared. It targets code that holds key material or plaintext in locals, where whatever the function leaves behind in its frame or in registers stays readable to whatever runs on that storage next. "On return" is shorthand for supported exits; clearing is also emitted on explicit exception cleanup paths where the target's exception model provides them. The closest existing attribute is zero_call_used_regs, which this follows for subject list and documentation layout. It is a separate attribute rather than a tenth mode of that one for two reasons: it covers stack memory as well as registers, and it is a single request rather than a choice of how much hardening to apply. The attribute takes no arguments and applies to functions only, with misuse diagnosed as an error rather than an ignored attribute. SimpleHandler covers Sema, so no handler is needed in SemaDeclAttr.cpp. This patch is parse, diagnose and document only, so the attribute currently has no effect on codegen. Lowering to IR function attributes is a follow-up, as are diagnostics for attribute combinations that would defeat the boundary (always_inline, musttail) and for targets that cannot honour the request. The documentation states the scope limits explicitly: the guarantee covers the annotated function's own frame and the machine state at its exits, is not inductive across the call graph, does not reach memory below the stack pointer, and does not survive leaving the frame by a route that never runs its exit code. Part of #1. Closes #2. --- clang/docs/ReleaseNotes.md | 2 + clang/include/clang/Basic/Attr.td | 7 +++ clang/include/clang/Basic/AttrDocs.td | 62 +++++++++++++++++++ ...a-attribute-supported-attributes-list.test | 1 + clang/test/Sema/attr-zeroize-on-return.c | 40 ++++++++++++ clang/test/SemaCXX/attr-zeroize-on-return.cpp | 30 +++++++++ 6 files changed, 142 insertions(+) create mode 100644 clang/test/Sema/attr-zeroize-on-return.c create mode 100644 clang/test/SemaCXX/attr-zeroize-on-return.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index d9b9c92950c98..756274ddd3812 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -199,6 +199,8 @@ features cannot lower the translation-unit ABI level; - Clang now properly propagates attributes on class and variable templates to their redeclarations, which will result in redeclarations not interfering with diagnostics. (#GH209812) +- Added the `zeroize_on_return` function attribute, which requests that a function clear its stack frame and the caller-observable register state at every supported exit. It takes no arguments and applies to functions only. + ### Improvements to Clang's diagnostics - More consistent rendering of Unicode characters in diagnostic messages. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index d874ccb1b8653..e9174de7858e2 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3564,6 +3564,13 @@ def ZeroCallUsedRegs : InheritableAttr { let Documentation = [ZeroCallUsedRegsDocs]; } +def ZeroizeOnReturn : InheritableAttr { + let Spellings = [Clang<"zeroize_on_return">]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let Documentation = [ZeroizeOnReturnDocs]; + let SimpleHandler = 1; +} + def Pascal : DeclOrTypeAttr { let Spellings = [Clang<"pascal">, CustomKeyword<"__pascal">, CustomKeyword<"_pascal">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 3b469437d21e4..1e434586a5477 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -8824,6 +8824,68 @@ flag. }]; } +def ZeroizeOnReturnDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +This attribute, when attached to a function, requests that the compiler clear +the function's stack frame and the caller-observable register state before +control leaves the function. It is meant for code that holds key material, +plaintext, or other secrets in local variables, where whatever the function +leaves behind in its frame or in registers stays readable to the code that +runs on that storage next. + +The name is shorthand for *supported exits*, not only ``return`` statements. +Clearing is emitted on explicit cleanup paths taken during an exception +unwind as well, on targets whose exception model provides them. + +The attribute takes no arguments. It is a single request rather than a set of +modes because it states an obligation over machine state, not a choice of how +much hardening to apply. + +**What is cleared.** The stack memory the function used, including storage the +compiler introduced rather than the source named, such as spill slots and +alignment padding: those can hold copies of a local. And the registers a +caller can observe once the function returns. + +The function's declared return value is excluded. So are the registers the +calling convention requires the function to preserve, since the epilogue +restores those to the values the caller already had. + +**What is not covered.** The guarantee is about the annotated function's own +frame and the machine state at its exits. Specifically: + +- The property is not inductive across the call graph. A protected function + does not clear the frames of the functions it calls; a callee that handles a + secret has to carry the attribute itself. +- Stack memory below the stack pointer at the point of the clear, which is + where a callee's frame lived, is not reached. +- Leaving the frame by a route that never runs its exit code escapes the + guarantee entirely. ``longjmp`` past the frame is the common case, as is an + unwind for which the target's exception model offers no cleanup path. +- Copies the program itself made elsewhere, on the heap or in a caller's + frame, are neither tracked nor cleared. + +On a target that has not implemented the clearing this attribute requests, the +request has no effect and compilation still succeeds. + +.. code-block:: c + + [[clang::zeroize_on_return]] + void chacha20_block(uint32_t out[16], const uint32_t key[8], uint32_t ctr) { + uint32_t state[16]; // secret-bearing working state + /* ... rounds ... */ + memcpy(out, state, sizeof(state)); // declared output: not cleared + } // at each supported exit, the frame + // holding `state` and the + // caller-observable registers are + // cleared + +This attribute asks for more than ``zero_call_used_regs``, which requests only +that some subset of the call-used registers be zeroed and leaves the stack +frame alone. + }]; +} + def DocHLSLSemantics : DocumentationCategory<"HLSL Semantics"> { let Content = [{ A semantic is a string attached to a shader input or output that conveys diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index 8bca68e2119e7..3a013a25c8ecc 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -243,4 +243,5 @@ // CHECK-NEXT: XRayInstrument (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: XRayLogArgs (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: ZeroCallUsedRegs (SubjectMatchRule_function) +// CHECK-NEXT: ZeroizeOnReturn (SubjectMatchRule_function) // CHECK-NEXT: End of supported attributes. diff --git a/clang/test/Sema/attr-zeroize-on-return.c b/clang/test/Sema/attr-zeroize-on-return.c new file mode 100644 index 0000000000000..46213fedda76d --- /dev/null +++ b/clang/test/Sema/attr-zeroize-on-return.c @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c23 %s + +#if !__has_attribute(zeroize_on_return) +#error "zeroize_on_return is not available via __has_attribute" +#endif + +#if !__has_c_attribute(clang::zeroize_on_return) +#error "clang::zeroize_on_return is not available via __has_c_attribute" +#endif + +// Both spellings apply to a definition and to a prototype. +[[clang::zeroize_on_return]] void std_definition(void) {} +[[clang::zeroize_on_return]] void std_prototype(void); + +__attribute__((zeroize_on_return)) void gnu_definition(void) {} +void gnu_prototype(void) __attribute__((zeroize_on_return)); + +// The attribute is inheritable, so a prototype carrying it reaches the +// definition in the same translation unit. +[[clang::zeroize_on_return]] void redeclared(void); +void redeclared(void) {} + +// It is a single request, so it takes no arguments. +__attribute__((zeroize_on_return(2))) void arg_int(void) {} // expected-error {{'zeroize_on_return' attribute takes no arguments}} +__attribute__((zeroize_on_return("all"))) void arg_string(void) {} // expected-error {{'zeroize_on_return' attribute takes no arguments}} + +// Functions only, and misuse is an error rather than an ignored attribute. +__attribute__((zeroize_on_return)) int global_var; // expected-error {{'zeroize_on_return' attribute only applies to functions}} +[[clang::zeroize_on_return]] int std_global_var; // expected-error {{'clang::zeroize_on_return' attribute only applies to functions}} +struct __attribute__((zeroize_on_return)) S { int x; }; // expected-error {{'zeroize_on_return' attribute only applies to functions}} +typedef int my_int __attribute__((zeroize_on_return)); // expected-error {{'zeroize_on_return' attribute only applies to functions}} + +// The attribute appertains to the declared variable, not to the function type +// it points at. +__attribute__((zeroize_on_return)) void (*fp)(void); // expected-error {{'zeroize_on_return' attribute only applies to functions}} + +void local(void) { + __attribute__((zeroize_on_return)) int x; // expected-error {{'zeroize_on_return' attribute only applies to functions}} + (void)x; +} diff --git a/clang/test/SemaCXX/attr-zeroize-on-return.cpp b/clang/test/SemaCXX/attr-zeroize-on-return.cpp new file mode 100644 index 0000000000000..0140bd31a2252 --- /dev/null +++ b/clang/test/SemaCXX/attr-zeroize-on-return.cpp @@ -0,0 +1,30 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s + +#if !__has_cpp_attribute(clang::zeroize_on_return) +#error "clang::zeroize_on_return is not available via __has_cpp_attribute" +#endif + +[[clang::zeroize_on_return]] void free_function() {} +__attribute__((zeroize_on_return)) void gnu_free_function() {} + +struct S { + [[clang::zeroize_on_return]] void member(); + [[clang::zeroize_on_return]] static void static_member() {} + [[clang::zeroize_on_return]] int field; // expected-error {{'clang::zeroize_on_return' attribute only applies to functions}} +}; + +[[clang::zeroize_on_return]] void S::member() {} + +template <typename T> [[clang::zeroize_on_return]] void tmpl(T) {} +template void tmpl<int>(int); + +void lambda() { + auto l = []() __attribute__((zeroize_on_return)) {}; + l(); +} + +class [[clang::zeroize_on_return]] C {}; // expected-error {{'clang::zeroize_on_return' attribute only applies to functions}} + +namespace N { +[[clang::zeroize_on_return]] int variable; // expected-error {{'clang::zeroize_on_return' attribute only applies to functions}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
