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
34using namespace clang;
35using namespace clang::ast_matchers;
36using namespace clang::randstruct;
37
38using field_names = std::vector<std::string>;
39
40constexpr const char Seed[] = "1234567890abcdef";
41
42static 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
49static std::vector<std::string> getFieldNamesFromRecord(const RecordDecl *RD) {
50 std::vector<std::string> Fields;
51
52 Fields.reserve(n: 8);
53 for (auto *Field : RD->fields())
54 Fields.push_back(Field->getNameAsString());
55
56 return Fields;
57}
58
59static 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
78static 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(C: LHS->getASTContext(), Name: RecordName);
83 const RecordDecl *RHSRD =
84 getRecordDeclFromAST(C: LHS->getASTContext(), Name: RecordName);
85
86 return getFieldNamesFromRecord(RD: LHSRD) == getFieldNamesFromRecord(RD: RHSRD);
87}
88
89static std::unique_ptr<ASTUnit>
90makeAST(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: Lang_C99);
93 Args.push_back(x: "-frandomize-layout-seed=" + std::string(Seed));
94
95 IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer();
96
97 std::unique_ptr<ASTUnit> AST = tooling::buildASTFromCodeWithArgs(
98 Code: SourceCode, Args, FileName: "input.c", ToolName: "clang-tool",
99 PCHContainerOps: std::make_shared<PCHContainerOperations>(),
100 Adjuster: tooling::getClangStripDependencyFileAdjuster(),
101 VirtualMappedFiles: tooling::FileContentMappings(), DiagConsumer: &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: Lang_C99);
112 Args.push_back(x: "-frandomize-layout-seed-file=" +
113 SeedFile.getFilename().str());
114
115 std::unique_ptr<ASTUnit> ASTFileSeed = tooling::buildASTFromCodeWithArgs(
116 Code: SourceCode, Args, FileName: "input.c", ToolName: "clang-tool",
117 PCHContainerOps: std::make_shared<PCHContainerOperations>(),
118 Adjuster: tooling::getClangStripDependencyFileAdjuster(),
119 VirtualMappedFiles: tooling::FileContentMappings(), DiagConsumer: &IgnoringConsumer);
120
121 if (!ExpectError) {
122 if (RecordNames.empty())
123 RecordNames.push_back(x: "test");
124
125 for (std::string Name : RecordNames)
126 EXPECT_TRUE(recordsEqual(AST, ASTFileSeed, Name));
127 }
128
129 return AST;
130}
131
132namespace clang {
133namespace ast_matchers {
134
135long declCount(const RecordDecl *RD) {
136 return llvm::count_if(RD->decls(), [&](const Decl *D) {
137 return isa<FieldDecl>(Val: D) || isa<RecordDecl>(Val: D);
138 });
139}
140
141#define RANDSTRUCT_TEST_SUITE_TEST RecordLayoutRandomizationTestSuiteTest
142
143TEST(RANDSTRUCT_TEST_SUITE_TEST, CanDetermineIfSubsequenceExists) {
144 const field_names Seq = {"a", "b", "c", "d"};
145
146 EXPECT_TRUE(isSubsequence(Seq, {"b", "c"}));
147 EXPECT_TRUE(isSubsequence(Seq, {"a", "b", "c", "d"}));
148 EXPECT_TRUE(isSubsequence(Seq, {"b", "c", "d"}));
149 EXPECT_TRUE(isSubsequence(Seq, {"a"}));
150 EXPECT_FALSE(isSubsequence(Seq, {"a", "d"}));
151}
152
153#define RANDSTRUCT_TEST RecordLayoutRandomization
154
155TEST(RANDSTRUCT_TEST, UnmarkedStruct) {
156 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
157 struct test {
158 int bacon;
159 long lettuce;
160 long long tomato;
161 float mayonnaise;
162 };
163 )c");
164
165 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
166
167 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
168 long OriginalDeclCount = declCount(RD);
169
170 EXPECT_FALSE(RD->hasAttr<RandomizeLayoutAttr>());
171 EXPECT_FALSE(RD->isRandomized());
172 EXPECT_EQ(OriginalDeclCount, declCount(RD));
173}
174
175TEST(RANDSTRUCT_TEST, MarkedNoRandomize) {
176 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
177 struct test {
178 int bacon;
179 long lettuce;
180 long long tomato;
181 float mayonnaise;
182 } __attribute__((no_randomize_layout));
183 )c");
184
185 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
186
187 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
188 long OriginalDeclCount = declCount(RD);
189
190 EXPECT_TRUE(RD->hasAttr<NoRandomizeLayoutAttr>());
191 EXPECT_FALSE(RD->isRandomized());
192 EXPECT_EQ(OriginalDeclCount, declCount(RD));
193}
194
195TEST(RANDSTRUCT_TEST, MarkedRandomize) {
196 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
197 struct test {
198 int bacon;
199 long lettuce;
200 long long tomato;
201 float mayonnaise;
202 } __attribute__((randomize_layout));
203 )c");
204
205 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
206
207 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
208 long OriginalDeclCount = declCount(RD);
209
210 EXPECT_TRUE(RD->hasAttr<RandomizeLayoutAttr>());
211 EXPECT_TRUE(RD->isRandomized());
212 EXPECT_EQ(OriginalDeclCount, declCount(RD));
213}
214
215TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) {
216 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
217 struct test __attribute__((randomize_layout));
218 struct test {
219 int bacon;
220 long lettuce;
221 long long tomato;
222 float mayonnaise;
223 } __attribute__((no_randomize_layout));
224 )c",
225 ExpectError: true);
226
227 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
228
229 const DiagnosticsEngine &Diags = AST->getDiagnostics();
230
231 EXPECT_FALSE(Diags.hasFatalErrorOccurred());
232 EXPECT_FALSE(Diags.hasUncompilableErrorOccurred());
233 EXPECT_FALSE(Diags.hasUnrecoverableErrorOccurred());
234 EXPECT_EQ(Diags.getNumWarnings(), 1u);
235 EXPECT_EQ(Diags.getNumErrors(), 0u);
236}
237
238TEST(RANDSTRUCT_TEST, MismatchedAttrsRandomizeVsNoRandomize) {
239 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
240 struct test {
241 int bacon;
242 long lettuce;
243 long long tomato;
244 float mayonnaise;
245 } __attribute__((randomize_layout)) __attribute__((no_randomize_layout));
246 )c",
247 ExpectError: true);
248
249 EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred());
250
251 const DiagnosticsEngine &Diags = AST->getDiagnostics();
252
253 EXPECT_TRUE(Diags.hasUncompilableErrorOccurred());
254 EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred());
255 EXPECT_EQ(Diags.getNumWarnings(), 0u);
256 EXPECT_EQ(Diags.getNumErrors(), 1u);
257}
258
259TEST(RANDSTRUCT_TEST, MismatchedAttrsNoRandomizeVsRandomize) {
260 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
261 struct test3 {
262 int bacon;
263 long lettuce;
264 long long tomato;
265 float mayonnaise;
266 } __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
267 )c",
268 ExpectError: true);
269
270 EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred());
271
272 const DiagnosticsEngine &Diags = AST->getDiagnostics();
273
274 EXPECT_TRUE(Diags.hasUncompilableErrorOccurred());
275 EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred());
276 EXPECT_EQ(Diags.getNumWarnings(), 0u);
277 EXPECT_EQ(Diags.getNumErrors(), 1u);
278}
279
280TEST(RANDSTRUCT_TEST, CheckAdjacentBitfieldsRemainAdjacentAfterRandomization) {
281 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
282 struct test {
283 int a;
284 int b;
285 int x : 1;
286 int y : 1;
287 int z : 1;
288 int c;
289 } __attribute__((randomize_layout));
290 )c");
291
292 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
293
294 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
295 long OriginalDeclCount = declCount(RD);
296
297 const field_names Actual = getFieldNamesFromRecord(RD);
298 const field_names Subseq = {"x", "y", "z"};
299
300 EXPECT_TRUE(RD->isRandomized());
301 EXPECT_TRUE(isSubsequence(Actual, Subseq));
302 EXPECT_EQ(OriginalDeclCount, declCount(RD));
303}
304
305TEST(RANDSTRUCT_TEST, CheckFlexibleArrayMemberRemainsAtEndOfStructure1) {
306 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
307 struct test {
308 int a;
309 int b;
310 int c;
311 int d;
312 int e;
313 int f;
314 int g;
315 int h;
316 char name[];
317 } __attribute__((randomize_layout));
318 )c");
319
320 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
321
322 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
323 long OriginalDeclCount = declCount(RD);
324
325 EXPECT_TRUE(RD->hasFlexibleArrayMember());
326 EXPECT_TRUE(RD->isRandomized());
327 EXPECT_EQ(OriginalDeclCount, declCount(RD));
328 EXPECT_EQ(getFieldNamesFromRecord(RD).back(), "name");
329}
330
331TEST(RANDSTRUCT_TEST, CheckFlexibleArrayMemberRemainsAtEndOfStructure2) {
332 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
333 struct test {
334 int a;
335 int b;
336 int c;
337 int d;
338 int e;
339 int f;
340 int g;
341 int h;
342 char name[0];
343 } __attribute__((randomize_layout));
344 )c");
345
346 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
347
348 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
349 long OriginalDeclCount = declCount(RD);
350
351 EXPECT_FALSE(RD->hasFlexibleArrayMember());
352 EXPECT_TRUE(RD->isRandomized());
353 EXPECT_EQ(OriginalDeclCount, declCount(RD));
354 EXPECT_EQ(getFieldNamesFromRecord(RD).back(), "name");
355}
356
357TEST(RANDSTRUCT_TEST, CheckFlexibleArrayMemberRemainsAtEndOfStructure3) {
358 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
359 struct test {
360 int a;
361 int b;
362 int c;
363 int d;
364 int e;
365 int f;
366 int g;
367 int h;
368 char name[1];
369 } __attribute__((randomize_layout));
370 )c");
371
372 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
373
374 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
375 long OriginalDeclCount = declCount(RD);
376
377 EXPECT_FALSE(RD->hasFlexibleArrayMember());
378 EXPECT_TRUE(RD->isRandomized());
379 EXPECT_EQ(OriginalDeclCount, declCount(RD));
380 EXPECT_EQ(getFieldNamesFromRecord(RD).back(), "name");
381}
382
383TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) {
384 std::unique_ptr<ASTUnit> AST =
385 makeAST(SourceCode: R"c(
386 struct test_struct {
387 char a;
388 float b[3];
389 short c;
390 int d;
391 } __attribute__((packed, randomize_layout));
392
393 struct another_struct {
394 char a;
395 char b[5];
396 int c;
397 } __attribute__((packed, randomize_layout));
398
399 struct last_struct {
400 char a;
401 long long b;
402 int c[];
403 } __attribute__((packed, randomize_layout));
404 )c",
405 ExpectError: false,
406 RecordNames: std::vector<std::string>(
407 {"test_struct", "another_struct", "last_struct"}));
408
409 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
410
411 // FIXME (?): calling getASTRecordLayout is probably a necessary evil so that
412 // Clang's RecordBuilders can actually flesh out the information like
413 // alignment, etc.
414 {
415 const RecordDecl *RD =
416 getRecordDeclFromAST(C: AST->getASTContext(), Name: "test_struct");
417 const ASTRecordLayout *Layout =
418 &AST->getASTContext().getASTRecordLayout(D: RD);
419 long OriginalDeclCount = declCount(RD);
420
421 EXPECT_TRUE(RD->isRandomized());
422 EXPECT_EQ(19, Layout->getSize().getQuantity());
423 EXPECT_EQ(OriginalDeclCount, declCount(RD));
424 }
425
426 {
427 const RecordDecl *RD =
428 getRecordDeclFromAST(C: AST->getASTContext(), Name: "another_struct");
429 const ASTRecordLayout *Layout =
430 &AST->getASTContext().getASTRecordLayout(D: RD);
431 long OriginalDeclCount = declCount(RD);
432
433 EXPECT_TRUE(RD->isRandomized());
434 EXPECT_EQ(10, Layout->getSize().getQuantity());
435 EXPECT_EQ(OriginalDeclCount, declCount(RD));
436 }
437
438 {
439 const RecordDecl *RD =
440 getRecordDeclFromAST(C: AST->getASTContext(), Name: "last_struct");
441 const ASTRecordLayout *Layout =
442 &AST->getASTContext().getASTRecordLayout(D: RD);
443 long OriginalDeclCount = declCount(RD);
444
445 EXPECT_TRUE(RD->isRandomized());
446 EXPECT_EQ(9, Layout->getSize().getQuantity());
447 EXPECT_EQ(OriginalDeclCount, declCount(RD));
448 }
449}
450
451TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) {
452 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
453 struct test {
454 int a : 1;
455 int : 0;
456 int b : 1;
457 } __attribute__((randomize_layout));
458 )c");
459
460 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
461
462 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
463 long OriginalDeclCount = declCount(RD);
464
465 EXPECT_TRUE(RD->isRandomized());
466 EXPECT_EQ(OriginalDeclCount, declCount(RD));
467}
468
469TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) {
470 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
471 union test {
472 int a;
473 int b;
474 int c;
475 int d;
476 int e;
477 int f;
478 } __attribute__((randomize_layout));
479 )c");
480
481 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
482
483 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
484 long OriginalDeclCount = declCount(RD);
485
486 EXPECT_FALSE(RD->isRandomized());
487 EXPECT_EQ(OriginalDeclCount, declCount(RD));
488}
489
490TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) {
491 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
492 struct test {
493 int a;
494 struct sub_struct {
495 int b;
496 int c;
497 int d;
498 int e;
499 int f;
500 } __attribute__((randomize_layout)) s;
501 int f;
502 struct {
503 int g;
504 int h;
505 int i;
506 int j;
507 int k;
508 };
509 int l;
510 union {
511 int m;
512 int n;
513 int o;
514 int p;
515 int q;
516 };
517 int r;
518 } __attribute__((randomize_layout));
519 )c");
520
521 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
522
523 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
524 long OriginalDeclCount = declCount(RD);
525
526 EXPECT_TRUE(RD->isRandomized());
527 EXPECT_EQ(OriginalDeclCount, declCount(RD));
528
529 bool AnonStructTested = false;
530 bool AnonUnionTested = false;
531
532 for (const Decl *D : RD->decls())
533 if (const FieldDecl *FD = dyn_cast<FieldDecl>(D)) {
534 if (const auto *Record = FD->getType()->getAs<RecordType>()) {
535 RD = Record->getDecl();
536 if (RD->isAnonymousStructOrUnion()) {
537 // These field orders shouldn't change.
538 if (RD->isUnion()) {
539 const field_names Expected = {"m", "n", "o", "p", "q"};
540
541 EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
542 AnonUnionTested = true;
543 } else {
544 const field_names Expected = {"g", "h", "i", "j", "k"};
545
546 EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
547 AnonStructTested = true;
548 }
549 }
550 }
551 }
552
553 EXPECT_TRUE(AnonStructTested);
554 EXPECT_TRUE(AnonUnionTested);
555}
556
557TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsReferenced) {
558 std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
559 struct test {
560 int bacon;
561 long lettuce;
562 struct { double avocado; char blech; };
563 long long tomato;
564 union { char toast[8]; unsigned toast_thing; };
565 float mayonnaise;
566 } __attribute__((randomize_layout));
567
568 int foo(struct test *t) {
569 return t->blech;
570 }
571
572 char *bar(struct test *t) {
573 return t->toast;
574 }
575 )c");
576
577 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
578
579 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
580 long OriginalDeclCount = declCount(RD);
581
582 EXPECT_TRUE(RD->isRandomized());
583 EXPECT_EQ(OriginalDeclCount, declCount(RD));
584}
585
586TEST(RANDSTRUCT_TEST, AutoRandomizeStructOfFunctionPointers) {
587 const std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
588 typedef void (*func_ptr)();
589
590 struct test {
591 func_ptr a;
592 func_ptr b;
593 func_ptr c;
594 func_ptr d;
595 func_ptr e;
596 func_ptr f;
597 func_ptr g;
598 };
599 )c");
600
601 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
602
603 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
604
605 EXPECT_TRUE(RD->isRandomized());
606}
607
608TEST(RANDSTRUCT_TEST, DisableAutoRandomizeStructOfFunctionPointers) {
609 const std::unique_ptr<ASTUnit> AST = makeAST(SourceCode: R"c(
610 typedef void (*func_ptr)();
611
612 struct test {
613 func_ptr a;
614 func_ptr b;
615 func_ptr c;
616 func_ptr d;
617 func_ptr e;
618 func_ptr f;
619 func_ptr g;
620 } __attribute__((no_randomize_layout));
621 )c");
622
623 EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
624
625 const RecordDecl *RD = getRecordDeclFromAST(C: AST->getASTContext(), Name: "test");
626
627 EXPECT_FALSE(RD->isRandomized());
628}
629
630} // namespace ast_matchers
631} // namespace clang
632

source code of clang/unittests/AST/RandstructTest.cpp