1 //===- unittest/AST/RandstructTest.cpp ------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file contains tests for Clang's structure field layout randomization. 10 // 11 //===----------------------------------------------------------------------===// 12 13 /* 14 * Build this test suite by running `make ASTTests` in the build folder. 15 * 16 * Run this test suite by running the following in the build folder: 17 * ` ./tools/clang/unittests/AST/ASTTests 18 * --gtest_filter=RecordLayoutRandomization*` 19 */ 20 21 #include "clang/AST/Randstruct.h" 22 #include "gtest/gtest.h" 23 24 #include "DeclMatcher.h" 25 #include "clang/AST/RecordLayout.h" 26 #include "clang/ASTMatchers/ASTMatchers.h" 27 #include "clang/Frontend/ASTUnit.h" 28 #include "clang/Testing/CommandLineArgs.h" 29 #include "clang/Tooling/Tooling.h" 30 #include "llvm/Support/ToolOutputFile.h" 31 32 #include <vector> 33 34 using namespace clang; 35 using namespace clang::ast_matchers; 36 using namespace clang::randstruct; 37 38 using field_names = std::vector<std::string>; 39 40 constexpr const char Seed[] = "1234567890abcdef"; 41 42 static RecordDecl *getRecordDeclFromAST(const ASTContext &C, 43 const std::string &Name) { 44 RecordDecl *RD = FirstDeclMatcher<RecordDecl>().match( 45 C.getTranslationUnitDecl(), recordDecl(hasName(Name))); 46 return RD; 47 } 48 49 static std::vector<std::string> getFieldNamesFromRecord(const RecordDecl *RD) { 50 std::vector<std::string> Fields; 51 52 Fields.reserve(8); 53 for (auto *Field : RD->fields()) 54 Fields.push_back(Field->getNameAsString()); 55 56 return Fields; 57 } 58 59 static bool isSubsequence(const field_names &Seq, const field_names &Subseq) { 60 unsigned SeqLen = Seq.size(); 61 unsigned SubLen = Subseq.size(); 62 63 bool IsSubseq = false; 64 for (unsigned I = 0; I < SeqLen; ++I) 65 if (Seq[I] == Subseq[0]) { 66 IsSubseq = true; 67 for (unsigned J = 0; J + I < SeqLen && J < SubLen; ++J) { 68 if (Seq[J + I] != Subseq[J]) { 69 IsSubseq = false; 70 break; 71 } 72 } 73 } 74 75 return IsSubseq; 76 } 77 78 static bool recordsEqual(const std::unique_ptr<ASTUnit> &LHS, 79 const std::unique_ptr<ASTUnit> &RHS, 80 const std::string &RecordName) { 81 const RecordDecl *LHSRD = 82 getRecordDeclFromAST(LHS->getASTContext(), RecordName); 83 const RecordDecl *RHSRD = 84 getRecordDeclFromAST(LHS->getASTContext(), RecordName); 85 86 return getFieldNamesFromRecord(LHSRD) == getFieldNamesFromRecord(RHSRD); 87 } 88 89 static std::unique_ptr<ASTUnit> 90 makeAST(const std::string &SourceCode, bool ExpectError = false, 91 std::vector<std::string> RecordNames = std::vector<std::string>()) { 92 std::vector<std::string> Args = getCommandLineArgsForTesting(Lang_C99); 93 Args.push_back("-frandomize-layout-seed=" + std::string(Seed)); 94 95 IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer(); 96 97 std::unique_ptr<ASTUnit> AST = tooling::buildASTFromCodeWithArgs( 98 SourceCode, Args, "input.c", "clang-tool", 99 std::make_shared<PCHContainerOperations>(), 100 tooling::getClangStripDependencyFileAdjuster(), 101 tooling::FileContentMappings(), &IgnoringConsumer); 102 103 int SeedFileFD = -1; 104 llvm::SmallString<256> SeedFilename; 105 EXPECT_FALSE(llvm::sys::fs::createTemporaryFile("seed", "rng", SeedFileFD, 106 SeedFilename)); 107 llvm::ToolOutputFile SeedFile(SeedFilename, SeedFileFD); 108 SeedFile.os() << Seed << "\n"; 109 110 Args.clear(); 111 Args = getCommandLineArgsForTesting(Lang_C99); 112 Args.push_back("-frandomize-layout-seed-file=" + 113 SeedFile.getFilename().str()); 114 115 std::unique_ptr<ASTUnit> ASTFileSeed = tooling::buildASTFromCodeWithArgs( 116 SourceCode, Args, "input.c", "clang-tool", 117 std::make_shared<PCHContainerOperations>(), 118 tooling::getClangStripDependencyFileAdjuster(), 119 tooling::FileContentMappings(), &IgnoringConsumer); 120 121 if (!ExpectError) { 122 if (RecordNames.empty()) 123 RecordNames.push_back("test"); 124 125 for (std::string Name : RecordNames) 126 EXPECT_TRUE(recordsEqual(AST, ASTFileSeed, Name)); 127 } 128 129 return AST; 130 } 131 132 namespace clang { 133 namespace ast_matchers { 134 135 #define RANDSTRUCT_TEST_SUITE_TEST RecordLayoutRandomizationTestSuiteTest 136 137 TEST(RANDSTRUCT_TEST_SUITE_TEST, CanDetermineIfSubsequenceExists) { 138 const field_names Seq = {"a", "b", "c", "d"}; 139 140 EXPECT_TRUE(isSubsequence(Seq, {"b", "c"})); 141 EXPECT_TRUE(isSubsequence(Seq, {"a", "b", "c", "d"})); 142 EXPECT_TRUE(isSubsequence(Seq, {"b", "c", "d"})); 143 EXPECT_TRUE(isSubsequence(Seq, {"a"})); 144 EXPECT_FALSE(isSubsequence(Seq, {"a", "d"})); 145 } 146 147 #define RANDSTRUCT_TEST RecordLayoutRandomization 148 149 TEST(RANDSTRUCT_TEST, UnmarkedStruct) { 150 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 151 struct test { 152 int bacon; 153 long lettuce; 154 long long tomato; 155 float mayonnaise; 156 }; 157 )c"); 158 159 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 160 161 const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); 162 163 EXPECT_FALSE(RD->hasAttr<RandomizeLayoutAttr>()); 164 EXPECT_FALSE(RD->isRandomized()); 165 } 166 167 TEST(RANDSTRUCT_TEST, MarkedNoRandomize) { 168 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 169 struct test { 170 int bacon; 171 long lettuce; 172 long long tomato; 173 float mayonnaise; 174 } __attribute__((no_randomize_layout)); 175 )c"); 176 177 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 178 179 const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); 180 181 EXPECT_TRUE(RD->hasAttr<NoRandomizeLayoutAttr>()); 182 EXPECT_FALSE(RD->isRandomized()); 183 } 184 185 TEST(RANDSTRUCT_TEST, MarkedRandomize) { 186 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 187 struct test { 188 int bacon; 189 long lettuce; 190 long long tomato; 191 float mayonnaise; 192 } __attribute__((randomize_layout)); 193 )c"); 194 195 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 196 197 const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); 198 199 EXPECT_TRUE(RD->hasAttr<RandomizeLayoutAttr>()); 200 EXPECT_TRUE(RD->isRandomized()); 201 } 202 203 TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) { 204 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 205 struct test __attribute__((randomize_layout)); 206 struct test { 207 int bacon; 208 long lettuce; 209 long long tomato; 210 float mayonnaise; 211 } __attribute__((no_randomize_layout)); 212 )c", 213 true); 214 215 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 216 217 const DiagnosticsEngine &Diags = AST->getDiagnostics(); 218 219 EXPECT_FALSE(Diags.hasFatalErrorOccurred()); 220 EXPECT_FALSE(Diags.hasUncompilableErrorOccurred()); 221 EXPECT_FALSE(Diags.hasUnrecoverableErrorOccurred()); 222 EXPECT_EQ(Diags.getNumWarnings(), 1u); 223 EXPECT_EQ(Diags.getNumErrors(), 0u); 224 } 225 226 TEST(RANDSTRUCT_TEST, MismatchedAttrsRandomizeVsNoRandomize) { 227 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 228 struct test { 229 int bacon; 230 long lettuce; 231 long long tomato; 232 float mayonnaise; 233 } __attribute__((randomize_layout)) __attribute__((no_randomize_layout)); 234 )c", 235 true); 236 237 EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); 238 239 const DiagnosticsEngine &Diags = AST->getDiagnostics(); 240 241 EXPECT_TRUE(Diags.hasUncompilableErrorOccurred()); 242 EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred()); 243 EXPECT_EQ(Diags.getNumWarnings(), 0u); 244 EXPECT_EQ(Diags.getNumErrors(), 1u); 245 } 246 247 TEST(RANDSTRUCT_TEST, MismatchedAttrsNoRandomizeVsRandomize) { 248 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 249 struct test3 { 250 int bacon; 251 long lettuce; 252 long long tomato; 253 float mayonnaise; 254 } __attribute__((no_randomize_layout)) __attribute__((randomize_layout)); 255 )c", 256 true); 257 258 EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred()); 259 260 const DiagnosticsEngine &Diags = AST->getDiagnostics(); 261 262 EXPECT_TRUE(Diags.hasUncompilableErrorOccurred()); 263 EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred()); 264 EXPECT_EQ(Diags.getNumWarnings(), 0u); 265 EXPECT_EQ(Diags.getNumErrors(), 1u); 266 } 267 268 TEST(RANDSTRUCT_TEST, CheckAdjacentBitfieldsRemainAdjacentAfterRandomization) { 269 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 270 struct test { 271 int a; 272 int b; 273 int x : 1; 274 int y : 1; 275 int z : 1; 276 int c; 277 } __attribute__((randomize_layout)); 278 )c"); 279 280 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 281 282 const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); 283 const field_names Actual = getFieldNamesFromRecord(RD); 284 const field_names Subseq = {"x", "y", "z"}; 285 286 EXPECT_TRUE(RD->isRandomized()); 287 EXPECT_TRUE(isSubsequence(Actual, Subseq)); 288 } 289 290 TEST(RANDSTRUCT_TEST, CheckVariableLengthArrayMemberRemainsAtEndOfStructure) { 291 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 292 struct test { 293 int a; 294 double b; 295 short c; 296 char name[]; 297 } __attribute__((randomize_layout)); 298 )c"); 299 300 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 301 302 const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); 303 304 EXPECT_TRUE(RD->isRandomized()); 305 } 306 307 TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) { 308 std::unique_ptr<ASTUnit> AST = 309 makeAST(R"c( 310 struct test_struct { 311 char a; 312 float b[3]; 313 short c; 314 int d; 315 } __attribute__((packed, randomize_layout)); 316 317 struct another_struct { 318 char a; 319 char b[5]; 320 int c; 321 } __attribute__((packed, randomize_layout)); 322 323 struct last_struct { 324 char a; 325 long long b; 326 int c[]; 327 } __attribute__((packed, randomize_layout)); 328 )c", 329 false, 330 std::vector<std::string>( 331 {"test_struct", "another_struct", "last_struct"})); 332 333 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 334 335 // FIXME (?): calling getASTRecordLayout is probably a necessary evil so that 336 // Clang's RecordBuilders can actually flesh out the information like 337 // alignment, etc. 338 { 339 const RecordDecl *RD = 340 getRecordDeclFromAST(AST->getASTContext(), "test_struct"); 341 const ASTRecordLayout *Layout = 342 &AST->getASTContext().getASTRecordLayout(RD); 343 344 EXPECT_TRUE(RD->isRandomized()); 345 EXPECT_EQ(19, Layout->getSize().getQuantity()); 346 } 347 348 { 349 const RecordDecl *RD = 350 getRecordDeclFromAST(AST->getASTContext(), "another_struct"); 351 const ASTRecordLayout *Layout = 352 &AST->getASTContext().getASTRecordLayout(RD); 353 354 EXPECT_TRUE(RD->isRandomized()); 355 EXPECT_EQ(10, Layout->getSize().getQuantity()); 356 } 357 358 { 359 const RecordDecl *RD = 360 getRecordDeclFromAST(AST->getASTContext(), "last_struct"); 361 const ASTRecordLayout *Layout = 362 &AST->getASTContext().getASTRecordLayout(RD); 363 364 EXPECT_TRUE(RD->isRandomized()); 365 EXPECT_EQ(9, Layout->getSize().getQuantity()); 366 } 367 } 368 369 TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) { 370 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 371 struct test { 372 int a : 1; 373 int : 0; 374 int b : 1; 375 } __attribute__((randomize_layout)); 376 )c"); 377 378 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 379 380 const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); 381 382 EXPECT_TRUE(RD->isRandomized()); 383 } 384 385 TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) { 386 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 387 union test { 388 int a; 389 int b; 390 int c; 391 int d; 392 int e; 393 int f; 394 } __attribute__((randomize_layout)); 395 )c"); 396 397 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 398 399 const RecordDecl *RD = 400 getRecordDeclFromAST(AST->getASTContext(), "test"); 401 402 EXPECT_FALSE(RD->isRandomized()); 403 } 404 405 TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) { 406 std::unique_ptr<ASTUnit> AST = makeAST(R"c( 407 struct test { 408 int a; 409 struct sub_struct { 410 int b; 411 int c; 412 int d; 413 int e; 414 int f; 415 } __attribute__((randomize_layout)) s; 416 int f; 417 struct { 418 int g; 419 int h; 420 int i; 421 int j; 422 int k; 423 }; 424 int l; 425 union { 426 int m; 427 int n; 428 int o; 429 int p; 430 int q; 431 }; 432 int r; 433 } __attribute__((randomize_layout)); 434 )c"); 435 436 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred()); 437 438 const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test"); 439 440 EXPECT_TRUE(RD->isRandomized()); 441 442 bool AnonStructTested = false; 443 bool AnonUnionTested = false; 444 445 for (const Decl *D : RD->decls()) 446 if (const FieldDecl *FD = dyn_cast<FieldDecl>(D)) { 447 if (const auto *Record = FD->getType()->getAs<RecordType>()) { 448 RD = Record->getDecl(); 449 if (RD->isAnonymousStructOrUnion()) { 450 // These field orders shouldn't change. 451 if (RD->isUnion()) { 452 const field_names Expected = {"m", "n", "o", "p", "q"}; 453 454 EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); 455 AnonUnionTested = true; 456 } else { 457 const field_names Expected = {"g", "h", "i", "j", "k"}; 458 459 EXPECT_EQ(Expected, getFieldNamesFromRecord(RD)); 460 AnonStructTested = true; 461 } 462 } 463 } 464 } 465 466 EXPECT_TRUE(AnonStructTested); 467 EXPECT_TRUE(AnonUnionTested); 468 } 469 470 } // namespace ast_matchers 471 } // namespace clang 472