void updated this revision to Diff 418356.
void marked 2 inline comments as done.
void added a comment.

Move casting check into the SemaCast where it belongs.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D121556/new/

https://reviews.llvm.org/D121556

Files:
  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/Parse/ParseDeclCXX.cpp
  clang/lib/Sema/SemaCast.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,409 @@
+//===- 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("-frandstruct-seed=1234567890abcdef");
+  return tooling::buildASTFromCodeWithArgs(SourceCode, Args, "input.c");
+}
+
+RecordDecl *getRecordDeclFromAST(const ASTContext &C, const std::string &Name) {
+  return FirstDeclMatcher<RecordDecl>().match(C.getTranslationUnitDecl(),
+                                              recordDecl(hasName(Name)));
+}
+
+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->getAttr<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_FALSE(RD->getAttr<RandomizeLayoutAttr>());
+  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");
+  const field_names Expected = {"mayonnaise", "bacon", "tomato", "lettuce"};
+
+  ASSERT_TRUE(RD->getAttr<RandomizeLayoutAttr>());
+  ASSERT_TRUE(RD->isRandomized());
+  ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, MultipleAttrsNoRandomize) {
+  const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+    struct test {
+        int bacon;
+        long lettuce;
+        long long tomato;
+        float mayonnaise;
+    } __attribute__((randomize_layout)) __attribute__((no_randomize_layout));
+  )c");
+
+  const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
+  const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"};
+
+  ASSERT_FALSE(RD->getAttr<RandomizeLayoutAttr>());
+  ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+TEST(RANDSTRUCT_TEST, MultipleAttrsRandomize) {
+  const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+    struct test {
+        int bacon;
+        long lettuce;
+        long long tomato;
+        float mayonnaise;
+    } __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
+  )c");
+
+  const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
+  const field_names Expected = {"mayonnaise", "bacon", "tomato", "lettuce"};
+
+  ASSERT_TRUE(RD->getAttr<RandomizeLayoutAttr>());
+  ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+}
+
+// FIXME: Clang trips an assertion in the DiagnosticsEngine when the warning is
+// emitted while running under the test suite:
+// clang/lib/Frontend/TextDiagnosticPrinter.cpp:150: virtual void
+// clang::TextDiagnosticPrinter::HandleDiagnostic(clang::DiagnosticsEngine::Level,
+// const clang::Diagnostic&): Assertion `TextDiag && "UnExpected diagnostic
+// outside source file processing"' failed.
+//
+// Although the test *technically* is marked as pass; outside of the test suite
+// this functionality works and no assertion is tripped.
+TEST(
+    RANDSTRUCT_TEST,
+    DISABLED_EmitWarningWhenStructureIsMarkedWithBothRandomizeAndNoRandomizeAttributes) {
+  const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
+    struct test_struct {
+        int a;
+        int b;
+        int c;
+    } __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
+  )c");
+
+  ASSERT_EQ(AST->getASTContext().getDiagnostics().getNumWarnings(), 1UL);
+}
+
+// End of FIXME regarding DiagnosticsEngine assertion tests.
+
+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");
+
+  const field_names Expected = {"c", "x", "y", "z", "b", "a"};
+  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");
+  const field_names Expected = {"b", "c", "a", "name"};
+
+  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);
+    const field_names Expected = {"c", "a", "d", "b"};
+
+    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);
+    const field_names Expected = {"c", "a", "b"};
+
+    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", "a", "c"};
+
+    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");
+  const field_names Expected = {"", "a", "b"};
+
+  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(shouldRandomize(AST->getASTContext(), RD));
+  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");
+  const field_names Expected = {"f", "a", "l", "", "", "s", "r"};
+
+  ASSERT_EQ(Expected, getFieldNamesFromRecord(RD));
+
+  bool AnonStructTested = false;
+  bool AnonUnionTested = false;
+  for (const Decl *D : RD->decls())
+    if (const RecordDecl *SubRD = dyn_cast<RecordDecl>(D)) {
+      if (SubRD->isAnonymousStructOrUnion()) {
+        if (SubRD->isUnion()) {
+          const field_names Expected = {"m", "n", "o", "p", "q"};
+
+          ASSERT_EQ(Expected, getFieldNamesFromRecord(SubRD));
+          AnonUnionTested = true;
+        } else {
+          const field_names Expected = {"g", "h", "i", "j", "k"};
+
+          ASSERT_EQ(Expected, getFieldNamesFromRecord(SubRD));
+          AnonStructTested = true;
+        }
+      } else if (SubRD->isStruct()) {
+        const field_names Expected = {"d", "e", "f", "c", "b"};
+        ASSERT_EQ(Expected, getFieldNamesFromRecord(SubRD));
+      }
+    }
+
+  ASSERT_TRUE(AnonStructTested);
+  ASSERT_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
@@ -8561,6 +8561,13 @@
   case ParsedAttr::AT_Section:
     handleSectionAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_RandomizeLayout:
