This revision was not accepted when it landed; it landed in state "Needs Review". This revision was automatically updated to reflect the committed changes. Closed by commit rG7aa8c38a9e19: [randstruct] Add randomize structure layout support (authored by connorkuehl, committed by void).
Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D121556/new/ https://reviews.llvm.org/D121556 Files: clang/docs/ReleaseNotes.rst clang/include/clang/AST/Decl.h clang/include/clang/AST/DeclBase.h clang/include/clang/AST/Randstruct.h clang/include/clang/Basic/Attr.td clang/include/clang/Basic/AttrDocs.td clang/include/clang/Basic/DiagnosticDriverKinds.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Basic/LangOptions.h clang/include/clang/Driver/Options.td clang/lib/AST/CMakeLists.txt clang/lib/AST/Decl.cpp clang/lib/AST/Randstruct.cpp clang/lib/Driver/ToolChains/Clang.cpp clang/lib/Frontend/CompilerInvocation.cpp clang/lib/Sema/SemaCast.cpp clang/lib/Sema/SemaDecl.cpp clang/lib/Sema/SemaDeclAttr.cpp clang/test/Misc/pragma-attribute-supported-attributes-list.test clang/unittests/AST/CMakeLists.txt clang/unittests/AST/RandstructTest.cpp
Index: clang/unittests/AST/RandstructTest.cpp =================================================================== --- /dev/null +++ clang/unittests/AST/RandstructTest.cpp @@ -0,0 +1,424 @@ +//===- 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) { + std::vector<std::string> Args = getCommandLineArgsForTesting(Lang_C99); + Args.push_back("-frandomize-layout-seed=1234567890abcdef"); + + IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer(); + + return tooling::buildASTFromCodeWithArgs( + SourceCode, Args, "input.c", "clang-tool", + std::make_shared<PCHContainerOperations>(), + tooling::getClangStripDependencyFileAdjuster(), + tooling::FileContentMappings(), &IgnoringConsumer); +} + +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"}; + + EXPECT_TRUE(isSubsequence(Seq, {"b", "c"})); + EXPECT_TRUE(isSubsequence(Seq, {"a", "b", "c", "d"})); + EXPECT_TRUE(isSubsequence(Seq, {"b", "c", "d"})); + EXPECT_TRUE(isSubsequence(Seq, {"a"})); + EXPECT_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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + + EXPECT_FALSE(RD->hasAttr<RandomizeLayoutAttr>()); + EXPECT_FALSE(RD->isRandomized()); +} + +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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + + EXPECT_TRUE(RD->hasAttr<NoRandomizeLayoutAttr>()); + EXPECT_FALSE(RD->isRandomized()); +} + +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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + + EXPECT_TRUE(RD->hasAttr<RandomizeLayoutAttr>()); + EXPECT_TRUE(RD->isRandomized()); +} + +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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + 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"); + + EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); + + 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"); + + EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); + + 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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + const field_names Actual = getFieldNamesFromRecord(RD); + const field_names Subseq = {"x", "y", "z"}; + + EXPECT_TRUE(RD->isRandomized()); + EXPECT_TRUE(isSubsequence(Actual, Subseq)); +} + +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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + + EXPECT_TRUE(RD->isRandomized()); +} + +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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + // 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); + + EXPECT_TRUE(RD->isRandomized()); + EXPECT_EQ(19, Layout->getSize().getQuantity()); + } + + { + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "another_struct"); + const ASTRecordLayout *Layout = + &AST->getASTContext().getASTRecordLayout(RD); + + EXPECT_TRUE(RD->isRandomized()); + EXPECT_EQ(10, Layout->getSize().getQuantity()); + } + + { + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "last_struct"); + const ASTRecordLayout *Layout = + &AST->getASTContext().getASTRecordLayout(RD); + + EXPECT_TRUE(RD->isRandomized()); + EXPECT_EQ(9, Layout->getSize().getQuantity()); + } +} + +TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) { + const std::unique_ptr<ASTUnit> AST = makeAST(R"c( + struct test { + int a : 1; + int : 0; + int b : 1; + } __attribute__((randomize_layout)); + )c"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); + + EXPECT_TRUE(RD->isRandomized()); +} + +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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "test_union"); + + EXPECT_FALSE(RD->isRandomized()); +} + +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"); + + EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); + + const RecordDecl *RD = + getRecordDeclFromAST(AST->getASTContext(), "test_struct"); + + EXPECT_TRUE(RD->isRandomized()); + + 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()) { + // These field orders shouldn't change. + if (RD->isUnion()) { + const field_names Expected = {"m", "n", "o", "p", "q"}; + + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + AnonUnionTested = true; + } else { + const field_names Expected = {"g", "h", "i", "j", "k"}; + + EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); + AnonStructTested = true; + } + } + } + } + + EXPECT_TRUE(AnonStructTested); + EXPECT_TRUE(AnonUnionTested); +} + +} // namespace ast_matchers +} // namespace clang Index: clang/unittests/AST/CMakeLists.txt =================================================================== --- clang/unittests/AST/CMakeLists.txt +++ clang/unittests/AST/CMakeLists.txt @@ -25,6 +25,7 @@ EvaluateAsRValueTest.cpp ExternalASTSourceTest.cpp NamedDeclPrinterTest.cpp + RandstructTest.cpp RecursiveASTVisitorTest.cpp SizelessTypesTest.cpp SourceLocationTest.cpp Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -104,6 +104,7 @@ // 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) @@ -148,6 +149,7 @@ // 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) Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -5121,6 +5121,21 @@ } } +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()) @@ -8635,6 +8650,12 @@ 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; Index: clang/lib/Sema/SemaDecl.cpp =================================================================== --- clang/lib/Sema/SemaDecl.cpp +++ clang/lib/Sema/SemaDecl.cpp @@ -24,6 +24,7 @@ #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" @@ -17968,6 +17969,18 @@ // 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(); Index: clang/lib/Sema/SemaCast.cpp =================================================================== --- clang/lib/Sema/SemaCast.cpp +++ clang/lib/Sema/SemaCast.cpp @@ -3129,6 +3129,23 @@ 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); Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -94,6 +94,7 @@ #include <cassert> #include <cstddef> #include <cstring> +#include <fstream> #include <memory> #include <string> #include <tuple> @@ -3685,6 +3686,9 @@ 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, @@ -4237,6 +4241,19 @@ 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; } Index: clang/lib/Driver/ToolChains/Clang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Clang.cpp +++ clang/lib/Driver/ToolChains/Clang.cpp @@ -5907,6 +5907,14 @@ 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)) { Index: clang/lib/AST/Randstruct.cpp =================================================================== --- /dev/null +++ clang/lib/AST/Randstruct.cpp @@ -0,0 +1,223 @@ +//===--- 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 Index: clang/lib/AST/Decl.cpp =================================================================== --- clang/lib/AST/Decl.cpp +++ clang/lib/AST/Decl.cpp @@ -30,6 +30,7 @@ #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" @@ -4583,6 +4584,7 @@ setHasNonTrivialToPrimitiveCopyCUnion(false); setParamDestroyedInCallee(false); setArgPassingRestrictions(APK_CanPassInRegs); + setIsRandomized(false); } RecordDecl *RecordDecl::Create(const ASTContext &C, TagKind TK, DeclContext *DC, @@ -4666,6 +4668,12 @@ 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?"); Index: clang/lib/AST/CMakeLists.txt =================================================================== --- clang/lib/AST/CMakeLists.txt +++ clang/lib/AST/CMakeLists.txt @@ -97,6 +97,7 @@ ParentMap.cpp PrintfFormatString.cpp QualTypeNames.cpp + Randstruct.cpp RawCommentList.cpp RecordLayout.cpp RecordLayoutBuilder.cpp Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -2122,6 +2122,12 @@ 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">>; Index: clang/include/clang/Basic/LangOptions.h =================================================================== --- clang/include/clang/Basic/LangOptions.h +++ clang/include/clang/Basic/LangOptions.h @@ -445,6 +445,9 @@ /// 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. Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11588,5 +11588,7 @@ 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. - Index: clang/include/clang/Basic/DiagnosticDriverKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticDriverKinds.td +++ clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -165,6 +165,8 @@ "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< Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -6379,3 +6379,35 @@ 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. + }]; +} Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -3957,3 +3957,18 @@ 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]>; Index: clang/include/clang/AST/Randstruct.h =================================================================== --- /dev/null +++ clang/include/clang/AST/Randstruct.h @@ -0,0 +1,38 @@ +//===- 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 Index: clang/include/clang/AST/DeclBase.h =================================================================== --- clang/include/clang/AST/DeclBase.h +++ clang/include/clang/AST/DeclBase.h @@ -313,6 +313,7 @@ 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. @@ -1540,10 +1541,13 @@ /// 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 Index: clang/include/clang/AST/Decl.h =================================================================== --- clang/include/clang/AST/Decl.h +++ clang/include/clang/AST/Decl.h @@ -4051,6 +4051,12 @@ 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. /// Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -54,6 +54,20 @@ 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``
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits