//===- 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=RecordLayoutRandomization*` */ #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 "llvm/Support/ToolOutputFile.h" #include using namespace clang; using namespace clang::ast_matchers; using namespace clang::randstruct; using field_names = std::vector; constexpr const char Seed[] = "1234567890abcdef"; static RecordDecl *getRecordDeclFromAST(const ASTContext &C, const std::string &Name) { RecordDecl *RD = FirstDeclMatcher().match( C.getTranslationUnitDecl(), recordDecl(hasName(Name))); return RD; } static std::vector getFieldNamesFromRecord(const RecordDecl *RD) { std::vector Fields; Fields.reserve(8); for (auto *Field : RD->fields()) Fields.push_back(Field->getNameAsString()); return Fields; } static 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; } static bool recordsEqual(const std::unique_ptr &LHS, const std::unique_ptr &RHS, const std::string &RecordName) { const RecordDecl *LHSRD = getRecordDeclFromAST(LHS->getASTContext(), RecordName); const RecordDecl *RHSRD = getRecordDeclFromAST(LHS->getASTContext(), RecordName); return getFieldNamesFromRecord(LHSRD) == getFieldNamesFromRecord(RHSRD); } static std::unique_ptr makeAST(const std::string &SourceCode, bool ExpectError = false, std::vector RecordNames = std::vector()) { std::vector Args = getCommandLineArgsForTesting(Lang_C99); Args.push_back("-frandomize-layout-seed=" + std::string(Seed)); IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer(); std::unique_ptr AST = tooling::buildASTFromCodeWithArgs( SourceCode, Args, "input.c", "clang-tool", std::make_shared(), tooling::getClangStripDependencyFileAdjuster(), tooling::FileContentMappings(), &IgnoringConsumer); int SeedFileFD = -1; llvm::SmallString<256> SeedFilename; EXPECT_FALSE(llvm::sys::fs::createTemporaryFile("seed", "rng", SeedFileFD, SeedFilename)); llvm::ToolOutputFile SeedFile(SeedFilename, SeedFileFD); SeedFile.os() << Seed << "\n"; Args.clear(); Args = getCommandLineArgsForTesting(Lang_C99); Args.push_back("-frandomize-layout-seed-file=" + SeedFile.getFilename().str()); std::unique_ptr ASTFileSeed = tooling::buildASTFromCodeWithArgs( SourceCode, Args, "input.c", "clang-tool", std::make_shared(), tooling::getClangStripDependencyFileAdjuster(), tooling::FileContentMappings(), &IgnoringConsumer); if (!ExpectError) { if (RecordNames.empty()) RecordNames.push_back("test"); for (std::string Name : RecordNames) EXPECT_TRUE(recordsEqual(AST, ASTFileSeed, Name)); } return AST; } namespace clang { namespace ast_matchers { long declCount(const RecordDecl *RD) { return llvm::count_if(RD->decls(), [&](const Decl *D) { return isa(D) || isa(D); }); } #define RANDSTRUCT_TEST_SUITE_TEST RecordLayoutRandomizationTestSuiteTest 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 RecordLayoutRandomization TEST(RANDSTRUCT_TEST, UnmarkedStruct) { std::unique_ptr 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"); long OriginalDeclCount = declCount(RD); EXPECT_FALSE(RD->hasAttr()); EXPECT_FALSE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } TEST(RANDSTRUCT_TEST, MarkedNoRandomize) { std::unique_ptr 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"); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->hasAttr()); EXPECT_FALSE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } TEST(RANDSTRUCT_TEST, MarkedRandomize) { std::unique_ptr 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"); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->hasAttr()); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) { std::unique_ptr 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", true); EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); const 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) { std::unique_ptr AST = makeAST(R"c( struct test { int bacon; long lettuce; long long tomato; float mayonnaise; } __attribute__((randomize_layout)) __attribute__((no_randomize_layout)); )c", true); EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); const 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) { std::unique_ptr AST = makeAST(R"c( struct test3 { int bacon; long lettuce; long long tomato; float mayonnaise; } __attribute__((no_randomize_layout)) __attribute__((randomize_layout)); )c", true); EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); const 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) { std::unique_ptr 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"); long OriginalDeclCount = declCount(RD); const field_names Actual = getFieldNamesFromRecord(RD); const field_names Subseq = {"x", "y", "z"}; EXPECT_TRUE(RD->isRandomized()); EXPECT_TRUE(isSubsequence(Actual, Subseq)); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } TEST(RANDSTRUCT_TEST, CheckFlexibleArrayMemberRemainsAtEndOfStructure1) { std::unique_ptr AST = makeAST(R"c( struct test { int a; int b; int c; int d; int e; int f; int g; int h; char name[]; } __attribute__((randomize_layout)); )c"); EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->hasFlexibleArrayMember()); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); EXPECT_EQ(getFieldNamesFromRecord(RD).back(), "name"); } TEST(RANDSTRUCT_TEST, CheckFlexibleArrayMemberRemainsAtEndOfStructure2) { std::unique_ptr AST = makeAST(R"c( struct test { int a; int b; int c; int d; int e; int f; int g; int h; char name[0]; } __attribute__((randomize_layout)); )c"); EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); long OriginalDeclCount = declCount(RD); EXPECT_FALSE(RD->hasFlexibleArrayMember()); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); EXPECT_EQ(getFieldNamesFromRecord(RD).back(), "name"); } TEST(RANDSTRUCT_TEST, CheckFlexibleArrayMemberRemainsAtEndOfStructure3) { std::unique_ptr AST = makeAST(R"c( struct test { int a; int b; int c; int d; int e; int f; int g; int h; char name[1]; } __attribute__((randomize_layout)); )c"); EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); long OriginalDeclCount = declCount(RD); EXPECT_FALSE(RD->hasFlexibleArrayMember()); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); EXPECT_EQ(getFieldNamesFromRecord(RD).back(), "name"); } TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) { std::unique_ptr 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", false, std::vector( {"test_struct", "another_struct", "last_struct"})); 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); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(19, Layout->getSize().getQuantity()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } { const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "another_struct"); const ASTRecordLayout *Layout = &AST->getASTContext().getASTRecordLayout(RD); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(10, Layout->getSize().getQuantity()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } { const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "last_struct"); const ASTRecordLayout *Layout = &AST->getASTContext().getASTRecordLayout(RD); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(9, Layout->getSize().getQuantity()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } } TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) { std::unique_ptr 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"); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) { std::unique_ptr AST = makeAST(R"c( union test { 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"); long OriginalDeclCount = declCount(RD); EXPECT_FALSE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) { std::unique_ptr AST = makeAST(R"c( struct test { 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"); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); bool AnonStructTested = false; bool AnonUnionTested = false; for (const Decl *D : RD->decls()) if (const FieldDecl *FD = dyn_cast(D)) { if (const auto *Record = FD->getType()->getAs()) { 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); } TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsReferenced) { std::unique_ptr AST = makeAST(R"c( struct test { int bacon; long lettuce; struct { double avocado; char blech; }; long long tomato; union { char toast[8]; unsigned toast_thing; }; float mayonnaise; } __attribute__((randomize_layout)); int foo(struct test *t) { return t->blech; } char *bar(struct test *t) { return t->toast; } )c"); EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); long OriginalDeclCount = declCount(RD); EXPECT_TRUE(RD->isRandomized()); EXPECT_EQ(OriginalDeclCount, declCount(RD)); } TEST(RANDSTRUCT_TEST, AutoRandomizeStructOfFunctionPointers) { const std::unique_ptr AST = makeAST(R"c( typedef void (*func_ptr)(); struct test { func_ptr a; func_ptr b; func_ptr c; func_ptr d; func_ptr e; func_ptr f; func_ptr g; }; )c"); EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); EXPECT_TRUE(RD->isRandomized()); } TEST(RANDSTRUCT_TEST, DisableAutoRandomizeStructOfFunctionPointers) { const std::unique_ptr AST = makeAST(R"c( typedef void (*func_ptr)(); struct test { func_ptr a; func_ptr b; func_ptr c; func_ptr d; func_ptr e; func_ptr f; func_ptr g; } __attribute__((no_randomize_layout)); )c"); EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); EXPECT_FALSE(RD->isRandomized()); } } // namespace ast_matchers } // namespace clang