+    handleSimpleAttribute<RandomizeLayoutAttr>(S, D, AL);
+    break;
+  case ParsedAttr::AT_NoRandomizeLayout:
+    // Drop the "randomize_layout" attribute if it's on the decl.
+    D->dropAttr<RandomizeLayoutAttr>();
+    break;
   case ParsedAttr::AT_CodeSeg:
     handleCodeSegAttr(S, D, AL);
     break;
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 != DestRD &&
+	SrcRD->hasAttr<RandomizeLayoutAttr>()) {
+      // The struct we are casting the pointer from was randomized.
+      Self.Diag(OpRange.getBegin(), diag::err_cast_from_randomized_struct)
+          << SrcTy << DestTy;
+      SrcExpr = ExprError();
+      return;
+    }
+  }
+
   DiagnoseCastOfObjCSEL(Self, SrcExpr, DestType);
   DiagnoseCallingConvCast(Self, SrcExpr, DestType, OpRange);
   DiagnoseBadFunctionCast(Self, SrcExpr, DestType);
Index: clang/lib/Parse/ParseDeclCXX.cpp
===================================================================
--- clang/lib/Parse/ParseDeclCXX.cpp
+++ clang/lib/Parse/ParseDeclCXX.cpp
@@ -10,15 +10,16 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "clang/Parse/Parser.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/PrettyDeclStackTrace.h"
+#include "clang/AST/Randstruct.h"
 #include "clang/Basic/Attributes.h"
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/OperatorKinds.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Parse/ParseDiagnostic.h"
+#include "clang/Parse/Parser.h"
 #include "clang/Parse/RAIIObjectsForParser.h"
 #include "clang/Sema/DeclSpec.h"
 #include "clang/Sema/ParsedTemplate.h"
@@ -2057,10 +2058,25 @@
     }
   }
 
-  if (!TagOrTempResult.isInvalid())
+  if (!TagOrTempResult.isInvalid()) {
     // Delayed processing of attributes.
     Actions.ProcessDeclAttributeDelayed(TagOrTempResult.get(), attrs);
 
+    RecordDecl *RD = TagOrTempResult.getAs<RecordDecl>();
+    if (randstruct::shouldRandomize(Actions.getASTContext(), RD)) {
+      SmallVector<Decl *, 64> FinalOrdering;
+      randstruct::randomizeStructureLayout(Actions.getASTContext(), RD,
+                                           FinalOrdering);
+      if (RD->isRandomized()) {
+        for (Decl *D : FinalOrdering)
+          RD->removeDecl(D);
+
+        for (Decl *D : FinalOrdering)
+          RD->addDecl(D);
+      }
+    }
+  }
+
   const char *PrevSpec = nullptr;
   unsigned DiagID;
   bool Result;
Index: clang/lib/Frontend/CompilerInvocation.cpp
===================================================================
--- clang/lib/Frontend/CompilerInvocation.cpp
+++ clang/lib/Frontend/CompilerInvocation.cpp
@@ -8,6 +8,7 @@
 
 #include "clang/Frontend/CompilerInvocation.h"
 #include "TestModuleFileExtension.h"
+#include "clang/AST/Randstruct.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/CodeGenOptions.h"
@@ -94,6 +95,7 @@
 #include <cassert>
 #include <cstddef>
 #include <cstring>
+#include <fstream>
 #include <memory>
 #include <string>
 #include <tuple>
