blob: c22d866ed4fad07516c9737ad3faa8d5794aefa0 [file] [log] [blame]
//===- 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 <vector>
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::randstruct;
using field_names = std::vector<std::string>;
constexpr const char Seed[] = "1234567890abcdef";
static RecordDecl *getRecordDeclFromAST(const ASTContext &C,
const std::string &Name) {
RecordDecl *RD = FirstDeclMatcher<RecordDecl>().match(
C.getTranslationUnitDecl(), recordDecl(hasName(Name)));
return RD;
}
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 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<ASTUnit> &LHS,
const std::unique_ptr<ASTUnit> &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<ASTUnit>
makeAST(const std::string &SourceCode, bool ExpectError = false,
std::vector<std::string> RecordNames = std::vector<std::string>()) {
std::vector<std::string> Args = getCommandLineArgsForTesting(Lang_C99);
Args.push_back("-frandomize-layout-seed=" + std::string(Seed));
IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer();
std::unique_ptr<ASTUnit> AST = tooling::buildASTFromCodeWithArgs(
SourceCode, Args, "input.c", "clang-tool",
std::make_shared<PCHContainerOperations>(),
tooling::getClangStripDependencyFileAdjuster(),
tooling::FileContentMappings(), &IgnoringConsumer);
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<ASTUnit> ASTFileSeed = tooling::buildASTFromCodeWithArgs(
SourceCode, Args, "input.c", "clang-tool",
std::make_shared<PCHContainerOperations>(),
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<FieldDecl>(D) || isa<RecordDecl>(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<ASTUnit> AST = makeAST(R"c(
struct test {
int bacon;
long lettuce;
long long tomato;
float mayonnaise;
};
)c");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
long OriginalDeclCount = declCount(RD);
EXPECT_FALSE(RD->hasAttr<RandomizeLayoutAttr>());
EXPECT_FALSE(RD->isRandomized());
EXPECT_EQ(OriginalDeclCount, declCount(RD));
}
TEST(RANDSTRUCT_TEST, MarkedNoRandomize) {
std::unique_ptr<ASTUnit> AST = makeAST(R"c(
struct test {
int bacon;
long lettuce;
long long tomato;
float mayonnaise;
} __attribute__((no_randomize_layout));
)c");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
long OriginalDeclCount = declCount(RD);
EXPECT_TRUE(RD->hasAttr<NoRandomizeLayoutAttr>());
EXPECT_FALSE(RD->isRandomized());
EXPECT_EQ(OriginalDeclCount, declCount(RD));
}
TEST(RANDSTRUCT_TEST, MarkedRandomize) {
std::unique_ptr<ASTUnit> AST = makeAST(R"c(
struct test {
int bacon;
long lettuce;
long long tomato;
float mayonnaise;
} __attribute__((randomize_layout));
)c");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
long OriginalDeclCount = declCount(RD);
EXPECT_TRUE(RD->hasAttr<RandomizeLayoutAttr>());
EXPECT_TRUE(RD->isRandomized());
EXPECT_EQ(OriginalDeclCount, declCount(RD));
}
TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) {
std::unique_ptr<ASTUnit> AST = makeAST(R"c(
struct test __attribute__((randomize_layout));
struct test {
int bacon;
long lettuce;
long long tomato;
float mayonnaise;
} __attribute__((no_randomize_layout));
)c",
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<ASTUnit> 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<ASTUnit> AST = makeAST(R"c(
struct test3 {
int bacon;
long lettuce;
long long tomato;
float mayonnaise;
} __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
)c",
true);
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<ASTUnit> AST = makeAST(R"c(
struct test {
int a;
int b;
int x : 1;
int y : 1;
int z : 1;
int c;
} __attribute__((randomize_layout));
)c");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
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<ASTUnit> 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<ASTUnit> 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<ASTUnit> 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<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",
false,
std::vector<std::string>(
{"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<ASTUnit> AST = makeAST(R"c(
struct test {
int a : 1;
int : 0;
int b : 1;
} __attribute__((randomize_layout));
)c");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
long OriginalDeclCount = declCount(RD);
EXPECT_TRUE(RD->isRandomized());
EXPECT_EQ(OriginalDeclCount, declCount(RD));
}
TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) {
std::unique_ptr<ASTUnit> 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<ASTUnit> 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<FieldDecl>(D)) {
if (const auto *Record = FD->getType()->getAs<RecordType>()) {
RD = Record->getDecl();
if (RD->isAnonymousStructOrUnion()) {
// These field orders shouldn't change.
if (RD->isUnion()) {
const field_names Expected = {"m", "n", "o", "p", "q"};
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
AnonUnionTested = true;
} else {
const field_names Expected = {"g", "h", "i", "j", "k"};
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
AnonStructTested = true;
}
}
}
}
EXPECT_TRUE(AnonStructTested);
EXPECT_TRUE(AnonUnionTested);
}
TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsReferenced) {
std::unique_ptr<ASTUnit> 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<ASTUnit> 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<ASTUnit> 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