void created this revision.
Herald added subscribers: dang, jdoerfert, mgorny.
Herald added a reviewer: aaron.ballman.
Herald added a project: All.
void requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Build the unit tests with (in the build directory):

  $ make ASTTests

Run the unit tests with (in the build directory):

  $ ./tools/clang/unittests/AST/ASTTests 
--gtest_filter=StructureLayoutRandomization*

This initial commit outlines the specification for Clang Randstruct.
Unit tests must exercise each path of a new chunk of code for
Randstruct. Among these unit tests are also special cases outlined in
the RFC feedback on Phabricator: https://reviews.llvm.org/D59254#1429401


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D120857

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/DiagnosticASTKinds.td
  clang/include/clang/Basic/DiagnosticDriverKinds.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/include/clang/Driver/Options.td
  clang/lib/AST/CMakeLists.txt
  clang/lib/AST/Decl.cpp
  clang/lib/AST/DeclBase.cpp
  clang/lib/AST/Randstruct.cpp
  clang/lib/AST/RecordLayoutBuilder.cpp
  clang/lib/Driver/ToolChains/Clang.cpp
  clang/lib/Frontend/CompilerInvocation.cpp
  clang/lib/Sema/AnalysisBasedWarnings.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,413 @@
+//===- 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::randstruct;
+
+namespace clang {
+namespace ast_matchers {
+
+static std::unique_ptr<ASTUnit> makeAST(const std::string &SourceCode,
+                                        TestLanguage Lang) {
+  const auto Args = getCommandLineArgsForTesting(Lang);
+  auto AST = tooling::buildASTFromCodeWithArgs(SourceCode, Args, "input.cc");
+  return AST;
+}
+
+static RecordDecl *getRecordDeclFromAST(const ASTContext &C,
+                                        const std::string &Name) {
+  return FirstDeclMatcher<RecordDecl>().match(C.getTranslationUnitDecl(),
+                                              recordDecl(hasName(Name)));
+}
+
+static 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;
+}
+
+static bool isSubsequence(const std::vector<std::string> &Seq,
+                          const std::vector<std::string> &Subseq) {
+  const auto SeqLen = Seq.size();
+  const auto SubLen = Subseq.size();
+
+  auto IsSubseq = false;
+  for (auto I = 0u; I < SeqLen; ++I) {
+    if (Seq[I] == Subseq[0]) {
+      IsSubseq = true;
+      for (auto J = 0u; J + I < SeqLen && J < SubLen; ++J) {
+        if (Seq[J + I] != Subseq[J]) {
+          IsSubseq = false;
+          break;
+        }
+      }
+    }
+  }
+  return IsSubseq;
+}
+
+#define RANDSTRUCT_TEST_SUITE_TEST StructureLayoutRandomizationTestSuiteTest
+
+TEST(RANDSTRUCT_TEST_SUITE_TEST, CanDetermineIfSubsequenceExists) {
+  const std::vector<std::string> S0 = {"a", "b", "c", "d"};
+  ASSERT_TRUE(isSubsequence(S0, {"b", "c"}));
+  ASSERT_TRUE(isSubsequence(S0, {"a", "b", "c", "d"}));
+  ASSERT_TRUE(isSubsequence(S0, {"b", "c", "d"}));
+  ASSERT_TRUE(isSubsequence(S0, {"a"}));
+  ASSERT_FALSE(isSubsequence(S0, {"a", "d"}));
+}
+
+#define RANDSTRUCT_TEST StructureLayoutRandomization
+
+TEST(RANDSTRUCT_TEST, UnmarkedStructuresAreNotRandomized) {
+  std::string Code =
+      R"(
+        struct dont_randomize_me {
+            int potato;
+            float tomato;
+            long cabbage;
+        };
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD =
+      getRecordDeclFromAST(AST->getASTContext(), "dont_randomize_me");
+  const std::vector<std::string> Expected = {"potato", "tomato", "cabbage"};
+  const std::vector<std::string> Actual = getFieldNamesFromRecord(RD);
+
+  ASSERT_EQ(Expected, Actual);
+}
+
+TEST(RANDSTRUCT_TEST, StructuresCanBeMarkedWithRandomizeLayoutAttr) {
+  std::string Code =
+      R"(
+        struct marked {
+            int bacon;
+            long lettuce;
+        } __attribute__((randomize_layout));
+
+        struct not_marked {
+            double cookies;
+        };
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD0 = getRecordDeclFromAST(AST->getASTContext(), "marked");
+  const auto *RD1 = getRecordDeclFromAST(AST->getASTContext(), "not_marked");
+
+  ASSERT_TRUE(RD0->getAttr<RandomizeLayoutAttr>());
+  ASSERT_FALSE(RD1->getAttr<RandomizeLayoutAttr>());
+}
+
+TEST(RANDSTRUCT_TEST, StructuresCanBeMarkedWithNoRandomizeLayoutAttr) {
+  std::string Code =
+      R"(
+        struct marked {
+            int bacon;
+            long lettuce;
+        } __attribute__((no_randomize_layout));
+
+        struct not_marked {
+            double cookies;
+        };
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD0 = getRecordDeclFromAST(AST->getASTContext(), "marked");
+  const auto *RD1 = getRecordDeclFromAST(AST->getASTContext(), "not_marked");
+
+  ASSERT_TRUE(RD0->getAttr<NoRandomizeLayoutAttr>());
+  ASSERT_FALSE(RD1->getAttr<NoRandomizeLayoutAttr>());
+}
+
+TEST(RANDSTRUCT_TEST, StructuresLayoutFieldLocationsCanBeRandomized) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a;
+            int b;
+            int c;
+            int d;
+            int e;
+            int f;
+        };
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+  randomizeStructureLayout(AST->getASTContext(), RD);
+  const std::vector<std::string> Before = {"a", "b", "c", "d", "e", "f"};
+  const std::vector<std::string> After = getFieldNamesFromRecord(RD);
+
+  // FIXME: Could this be a brittle test? Planning on having a separate unit
+  // test for reproducible randomizations with seed.
+  ASSERT_NE(Before, After);
+}
+
+TEST(RANDSTRUCT_TEST,
+     StructuresMarkedWithNoRandomizeLayoutShouldBeRejectedAndUnchanged) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a;
+            int b;
+            int c;
+            int d;
+            int e;
+            int f;
+        } __attribute__((no_randomize_layout));
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+
+  ASSERT_FALSE(randstruct::shouldRandomize(AST->getASTContext(), 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) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a;
+            int b;
+            int c;
+        } __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  ASSERT_EQ(AST->getASTContext().getDiagnostics().getNumWarnings(), 1UL);
+}
+
+TEST(RANDSTRUCT_TEST,
+     DISABLED_StructureMarkedWithBothAttributesRemainsUnchanged) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a;
+            int b;
+            int c;
+        } __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+  // FIXME getASTRecordLayout isn't the function we're testing here, it just
+  // happens to be the place where Randstruct makes a decision and then proceeds
+  // with that decision. I wonder if this is better as an integration test
+  // somehow.
+  static_cast<void>(AST->getASTContext().getASTRecordLayout(RD));
+
+  const std::vector<std::string> Expected = {"a", "b", "c"};
+  const std::vector<std::string> Actual = getFieldNamesFromRecord(RD);
+  ASSERT_EQ(Expected, Actual);
+}
+
+// End of FIXME regarding DiagnosticsEngine assertion tests.
+
+TEST(RANDSTRUCT_TEST, AdjacentBitfieldsRemainAdjacentAfterRandomization) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a;
+            int b;
+            int x : 1;
+            int y : 1;
+            int z : 1;
+            int c;
+        } __attribute__((randomize_layout));
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+  randomizeStructureLayout(AST->getASTContext(), RD);
+
+  const std::vector<std::string> Actual = getFieldNamesFromRecord(RD);
+  const std::vector<std::string> Subseq = {"x", "y", "z"};
+  ASSERT_TRUE(isSubsequence(Actual, Subseq));
+}
+
+TEST(RANDSTRUCT_TEST, VariableLengthArrayMemberRemainsAtEndOfStructure) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a;
+            double b;
+            short c;
+            char name[];
+        };
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+  randomizeStructureLayout(AST->getASTContext(), RD);
+
+  std::vector<std::string> Fields = getFieldNamesFromRecord(RD);
+  const auto VLA = std::find(Fields.begin(), Fields.end(), "name");
+  ASSERT_TRUE(VLA + 1 == Fields.end());
+}
+
+TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            char a;
+            short b;
+            int c;
+        } __attribute__((packed, randomize_layout));
+
+        struct another_struct {
+            char a;
+            int c;
+        } __attribute__((packed, randomize_layout));
+
+        struct last_struct {
+            char a;
+            long long b;
+        } __attribute__((packed, randomize_layout));
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+  // FIXME (?): calling getASTRecordLayout is probably a necessary evil so that
+  // Clang's RecordBuilders can actually flesh out the information like
+  // alignment, etc.
+  const auto &Layout = AST->getASTContext().getASTRecordLayout(RD);
+
+  ASSERT_EQ(7, Layout.getSize().getQuantity());
+
+  const auto *RD1 =
+      getRecordDeclFromAST(AST->getASTContext(), "another_struct");
+  const auto &Layout1 = AST->getASTContext().getASTRecordLayout(RD1);
+
+  ASSERT_EQ(5, Layout1.getSize().getQuantity());
+
+  const auto *RD2 = getRecordDeclFromAST(AST->getASTContext(), "last_struct");
+  const auto &Layout2 = AST->getASTContext().getASTRecordLayout(RD2);
+
+  ASSERT_EQ(9, Layout2.getSize().getQuantity());
+}
+
+TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a : 1;
+            int   : 0;
+            int b : 1;
+        };
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+  const std::vector<std::string> Before = getFieldNamesFromRecord(RD);
+  ASSERT_TRUE(isSubsequence(Before, {"a", "", "b"}));
+
+  randomizeStructureLayout(AST->getASTContext(), RD);
+  const std::vector<std::string> After = getFieldNamesFromRecord(RD);
+  ASSERT_FALSE(isSubsequence(After, {"a", "", "b"}));
+}
+
+TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) {
+  std::string Code =
+      R"(
+        union test_union {
+            int a;
+            int b;
+            int c;
+            int d;
+            int e;
+            int f;
+            int g;
+        } __attribute__((randomize_layout));
+        )";
+
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_union");
+  ASSERT_FALSE(shouldRandomize(AST->getASTContext(), RD));
+}
+
+TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) {
+  std::string Code =
+      R"(
+        struct test_struct {
+            int a;
+            struct {
+                int b;
+                int c;
+                int d;
+            };
+            int e;
+            union {
+                int f;
+                int h;
+                int j;
+            };
+            int k;
+        } __attribute__((randomize_layout));
+        )";
+  const auto AST = makeAST(Code, Lang_C99);
+  const auto *RD = getRecordDeclFromAST(AST->getASTContext(), "test_struct");
+
+  randomizeStructureLayout(AST->getASTContext(), RD);
+
+  bool AnonStructTested = false;
+  bool AnonUnionTested = false;
+  for (auto f : RD->decls()) {
+    if (auto *SubRD = dyn_cast<RecordDecl>(f))
+      if (SubRD->isAnonymousStructOrUnion()) {
+        if (isSubsequence(getFieldNamesFromRecord(SubRD), {"b", "c", "d"})) {
+          AnonStructTested = true;
+        }
+        if (isSubsequence(getFieldNamesFromRecord(SubRD), {"f", "h", "j"})) {
+          AnonUnionTested = true;
+        }
+      }
+  }
+  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
@@ -8547,6 +8547,12 @@
   case ParsedAttr::AT_Section:
     handleSectionAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_RandomizeLayout:
+    handleSimpleAttribute<RandomizeLayoutAttr>(S, D, AL);
+    break;
+  case ParsedAttr::AT_NoRandomizeLayout:
+    handleSimpleAttribute<NoRandomizeLayoutAttr>(S, D, AL);
+    break;
   case ParsedAttr::AT_CodeSeg:
     handleCodeSegAttr(S, D, AL);
     break;
Index: clang/lib/Sema/AnalysisBasedWarnings.cpp
===================================================================
--- clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -19,6 +19,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/ParentMap.h"
+#include "clang/AST/Randstruct.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/StmtObjC.h"
@@ -2140,6 +2141,61 @@
 } // namespace consumed
 } // namespace clang
 
+//===----------------------------------------------------------------------===//
+// Checking for bad casts from randomize structs
+//===----------------------------------------------------------------------===//
+namespace clang {
+namespace randstruct {
+namespace casts {
+
+class BadCastsASTWalk : public RecursiveASTVisitor<BadCastsASTWalk> {
+  Sema &S;
+
+public:
+  BadCastsASTWalk(Sema &S) : S(S){};
+
+  bool VisitCastExpr(const CastExpr *E) {
+    switch (E->getCastKind()) {
+    case CK_BitCast: {
+      const Expr *SubExpr = E->getSubExpr();
+      if (auto ICE = dyn_cast<ImplicitCastExpr>(SubExpr)) {
+        CanQualType E_T = S.Context.getCanonicalType(E->getType());
+        CanQualType ICE_T = S.Context.getCanonicalType(ICE->getType());
+        if (isa<PointerType>(E_T) && isa<PointerType>(ICE_T)) {
+          auto E_SP = ((QualType)E_T)->getPointeeType();
+          auto ICE_PT = ((QualType)ICE_T)->getPointeeType();
+
+          auto E_R = E_SP->getAsRecordDecl();
+          auto ICE_R = ICE_PT->getAsRecordDecl();
+
+          if (E_R != nullptr && ICE_R != nullptr && E_R != ICE_R) {
+            if (ICE_R->isRandomized()) {
+              // The struct we are casting the pointer from was randomized.
+              S.Diag(E->getExprLoc(), diag::cast_from_randomized_struct)
+                  << ICE_PT << ICE_R;
+              break;
+            }
+          }
+        }
+      }
+      break;
+    }
+    default:
+      break;
+    }
+
+    return true;
+  }
+};
+
+void checkForBadCasts(Sema &S, AnalysisDeclContext &AC) {
+  BadCastsASTWalk walker(S);
+  walker.TraverseStmt(AC.getBody());
+}
+} // namespace casts
+} // namespace randstruct
+} // namespace clang
+
 //===----------------------------------------------------------------------===//
 // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
 //  warnings on a function, method, or block.
@@ -2451,6 +2507,9 @@
       ++NumFunctionsWithBadCFGs;
     }
   }
+
+  // FIXME: Any way to get a handle to a RecordDecl struct here?
+  clang::randstruct::casts::checkForBadCasts(S, AC);
 }
 
 void clang::sema::AnalysisBasedWarnings::PrintStats() const {
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>
@@ -2695,6 +2697,21 @@
     Opts.ProgramAction = frontend::PluginAction;
     Opts.ActionName = A->getValue();
   }
+
+  if (const Arg *A = Args.getLastArg(OPT_randstruct_seed_filename_EQ)) {
+    std::string seed_filename = A->getValue(0);
+    std::ifstream seed_file(seed_filename.c_str());
+    if (!seed_file.is_open()) {
+      Diags.Report(diag::err_drv_cannot_open_randstruct_seed_filename)
+          << A->getValue(0);
+    }
+    std::getline(seed_file, randstruct::SEED);
+  }
+
+  if (const Arg *A = Args.getLastArg(OPT_randstruct_seed_EQ)) {
+    randstruct::SEED = A->getValue(0);
+  }
+
   for (const auto *AA : Args.filtered(OPT_plugin_arg))
     Opts.PluginArgs[AA->getValue(0)].emplace_back(AA->getValue(1));
 
Index: clang/lib/Driver/ToolChains/Clang.cpp
===================================================================
--- clang/lib/Driver/ToolChains/Clang.cpp
+++ clang/lib/Driver/ToolChains/Clang.cpp
@@ -5870,6 +5870,16 @@
     CmdArgs.push_back(
         Args.MakeArgString("-fmessage-length=" + Twine(MessageLength)));
 
+  if (Arg *A = Args.getLastArg(options::OPT_randstruct_seed_EQ)) {
+    CmdArgs.push_back("-randstruct-seed");
+    CmdArgs.push_back(A->getValue(0));
+  }
+
+  if (Arg *A = Args.getLastArg(options::OPT_randstruct_seed_filename_EQ)) {
+    CmdArgs.push_back("-randstruct-seed-filename");
+    CmdArgs.push_back(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/RecordLayoutBuilder.cpp
===================================================================
--- clang/lib/AST/RecordLayoutBuilder.cpp
+++ clang/lib/AST/RecordLayoutBuilder.cpp
@@ -14,8 +14,9 @@
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/AST/Expr.h"
-#include "clang/AST/VTableBuilder.h"
+#include "clang/AST/Randstruct.h"
 #include "clang/AST/RecordLayout.h"
+#include "clang/AST/VTableBuilder.h"
 #include "clang/Basic/TargetInfo.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/Support/Format.h"
@@ -3285,6 +3286,10 @@
   const ASTRecordLayout *Entry = ASTRecordLayouts[D];
   if (Entry) return *Entry;
 
+  if (randstruct::shouldRandomize(*this, D)) {
+    randstruct::randomizeStructureLayout(*this, D);
+  }
+
   const ASTRecordLayout *NewEntry = nullptr;
 
   if (isMsLayout(*this)) {
Index: clang/lib/AST/Randstruct.cpp
===================================================================
--- /dev/null
+++ clang/lib/AST/Randstruct.cpp
@@ -0,0 +1,250 @@
+//===--- 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 {
+
+std::string SEED;
+
+struct Randstruct {
+  void randomize(const ASTContext &Context,
+                 SmallVectorImpl<FieldDecl *> &OutFields,
+                 std::mt19937 &Rng) const;
+  void commit(const RecordDecl *RD,
+              SmallVectorImpl<Decl *> &NewDeclOrder) const;
+};
+
+bool shouldRandomize(const ASTContext &Context, const RecordDecl *RD) {
+  if (RD->isUnion())
+    return false;
+
+  bool HasRandAttr = RD->getAttr<RandomizeLayoutAttr>() != nullptr;
+  bool HasNoRandAttr = RD->getAttr<NoRandomizeLayoutAttr>() != nullptr;
+  if (HasRandAttr && HasNoRandAttr)
+    Context.getDiagnostics().Report(RD->getLocation(),
+                                    diag::warn_randomize_attr_conflict);
+
+  return !HasNoRandAttr && HasRandAttr;
+}
+
+void randomizeStructureLayout(const ASTContext &Context, const RecordDecl *RD) {
+  constexpr auto SMALL_VEC_SIZE = 16UL;
+  SmallVector<Decl *, SMALL_VEC_SIZE> Others;
+  SmallVector<FieldDecl *, SMALL_VEC_SIZE> Fields;
+  FieldDecl *VLA = nullptr;
+  unsigned index = 1;
+
+  std::set<Decl *> MutateGuard;
+  for (auto *Decl : RD->decls()) {
+    MutateGuard.insert(Decl);
+    if (isa<FieldDecl>(Decl)) {
+      auto *Field = cast<FieldDecl>(Decl);
+      Field->setOriginalFieldIndex(index);
+      ++index;
+      Fields.push_back(Field);
+    } else {
+      Others.push_back(Decl);
+    }
+  }
+
+  if (Fields.size() > 0) {
+    // Struct might end with a variable-length array or an array of size 0 or 1:
+    auto MaybeVLA = Fields.back();
+    auto Type = MaybeVLA->getType();
+    auto *CA = dyn_cast<ConstantArrayType>(Type);
+    if ((CA && CA->getSize().sle(2)) || Type->isIncompleteArrayType() ||
+        RD->hasFlexibleArrayMember()) {
+      VLA = Fields.pop_back_val();
+    }
+  }
+
+  std::stringstream ss(SEED);
+  ss << ":" << RD->getNameAsString();
+  std::string seed = ss.str();
+  std::seed_seq sseq(seed.begin(), seed.end());
+  std::mt19937 Rng(sseq);
+  Randstruct Rand;
+  Rand.randomize(Context, Fields, Rng);
+
+  SmallVector<Decl *, SMALL_VEC_SIZE> NewOrder = Others;
+  NewOrder.insert(NewOrder.end(), Fields.begin(), Fields.end());
+  if (VLA) {
+    NewOrder.push_back(VLA);
+  }
+
+  assert(MutateGuard.size() == NewOrder.size() &&
+         "Decl count has been altered after Randstruct randomization!");
+  Rand.commit(RD, NewOrder);
+  // FIXME: Oof, const_cast
+  const_cast<RecordDecl *>(RD)->setIsRandomized(true);
+}
+
+// FIXME: Replace this with some discovery once that mechanism exists.
+const auto CACHE_LINE = 64;
+
+class Bucket {
+  std::vector<FieldDecl *> Fields;
+  int Size = 0;
+
+public:
+  virtual ~Bucket() = default;
+
+  std::vector<FieldDecl *> &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; }
+};
+
+struct BitfieldRun : public Bucket {
+  bool canFit(int FieldSize) const override { return true; }
+  bool isBitfieldRun() const override { return true; }
+};
+
+void Bucket::addField(FieldDecl *Field, int FieldSize) {
+  Size += FieldSize;
+  Fields.push_back(Field);
+}
+
+void Randstruct::randomize(const ASTContext &Context,
+                           SmallVectorImpl<FieldDecl *> &FieldsOut,
+                           std::mt19937 &Rng) const {
+  // FIXME: Replace std::vector with LLVM ADT
+  using namespace randstruct;
+  // All of the Buckets produced by best-effort cache-line algorithm.
+  std::vector<std::unique_ptr<Bucket>> Buckets;
+
+  // The current bucket of fields that we are trying to fill to a cache-line.
+  std::unique_ptr<Bucket> CurrentBucket = nullptr;
+  // The current bucket containing the run of adjacent  bitfields to ensure
+  // they remain adjacent.
+  std::unique_ptr<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.
+  auto Skipped = 0ul;
+
+  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 Field = FieldsOut.begin();
+    auto *F = llvm::cast<FieldDecl>(*Field);
+
+    if (F->isBitField() && !F->isZeroLengthBitField(Context)) {
+      // Start a bitfield run if this is the first bitfield
+      // we have found.
+      if (!CurrentBitfieldRun) {
+        CurrentBitfieldRun = std::make_unique<BitfieldRun>();
+      }
+
+      // We've placed the field, and can remove it from the
+      // "awaiting Buckets" vector called "Fields"
+      CurrentBitfieldRun->addField(F, /*FieldSize is irrelevant here*/ 1);
+      FieldsOut.erase(Field);
+    } else {
+      // 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>();
+      }
+
+      auto Width = Context.getTypeInfo(F->getType()).Width;
+      if (Width >= CACHE_LINE) {
+        std::unique_ptr<Bucket> OverSized = std::make_unique<Bucket>();
+        OverSized->addField(F, Width);
+        FieldsOut.erase(Field);
+        Buckets.push_back(std::move(OverSized));
+        continue;
+      }
+
+      // If we can fit, add it.
+      if (CurrentBucket->canFit(Width)) {
+        CurrentBucket->addField(F, Width);
+        FieldsOut.erase(Field);
+
+        // 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(F);
+        FieldsOut.erase(Field);
+      }
+    }
+  }
+
+  // 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 our Buckets.
+  SmallVector<FieldDecl *, 16> FinalOrder;
+  for (auto &Bucket : Buckets) {
+    auto &Randomized = Bucket->fields();
+    if (!Bucket->isBitfieldRun()) {
+      std::shuffle(std::begin(Randomized), std::end(Randomized), Rng);
+    }
+    FinalOrder.insert(FinalOrder.end(), Randomized.begin(), Randomized.end());
+  }
+
+  FieldsOut = FinalOrder;
+}
+
+void Randstruct::commit(const RecordDecl *RD,
+                        SmallVectorImpl<Decl *> &NewDeclOrder) const {
+  std::tie(RD->FirstDecl, RD->LastDecl) =
+      DeclContext::BuildDeclChain(NewDeclOrder, false);
+}
+
+} // namespace randstruct
+} // namespace clang
Index: clang/lib/AST/DeclBase.cpp
===================================================================
--- clang/lib/AST/DeclBase.cpp
+++ clang/lib/AST/DeclBase.cpp
@@ -1344,6 +1344,9 @@
     PrevDecl = D;
   }
 
+  // Last item in the linked list should have a null next pointer.
+  PrevDecl->NextInContextAndBits.setPointer(nullptr);
+
   return std::make_pair(FirstNewDecl, PrevDecl);
 }
 
Index: clang/lib/AST/Decl.cpp
===================================================================
--- clang/lib/AST/Decl.cpp
+++ clang/lib/AST/Decl.cpp
@@ -4569,6 +4569,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
@@ -2114,6 +2114,8 @@
 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 randstruct_seed_EQ : Joined<["-"], "randstruct-seed=">, Group<f_Group>;
+def randstruct_seed_filename_EQ : Joined<["-"], "randstruct-seed-filename=">, Group<f_Group>;
 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/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11527,4 +11527,7 @@
   "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]">;
+
+def cast_from_randomized_struct : Error<
+  "casting between randomized structure pointer types %0 and %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_filename : 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/DiagnosticASTKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticASTKinds.td
+++ clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -593,4 +593,9 @@
 def warn_unaligned_access : Warning<
   "field %1 within %0 is less aligned than %2 and is usually due to %0 being "
   "packed, which can lead to unaligned accesses">, InGroup<UnalignedAccess>, DefaultIgnore;
+
+def warn_randomize_attr_conflict : Warning<
+  "struct declared with 'randomize_layout' and 'no_randomize_layout' attributes; "
+  "attribute 'no_randomize_layout' takes precedence">, InGroup<DiagGroup<"no-randomize-layout">>;
+
 }
