xref: /llvm-project/clang/unittests/AST/RandstructTest.cpp (revision 8c77a75fb6a82a4cc2182bca89e007e7190a83de)
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