There is an analogous ``zero_call_used_regs`` attribute to allow for finer
control of this feature.
+- Clang now supports randomizing structure layout in C. This feature is a
+ compile-time hardening technique, making it more difficult for an attacker to
+ retrieve data from structures. Specify randomization with the
+ ``randomize_layout`` attribute. The corresponding ``no_randomize_layout``
+ attribute can be used to turn the feature off.
+
+ A seed value is required to enable randomization, and is deterministic based
+ on a seed value. Use the ``-frandomize-layout-seed=`` or
+ ``-frandomize-layout-seed-file=`` flags.
+
+ .. note::
+
+ Randomizing structure layout is a C-only feature.
+
Bug Fixes
------------------
- ``CXXNewExpr::getArraySize()`` previously returned a ``llvm::Optional``
RecordDeclBits.ParamDestroyedInCallee = V;
}
+ bool isRandomized() const { return RecordDeclBits.IsRandomized; }
+
+ void setIsRandomized(bool V) { RecordDeclBits.IsRandomized = V; }
+
+ void reorderFields(const SmallVectorImpl<Decl *> &Fields);
+
/// Determines whether this declaration represents the
/// injected class name.
///
friend class ASTReader;
friend class CXXClassMemberWrapper;
friend class LinkageComputer;
+ friend class RecordDecl;
template<typename decl_type> friend class Redeclarable;
/// Access - Used by C++ decls for the access specifier.
/// Represents the way this type is passed to a function.
uint64_t ArgPassingRestrictions : 2;
+
+ /// Indicates whether this struct has had its field layout randomized.
+ uint64_t IsRandomized : 1;
};
/// Number of non-inherited bits in RecordDeclBitfields.
- enum { NumRecordDeclBits = 14 };
+ enum { NumRecordDeclBits = 15 };
/// Stores the bits used by OMPDeclareReductionDecl.
/// If modified NumOMPDeclareReductionDeclBits and the accessor
--- /dev/null
+//===- Randstruct.h - Interfact for structure randomization -------*- C++ -*-=//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the interface for Clang's structure field layout
+// randomization.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_RANDSTRUCT_H
+#define LLVM_CLANG_AST_RANDSTRUCT_H
+
+namespace llvm {
+template <typename T> class ArrayRef;
+template <typename T> class SmallVectorImpl;
+class StringRef;
+} // end namespace llvm
+
+namespace clang {
+
+class ASTContext;
+class Decl;
+class RecordDecl;
+
+namespace randstruct {
+
+bool randomizeStructureLayout(const ASTContext &Context, llvm::StringRef Name,
+ llvm::ArrayRef<Decl *> Fields,
+ llvm::SmallVectorImpl<Decl *> &FinalOrdering);
+
+} // namespace randstruct
+} // namespace clang
+
+#endif // LLVM_CLANG_AST_RANDSTRUCT_H
let LangOpts = [HLSL];
let Documentation = [NumThreadsDocs];
}
+
+def RandomizeLayout : InheritableAttr {
+ let Spellings = [GCC<"randomize_layout">];
+ let Subjects = SubjectList<[Record]>;
+ let Documentation = [ClangRandomizeLayoutDocs];
+ let LangOpts = [COnly];
+}
+
+def NoRandomizeLayout : InheritableAttr {
+ let Spellings = [GCC<"no_randomize_layout">];
+ let Subjects = SubjectList<[Record]>;
+ let Documentation = [ClangRandomizeLayoutDocs];
+ let LangOpts = [COnly];
+}
+def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>;
The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-attributes-numthreads
}];
}
+
+def ClangRandomizeLayoutDocs : Documentation {
+ let Category = DocCatDecl;
+ let Heading = "randomize_layout, no_randomize_layout";
+ let Content = [{
+The attribute ``randomize_layout``, when attached to a C structure, selects it
+for structure layout field randomization; a compile-time hardening technique. A
+"seed" value, is specified via the ``-frandomize-layout-seed=`` command line flag.
+For example:
+
+.. code-block:: bash
+
+ SEED=`od -A n -t x8 -N 32 /dev/urandom | tr -d ' \n'`
+ make ... CFLAGS="-frandomize-layout-seed=$SEED" ...
+
+You can also supply the seed in a file with ``-frandomize-layout-seed-file=``.
+For example:
+
+.. code-block:: bash
+
+ od -A n -t x8 -N 32 /dev/urandom | tr -d ' \n' > /tmp/seed_file.txt
+ make ... CFLAGS="-frandomize-layout-seed-file=/tmp/seed_file.txt" ...
+
+The randomization is deterministic based for a given seed, so the entire
+program should be compiled with the same seed, but keep the seed safe
+otherwise.
+
+The attribute ``no_randomize_layout``, when attached to a C structure,
+instructs the compiler that this structure should not have its field layout
+randomized.
+ }];
+}
"invalid argument '-mno-amdgpu-ieee' only allowed with relaxed NaN handling">;
def err_drv_argument_not_allowed_with : Error<
"invalid argument '%0' not allowed with '%1'">;
+def err_drv_cannot_open_randomize_layout_seed_file : Error<
+ "cannot read randomize layout seed file '%0'">;
def err_drv_invalid_version_number : Error<
"invalid version number in '%0'">;
def err_drv_no_linker_llvm_support : Error<
def err_hlsl_numthreads_invalid : Error<"total number of threads cannot exceed %0">;
def err_hlsl_attribute_param_mismatch : Error<"%0 attribute parameters do not match the previous declaration">;
+// Layout randomization warning.
+def err_cast_from_randomized_struct : Error<
+ "casting from randomized structure pointer type %0 to %1">;
} // end of sema component.
-
/// The default stream kind used for HIP kernel launching.
GPUDefaultStreamKind GPUDefaultStream;
+ /// The seed used by the randomize structure layout feature.
+ std::string RandstructSeed;
+
LangOptions();
// Define accessors/mutators for language options of enumeration type.
def fmessage_length_EQ : Joined<["-"], "fmessage-length=">, Group<f_Group>, Flags<[CC1Option]>,
HelpText<"Format message diagnostics so that they fit within N columns">,
MarshallingInfoInt<DiagnosticOpts<"MessageLength">>;
+def frandomize_layout_seed_EQ : Joined<["-"], "frandomize-layout-seed=">,
+ MetaVarName<"<seed>">, Group<f_clang_Group>, Flags<[CC1Option]>,
+ HelpText<"The seed used by the randomize structure layout feature">;
+def frandomize_layout_seed_file_EQ : Joined<["-"], "frandomize-layout-seed-file=">,
+ MetaVarName<"<file>">, Group<f_clang_Group>, Flags<[CC1Option]>,
+ HelpText<"File holding the seed used by the randomize structure layout feature">;
def fms_compatibility : Flag<["-"], "fms-compatibility">, Group<f_Group>, Flags<[CC1Option, CoreOption]>,
HelpText<"Enable full Microsoft Visual C++ compatibility">,
MarshallingInfoFlag<LangOpts<"MSVCCompat">>;
ParentMap.cpp
PrintfFormatString.cpp
QualTypeNames.cpp
+ Randstruct.cpp
RawCommentList.cpp
RecordLayout.cpp
RecordLayoutBuilder.cpp
#include "clang/AST/ODRHash.h"
#include "clang/AST/PrettyDeclStackTrace.h"
#include "clang/AST/PrettyPrinter.h"
+#include "clang/AST/Randstruct.h"
#include "clang/AST/Redeclarable.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/TemplateBase.h"
setHasNonTrivialToPrimitiveCopyCUnion(false);
setParamDestroyedInCallee(false);
setArgPassingRestrictions(APK_CanPassInRegs);
+ setIsRandomized(false);
}
RecordDecl *RecordDecl::Create(const ASTContext &C, TagKind TK, DeclContext *DC,
return hasAttr<MSStructAttr>() || C.getLangOpts().MSBitfields == 1;
}
+void RecordDecl::reorderFields(const SmallVectorImpl<Decl *> &Fields) {
+ std::tie(FirstDecl, LastDecl) = DeclContext::BuildDeclChain(Fields, false);
+ LastDecl->NextInContextAndBits.setPointer(nullptr);
+ setIsRandomized(true);
+}
+
void RecordDecl::LoadFieldsFromExternalStorage() const {
ExternalASTSource *Source = getASTContext().getExternalSource();
assert(hasExternalLexicalStorage() && Source && "No external storage?");
--- /dev/null
+//===--- Randstruct.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the implementation for Clang's structure field layout
+// randomization.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Randstruct.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTDiagnostic.h"
+#include "clang/AST/Attr.h"
+#include "clang/Basic/Diagnostic.h"
+#include "llvm/ADT/SmallVector.h"
+
+#include <algorithm>
+#include <random>
+#include <set>
+#include <sstream>
+#include <string>
+
+using clang::ASTContext;
+using clang::FieldDecl;
+using llvm::SmallVector;
+
+namespace {
+
+// FIXME: Replace this with some discovery once that mechanism exists.
+enum { CACHE_LINE = 64 };
+
+// The Bucket class holds the struct fields we're trying to fill to a
+// cache-line.
+class Bucket {
+ SmallVector<FieldDecl *, 64> Fields;
+ int Size = 0;
+
+public:
+ virtual ~Bucket() = default;
+
+ SmallVector<FieldDecl *, 64> &fields() { return Fields; }
+ void addField(FieldDecl *Field, int FieldSize);
+ virtual bool canFit(int FieldSize) const {
+ return Size + FieldSize <= CACHE_LINE;
+ }
+ virtual bool isBitfieldRun() const { return false; }
+ bool full() const { return Size >= CACHE_LINE; }
+};
+
+void Bucket::addField(FieldDecl *Field, int FieldSize) {
+ Size += FieldSize;
+ Fields.push_back(Field);
+}
+
+struct BitfieldRunBucket : public Bucket {
+ bool canFit(int FieldSize) const override { return true; }
+ bool isBitfieldRun() const override { return true; }
+};
+
+void randomizeStructureLayoutImpl(const ASTContext &Context,
+ llvm::SmallVectorImpl<FieldDecl *> &FieldsOut,
+ std::mt19937 &RNG) {
+ // All of the Buckets produced by best-effort cache-line algorithm.
+ SmallVector<std::unique_ptr<Bucket>, 16> Buckets;
+
+ // The current bucket of fields that we are trying to fill to a cache-line.
+ std::unique_ptr<Bucket> CurrentBucket;
+
+ // The current bucket containing the run of adjacent bitfields to ensure they
+ // remain adjacent.
+ std::unique_ptr<BitfieldRunBucket> CurrentBitfieldRun;
+
+ // Tracks the number of fields that we failed to fit to the current bucket,
+ // and thus still need to be added later.
+ size_t Skipped = 0;
+
+ while (!FieldsOut.empty()) {
+ // If we've Skipped more fields than we have remaining to place, that means
+ // that they can't fit in our current bucket, and we need to start a new
+ // one.
+ if (Skipped >= FieldsOut.size()) {
+ Skipped = 0;
+ Buckets.push_back(std::move(CurrentBucket));
+ }
+
+ // Take the first field that needs to be put in a bucket.
+ auto FieldIter = FieldsOut.begin();
+ FieldDecl *FD = *FieldIter;
+
+ if (FD->isBitField() && !FD->isZeroLengthBitField(Context)) {
+ // Start a bitfield run if this is the first bitfield we have found.
+ if (!CurrentBitfieldRun)
+ CurrentBitfieldRun = std::make_unique<BitfieldRunBucket>();
+
+ // We've placed the field, and can remove it from the "awaiting Buckets"
+ // vector called "Fields."
+ CurrentBitfieldRun->addField(FD, /*FieldSize is irrelevant here*/ 1);
+ FieldsOut.erase(FieldIter);
+ continue;
+ }
+
+ // Else, current field is not a bitfield. If we were previously in a
+ // bitfield run, end it.
+ if (CurrentBitfieldRun)
+ Buckets.push_back(std::move(CurrentBitfieldRun));
+
+ // If we don't have a bucket, make one.
+ if (!CurrentBucket)
+ CurrentBucket = std::make_unique<Bucket>();
+
+ uint64_t Width = Context.getTypeInfo(FD->getType()).Width;
+ if (Width >= CACHE_LINE) {
+ std::unique_ptr<Bucket> OverSized = std::make_unique<Bucket>();
+ OverSized->addField(FD, Width);
+ FieldsOut.erase(FieldIter);
+ Buckets.push_back(std::move(OverSized));
+ continue;
+ }
+
+ // If it fits, add it.
+ if (CurrentBucket->canFit(Width)) {
+ CurrentBucket->addField(FD, Width);
+ FieldsOut.erase(FieldIter);
+
+ // If it's now full, tie off the bucket.
+ if (CurrentBucket->full()) {
+ Skipped = 0;
+ Buckets.push_back(std::move(CurrentBucket));
+ }
+ } else {
+ // We can't fit it in our current bucket. Move to the end for processing
+ // later.
+ ++Skipped; // Mark it skipped.
+ FieldsOut.push_back(FD);
+ FieldsOut.erase(FieldIter);
+ }
+ }
+
+ // Done processing the fields awaiting a bucket.
+
+ // If we were filling a bucket, tie it off.
+ if (CurrentBucket)
+ Buckets.push_back(std::move(CurrentBucket));
+
+ // If we were processing a bitfield run bucket, tie it off.
+ if (CurrentBitfieldRun)
+ Buckets.push_back(std::move(CurrentBitfieldRun));
+
+ std::shuffle(std::begin(Buckets), std::end(Buckets), RNG);
+
+ // Produce the new ordering of the elements from the Buckets.
+ SmallVector<FieldDecl *, 16> FinalOrder;
+ for (const std::unique_ptr<Bucket> &B : Buckets) {
+ llvm::SmallVectorImpl<FieldDecl *> &RandFields = B->fields();
+ if (!B->isBitfieldRun())
+ std::shuffle(std::begin(RandFields), std::end(RandFields), RNG);
+
+ FinalOrder.insert(FinalOrder.end(), RandFields.begin(), RandFields.end());
+ }
+
+ FieldsOut = FinalOrder;
+}
+
+} // anonymous namespace
+
+namespace clang {
+namespace randstruct {
+
+bool randomizeStructureLayout(const ASTContext &Context, StringRef Name,
+ ArrayRef<Decl *> Fields,
+ SmallVectorImpl<Decl *> &FinalOrdering) {
+ SmallVector<FieldDecl *, 64> RandomizedFields;
+
+ unsigned TotalNumFields = 0;
+ for (Decl *D : Fields) {
+ ++TotalNumFields;
+ if (auto *FD = dyn_cast<FieldDecl>(D))
+ RandomizedFields.push_back(FD);
+ else
+ FinalOrdering.push_back(D);
+ }
+
+ if (RandomizedFields.empty())
+ return false;
+
+ // Struct might end with a variable-length array or an array of size 0 or 1,
+ // in which case we don't want to randomize it.
+ FieldDecl *VLA = nullptr;
+ const auto *CA =
+ dyn_cast<ConstantArrayType>(RandomizedFields.back()->getType());
+ if ((CA && (CA->getSize().sle(2) || CA->isIncompleteArrayType())) ||
+ llvm::any_of(Fields, [](Decl *D) {
+ if (const FieldDecl *FD = dyn_cast<FieldDecl>(D)) {
+ const Type *FDTy = FD->getType().getTypePtr();
+ if (const RecordType *FDTTy = FDTy->getAs<RecordType>())
+ return FDTTy->getDecl()->hasFlexibleArrayMember();
+ }
+ return false;
+ }))
+ VLA = RandomizedFields.pop_back_val();
+
+ std::string Seed = (Context.getLangOpts().RandstructSeed + Name).str();
+ std::seed_seq SeedSeq(Seed.begin(), Seed.end());
+ std::mt19937 RNG(SeedSeq);
+
+ randomizeStructureLayoutImpl(Context, RandomizedFields, RNG);
+ if (VLA)
+ RandomizedFields.push_back(VLA);
+
+ FinalOrdering.insert(FinalOrdering.end(), RandomizedFields.begin(),
+ RandomizedFields.end());
+
+ assert(TotalNumFields == FinalOrdering.size() &&
+ "Decl count has been altered after Randstruct randomization!");
+ return true;
+}
+
+} // end namespace randstruct
+} // end namespace clang
CmdArgs.push_back(
Args.MakeArgString("-fmessage-length=" + Twine(MessageLength)));
+ if (Arg *A = Args.getLastArg(options::OPT_frandomize_layout_seed_EQ))
+ CmdArgs.push_back(
+ Args.MakeArgString("-frandomize-layout-seed=" + Twine(A->getValue(0))));
+
+ if (Arg *A = Args.getLastArg(options::OPT_frandomize_layout_seed_file_EQ))
+ CmdArgs.push_back(Args.MakeArgString("-frandomize-layout-seed-file=" +
+ Twine(A->getValue(0))));
+
// -fvisibility= and -fvisibility-ms-compat are of a piece.
if (const Arg *A = Args.getLastArg(options::OPT_fvisibility_EQ,
options::OPT_fvisibility_ms_compat)) {
#include <cassert>
#include <cstddef>
#include <cstring>
+#include <fstream>
#include <memory>
#include <string>
#include <tuple>
for (const auto &MP : Opts.MacroPrefixMap)
GenerateArg(Args, OPT_fmacro_prefix_map_EQ, MP.first + "=" + MP.second, SA);
+
+ if (!Opts.RandstructSeed.empty())
+ GenerateArg(Args, OPT_frandomize_layout_seed_EQ, Opts.RandstructSeed, SA);
}
bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
Diags.Report(diag::err_cc1_unbounded_vscale_min);
}
+ if (const Arg *A = Args.getLastArg(OPT_frandomize_layout_seed_file_EQ)) {
+ std::ifstream SeedFile(A->getValue(0));
+
+ if (!SeedFile.is_open())
+ Diags.Report(diag::err_drv_cannot_open_randomize_layout_seed_file)
+ << A->getValue(0);
+
+ std::getline(SeedFile, Opts.RandstructSeed);
+ }
+
+ if (const Arg *A = Args.getLastArg(OPT_frandomize_layout_seed_EQ))
+ Opts.RandstructSeed = A->getValue(0);
+
return Diags.getNumErrors() == NumErrorsBefore;
}
Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type)
<< SrcType << DestType << OpRange;
+ if (isa<PointerType>(SrcType) && isa<PointerType>(DestType)) {
+ QualType SrcTy = cast<PointerType>(SrcType)->getPointeeType();
+ QualType DestTy = cast<PointerType>(DestType)->getPointeeType();
+
+ const RecordDecl *SrcRD = SrcTy->getAsRecordDecl();
+ const RecordDecl *DestRD = DestTy->getAsRecordDecl();
+
+ if (SrcRD && DestRD && SrcRD->hasAttr<RandomizeLayoutAttr>() &&
+ SrcRD != DestRD) {
+ // The struct we are casting the pointer from was randomized.
+ Self.Diag(OpRange.getBegin(), diag::err_cast_from_randomized_struct)
+ << SrcType << DestType;
+ SrcExpr = ExprError();
+ return;
+ }
+ }
+
DiagnoseCastOfObjCSEL(Self, SrcExpr, DestType);
DiagnoseCallingConvCast(Self, SrcExpr, DestType, OpRange);
DiagnoseBadFunctionCast(Self, SrcExpr, DestType);
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/NonTrivialTypeVisitor.h"
+#include "clang/AST/Randstruct.h"
#include "clang/AST/StmtCXX.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/PartialDiagnostic.h"
// Handle attributes before checking the layout.
ProcessDeclAttributeList(S, Record, Attrs);
+ // Maybe randomize the field order.
+ if (!getLangOpts().CPlusPlus && Record->hasAttr<RandomizeLayoutAttr>() &&
+ !Record->isUnion() && !getLangOpts().RandstructSeed.empty() &&
+ !Record->isRandomized()) {
+ SmallVector<Decl *, 32> OrigFieldOrdering(Record->fields());
+ SmallVector<Decl *, 32> NewFieldOrdering;
+ if (randstruct::randomizeStructureLayout(
+ Context, Record->getNameAsString(), OrigFieldOrdering,
+ NewFieldOrdering))
+ Record->reorderFields(NewFieldOrdering);
+ }
+
// We may have deferred checking for a deleted destructor. Check now.
if (CXXRecord) {
auto *Dtor = CXXRecord->getDestructor();
}
}
+static void handleRandomizeLayoutAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ if (checkAttrMutualExclusion<NoRandomizeLayoutAttr>(S, D, AL))
+ return;
+ if (!D->hasAttr<RandomizeLayoutAttr>())
+ D->addAttr(::new (S.Context) RandomizeLayoutAttr(S.Context, AL));
+}
+
+static void handleNoRandomizeLayoutAttr(Sema &S, Decl *D,
+ const ParsedAttr &AL) {
+ if (checkAttrMutualExclusion<RandomizeLayoutAttr>(S, D, AL))
+ return;
+ if (!D->hasAttr<NoRandomizeLayoutAttr>())
+ D->addAttr(::new (S.Context) NoRandomizeLayoutAttr(S.Context, AL));
+}
+
bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
const FunctionDecl *FD) {
if (Attrs.isInvalid())
case ParsedAttr::AT_Section:
handleSectionAttr(S, D, AL);
break;
+ case ParsedAttr::AT_RandomizeLayout:
+ handleRandomizeLayoutAttr(S, D, AL);
+ break;
+ case ParsedAttr::AT_NoRandomizeLayout:
+ handleNoRandomizeLayoutAttr(S, D, AL);
+ break;
case ParsedAttr::AT_CodeSeg:
handleCodeSegAttr(S, D, AL);
break;
// CHECK-NEXT: NoMicroMips (SubjectMatchRule_function)
// CHECK-NEXT: NoMips16 (SubjectMatchRule_function)
// CHECK-NEXT: NoProfileFunction (SubjectMatchRule_function)
+// CHECK-NEXT: NoRandomizeLayout (SubjectMatchRule_record)
// CHECK-NEXT: NoSanitize (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_variable_is_global)
// CHECK-NEXT: NoSanitizeSpecific (SubjectMatchRule_function, SubjectMatchRule_variable_is_global)
// CHECK-NEXT: NoSpeculativeLoadHardening (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: PassObjectSize (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: PatchableFunctionEntry (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union)
+// CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record)
// CHECK-NEXT: ReleaseHandle (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function)
// CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)
EvaluateAsRValueTest.cpp
ExternalASTSourceTest.cpp
NamedDeclPrinterTest.cpp
+ RandstructTest.cpp
RecursiveASTVisitorTest.cpp
SizelessTypesTest.cpp
SourceLocationTest.cpp
--- /dev/null
+//===- unittest/AST/RandstructTest.cpp ------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains tests for Clang's structure field layout randomization.
+//
+//===----------------------------------------------------------------------===//
+
+/*
+ * Build this test suite by running `make ASTTests` in the build folder.
+ *
+ * Run this test suite by running the following in the build folder:
+ * ` ./tools/clang/unittests/AST/ASTTests
+ * --gtest_filter=StructureLayoutRandomization*`
+ */
+
+#include "clang/AST/Randstruct.h"
+#include "gtest/gtest.h"
+
+#include "DeclMatcher.h"
+#include "clang/AST/RecordLayout.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Testing/CommandLineArgs.h"
+#include "clang/Tooling/Tooling.h"
+
+#include <vector>
+
+using namespace clang;
+using namespace clang::ast_matchers;
+using namespace clang::randstruct;
+
+using field_names = std::vector<std::string>;
+
+namespace {
+
+std::unique_ptr<ASTUnit> makeAST(const std::string &SourceCode,
+ bool ExpectErr = false) {
+ std::vector<std::string> Args = getCommandLineArgsForTesting(Lang_C99);
+ Args.push_back("-frandomize-layout-seed=1234567890abcdef");
+
+ IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer();
+
+ std::unique_ptr<ASTUnit> AST = tooling::buildASTFromCodeWithArgs(
+ SourceCode, Args, "input.c", "clang-tool",
+ std::make_shared<PCHContainerOperations>(),
+ tooling::getClangStripDependencyFileAdjuster(),
+ tooling::FileContentMappings(), &IgnoringConsumer);
+
+ if (ExpectErr)
+ EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred());
+ else
+ EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
+
+ return AST;
+}
+
+RecordDecl *getRecordDeclFromAST(const ASTContext &C, const std::string &Name) {
+ RecordDecl *RD = FirstDeclMatcher<RecordDecl>().match(
+ C.getTranslationUnitDecl(), recordDecl(hasName(Name)));
+ return RD;
+}
+
+std::vector<std::string> getFieldNamesFromRecord(const RecordDecl *RD) {
+ std::vector<std::string> Fields;
+
+ Fields.reserve(8);
+ for (auto *Field : RD->fields())
+ Fields.push_back(Field->getNameAsString());
+
+ return Fields;
+}
+
+bool isSubsequence(const field_names &Seq, const field_names &Subseq) {
+ unsigned SeqLen = Seq.size();
+ unsigned SubLen = Subseq.size();
+
+ bool IsSubseq = false;
+ for (unsigned I = 0; I < SeqLen; ++I)
+ if (Seq[I] == Subseq[0]) {
+ IsSubseq = true;
+ for (unsigned J = 0; J + I < SeqLen && J < SubLen; ++J) {
+ if (Seq[J + I] != Subseq[J]) {
+ IsSubseq = false;
+ break;
+ }
+ }
+ }
+
+ return IsSubseq;
+}
+
+} // end anonymous namespace
+
+namespace clang {
+namespace ast_matchers {
+
+#define RANDSTRUCT_TEST_SUITE_TEST StructureLayoutRandomizationTestSuiteTest
+
+TEST(RANDSTRUCT_TEST_SUITE_TEST, CanDetermineIfSubsequenceExists) {
+ const field_names Seq = {"a", "b", "c", "d"};
+
+ ASSERT_TRUE(isSubsequence(Seq, {"b", "c"}));
+ ASSERT_TRUE(isSubsequence(Seq, {"a", "b", "c", "d"}));
+ ASSERT_TRUE(isSubsequence(Seq, {"b", "c", "d"}));
+ ASSERT_TRUE(isSubsequence(Seq, {"a"}));
+ ASSERT_FALSE(isSubsequence(Seq, {"a", "d"}));
+}
+
+#define RANDSTRUCT_TEST StructureLayoutRandomization
+
+TEST(RANDSTRUCT_TEST, UnmarkedStruct) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test {
+ int bacon;
+ long lettuce;
+ long long tomato;
+ float mayonnaise;
+ };
+ )c");
+
+ const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
+ const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"};
+
+ ASSERT_FALSE(RD->hasAttr<RandomizeLayoutAttr>());
+ ASSERT_FALSE(RD->isRandomized());
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, MarkedNoRandomize) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test {
+ int bacon;
+ long lettuce;
+ long long tomato;
+ float mayonnaise;
+ } __attribute__((no_randomize_layout));
+ )c");
+
+ const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
+ const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"};
+
+ ASSERT_TRUE(RD->hasAttr<NoRandomizeLayoutAttr>());
+ ASSERT_FALSE(RD->isRandomized());
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, MarkedRandomize) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test {
+ int bacon;
+ long lettuce;
+ long long tomato;
+ float mayonnaise;
+ } __attribute__((randomize_layout));
+ )c");
+
+ const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
+#ifdef _WIN32
+ const field_names Expected = {"lettuce", "bacon", "mayonnaise", "tomato"};
+#else
+ const field_names Expected = {"mayonnaise", "bacon", "tomato", "lettuce"};
+#endif
+
+ ASSERT_TRUE(RD->hasAttr<RandomizeLayoutAttr>());
+ ASSERT_TRUE(RD->isRandomized());
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test __attribute__((randomize_layout));
+ struct test {
+ int bacon;
+ long lettuce;
+ long long tomato;
+ float mayonnaise;
+ } __attribute__((no_randomize_layout));
+ )c");
+
+ DiagnosticsEngine &Diags = AST->getDiagnostics();
+
+ EXPECT_FALSE(Diags.hasFatalErrorOccurred());
+ EXPECT_FALSE(Diags.hasUncompilableErrorOccurred());
+ EXPECT_FALSE(Diags.hasUnrecoverableErrorOccurred());
+ EXPECT_EQ(Diags.getNumWarnings(), 1u);
+ EXPECT_EQ(Diags.getNumErrors(), 0u);
+}
+
+TEST(RANDSTRUCT_TEST, MismatchedAttrsRandomizeVsNoRandomize) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test2 {
+ int bacon;
+ long lettuce;
+ long long tomato;
+ float mayonnaise;
+ } __attribute__((randomize_layout)) __attribute__((no_randomize_layout));
+ )c", true);
+
+ DiagnosticsEngine &Diags = AST->getDiagnostics();
+
+ EXPECT_TRUE(Diags.hasUncompilableErrorOccurred());
+ EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred());
+ EXPECT_EQ(Diags.getNumWarnings(), 0u);
+ EXPECT_EQ(Diags.getNumErrors(), 1u);
+}
+
+TEST(RANDSTRUCT_TEST, MismatchedAttrsNoRandomizeVsRandomize) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test3 {
+ int bacon;
+ long lettuce;
+ long long tomato;
+ float mayonnaise;
+ } __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
+ )c", true);
+
+ DiagnosticsEngine &Diags = AST->getDiagnostics();
+
+ EXPECT_TRUE(Diags.hasUncompilableErrorOccurred());
+ EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred());
+ EXPECT_EQ(Diags.getNumWarnings(), 0u);
+ EXPECT_EQ(Diags.getNumErrors(), 1u);
+}
+
+TEST(RANDSTRUCT_TEST, CheckAdjacentBitfieldsRemainAdjacentAfterRandomization) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test {
+ int a;
+ int b;
+ int x : 1;
+ int y : 1;
+ int z : 1;
+ int c;
+ } __attribute__((randomize_layout));
+ )c");
+
+ const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
+
+#ifdef _WIN32
+ const field_names Expected = {"b", "a", "c", "x", "y", "z"};
+#else
+ const field_names Expected = {"c", "x", "y", "z", "b", "a"};
+#endif
+ const field_names Subseq = {"x", "y", "z"};
+ const field_names Actual = getFieldNamesFromRecord(RD);
+
+ ASSERT_TRUE(isSubsequence(Actual, Subseq));
+ ASSERT_EQ(Expected, Actual);
+}
+
+TEST(RANDSTRUCT_TEST, CheckVariableLengthArrayMemberRemainsAtEndOfStructure) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test {
+ int a;
+ double b;
+ short c;
+ char name[];
+ } __attribute__((randomize_layout));
+ )c");
+
+ const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
+#ifdef _WIN32
+ const field_names Expected = {"b", "a", "c", "name"};
+#else
+ const field_names Expected = {"b", "c", "a", "name"};
+#endif
+
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test_struct {
+ char a;
+ float b[3];
+ short c;
+ int d;
+ } __attribute__((packed, randomize_layout));
+
+ struct another_struct {
+ char a;
+ char b[5];
+ int c;
+ } __attribute__((packed, randomize_layout));
+
+ struct last_struct {
+ char a;
+ long long b;
+ int c[];
+ } __attribute__((packed, randomize_layout));
+ )c");
+
+ // FIXME (?): calling getASTRecordLayout is probably a necessary evil so that
+ // Clang's RecordBuilders can actually flesh out the information like
+ // alignment, etc.
+ {
+ const RecordDecl *RD =
+ getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+ const ASTRecordLayout *Layout =
+ &AST->getASTContext().getASTRecordLayout(RD);
+#ifdef _WIN32
+ const field_names Expected = {"a", "c", "d", "b"};
+#else
+ const field_names Expected = {"c", "a", "d", "b"};
+#endif
+
+ ASSERT_EQ(19, Layout->getSize().getQuantity());
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+ }
+
+ {
+ const RecordDecl *RD =
+ getRecordDeclFromAST(AST->getASTContext(), "another_struct");
+ const ASTRecordLayout *Layout =
+ &AST->getASTContext().getASTRecordLayout(RD);
+#ifdef _WIN32
+ const field_names Expected = {"a", "b", "c"};
+#else
+ const field_names Expected = {"c", "a", "b"};
+#endif
+
+ ASSERT_EQ(10, Layout->getSize().getQuantity());
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+ }
+
+ {
+ const RecordDecl *RD =
+ getRecordDeclFromAST(AST->getASTContext(), "last_struct");
+ const ASTRecordLayout *Layout =
+ &AST->getASTContext().getASTRecordLayout(RD);
+ const field_names Expected = {"b", "c", "a"};
+
+ ASSERT_EQ(9, Layout->getSize().getQuantity());
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+ }
+}
+
+TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test_struct {
+ int a : 1;
+ int : 0;
+ int b : 1;
+ } __attribute__((randomize_layout));
+ )c");
+
+ const RecordDecl *RD =
+ getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+#ifdef _WIN32
+ const field_names Expected = {"b", "a", ""};
+#else
+ const field_names Expected = {"", "a", "b"};
+#endif
+
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ union test_union {
+ int a;
+ int b;
+ int c;
+ int d;
+ int e;
+ int f;
+ } __attribute__((randomize_layout));
+ )c");
+
+ const RecordDecl *RD =
+ getRecordDeclFromAST(AST->getASTContext(), "test_union");
+ const field_names Expected = {"a", "b", "c", "d", "e", "f"};
+
+ ASSERT_FALSE(RD->isRandomized());
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) {
+ const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+ struct test_struct {
+ int a;
+ struct sub_struct {
+ int b;
+ int c;
+ int d;
+ int e;
+ int f;
+ } __attribute__((randomize_layout)) s;
+ int f;
+ struct {
+ int g;
+ int h;
+ int i;
+ int j;
+ int k;
+ };
+ int l;
+ union {
+ int m;
+ int n;
+ int o;
+ int p;
+ int q;
+ };
+ int r;
+ } __attribute__((randomize_layout));
+ )c");
+
+ const RecordDecl *RD =
+ getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+#ifdef _WIN32
+ const field_names Expected = {"", "s", "l", "", "r", "a", "f"};
+#else
+ const field_names Expected = {"f", "a", "l", "", "", "s", "r"};
+#endif
+
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+
+ bool AnonStructTested = false;
+ bool AnonUnionTested = false;
+ for (const Decl *D : RD->decls())
+ if (const FieldDecl *FD = dyn_cast<FieldDecl>(D)) {
+ if (const auto *Record = FD->getType()->getAs<RecordType>()) {
+ RD = Record->getDecl();
+ if (RD->isAnonymousStructOrUnion()) {
+ if (RD->isUnion()) {
+ const field_names Expected = {"m", "n", "o", "p", "q"};
+
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+ AnonUnionTested = true;
+ } else {
+ const field_names Expected = {"g", "h", "i", "j", "k"};
+
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+ AnonStructTested = true;
+ }
+ } else if (RD->isStruct()) {
+#ifdef _WIN32
+ const field_names Expected = {"b", "c", "f", "d", "e"};
+#else
+ const field_names Expected = {"d", "e", "f", "c", "b"};
+#endif
+ ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+ }
+ }
+ }
+
+ ASSERT_TRUE(AnonStructTested);
+ ASSERT_TRUE(AnonUnionTested);
+}
+
+} // namespace ast_matchers
+} // namespace clang