@@ -4487,6 +4489,20 @@
   ParseDiagnosticArgs(Res.getDiagnosticOpts(), Args, &Diags,
                       /*DefaultDiagColor=*/false);
   ParseFrontendArgs(Res.getFrontendOpts(), Args, Diags, LangOpts.IsHeaderFile);
+
+  if (const Arg *A = Args.getLastArg(OPT_frandstruct_seed_file_EQ)) {
+    std::ifstream SeedFile(A->getValue(0));
+
+    if (!SeedFile.is_open())
+      Diags.Report(diag::err_drv_cannot_open_randstruct_seed_file)
+          << A->getValue(0);
+
+    std::getline(SeedFile, LangOpts.RandstructSeed);
+  }
+
+  if (const Arg *A = Args.getLastArg(OPT_frandstruct_seed_EQ))
+    LangOpts.RandstructSeed = A->getValue(0);
+
   // FIXME: We shouldn't have to pass the DashX option around here
   InputKind DashX = Res.getFrontendOpts().DashX;
   ParseTargetArgs(Res.getTargetOpts(), Args, Diags);
Index: clang/lib/Driver/ToolChains/Clang.cpp
===================================================================
--- clang/lib/Driver/ToolChains/Clang.cpp
+++ clang/lib/Driver/ToolChains/Clang.cpp
@@ -5913,6 +5913,14 @@
     CmdArgs.push_back(
         Args.MakeArgString("-fmessage-length=" + Twine(MessageLength)));
 
