xref: /llvm-project/clang/unittests/AST/RandstructTest.cpp (revision a7815d33bf8f955f2a1888abbccf974bd4858f79)
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   const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"};
163 
164   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
165   EXPECT_FALSE(RD->hasAttr<RandomizeLayoutAttr>());
166   EXPECT_FALSE(RD->isRandomized());
167 }
168 
169 TEST(RANDSTRUCT_TEST, MarkedNoRandomize) {
170   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
171     struct test {
172         int bacon;
173         long lettuce;
174         long long tomato;
175         float mayonnaise;
176     } __attribute__((no_randomize_layout));
177   )c");
178 
179   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
180 
181   const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
182   const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"};
183 
184   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
185   EXPECT_TRUE(RD->hasAttr<NoRandomizeLayoutAttr>());
186   EXPECT_FALSE(RD->isRandomized());
187 }
188 
189 TEST(RANDSTRUCT_TEST, MarkedRandomize) {
190   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
191     struct test {
192         int bacon;
193         long lettuce;
194         long long tomato;
195         float mayonnaise;
196     } __attribute__((randomize_layout));
197   )c");
198 
199   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
200 
201   const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
202   const field_names Expected = {"lettuce", "bacon", "mayonnaise", "tomato"};
203 
204   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
205   EXPECT_TRUE(RD->hasAttr<RandomizeLayoutAttr>());
206   EXPECT_TRUE(RD->isRandomized());
207 }
208 
209 TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) {
210   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
211     struct test __attribute__((randomize_layout));
212     struct test {
213         int bacon;
214         long lettuce;
215         long long tomato;
216         float mayonnaise;
217     } __attribute__((no_randomize_layout));
218   )c", true);
219 
220   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
221 
222   const DiagnosticsEngine &Diags = AST->getDiagnostics();
223 
224   EXPECT_FALSE(Diags.hasFatalErrorOccurred());
225   EXPECT_FALSE(Diags.hasUncompilableErrorOccurred());
226   EXPECT_FALSE(Diags.hasUnrecoverableErrorOccurred());
227   EXPECT_EQ(Diags.getNumWarnings(), 1u);
228   EXPECT_EQ(Diags.getNumErrors(), 0u);
229 }
230 
231 TEST(RANDSTRUCT_TEST, MismatchedAttrsRandomizeVsNoRandomize) {
232   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
233     struct test {
234         int bacon;
235         long lettuce;
236         long long tomato;
237         float mayonnaise;
238     } __attribute__((randomize_layout)) __attribute__((no_randomize_layout));
239   )c", true);
240 
241   EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred());
242 
243   const DiagnosticsEngine &Diags = AST->getDiagnostics();
244 
245   EXPECT_TRUE(Diags.hasUncompilableErrorOccurred());
246   EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred());
247   EXPECT_EQ(Diags.getNumWarnings(), 0u);
248   EXPECT_EQ(Diags.getNumErrors(), 1u);
249 }
250 
251 TEST(RANDSTRUCT_TEST, MismatchedAttrsNoRandomizeVsRandomize) {
252   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
253     struct test3 {
254         int bacon;
255         long lettuce;
256         long long tomato;
257         float mayonnaise;
258     } __attribute__((no_randomize_layout)) __attribute__((randomize_layout));
259   )c", true);
260 
261   EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred());
262 
263   const DiagnosticsEngine &Diags = AST->getDiagnostics();
264 
265   EXPECT_TRUE(Diags.hasUncompilableErrorOccurred());
266   EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred());
267   EXPECT_EQ(Diags.getNumWarnings(), 0u);
268   EXPECT_EQ(Diags.getNumErrors(), 1u);
269 }
270 
271 TEST(RANDSTRUCT_TEST, CheckAdjacentBitfieldsRemainAdjacentAfterRandomization) {
272   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
273     struct test {
274         int a;
275         int b;
276         int x : 1;
277         int y : 1;
278         int z : 1;
279         int c;
280     } __attribute__((randomize_layout));
281   )c");
282 
283   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
284 
285   const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
286   const field_names Expected = {
287       "a", "b", "c", "x", "y", "z" // x, y, z needs to be a subsequnce.
288   };
289 
290   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
291   EXPECT_TRUE(RD->isRandomized());
292 }
293 
294 // FIXME: Enable when fix for flexible arrays is submitted.
295 TEST(RANDSTRUCT_TEST, DISABLED_CheckVariableLengthArrayMemberRemainsAtEndOfStructure) {
296   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
297     struct test {
298         int a;
299         double b;
300         short c;
301         char name[];
302     } __attribute__((randomize_layout));
303   )c");
304 
305   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
306 
307   const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
308   const field_names Expected = {"c", "a", "b", "name"};
309 
310   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
311   EXPECT_TRUE(RD->isRandomized());
312 }
313 
314 TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) {
315   std::unique_ptr<ASTUnit> AST =
316       makeAST(R"c(
317     struct test_struct {
318         char a;
319         float b[3];
320         short c;
321         int d;
322     } __attribute__((packed, randomize_layout));
323 
324     struct another_struct {
325         char a;
326         char b[5];
327         int c;
328     } __attribute__((packed, randomize_layout));
329 
330     struct last_struct {
331         char a;
332         long long b;
333         int c[];
334     } __attribute__((packed, randomize_layout));
335   )c", false, field_names({"test_struct", "another_struct", "last_struct"}));
336 
337   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
338 
339   // FIXME (?): calling getASTRecordLayout is probably a necessary evil so that
340   // Clang's RecordBuilders can actually flesh out the information like
341   // alignment, etc.
342   {
343     const RecordDecl *RD =
344         getRecordDeclFromAST(AST->getASTContext(), "test_struct");
345     const ASTRecordLayout *Layout =
346         &AST->getASTContext().getASTRecordLayout(RD);
347     const field_names Expected = {"b", "a", "c", "d"};
348 
349     EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
350     EXPECT_TRUE(RD->isRandomized());
351     EXPECT_EQ(19, Layout->getSize().getQuantity());
352   }
353 
354   {
355     const RecordDecl *RD =
356         getRecordDeclFromAST(AST->getASTContext(), "another_struct");
357     const ASTRecordLayout *Layout =
358         &AST->getASTContext().getASTRecordLayout(RD);
359     const field_names Expected = {"c", "b", "a"};
360 
361     EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
362     EXPECT_TRUE(RD->isRandomized());
363     EXPECT_EQ(10, Layout->getSize().getQuantity());
364   }
365 
366   {
367     const RecordDecl *RD =
368         getRecordDeclFromAST(AST->getASTContext(), "last_struct");
369     const ASTRecordLayout *Layout =
370         &AST->getASTContext().getASTRecordLayout(RD);
371     const field_names Expected = {"a", "c", "b"};
372 
373     EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
374     EXPECT_TRUE(RD->isRandomized());
375     EXPECT_EQ(9, Layout->getSize().getQuantity());
376   }
377 }
378 
379 TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) {
380   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
381     struct test {
382         int a : 1;
383         int   : 0;
384         int b : 1;
385     } __attribute__((randomize_layout));
386   )c");
387 
388   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
389 
390   const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
391   const field_names Expected = {"a", "b", ""};
392 
393   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
394   EXPECT_TRUE(RD->isRandomized());
395 }
396 
397 TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) {
398   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
399     union test {
400         int a;
401         int b;
402         int c;
403         int d;
404         int e;
405         int f;
406     } __attribute__((randomize_layout));
407   )c");
408 
409   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
410 
411   const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
412   const field_names Expected = {"a", "b", "c", "d", "e", "f"};
413 
414   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
415   EXPECT_FALSE(RD->isRandomized());
416 }
417 
418 TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) {
419   std::unique_ptr<ASTUnit> AST = makeAST(R"c(
420     struct test {
421         int a;
422         struct sub_struct {
423             int b;
424             int c;
425             int d;
426             int e;
427             int f;
428         } __attribute__((randomize_layout)) s;
429         int f;
430         struct {
431             int g;
432             int h;
433             int i;
434             int j;
435             int k;
436         };
437         int l;
438         union {
439             int m;
440             int n;
441             int o;
442             int p;
443             int q;
444         };
445         int r;
446     } __attribute__((randomize_layout));
447   )c");
448 
449   EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
450 
451   const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
452   const field_names Expected = {"", "l", "", "r", "s", "a", "f"};
453 
454   EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
455   EXPECT_TRUE(RD->isRandomized());
456 
457   bool AnonStructTested = false;
458   bool AnonUnionTested = false;
459 
460   for (const Decl *D : RD->decls())
461     if (const FieldDecl *FD = dyn_cast<FieldDecl>(D)) {
462       if (const auto *Record = FD->getType()->getAs<RecordType>()) {
463         RD = Record->getDecl();
464         if (RD->isAnonymousStructOrUnion()) {
465           // These field orders shouldn't change.
466           if (RD->isUnion()) {
467             const field_names Expected = {"m", "n", "o", "p", "q"};
468 
469             EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
470             AnonUnionTested = true;
471           } else {
472             const field_names Expected = {"g", "h", "i", "j", "k"};
473 
474             EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
475             AnonStructTested = true;
476           }
477         }
478       }
479     }
480 
481   EXPECT_TRUE(AnonStructTested);
482   EXPECT_TRUE(AnonUnionTested);
483 }
484 
485 } // namespace ast_matchers
486 } // namespace clang
487