Index: clang/include/clang/Basic/AttrDocs.td
===================================================================
--- clang/include/clang/Basic/AttrDocs.td
+++ clang/include/clang/Basic/AttrDocs.td
@@ -6346,3 +6346,15 @@
 .. _Return-Oriented Programming: https://en.wikipedia.org/wiki/Return-oriented_programming
   }];
 }
+
+def ClangRandstructDocs : Documentation {
+  let Category = DocCatVariable;
+  let Heading = "randomize_layout, no_randomize_layout";
+  let Content = [{
+The attribute ``randomize_layout`` can be added to a record-type definition to select
+it for structure layout field randomization; a compile-time hardening technique.
+
+The attribute ``no_randomize_layout`` can be added to a record-type definition to instruct
+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
@@ -3933,3 +3933,19 @@
   let Subjects = SubjectList<[Function], ErrorDiag>;
   let Documentation = [ErrorAttrDocs];
 }
+
+def RandomizeLayout : InheritableAttr {
+  let Spellings = [GCC<"randomize_layout">, Declspec<"randomize_layout">,
+    Keyword<"randomize_layout">];
+  let Subjects = SubjectList<[Record]>;
+  let Documentation = [ClangRandstructDocs];
+  let LangOpts = [COnly];
+}
+
+def NoRandomizeLayout : InheritableAttr {
+  let Spellings = [GCC<"no_randomize_layout">, Declspec<"no_randomize_layout">,
+    Keyword<"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,34 @@
+//===- 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 clang {
+
+class ASTContext;
+class RecordDecl;
+
+namespace randstruct {
+
+extern std::string SEED;
+
+bool shouldRandomize(const ASTContext &Context, const RecordDecl *RD);
+void randomizeStructureLayout(const ASTContext &Context, const RecordDecl *RD);
+
+} // 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
@@ -64,6 +64,10 @@
 class TranslationUnitDecl;
 class UsingDirectiveDecl;
 
+namespace randstruct {
+struct Randstruct;
+} // end namespace randstruct
+
 /// Captures the result of checking the availability of a
 /// declaration.
 enum AvailabilityResult {
@@ -1363,6 +1367,7 @@
   /// For hasNeedToReconcileExternalVisibleStorage,
   /// hasLazyLocalLexicalLookups, hasLazyExternalLexicalLookups
   friend class ASTWriter;
+  friend struct randstruct::Randstruct;
 
   // We use uint64_t in the bit-fields below since some bit-fields
   // cross the unsigned boundary and this breaks the packing.
@@ -1540,10 +1545,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
@@ -2839,6 +2839,7 @@
   unsigned BitField : 1;
   unsigned Mutable : 1;
   mutable unsigned CachedFieldIndex : 30;
+  mutable unsigned OriginalFieldIndex : 30;
 
   /// The kinds of value we can store in InitializerOrBitWidth.
   ///
@@ -2883,12 +2884,12 @@
 
 protected:
   FieldDecl(Kind DK, DeclContext *DC, SourceLocation StartLoc,
-            SourceLocation IdLoc, IdentifierInfo *Id,
-            QualType T, TypeSourceInfo *TInfo, Expr *BW, bool Mutable,
+            SourceLocation IdLoc, IdentifierInfo *Id, QualType T,
+            TypeSourceInfo *TInfo, Expr *BW, bool Mutable,
             InClassInitStyle InitStyle)
-    : DeclaratorDecl(DK, DC, IdLoc, Id, T, TInfo, StartLoc),
-      BitField(false), Mutable(Mutable), CachedFieldIndex(0),
-      InitStorage(nullptr, (InitStorageKind) InitStyle) {
+      : DeclaratorDecl(DK, DC, IdLoc, Id, T, TInfo, StartLoc), BitField(false),
+        Mutable(Mutable), CachedFieldIndex(0), OriginalFieldIndex(0),
+        InitStorage(nullptr, (InitStorageKind)InitStyle) {
     if (BW)
       setBitWidth(BW);
   }
@@ -2909,6 +2910,16 @@
   /// as appropriate for passing to ASTRecordLayout::getFieldOffset.
   unsigned getFieldIndex() const;
 
+  /// For struct field reorg, this is the original index, 1-based, or
+  /// 0 if reorg did not happen.
+  unsigned getOriginalFieldIndex() const { return OriginalFieldIndex; }
+
+  /// For struct field reorg, sets a 1-based index.
+  void setOriginalFieldIndex(unsigned Idx) {
+    assert(Idx && "Invalid original field index");
+    OriginalFieldIndex = Idx;
+  }
+
   /// Determines whether this field is mutable (C++ only).
   bool isMutable() const { return Mutable; }
 
@@ -4051,6 +4062,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