+  if (Arg *A = Args.getLastArg(options::OPT_frandstruct_seed_EQ))
+    CmdArgs.push_back(
+        Args.MakeArgString("-frandstruct-seed=" + Twine(A->getValue(0))));
+
+  if (Arg *A = Args.getLastArg(options::OPT_frandstruct_seed_file_EQ))
+    CmdArgs.push_back(
+        Args.MakeArgString("-frandstruct-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,220 @@
+//===--- 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>
+
+namespace clang {
+namespace randstruct {
+
+// 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; }
+};
+
+bool shouldRandomize(const ASTContext &Context, const RecordDecl *RD) {
+  const LangOptions &LangOpts = Context.getLangOpts();
+  return LangOpts.C99 && !LangOpts.RandstructSeed.empty() && !RD->isUnion() &&
+         RD->hasAttr<RandomizeLayoutAttr>();
+}
+
+void randomizeStructureLayoutImpl(const ASTContext &Context,
+                                  SmallVectorImpl<FieldDecl *> &FieldsOut,
+                                  std::mt19937 &RNG) {
+  // All of the Buckets produced by best-effort cache-line algorithm.
+  SmallVector<std::unique_ptr<randstruct::Bucket>, 16> Buckets;
+
+  // The current bucket of fields that we are trying to fill to a cache-line.
+  std::unique_ptr<randstruct::Bucket> CurrentBucket = nullptr;
+
+  // The current bucket containing the run of adjacent bitfields to ensure they
+  // remain adjacent.
+  std::unique_ptr<randstruct::Bucket> CurrentBitfieldRun = nullptr;
+
+  // 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<randstruct::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<randstruct::Bucket>();
+
+    uint64_t Width = Context.getTypeInfo(FD->getType()).Width;
+    if (Width >= randstruct::CACHE_LINE) {
+      std::unique_ptr<randstruct::Bucket> OverSized =
+          std::make_unique<randstruct::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<randstruct::Bucket> &B : Buckets) {
+    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;
+}
+
+void randomizeStructureLayout(const ASTContext &Context, RecordDecl *RD,
+                              SmallVectorImpl<Decl *> &FinalOrdering) {
+  if (!shouldRandomize(Context, RD))
+    return;
+
+  SmallVector<FieldDecl *, 64> RandomizedFields;
+
+  unsigned TotalNumFields = 0;
+  for (Decl *D : RD->decls()) {
+    ++TotalNumFields;
+    if (auto *FD = dyn_cast<FieldDecl>(D)) {
+      RandomizedFields.push_back(FD);
+    } else {
+      FinalOrdering.push_back(D);
+    }
+  }
+
+  if (RandomizedFields.empty())
+    return;
+
+  // 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())) ||
+      RD->hasFlexibleArrayMember())
+    VLA = RandomizedFields.pop_back_val();
+
+  std::string Seed =
+      Context.getLangOpts().RandstructSeed + RD->getNameAsString();
+  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!");
+
+  RD->setIsRandomized(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"
@@ -4577,6 +4578,7 @@
   setHasNonTrivialToPrimitiveCopyCUnion(false);
   setParamDestroyedInCallee(false);
   setArgPassingRestrictions(APK_CanPassInRegs);
+  setIsRandomized(false);
 }
 
 RecordDecl *RecordDecl::Create(const ASTContext &C, TagKind TK, DeclContext *DC,
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
@@ -2117,6 +2117,18 @@
 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 frandstruct_seed_EQ
+    : Joined<["-"], "frandstruct-seed=">,
+      MetaVarName<"<seed>">,
+      Group<f_clang_Group>,
+      Flags<[CC1Option, CoreOption]>,
+      HelpText<"The seed used by the randomization structure layout feature">;
+def frandstruct_seed_file_EQ
+    : Joined<["-"], "frandstruct-seed-file=">,
+      MetaVarName<"<file>">,
+      Group<f_clang_Group>,
+      HelpText<"File holding the seed used by the randomization structure layout feature">,
+      Flags<[CC1Option, CoreOption]>;
 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
@@ -415,6 +415,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
@@ -11556,4 +11556,8 @@
   "builtin requires at least one of the following extensions support to be enabled : %0">;
 def err_riscv_builtin_invalid_lmul : Error<
   "LMUL argument must be in the range [0,3] or [5,7]">;
+
+// 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_randstruct_seed_file : Error<
+  "cannot read randstruct 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
@@ -6368,3 +6368,28 @@
 .. _Return-Oriented Programming: https://en.wikipedia.org/wiki/Return-oriented_programming
   }];
 }
+
+def ClangRandstructDocs : 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 ``-frandstruct-seed=`` command line flag.
+You can also supply the seed in a file with ``-frandstruct-seed-file=``.
+For example:
+
+.. code-block:: bash
+
+  SEED=`od -A n -t x8 -N 32 /dev/urandom | tr -d ' \n'`
+  make ... CFLAGS="-frandstruct-seed=$SEED" ...
+
+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
@@ -3937,3 +3937,19 @@
   let Subjects = SubjectList<[Function], ErrorDiag>;
   let Documentation = [ErrorAttrDocs];
 }
+
+def RandomizeLayout : InheritableAttr {
+  let Spellings = [GCC<"randomize_layout">];
+  let Subjects = SubjectList<[Record]>;
+  let Documentation = [ClangRandstructDocs];
+  let LangOpts = [COnly];
+}
+
+// The "no_randomize_layout" attribute isn't added to a Decl. Instead, it's
+// used to drop the "randomize_layout" attribute.
+def NoRandomizeLayout : InheritableAttr {
+  let Spellings = [GCC<"no_randomize_layout">];
+  let Subjects = SubjectList<[Record]>;
+  let Documentation = [ClangRandstructDocs];
+  let LangOpts = [COnly];
+}
Index: clang/include/clang/AST/Randstruct.h
===================================================================
--- /dev/null
+++ clang/include/clang/AST/Randstruct.h
@@ -0,0 +1,41 @@
+//===- 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
+
+#include <string>
+
+namespace llvm {
+template <typename T> class SmallVectorImpl;
+} // end namespace llvm
+
+namespace clang {
+
+class ASTContext;
+class Decl;
+class FieldDecl;
+class RecordDecl;
+
+namespace randstruct {
+
+using llvm::SmallVectorImpl;
+
+bool shouldRandomize(const ASTContext &Context, const RecordDecl *RD);
+void randomizeStructureLayout(const ASTContext &Context, RecordDecl *RD,
+                              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
@@ -1540,10 +1540,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,10 @@
     RecordDeclBits.ParamDestroyedInCallee = V;
   }
 
+  bool isRandomized() const { return RecordDeclBits.IsRandomized; }
+
+  void setIsRandomized(bool V) { RecordDeclBits.IsRandomized = V; }
+
   /// Determines whether this declaration represents the
   /// injected class name.
   ///
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to