Author: Marco Elver Date: 2025-10-08T20:59:24+02:00 New Revision: 774ffe5cce7392e6b4e29c83e725337727c2c994
URL: https://github.com/llvm/llvm-project/commit/774ffe5cce7392e6b4e29c83e725337727c2c994 DIFF: https://github.com/llvm/llvm-project/commit/774ffe5cce7392e6b4e29c83e725337727c2c994.diff LOG: [Clang] Wire up -fsanitize=alloc-token (#156839) Wire up the `-fsanitize=alloc-token` command-line option, hooking up the `AllocToken` pass -- it provides allocation tokens to compatible runtime allocators, enabling different heap organization strategies, e.g. hardening schemes based on heap partitioning. The instrumentation rewrites standard allocation calls into variants that accept an additional `size_t token_id` argument. For example, calls to `malloc(size)` become `__alloc_token_malloc(size, token_id)`, and a C++ `new MyType` expression will call `__alloc_token__Znwm(size, token_id)`. Currently untyped allocation calls do not yet have `!alloc_token` metadata, and therefore receive the fallback token only. This will be fixed in subsequent changes through best-effort type-inference. One benefit of the instrumentation approach is that it can be applied transparently to large codebases, and scales in deployment as other sanitizers. Similarly to other sanitizers, instrumentation can selectively be controlled using `__attribute__((no_sanitize("alloc-token")))`. Support for sanitizer ignorelists to disable instrumentation for specific functions or source files is implemented. See clang/docs/AllocToken.rst for more usage instructions. Link: https://discourse.llvm.org/t/rfc-a-framework-for-allocator-partitioning-hints/87434 --- This change is part of the following series: 1. https://github.com/llvm/llvm-project/pull/160131 2. https://github.com/llvm/llvm-project/pull/156838 3. https://github.com/llvm/llvm-project/pull/162098 4. https://github.com/llvm/llvm-project/pull/162099 5. https://github.com/llvm/llvm-project/pull/156839 6. https://github.com/llvm/llvm-project/pull/156840 7. https://github.com/llvm/llvm-project/pull/156841 8. https://github.com/llvm/llvm-project/pull/156842 Added: clang/docs/AllocToken.rst clang/test/CodeGen/alloc-token-ignorelist.c clang/test/CodeGen/alloc-token-lower.c clang/test/CodeGen/alloc-token.c clang/test/CodeGenCXX/alloc-token.cpp clang/test/Driver/fsanitize-alloc-token.c clang/test/Preprocessor/alloc_token.cpp Modified: clang/docs/ReleaseNotes.rst clang/docs/UsersManual.rst clang/docs/index.rst clang/include/clang/Basic/CodeGenOptions.def clang/include/clang/Basic/CodeGenOptions.h clang/include/clang/Driver/Options.td clang/include/clang/Driver/SanitizerArgs.h clang/lib/CodeGen/BackendUtil.cpp clang/lib/Driver/SanitizerArgs.cpp clang/lib/Driver/ToolChain.cpp clang/lib/Driver/ToolChains/Clang.cpp clang/lib/Frontend/CompilerInvocation.cpp clang/lib/Frontend/InitPreprocessor.cpp Removed: ################################################################################ diff --git a/clang/docs/AllocToken.rst b/clang/docs/AllocToken.rst new file mode 100644 index 0000000000000..fb5c060bed939 --- /dev/null +++ b/clang/docs/AllocToken.rst @@ -0,0 +1,173 @@ +================= +Allocation Tokens +================= + +.. contents:: + :local: + +Introduction +============ + +Clang provides support for allocation tokens to enable allocator-level heap +organization strategies. Clang assigns mode-dependent token IDs to allocation +calls; the runtime behavior depends entirely on the implementation of a +compatible memory allocator. + +Possible allocator strategies include: + +* **Security Hardening**: Placing allocations into separate, isolated heap + partitions. For example, separating pointer-containing types from raw data + can mitigate exploits that rely on overflowing a primitive buffer to corrupt + object metadata. + +* **Memory Layout Optimization**: Grouping related allocations to improve data + locality and cache utilization. + +* **Custom Allocation Policies**: Applying diff erent management strategies to + diff erent partitions. + +Token Assignment Mode +===================== + +The default mode to calculate tokens is: + +* ``typehash``: This mode assigns a token ID based on the hash of the allocated + type's name. + +Other token ID assignment modes are supported, but they may be subject to +change or removal. These may (experimentally) be selected with ``-mllvm +-alloc-token-mode=<mode>``: + +* ``random``: This mode assigns a statically-determined random token ID to each + allocation site. + +* ``increment``: This mode assigns a simple, incrementally increasing token ID + to each allocation site. + +Allocation Token Instrumentation +================================ + +To enable instrumentation of allocation functions, code can be compiled with +the ``-fsanitize=alloc-token`` flag: + +.. code-block:: console + + % clang++ -fsanitize=alloc-token example.cc + +The instrumentation transforms allocation calls to include a token ID. For +example: + +.. code-block:: c + + // Original: + ptr = malloc(size); + + // Instrumented: + ptr = __alloc_token_malloc(size, <token id>); + +The following command-line options affect generated token IDs: + +* ``-falloc-token-max=<N>`` + Configures the maximum number of tokens. No max by default (tokens bounded + by ``SIZE_MAX``). + + .. code-block:: console + + % clang++ -fsanitize=alloc-token -falloc-token-max=512 example.cc + +Runtime Interface +----------------- + +A compatible runtime must be provided that implements the token-enabled +allocation functions. The instrumentation generates calls to functions that +take a final ``size_t token_id`` argument. + +.. code-block:: c + + // C standard library functions + void *__alloc_token_malloc(size_t size, size_t token_id); + void *__alloc_token_calloc(size_t count, size_t size, size_t token_id); + void *__alloc_token_realloc(void *ptr, size_t size, size_t token_id); + // ... + + // C++ operators (mangled names) + // operator new(size_t, size_t) + void *__alloc_token__Znwm(size_t size, size_t token_id); + // operator new[](size_t, size_t) + void *__alloc_token__Znam(size_t size, size_t token_id); + // ... other variants like nothrow, etc., are also instrumented. + +Fast ABI +-------- + +An alternative ABI can be enabled with ``-fsanitize-alloc-token-fast-abi``, +which encodes the token ID hint in the allocation function name. + +.. code-block:: c + + void *__alloc_token_0_malloc(size_t size); + void *__alloc_token_1_malloc(size_t size); + void *__alloc_token_2_malloc(size_t size); + ... + void *__alloc_token_0_Znwm(size_t size); + void *__alloc_token_1_Znwm(size_t size); + void *__alloc_token_2_Znwm(size_t size); + ... + +This ABI provides a more efficient alternative where +``-falloc-token-max`` is small. + +Disabling Instrumentation +------------------------- + +To exclude specific functions from instrumentation, you can use the +``no_sanitize("alloc-token")`` attribute: + +.. code-block:: c + + __attribute__((no_sanitize("alloc-token"))) + void* custom_allocator(size_t size) { + return malloc(size); // Uses original malloc + } + +Note: Independent of any given allocator support, the instrumentation aims to +remain performance neutral. As such, ``no_sanitize("alloc-token")`` +functions may be inlined into instrumented functions and vice-versa. If +correctness is affected, such functions should explicitly be marked +``noinline``. + +The ``__attribute__((disable_sanitizer_instrumentation))`` is also supported to +disable this and other sanitizer instrumentations. + +Suppressions File (Ignorelist) +------------------------------ + +AllocToken respects the ``src`` and ``fun`` entity types in the +:doc:`SanitizerSpecialCaseList`, which can be used to omit specified source +files or functions from instrumentation. + +.. code-block:: bash + + [alloc-token] + # Exclude specific source files + src:third_party/allocator.c + # Exclude function name patterns + fun:*custom_malloc* + fun:LowLevel::* + +.. code-block:: console + + % clang++ -fsanitize=alloc-token -fsanitize-ignorelist=my_ignorelist.txt example.cc + +Conditional Compilation with ``__SANITIZE_ALLOC_TOKEN__`` +----------------------------------------------------------- + +In some cases, one may need to execute diff erent code depending on whether +AllocToken instrumentation is enabled. The ``__SANITIZE_ALLOC_TOKEN__`` macro +can be used for this purpose. + +.. code-block:: c + + #ifdef __SANITIZE_ALLOC_TOKEN__ + // Code specific to -fsanitize=alloc-token builds + #endif diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 5e9a71e1e74d6..9a0d69c6c1b0e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -257,10 +257,16 @@ Non-comprehensive list of changes in this release - Fixed a crash when the second argument to ``__builtin_assume_aligned`` was not constant (#GH161314) +- Introduce support for :doc:`allocation tokens <AllocToken>` to enable + allocator-level heap organization strategies. A feature to instrument all + allocation functions with a token ID can be enabled via the + ``-fsanitize=alloc-token`` flag. + New Compiler Flags ------------------ - New option ``-fno-sanitize-debug-trap-reasons`` added to disable emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``). - New option ``-fsanitize-debug-trap-reasons=`` added to control emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``). +- New options for enabling allocation token instrumentation: ``-fsanitize=alloc-token``, ``-falloc-token-max=``, ``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``. Lanai Support diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst index a8bbf146431ea..12c2ada062625 100644 --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -2155,13 +2155,11 @@ are listed below. .. option:: -f[no-]sanitize=check1,check2,... - Turn on runtime checks for various forms of undefined or suspicious - behavior. + Turn on runtime checks or mitigations for various forms of undefined or + suspicious behavior. These are disabled by default. - This option controls whether Clang adds runtime checks for various - forms of undefined or suspicious behavior, and is disabled by - default. If a check fails, a diagnostic message is produced at - runtime explaining the problem. The main checks are: + The following options enable runtime checks for various forms of undefined + or suspicious behavior: - .. _opt_fsanitize_address: @@ -2195,6 +2193,14 @@ are listed below. - ``-fsanitize=realtime``: :doc:`RealtimeSanitizer`, a real-time safety checker. + The following options enable runtime mitigations for various forms of + undefined or suspicious behavior: + + - ``-fsanitize=alloc-token``: Enables :doc:`allocation tokens <AllocToken>` + for allocator-level heap organization strategies, such as for security + hardening. It passes type-derived token IDs to a compatible memory + allocator. Requires linking against a token-aware allocator. + There are more fine-grained checks available: see the :ref:`list <ubsan-checks>` of specific kinds of undefined behavior that can be detected and the :ref:`list <cfi-schemes>` diff --git a/clang/docs/index.rst b/clang/docs/index.rst index e238518cff38e..272ae54bd9278 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -40,6 +40,7 @@ Using Clang as a Compiler SanitizerCoverage SanitizerStats SanitizerSpecialCaseList + AllocToken BoundsSafety BoundsSafetyAdoptionGuide BoundsSafetyImplPlans diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 872f73ebf3810..d924cb4f14c2c 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -306,6 +306,8 @@ CODEGENOPT(SanitizeBinaryMetadataCovered, 1, 0, Benign) ///< Emit PCs for covere CODEGENOPT(SanitizeBinaryMetadataAtomics, 1, 0, Benign) ///< Emit PCs for atomic operations. CODEGENOPT(SanitizeBinaryMetadataUAR, 1, 0, Benign) ///< Emit PCs for start of functions ///< that are subject for use-after-return checking. +CODEGENOPT(SanitizeAllocTokenFastABI, 1, 0, Benign) ///< Use the AllocToken fast ABI. +CODEGENOPT(SanitizeAllocTokenExtended, 1, 0, Benign) ///< Extend coverage to custom allocation functions. CODEGENOPT(SanitizeStats , 1, 0, Benign) ///< Collect statistics for sanitizers. ENUM_CODEGENOPT(SanitizeDebugTrapReasons, SanitizeDebugTrapReasonKind, 2, SanitizeDebugTrapReasonKind::Detailed, Benign) ///< Control how "trap reasons" are emitted in debug info CODEGENOPT(SimplifyLibCalls , 1, 1, Benign) ///< Set when -fbuiltin is enabled. diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index 5d5cf250b56b9..cae06c3c9495a 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -447,6 +447,10 @@ class CodeGenOptions : public CodeGenOptionsBase { std::optional<double> AllowRuntimeCheckSkipHotCutoff; + /// Maximum number of allocation tokens (0 = no max), nullopt if none set (use + /// pass default). + std::optional<uint64_t> AllocTokenMax; + /// List of backend command-line options for -fembed-bitcode. std::vector<uint8_t> CmdArgs; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index c8e96e125733c..ec38231f906eb 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2731,8 +2731,25 @@ def fsanitize_skip_hot_cutoff_EQ "(0.0 [default] = skip none; 1.0 = skip all). " "Argument format: <sanitizer1>=<value1>,<sanitizer2>=<value2>,...">; +defm sanitize_alloc_token_fast_abi : BoolOption<"f", "sanitize-alloc-token-fast-abi", + CodeGenOpts<"SanitizeAllocTokenFastABI">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption], "Use the AllocToken fast ABI">, + NegFlag<SetFalse, [], [ClangOption], "Use the default AllocToken ABI">>, + Group<f_clang_Group>; +defm sanitize_alloc_token_extended : BoolOption<"f", "sanitize-alloc-token-extended", + CodeGenOpts<"SanitizeAllocTokenExtended">, DefaultFalse, + PosFlag<SetTrue, [], [ClangOption], "Enable">, + NegFlag<SetFalse, [], [ClangOption], "Disable">, + BothFlags<[], [ClangOption], " extended coverage to custom allocation functions">>, + Group<f_clang_Group>; + } // end -f[no-]sanitize* flags +def falloc_token_max_EQ : Joined<["-"], "falloc-token-max=">, + Group<f_Group>, Visibility<[ClangOption, CC1Option]>, + MetaVarName<"<N>">, + HelpText<"Limit to maximum N allocation tokens (0 = no max)">; + def fallow_runtime_check_skip_hot_cutoff_EQ : Joined<["-"], "fallow-runtime-check-skip-hot-cutoff=">, Group<f_clang_Group>, diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 2b72268c8606c..eea7897e96afd 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -75,6 +75,8 @@ class SanitizerArgs { llvm::AsanDetectStackUseAfterReturnMode::Invalid; std::string MemtagMode; + bool AllocTokenFastABI = false; + bool AllocTokenExtended = false; public: /// Parses the sanitizer arguments from an argument list. diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 64f1917739e12..2d959827d6972 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -60,11 +60,13 @@ #include "llvm/TargetParser/Triple.h" #include "llvm/Transforms/HipStdPar/HipStdPar.h" #include "llvm/Transforms/IPO/EmbedBitcodePass.h" +#include "llvm/Transforms/IPO/InferFunctionAttrs.h" #include "llvm/Transforms/IPO/LowerTypeTests.h" #include "llvm/Transforms/IPO/ThinLTOBitcodeWriter.h" #include "llvm/Transforms/InstCombine/InstCombine.h" #include "llvm/Transforms/Instrumentation/AddressSanitizer.h" #include "llvm/Transforms/Instrumentation/AddressSanitizerOptions.h" +#include "llvm/Transforms/Instrumentation/AllocToken.h" #include "llvm/Transforms/Instrumentation/BoundsChecking.h" #include "llvm/Transforms/Instrumentation/DataFlowSanitizer.h" #include "llvm/Transforms/Instrumentation/GCOVProfiler.h" @@ -232,6 +234,14 @@ class EmitAssemblyHelper { }; } // namespace +static AllocTokenOptions getAllocTokenOptions(const CodeGenOptions &CGOpts) { + AllocTokenOptions Opts; + Opts.MaxTokens = CGOpts.AllocTokenMax; + Opts.Extended = CGOpts.SanitizeAllocTokenExtended; + Opts.FastABI = CGOpts.SanitizeAllocTokenFastABI; + return Opts; +} + static SanitizerCoverageOptions getSancovOptsFromCGOpts(const CodeGenOptions &CGOpts) { SanitizerCoverageOptions Opts; @@ -789,6 +799,16 @@ static void addSanitizers(const Triple &TargetTriple, MPM.addPass(DataFlowSanitizerPass(LangOpts.NoSanitizeFiles, PB.getVirtualFileSystemPtr())); } + + if (LangOpts.Sanitize.has(SanitizerKind::AllocToken)) { + if (Level == OptimizationLevel::O0) { + // The default pass builder only infers libcall function attrs when + // optimizing, so we insert it here because we need it for accurate + // memory allocation function detection. + MPM.addPass(InferFunctionAttrsPass()); + } + MPM.addPass(AllocTokenPass(getAllocTokenOptions(CodeGenOpts))); + } }; if (ClSanitizeOnOptimizerEarlyEP) { PB.registerOptimizerEarlyEPCallback( diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index 7ce1afe6f2e6a..5dd48f53b9069 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -61,8 +61,9 @@ static const SanitizerMask RecoverableByDefault = SanitizerKind::ImplicitConversion | SanitizerKind::Nullability | SanitizerKind::FloatDivideByZero | SanitizerKind::ObjCCast | SanitizerKind::Vptr; -static const SanitizerMask Unrecoverable = - SanitizerKind::Unreachable | SanitizerKind::Return; +static const SanitizerMask Unrecoverable = SanitizerKind::Unreachable | + SanitizerKind::Return | + SanitizerKind::AllocToken; static const SanitizerMask AlwaysRecoverable = SanitizerKind::KernelAddress | SanitizerKind::KernelHWAddress | SanitizerKind::KCFI; @@ -84,7 +85,8 @@ static const SanitizerMask CFIClasses = static const SanitizerMask CompatibleWithMinimalRuntime = TrappingSupported | SanitizerKind::Scudo | SanitizerKind::ShadowCallStack | SanitizerKind::MemtagStack | SanitizerKind::MemtagHeap | - SanitizerKind::MemtagGlobals | SanitizerKind::KCFI; + SanitizerKind::MemtagGlobals | SanitizerKind::KCFI | + SanitizerKind::AllocToken; enum CoverageFeature { CoverageFunc = 1 << 0, @@ -203,6 +205,7 @@ static void addDefaultIgnorelists(const Driver &D, SanitizerMask Kinds, {"tysan_blacklist.txt", SanitizerKind::Type}, {"dfsan_abilist.txt", SanitizerKind::DataFlow}, {"cfi_ignorelist.txt", SanitizerKind::CFI}, + {"alloc_token_ignorelist.txt", SanitizerKind::AllocToken}, {"ubsan_ignorelist.txt", SanitizerKind::Undefined | SanitizerKind::Vptr | SanitizerKind::Integer | SanitizerKind::Nullability | @@ -650,7 +653,12 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, std::make_pair(SanitizerKind::KCFI, SanitizerKind::Function), std::make_pair(SanitizerKind::Realtime, SanitizerKind::Address | SanitizerKind::Thread | - SanitizerKind::Undefined | SanitizerKind::Memory)}; + SanitizerKind::Undefined | SanitizerKind::Memory), + std::make_pair(SanitizerKind::AllocToken, + SanitizerKind::Address | SanitizerKind::HWAddress | + SanitizerKind::KernelAddress | + SanitizerKind::KernelHWAddress | + SanitizerKind::Memory)}; // Enable toolchain specific default sanitizers if not explicitly disabled. SanitizerMask Default = TC.getDefaultSanitizers() & ~AllRemove; @@ -1159,6 +1167,15 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, !TC.getTriple().isAndroid() && !TC.getTriple().isOSFuchsia(); } + if (AllAddedKinds & SanitizerKind::AllocToken) { + AllocTokenFastABI = Args.hasFlag( + options::OPT_fsanitize_alloc_token_fast_abi, + options::OPT_fno_sanitize_alloc_token_fast_abi, AllocTokenFastABI); + AllocTokenExtended = Args.hasFlag( + options::OPT_fsanitize_alloc_token_extended, + options::OPT_fno_sanitize_alloc_token_extended, AllocTokenExtended); + } + LinkRuntimes = Args.hasFlag(options::OPT_fsanitize_link_runtime, options::OPT_fno_sanitize_link_runtime, !Args.hasArg(options::OPT_r)); @@ -1527,6 +1544,12 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, Sanitizers.has(SanitizerKind::Address)) CmdArgs.push_back("-fno-assume-sane-operator-new"); + // Flags for -fsanitize=alloc-token. + if (AllocTokenFastABI) + CmdArgs.push_back("-fsanitize-alloc-token-fast-abi"); + if (AllocTokenExtended) + CmdArgs.push_back("-fsanitize-alloc-token-extended"); + // libFuzzer wants to intercept calls to certain library functions, so the // following -fno-builtin-* flags force the compiler to emit interposable // libcalls to these functions. Other sanitizers effectively do the same thing diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp index a9041d26c7ba4..3d5cac62afe01 100644 --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -1623,7 +1623,8 @@ SanitizerMask ToolChain::getSupportedSanitizers() const { SanitizerKind::CFICastStrict | SanitizerKind::FloatDivideByZero | SanitizerKind::KCFI | SanitizerKind::UnsignedIntegerOverflow | SanitizerKind::UnsignedShiftBase | SanitizerKind::ImplicitConversion | - SanitizerKind::Nullability | SanitizerKind::LocalBounds; + SanitizerKind::Nullability | SanitizerKind::LocalBounds | + SanitizerKind::AllocToken; if (getTriple().getArch() == llvm::Triple::x86 || getTriple().getArch() == llvm::Triple::x86_64 || getTriple().getArch() == llvm::Triple::arm || diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 107b9ffd439a3..d326a81feb762 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7618,6 +7618,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, // features enabled through -Xclang -target-feature flags. SanitizeArgs.addArgs(TC, Args, CmdArgs, InputType); + Args.AddLastArg(CmdArgs, options::OPT_falloc_token_max_EQ); + #if CLANG_ENABLE_CIR // Forward -mmlir arguments to to the MLIR option parser. for (const Arg *A : Args.filtered(options::OPT_mmlir)) { diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 50fd50aaba38d..292adce8180bc 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1833,6 +1833,10 @@ void CompilerInvocationBase::GenerateCodeGenArgs(const CodeGenOptions &Opts, serializeSanitizerKinds(Opts.SanitizeAnnotateDebugInfo)) GenerateArg(Consumer, OPT_fsanitize_annotate_debug_info_EQ, Sanitizer); + if (Opts.AllocTokenMax) + GenerateArg(Consumer, OPT_falloc_token_max_EQ, + std::to_string(*Opts.AllocTokenMax)); + if (!Opts.EmitVersionIdentMetadata) GenerateArg(Consumer, OPT_Qn); @@ -2346,6 +2350,15 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, } } + if (const auto *Arg = Args.getLastArg(options::OPT_falloc_token_max_EQ)) { + StringRef S = Arg->getValue(); + uint64_t Value = 0; + if (S.getAsInteger(0, Value)) + Diags.Report(diag::err_drv_invalid_value) << Arg->getAsString(Args) << S; + else + Opts.AllocTokenMax = Value; + } + Opts.EmitVersionIdentMetadata = Args.hasFlag(OPT_Qy, OPT_Qn, true); if (!LangOpts->CUDAIsDevice) diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 877ab02850667..b899fb9c6494a 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -1530,6 +1530,8 @@ static void InitializePredefinedMacros(const TargetInfo &TI, Builder.defineMacro("__SANITIZE_HWADDRESS__"); if (LangOpts.Sanitize.has(SanitizerKind::Thread)) Builder.defineMacro("__SANITIZE_THREAD__"); + if (LangOpts.Sanitize.has(SanitizerKind::AllocToken)) + Builder.defineMacro("__SANITIZE_ALLOC_TOKEN__"); // Target OS macro definitions. if (PPOpts.DefineTargetOSMacros) { diff --git a/clang/test/CodeGen/alloc-token-ignorelist.c b/clang/test/CodeGen/alloc-token-ignorelist.c new file mode 100644 index 0000000000000..954e6e5964773 --- /dev/null +++ b/clang/test/CodeGen/alloc-token-ignorelist.c @@ -0,0 +1,27 @@ +// Test AllocToken respects ignorelist for functions and files. +// +// RUN: %clang_cc1 -fsanitize=alloc-token -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-ALLOW +// +// RUN: echo "fun:excluded_by_all" > %t.func.ignorelist +// RUN: %clang_cc1 -fsanitize=alloc-token -fsanitize-ignorelist=%t.func.ignorelist -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-FUN +// +// RUN: echo "src:%s" | sed -e 's/\\/\\\\/g' > %t.file.ignorelist +// RUN: %clang_cc1 -fsanitize=alloc-token -fsanitize-ignorelist=%t.file.ignorelist -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-SRC + +extern void* malloc(unsigned long size); + +// CHECK-LABEL: define{{.*}} @excluded_by_all( +void* excluded_by_all(unsigned long size) { + // CHECK-ALLOW: call ptr @__alloc_token_malloc( + // CHECK-FUN: call ptr @malloc( + // CHECK-SRC: call ptr @malloc( + return malloc(size); +} + +// CHECK-LABEL: define{{.*}} @excluded_by_src( +void* excluded_by_src(unsigned long size) { + // CHECK-ALLOW: call ptr @__alloc_token_malloc( + // CHECK-FUN: call ptr @__alloc_token_malloc( + // CHECK-SRC: call ptr @malloc( + return malloc(size); +} diff --git a/clang/test/CodeGen/alloc-token-lower.c b/clang/test/CodeGen/alloc-token-lower.c new file mode 100644 index 0000000000000..75197bb3dbd44 --- /dev/null +++ b/clang/test/CodeGen/alloc-token-lower.c @@ -0,0 +1,22 @@ +// Test optimization pipelines do not interfere with AllocToken lowering, and we +// pass on function attributes correctly. +// +// RUN: %clang_cc1 -fsanitize=alloc-token -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -O1 -fsanitize=alloc-token -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -O2 -fsanitize=alloc-token -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s + +typedef __typeof(sizeof(int)) size_t; + +void *malloc(size_t size); + +// CHECK-LABEL: @test_malloc( +// CHECK: call{{.*}} ptr @__alloc_token_malloc(i64 noundef 4, i64 0) +void *test_malloc() { + return malloc(sizeof(int)); +} + +// CHECK-LABEL: @no_sanitize_malloc( +// CHECK: call{{.*}} ptr @malloc(i64 noundef 4) +void *no_sanitize_malloc(size_t size) __attribute__((no_sanitize("alloc-token"))) { + return malloc(sizeof(int)); +} diff --git a/clang/test/CodeGen/alloc-token.c b/clang/test/CodeGen/alloc-token.c new file mode 100644 index 0000000000000..d1160adc060ba --- /dev/null +++ b/clang/test/CodeGen/alloc-token.c @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -fsanitize=alloc-token -triple x86_64-linux-gnu -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s + +typedef __typeof(sizeof(int)) size_t; + +void *aligned_alloc(size_t alignment, size_t size); +void *malloc(size_t size); +void *calloc(size_t num, size_t size); +void *realloc(void *ptr, size_t size); +void *reallocarray(void *ptr, size_t nmemb, size_t size); +void *memalign(size_t alignment, size_t size); +void *valloc(size_t size); +void *pvalloc(size_t size); +int posix_memalign(void **memptr, size_t alignment, size_t size); + +void *sink; + +// CHECK-LABEL: define dso_local void @test_malloc_like( +// CHECK: call ptr @malloc(i64 noundef 4) +// CHECK: call ptr @calloc(i64 noundef 3, i64 noundef 4) +// CHECK: call ptr @realloc(ptr noundef {{.*}}, i64 noundef 8) +// CHECK: call ptr @reallocarray(ptr noundef {{.*}}, i64 noundef 5, i64 noundef 8) +// CHECK: call align 128 ptr @aligned_alloc(i64 noundef 128, i64 noundef 1024) +// CHECK: call align 16 ptr @memalign(i64 noundef 16, i64 noundef 256) +// CHECK: call ptr @valloc(i64 noundef 4096) +// CHECK: call ptr @pvalloc(i64 noundef 8192) +// CHECK: call i32 @posix_memalign(ptr noundef @sink, i64 noundef 64, i64 noundef 4) +void test_malloc_like() { + sink = malloc(sizeof(int)); + sink = calloc(3, sizeof(int)); + sink = realloc(sink, sizeof(long)); + sink = reallocarray(sink, 5, sizeof(long)); + sink = aligned_alloc(128, 1024); + sink = memalign(16, 256); + sink = valloc(4096); + sink = pvalloc(8192); + posix_memalign(&sink, 64, sizeof(int)); +} diff --git a/clang/test/CodeGenCXX/alloc-token.cpp b/clang/test/CodeGenCXX/alloc-token.cpp new file mode 100644 index 0000000000000..52bad9c54fb3b --- /dev/null +++ b/clang/test/CodeGenCXX/alloc-token.cpp @@ -0,0 +1,141 @@ +// RUN: %clang_cc1 -fsanitize=alloc-token -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s + +#include "../Analysis/Inputs/system-header-simulator-cxx.h" +extern "C" { +void *aligned_alloc(size_t alignment, size_t size); +void *malloc(size_t size); +void *calloc(size_t num, size_t size); +void *realloc(void *ptr, size_t size); +void *reallocarray(void *ptr, size_t nmemb, size_t size); +void *memalign(size_t alignment, size_t size); +void *valloc(size_t size); +void *pvalloc(size_t size); +int posix_memalign(void **memptr, size_t alignment, size_t size); + +struct __sized_ptr_t { + void *p; + size_t n; +}; +enum class __hot_cold_t : uint8_t; +__sized_ptr_t __size_returning_new(size_t size); +__sized_ptr_t __size_returning_new_hot_cold(size_t, __hot_cold_t); +__sized_ptr_t __size_returning_new_aligned(size_t, std::align_val_t); +__sized_ptr_t __size_returning_new_aligned_hot_cold(size_t, std::align_val_t, __hot_cold_t); +} + +void *sink; // prevent optimizations from removing the calls + +// CHECK-LABEL: define dso_local void @_Z16test_malloc_likev( +// CHECK: call ptr @malloc(i64 noundef 4) +// CHECK: call ptr @calloc(i64 noundef 3, i64 noundef 4) +// CHECK: call ptr @realloc(ptr noundef {{.*}}, i64 noundef 8) +// CHECK: call ptr @reallocarray(ptr noundef {{.*}}, i64 noundef 5, i64 noundef 8) +// CHECK: call align 128 ptr @aligned_alloc(i64 noundef 128, i64 noundef 1024) +// CHECK: call ptr @memalign(i64 noundef 16, i64 noundef 256) +// CHECK: call ptr @valloc(i64 noundef 4096) +// CHECK: call ptr @pvalloc(i64 noundef 8192) +// CHECK: call i32 @posix_memalign(ptr noundef @sink, i64 noundef 64, i64 noundef 4) +void test_malloc_like() { + sink = malloc(sizeof(int)); + sink = calloc(3, sizeof(int)); + sink = realloc(sink, sizeof(long)); + sink = reallocarray(sink, 5, sizeof(long)); + sink = aligned_alloc(128, 1024); + sink = memalign(16, 256); + sink = valloc(4096); + sink = pvalloc(8192); + posix_memalign(&sink, 64, sizeof(int)); +} + +// CHECK-LABEL: define dso_local void @_Z17test_operator_newv( +// CHECK: call noalias noundef nonnull ptr @_Znwm(i64 noundef 4) +// CHECK: call noalias noundef nonnull ptr @_Znwm(i64 noundef 4) +void test_operator_new() { + sink = __builtin_operator_new(sizeof(int)); + sink = ::operator new(sizeof(int)); +} + +// CHECK-LABEL: define dso_local void @_Z25test_operator_new_nothrowv( +// CHECK: call noalias noundef ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, ptr noundef nonnull align 1 dereferenceable(1) @_ZSt7nothrow) +// CHECK: call noalias noundef ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, ptr noundef nonnull align 1 dereferenceable(1) @_ZSt7nothrow) +void test_operator_new_nothrow() { + sink = __builtin_operator_new(sizeof(int), std::nothrow); + sink = ::operator new(sizeof(int), std::nothrow); +} + +// CHECK-LABEL: define dso_local noundef ptr @_Z8test_newv( +// CHECK: call noalias noundef nonnull ptr @_Znwm(i64 noundef 4){{.*}} !alloc_token [[META_INT:![0-9]+]] +int *test_new() { + return new int; +} + +// CHECK-LABEL: define dso_local noundef ptr @_Z14test_new_arrayv( +// CHECK: call noalias noundef nonnull ptr @_Znam(i64 noundef 40){{.*}} !alloc_token [[META_INT]] +int *test_new_array() { + return new int[10]; +} + +// CHECK-LABEL: define dso_local noundef ptr @_Z16test_new_nothrowv( +// CHECK: call noalias noundef ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, ptr noundef nonnull align 1 dereferenceable(1) @_ZSt7nothrow){{.*}} !alloc_token [[META_INT]] +int *test_new_nothrow() { + return new (std::nothrow) int; +} + +// CHECK-LABEL: define dso_local noundef ptr @_Z22test_new_array_nothrowv( +// CHECK: call noalias noundef ptr @_ZnamRKSt9nothrow_t(i64 noundef 40, ptr noundef nonnull align 1 dereferenceable(1) @_ZSt7nothrow){{.*}} !alloc_token [[META_INT]] +int *test_new_array_nothrow() { + return new (std::nothrow) int[10]; +} + +// CHECK-LABEL: define dso_local void @_Z23test_size_returning_newv( +// CHECK: call { ptr, i64 } @__size_returning_new(i64 noundef 8) +// CHECK: call { ptr, i64 } @__size_returning_new_hot_cold(i64 noundef 8, i8 noundef zeroext 1) +// CHECK: call { ptr, i64 } @__size_returning_new_aligned(i64 noundef 8, i64 noundef 32) +// CHECK: call { ptr, i64 } @__size_returning_new_aligned_hot_cold(i64 noundef 8, i64 noundef 32, i8 noundef zeroext 1) +void test_size_returning_new() { + sink = __size_returning_new(sizeof(long)).p; + sink = __size_returning_new_hot_cold(sizeof(long), __hot_cold_t{1}).p; + sink = __size_returning_new_aligned(sizeof(long), std::align_val_t{32}).p; + sink = __size_returning_new_aligned_hot_cold(sizeof(long), std::align_val_t{32}, __hot_cold_t{1}).p; +} + +class TestClass { +public: + virtual void Foo(); + virtual ~TestClass(); + int data[16]; +}; + +void may_throw(); + +// CHECK-LABEL: define dso_local noundef ptr @_Z27test_exception_handling_newv( +// CHECK: invoke noalias noundef nonnull ptr @_Znwm(i64 noundef 72) +// CHECK-NEXT: !alloc_token [[META_TESTCLASS:![0-9]+]] +TestClass *test_exception_handling_new() { + try { + TestClass *obj = new TestClass(); + may_throw(); + return obj; + } catch (...) { + return nullptr; + } +} + +// CHECK-LABEL: define dso_local noundef ptr @_Z14test_new_classv( +// CHECK: call noalias noundef nonnull ptr @_Znwm(i64 noundef 72){{.*}} !alloc_token [[META_TESTCLASS]] +TestClass *test_new_class() { + TestClass *obj = new TestClass(); + obj->data[0] = 42; + return obj; +} + +// CHECK-LABEL: define dso_local noundef ptr @_Z20test_new_class_arrayv( +// CHECK: call noalias noundef nonnull ptr @_Znam(i64 noundef 728){{.*}} !alloc_token [[META_TESTCLASS]] +TestClass *test_new_class_array() { + TestClass* arr = new TestClass[10]; + arr[0].data[0] = 123; + return arr; +} + +// CHECK: [[META_INT]] = !{!"int"} +// CHECK: [[META_TESTCLASS]] = !{!"TestClass"} diff --git a/clang/test/Driver/fsanitize-alloc-token.c b/clang/test/Driver/fsanitize-alloc-token.c new file mode 100644 index 0000000000000..2964f60c4f26f --- /dev/null +++ b/clang/test/Driver/fsanitize-alloc-token.c @@ -0,0 +1,43 @@ +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-TOKEN-ALLOC +// CHECK-TOKEN-ALLOC: "-fsanitize=alloc-token" + +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -fno-sanitize=alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TOKEN-ALLOC +// CHECK-NO-TOKEN-ALLOC-NOT: "-fsanitize=alloc-token" + +// RUN: %clang --target=x86_64-linux-gnu -flto -fvisibility=hidden -fno-sanitize-ignorelist -fsanitize=alloc-token,undefined,cfi %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-COMPATIBLE +// CHECK-COMPATIBLE: "-fsanitize={{.*}}alloc-token" + +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-MINIMAL +// CHECK-MINIMAL: "-fsanitize=alloc-token" +// CHECK-MINIMAL: "-fsanitize-minimal-runtime" + +// RUN: %clang --target=arm-arm-non-eabi -fsanitize=alloc-token %s -### 2>&1 | FileCheck %s -check-prefix=CHECK-BAREMETAL +// RUN: %clang --target=aarch64-none-elf -fsanitize=alloc-token %s -### 2>&1 | FileCheck %s -check-prefix=CHECK-BAREMETAL +// CHECK-BAREMETAL: "-fsanitize=alloc-token" + +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=alloc-token,address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INCOMPATIBLE-ADDRESS +// CHECK-INCOMPATIBLE-ADDRESS: error: invalid argument '-fsanitize=alloc-token' not allowed with '-fsanitize=address' + +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=alloc-token,memory %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INCOMPATIBLE-MEMORY +// CHECK-INCOMPATIBLE-MEMORY: error: invalid argument '-fsanitize=alloc-token' not allowed with '-fsanitize=memory' + +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -fsanitize-trap=alloc-token %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INCOMPATIBLE-TRAP +// CHECK-INCOMPATIBLE-TRAP: error: unsupported argument 'alloc-token' to option '-fsanitize-trap=' + +// RUN: not %clang --target=x86_64-linux-gnu %s -fsanitize=alloc-token -fsanitize-recover=alloc-token -### 2>&1 | FileCheck %s --check-prefix=CHECK-INCOMPATIBLE-RECOVER +// CHECK-INCOMPATIBLE-RECOVER: unsupported argument 'alloc-token' to option '-fsanitize-recover=' + +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -fsanitize-alloc-token-fast-abi %s -### 2>&1 | FileCheck -check-prefix=CHECK-FASTABI %s +// CHECK-FASTABI: "-fsanitize-alloc-token-fast-abi" +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -fsanitize-alloc-token-fast-abi -fno-sanitize-alloc-token-fast-abi %s -### 2>&1 | FileCheck -check-prefix=CHECK-NOFASTABI %s +// CHECK-NOFASTABI-NOT: "-fsanitize-alloc-token-fast-abi" + +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -fsanitize-alloc-token-extended %s -### 2>&1 | FileCheck -check-prefix=CHECK-EXTENDED %s +// CHECK-EXTENDED: "-fsanitize-alloc-token-extended" +// RUN: %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -fsanitize-alloc-token-extended -fno-sanitize-alloc-token-extended %s -### 2>&1 | FileCheck -check-prefix=CHECK-NOEXTENDED %s +// CHECK-NOEXTENDED-NOT: "-fsanitize-alloc-token-extended" + +// RUN: %clang --target=x86_64-linux-gnu -falloc-token-max=0 -falloc-token-max=42 %s -### 2>&1 | FileCheck -check-prefix=CHECK-MAX %s +// CHECK-MAX: "-falloc-token-max=42" +// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=alloc-token -falloc-token-max=-1 %s 2>&1 | FileCheck -check-prefix=CHECK-INVALID-MAX %s +// CHECK-INVALID-MAX: error: invalid value diff --git a/clang/test/Preprocessor/alloc_token.cpp b/clang/test/Preprocessor/alloc_token.cpp new file mode 100644 index 0000000000000..0c51bfb9405f2 --- /dev/null +++ b/clang/test/Preprocessor/alloc_token.cpp @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -E -fsanitize=alloc-token %s -o - | FileCheck --check-prefix=CHECK-SANITIZE %s +// RUN: %clang_cc1 -E %s -o - | FileCheck --check-prefix=CHECK-DEFAULT %s + +#if __SANITIZE_ALLOC_TOKEN__ +// CHECK-SANITIZE: has_sanitize_alloc_token +int has_sanitize_alloc_token(); +#else +// CHECK-DEFAULT: no_sanitize_alloc_token +int no_sanitize_alloc_token(); +#endif _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
