Revert D121556 "[randstruct] Add randomize structure layout support"

This reverts commit 3f0587d0c6.

Not all tests pass after a few rounds of fixes.

I spot one failure that std::shuffle (potentially different results with
different STL implementations) was misused and replaced it with llvm::shuffle,
but there appears to be another failure in a Windows build.

The latest failure is reported on https://reviews.llvm.org/D121556#3440383
This commit is contained in:
Fangrui Song 2022-04-08 18:37:26 -07:00
parent 62c8b185ff
commit a58d0af058
23 changed files with 2 additions and 879 deletions

View File

@ -54,20 +54,6 @@ Major New Features
There is an analogous ``zero_call_used_regs`` attribute to allow for finer
control of this feature.
- Clang now supports randomizing structure layout in C. This feature is a
compile-time hardening technique, making it more difficult for an attacker to
retrieve data from structures. Specify randomization with the
``randomize_layout`` attribute. The corresponding ``no_randomize_layout``
attribute can be used to turn the feature off.
A seed value is required to enable randomization, and is deterministic based
on a seed value. Use the ``-frandomize-layout-seed=`` or
``-frandomize-layout-seed-file=`` flags.
.. note::
Randomizing structure layout is a C-only feature.
Bug Fixes
------------------
- ``CXXNewExpr::getArraySize()`` previously returned a ``llvm::Optional``

View File

@ -4051,12 +4051,6 @@ public:
RecordDeclBits.ParamDestroyedInCallee = V;
}
bool isRandomized() const { return RecordDeclBits.IsRandomized; }
void setIsRandomized(bool V) { RecordDeclBits.IsRandomized = V; }
void reorderFields(const SmallVectorImpl<Decl *> &Fields);
/// Determines whether this declaration represents the
/// injected class name.
///

View File

@ -313,7 +313,6 @@ protected:
friend class ASTReader;
friend class CXXClassMemberWrapper;
friend class LinkageComputer;
friend class RecordDecl;
template<typename decl_type> friend class Redeclarable;
/// Access - Used by C++ decls for the access specifier.
@ -1541,13 +1540,10 @@ class DeclContext {
/// Represents the way this type is passed to a function.
uint64_t ArgPassingRestrictions : 2;
/// Indicates whether this struct has had its field layout randomized.
uint64_t IsRandomized : 1;
};
/// Number of non-inherited bits in RecordDeclBitfields.
enum { NumRecordDeclBits = 15 };
enum { NumRecordDeclBits = 14 };
/// Stores the bits used by OMPDeclareReductionDecl.
/// If modified NumOMPDeclareReductionDeclBits and the accessor

View File

@ -1,38 +0,0 @@
//===- Randstruct.h - Interfact for structure randomization -------*- C++ -*-=//
//
// 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 the interface for Clang's structure field layout
// randomization.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_AST_RANDSTRUCT_H
#define LLVM_CLANG_AST_RANDSTRUCT_H
namespace llvm {
template <typename T> class ArrayRef;
template <typename T> class SmallVectorImpl;
class StringRef;
} // end namespace llvm
namespace clang {
class ASTContext;
class Decl;
class RecordDecl;
namespace randstruct {
bool randomizeStructureLayout(const ASTContext &Context, llvm::StringRef Name,
llvm::ArrayRef<Decl *> Fields,
llvm::SmallVectorImpl<Decl *> &FinalOrdering);
} // namespace randstruct
} // namespace clang
#endif // LLVM_CLANG_AST_RANDSTRUCT_H

View File

@ -3957,18 +3957,3 @@ def HLSLNumThreads: InheritableAttr {
let LangOpts = [HLSL];
let Documentation = [NumThreadsDocs];
}
def RandomizeLayout : InheritableAttr {
let Spellings = [GCC<"randomize_layout">];
let Subjects = SubjectList<[Record]>;
let Documentation = [ClangRandomizeLayoutDocs];
let LangOpts = [COnly];
}
def NoRandomizeLayout : InheritableAttr {
let Spellings = [GCC<"no_randomize_layout">];
let Subjects = SubjectList<[Record]>;
let Documentation = [ClangRandomizeLayoutDocs];
let LangOpts = [COnly];
}
def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>;

View File

@ -6379,35 +6379,3 @@ dictate the thread id. Total number of threads executed is ``X * Y * Z``.
The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sm5-attributes-numthreads
}];
}
def ClangRandomizeLayoutDocs : Documentation {
let Category = DocCatDecl;
let Heading = "randomize_layout, no_randomize_layout";
let Content = [{
The attribute ``randomize_layout``, when attached to a C structure, selects it
for structure layout field randomization; a compile-time hardening technique. A
"seed" value, is specified via the ``-frandomize-layout-seed=`` command line flag.
For example:
.. code-block:: bash
SEED=`od -A n -t x8 -N 32 /dev/urandom | tr -d ' \n'`
make ... CFLAGS="-frandomize-layout-seed=$SEED" ...
You can also supply the seed in a file with ``-frandomize-layout-seed-file=``.
For example:
.. code-block:: bash
od -A n -t x8 -N 32 /dev/urandom | tr -d ' \n' > /tmp/seed_file.txt
make ... CFLAGS="-frandomize-layout-seed-file=/tmp/seed_file.txt" ...
The randomization is deterministic based for a given seed, so the entire
program should be compiled with the same seed, but keep the seed safe
otherwise.
The attribute ``no_randomize_layout``, when attached to a C structure,
instructs the compiler that this structure should not have its field layout
randomized.
}];
}

View File

@ -165,8 +165,6 @@ def err_drv_amdgpu_ieee_without_no_honor_nans : Error<
"invalid argument '-mno-amdgpu-ieee' only allowed with relaxed NaN handling">;
def err_drv_argument_not_allowed_with : Error<
"invalid argument '%0' not allowed with '%1'">;
def err_drv_cannot_open_randomize_layout_seed_file : Error<
"cannot read randomize layout seed file '%0'">;
def err_drv_invalid_version_number : Error<
"invalid version number in '%0'">;
def err_drv_no_linker_llvm_support : Error<

View File

@ -11588,7 +11588,5 @@ def err_hlsl_numthreads_argument_oor : Error<"argument '%select{X|Y|Z}0' to numt
def err_hlsl_numthreads_invalid : Error<"total number of threads cannot exceed %0">;
def err_hlsl_attribute_param_mismatch : Error<"%0 attribute parameters do not match the previous declaration">;
// Layout randomization warning.
def err_cast_from_randomized_struct : Error<
"casting from randomized structure pointer type %0 to %1">;
} // end of sema component.

View File

@ -445,9 +445,6 @@ public:
/// The default stream kind used for HIP kernel launching.
GPUDefaultStreamKind GPUDefaultStream;
/// The seed used by the randomize structure layout feature.
std::string RandstructSeed;
LangOptions();
// Define accessors/mutators for language options of enumeration type.

View File

@ -2122,12 +2122,6 @@ defm merge_all_constants : BoolFOption<"merge-all-constants",
def fmessage_length_EQ : Joined<["-"], "fmessage-length=">, Group<f_Group>, Flags<[CC1Option]>,
HelpText<"Format message diagnostics so that they fit within N columns">,
MarshallingInfoInt<DiagnosticOpts<"MessageLength">>;
def frandomize_layout_seed_EQ : Joined<["-"], "frandomize-layout-seed=">,
MetaVarName<"<seed>">, Group<f_clang_Group>, Flags<[CC1Option]>,
HelpText<"The seed used by the randomize structure layout feature">;
def frandomize_layout_seed_file_EQ : Joined<["-"], "frandomize-layout-seed-file=">,
MetaVarName<"<file>">, Group<f_clang_Group>, Flags<[CC1Option]>,
HelpText<"File holding the seed used by the randomize structure layout feature">;
def fms_compatibility : Flag<["-"], "fms-compatibility">, Group<f_Group>, Flags<[CC1Option, CoreOption]>,
HelpText<"Enable full Microsoft Visual C++ compatibility">,
MarshallingInfoFlag<LangOpts<"MSVCCompat">>;

View File

@ -97,7 +97,6 @@ add_clang_library(clangAST
ParentMap.cpp
PrintfFormatString.cpp
QualTypeNames.cpp
Randstruct.cpp
RawCommentList.cpp
RecordLayout.cpp
RecordLayoutBuilder.cpp

View File

@ -30,7 +30,6 @@
#include "clang/AST/ODRHash.h"
#include "clang/AST/PrettyDeclStackTrace.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Randstruct.h"
#include "clang/AST/Redeclarable.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/TemplateBase.h"
@ -4584,7 +4583,6 @@ RecordDecl::RecordDecl(Kind DK, TagKind TK, const ASTContext &C,
setHasNonTrivialToPrimitiveCopyCUnion(false);
setParamDestroyedInCallee(false);
setArgPassingRestrictions(APK_CanPassInRegs);
setIsRandomized(false);
}
RecordDecl *RecordDecl::Create(const ASTContext &C, TagKind TK, DeclContext *DC,
@ -4668,12 +4666,6 @@ bool RecordDecl::isMsStruct(const ASTContext &C) const {
return hasAttr<MSStructAttr>() || C.getLangOpts().MSBitfields == 1;
}
void RecordDecl::reorderFields(const SmallVectorImpl<Decl *> &Fields) {
std::tie(FirstDecl, LastDecl) = DeclContext::BuildDeclChain(Fields, false);
LastDecl->NextInContextAndBits.setPointer(nullptr);
setIsRandomized(true);
}
void RecordDecl::LoadFieldsFromExternalStorage() const {
ExternalASTSource *Source = getASTContext().getExternalSource();
assert(hasExternalLexicalStorage() && Source && "No external storage?");

View File

@ -1,223 +0,0 @@
//===--- Randstruct.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 the implementation for Clang's structure field layout
// randomization.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/Randstruct.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/Attr.h"
#include "clang/Basic/Diagnostic.h"
#include "llvm/ADT/SmallVector.h"
#include <algorithm>
#include <random>
#include <set>
#include <sstream>
#include <string>
using clang::ASTContext;
using clang::FieldDecl;
using llvm::SmallVector;
namespace {
// FIXME: Replace this with some discovery once that mechanism exists.
enum { CACHE_LINE = 64 };
// The Bucket class holds the struct fields we're trying to fill to a
// cache-line.
class Bucket {
SmallVector<FieldDecl *, 64> Fields;
int Size = 0;
public:
virtual ~Bucket() = default;
SmallVector<FieldDecl *, 64> &fields() { return Fields; }
void addField(FieldDecl *Field, int FieldSize);
virtual bool canFit(int FieldSize) const {
return Size + FieldSize <= CACHE_LINE;
}
virtual bool isBitfieldRun() const { return false; }
bool full() const { return Size >= CACHE_LINE; }
};
void Bucket::addField(FieldDecl *Field, int FieldSize) {
Size += FieldSize;
Fields.push_back(Field);
}
struct BitfieldRunBucket : public Bucket {
bool canFit(int FieldSize) const override { return true; }
bool isBitfieldRun() const override { return true; }
};
void randomizeStructureLayoutImpl(const ASTContext &Context,
llvm::SmallVectorImpl<FieldDecl *> &FieldsOut,
std::mt19937 &RNG) {
// All of the Buckets produced by best-effort cache-line algorithm.
SmallVector<std::unique_ptr<Bucket>, 16> Buckets;
// The current bucket of fields that we are trying to fill to a cache-line.
std::unique_ptr<Bucket> CurrentBucket;
// The current bucket containing the run of adjacent bitfields to ensure they
// remain adjacent.
std::unique_ptr<BitfieldRunBucket> CurrentBitfieldRun;
// Tracks the number of fields that we failed to fit to the current bucket,
// and thus still need to be added later.
size_t Skipped = 0;
while (!FieldsOut.empty()) {
// If we've Skipped more fields than we have remaining to place, that means
// that they can't fit in our current bucket, and we need to start a new
// one.
if (Skipped >= FieldsOut.size()) {
Skipped = 0;
Buckets.push_back(std::move(CurrentBucket));
}
// Take the first field that needs to be put in a bucket.
auto FieldIter = FieldsOut.begin();
FieldDecl *FD = *FieldIter;
if (FD->isBitField() && !FD->isZeroLengthBitField(Context)) {
// Start a bitfield run if this is the first bitfield we have found.
if (!CurrentBitfieldRun)
CurrentBitfieldRun = std::make_unique<BitfieldRunBucket>();
// We've placed the field, and can remove it from the "awaiting Buckets"
// vector called "Fields."
CurrentBitfieldRun->addField(FD, /*FieldSize is irrelevant here*/ 1);
FieldsOut.erase(FieldIter);
continue;
}
// Else, current field is not a bitfield. If we were previously in a
// bitfield run, end it.
if (CurrentBitfieldRun)
Buckets.push_back(std::move(CurrentBitfieldRun));
// If we don't have a bucket, make one.
if (!CurrentBucket)
CurrentBucket = std::make_unique<Bucket>();
uint64_t Width = Context.getTypeInfo(FD->getType()).Width;
if (Width >= CACHE_LINE) {
std::unique_ptr<Bucket> OverSized = std::make_unique<Bucket>();
OverSized->addField(FD, Width);
FieldsOut.erase(FieldIter);
Buckets.push_back(std::move(OverSized));
continue;
}
// If it fits, add it.
if (CurrentBucket->canFit(Width)) {
CurrentBucket->addField(FD, Width);
FieldsOut.erase(FieldIter);
// If it's now full, tie off the bucket.
if (CurrentBucket->full()) {
Skipped = 0;
Buckets.push_back(std::move(CurrentBucket));
}
} else {
// We can't fit it in our current bucket. Move to the end for processing
// later.
++Skipped; // Mark it skipped.
FieldsOut.push_back(FD);
FieldsOut.erase(FieldIter);
}
}
// Done processing the fields awaiting a bucket.
// If we were filling a bucket, tie it off.
if (CurrentBucket)
Buckets.push_back(std::move(CurrentBucket));
// If we were processing a bitfield run bucket, tie it off.
if (CurrentBitfieldRun)
Buckets.push_back(std::move(CurrentBitfieldRun));
llvm::shuffle(std::begin(Buckets), std::end(Buckets), RNG);
// Produce the new ordering of the elements from the Buckets.
SmallVector<FieldDecl *, 16> FinalOrder;
for (const std::unique_ptr<Bucket> &B : Buckets) {
llvm::SmallVectorImpl<FieldDecl *> &RandFields = B->fields();
if (!B->isBitfieldRun())
llvm::shuffle(std::begin(RandFields), std::end(RandFields), RNG);
FinalOrder.insert(FinalOrder.end(), RandFields.begin(), RandFields.end());
}
FieldsOut = FinalOrder;
}
} // anonymous namespace
namespace clang {
namespace randstruct {
bool randomizeStructureLayout(const ASTContext &Context, StringRef Name,
ArrayRef<Decl *> Fields,
SmallVectorImpl<Decl *> &FinalOrdering) {
SmallVector<FieldDecl *, 64> RandomizedFields;
unsigned TotalNumFields = 0;
for (Decl *D : Fields) {
++TotalNumFields;
if (auto *FD = dyn_cast<FieldDecl>(D))
RandomizedFields.push_back(FD);
else
FinalOrdering.push_back(D);
}
if (RandomizedFields.empty())
return false;
// Struct might end with a variable-length array or an array of size 0 or 1,
// in which case we don't want to randomize it.
FieldDecl *VLA = nullptr;
const auto *CA =
dyn_cast<ConstantArrayType>(RandomizedFields.back()->getType());
if ((CA && (CA->getSize().sle(2) || CA->isIncompleteArrayType())) ||
llvm::any_of(Fields, [](Decl *D) {
if (const FieldDecl *FD = dyn_cast<FieldDecl>(D)) {
const Type *FDTy = FD->getType().getTypePtr();
if (const RecordType *FDTTy = FDTy->getAs<RecordType>())
return FDTTy->getDecl()->hasFlexibleArrayMember();
}
return false;
}))
VLA = RandomizedFields.pop_back_val();
std::string Seed = (Context.getLangOpts().RandstructSeed + Name).str();
std::seed_seq SeedSeq(Seed.begin(), Seed.end());
std::mt19937 RNG(SeedSeq);
randomizeStructureLayoutImpl(Context, RandomizedFields, RNG);
if (VLA)
RandomizedFields.push_back(VLA);
FinalOrdering.insert(FinalOrdering.end(), RandomizedFields.begin(),
RandomizedFields.end());
assert(TotalNumFields == FinalOrdering.size() &&
"Decl count has been altered after Randstruct randomization!");
return true;
}
} // end namespace randstruct
} // end namespace clang

View File

@ -5907,14 +5907,6 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back(
Args.MakeArgString("-fmessage-length=" + Twine(MessageLength)));
if (Arg *A = Args.getLastArg(options::OPT_frandomize_layout_seed_EQ))
CmdArgs.push_back(
Args.MakeArgString("-frandomize-layout-seed=" + Twine(A->getValue(0))));
if (Arg *A = Args.getLastArg(options::OPT_frandomize_layout_seed_file_EQ))
CmdArgs.push_back(Args.MakeArgString("-frandomize-layout-seed-file=" +
Twine(A->getValue(0))));
// -fvisibility= and -fvisibility-ms-compat are of a piece.
if (const Arg *A = Args.getLastArg(options::OPT_fvisibility_EQ,
options::OPT_fvisibility_ms_compat)) {

View File

@ -94,7 +94,6 @@
#include <cassert>
#include <cstddef>
#include <cstring>
#include <fstream>
#include <memory>
#include <string>
#include <tuple>
@ -3686,9 +3685,6 @@ void CompilerInvocation::GenerateLangArgs(const LangOptions &Opts,
for (const auto &MP : Opts.MacroPrefixMap)
GenerateArg(Args, OPT_fmacro_prefix_map_EQ, MP.first + "=" + MP.second, SA);
if (!Opts.RandstructSeed.empty())
GenerateArg(Args, OPT_frandomize_layout_seed_EQ, Opts.RandstructSeed, SA);
}
bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
@ -4241,19 +4237,6 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
Diags.Report(diag::err_cc1_unbounded_vscale_min);
}
if (const Arg *A = Args.getLastArg(OPT_frandomize_layout_seed_file_EQ)) {
std::ifstream SeedFile(A->getValue(0));
if (!SeedFile.is_open())
Diags.Report(diag::err_drv_cannot_open_randomize_layout_seed_file)
<< A->getValue(0);
std::getline(SeedFile, Opts.RandstructSeed);
}
if (const Arg *A = Args.getLastArg(OPT_frandomize_layout_seed_EQ))
Opts.RandstructSeed = A->getValue(0);
return Diags.getNumErrors() == NumErrorsBefore;
}

View File

@ -3129,23 +3129,6 @@ void CastOperation::CheckCStyleCast() {
Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type)
<< SrcType << DestType << OpRange;
if (isa<PointerType>(SrcType) && isa<PointerType>(DestType)) {
QualType SrcTy = cast<PointerType>(SrcType)->getPointeeType();
QualType DestTy = cast<PointerType>(DestType)->getPointeeType();
const RecordDecl *SrcRD = SrcTy->getAsRecordDecl();
const RecordDecl *DestRD = DestTy->getAsRecordDecl();
if (SrcRD && DestRD && SrcRD->hasAttr<RandomizeLayoutAttr>() &&
SrcRD != DestRD) {
// The struct we are casting the pointer from was randomized.
Self.Diag(OpRange.getBegin(), diag::err_cast_from_randomized_struct)
<< SrcType << DestType;
SrcExpr = ExprError();
return;
}
}
DiagnoseCastOfObjCSEL(Self, SrcExpr, DestType);
DiagnoseCallingConvCast(Self, SrcExpr, DestType, OpRange);
DiagnoseBadFunctionCast(Self, SrcExpr, DestType);

View File

@ -24,7 +24,6 @@
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/NonTrivialTypeVisitor.h"
#include "clang/AST/Randstruct.h"
#include "clang/AST/StmtCXX.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/PartialDiagnostic.h"
@ -17969,18 +17968,6 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
// Handle attributes before checking the layout.
ProcessDeclAttributeList(S, Record, Attrs);
// Maybe randomize the field order.
if (!getLangOpts().CPlusPlus && Record->hasAttr<RandomizeLayoutAttr>() &&
!Record->isUnion() && !getLangOpts().RandstructSeed.empty() &&
!Record->isRandomized()) {
SmallVector<Decl *, 32> OrigFieldOrdering(Record->fields());
SmallVector<Decl *, 32> NewFieldOrdering;
if (randstruct::randomizeStructureLayout(
Context, Record->getNameAsString(), OrigFieldOrdering,
NewFieldOrdering))
Record->reorderFields(NewFieldOrdering);
}
// We may have deferred checking for a deleted destructor. Check now.
if (CXXRecord) {
auto *Dtor = CXXRecord->getDestructor();

View File

@ -5121,21 +5121,6 @@ static void handleLifetimeCategoryAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
}
}
static void handleRandomizeLayoutAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
if (checkAttrMutualExclusion<NoRandomizeLayoutAttr>(S, D, AL))
return;
if (!D->hasAttr<RandomizeLayoutAttr>())
D->addAttr(::new (S.Context) RandomizeLayoutAttr(S.Context, AL));
}
static void handleNoRandomizeLayoutAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
if (checkAttrMutualExclusion<RandomizeLayoutAttr>(S, D, AL))
return;
if (!D->hasAttr<NoRandomizeLayoutAttr>())
D->addAttr(::new (S.Context) NoRandomizeLayoutAttr(S.Context, AL));
}
bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
const FunctionDecl *FD) {
if (Attrs.isInvalid())
@ -8650,12 +8635,6 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_Section:
handleSectionAttr(S, D, AL);
break;
case ParsedAttr::AT_RandomizeLayout:
handleRandomizeLayoutAttr(S, D, AL);
break;
case ParsedAttr::AT_NoRandomizeLayout:
handleNoRandomizeLayoutAttr(S, D, AL);
break;
case ParsedAttr::AT_CodeSeg:
handleCodeSegAttr(S, D, AL);
break;

View File

@ -104,7 +104,6 @@
// CHECK-NEXT: NoMicroMips (SubjectMatchRule_function)
// CHECK-NEXT: NoMips16 (SubjectMatchRule_function)
// CHECK-NEXT: NoProfileFunction (SubjectMatchRule_function)
// CHECK-NEXT: NoRandomizeLayout (SubjectMatchRule_record)
// CHECK-NEXT: NoSanitize (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_variable_is_global)
// CHECK-NEXT: NoSanitizeSpecific (SubjectMatchRule_function, SubjectMatchRule_variable_is_global)
// CHECK-NEXT: NoSpeculativeLoadHardening (SubjectMatchRule_function, SubjectMatchRule_objc_method)
@ -149,7 +148,6 @@
// CHECK-NEXT: PassObjectSize (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: PatchableFunctionEntry (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union)
// CHECK-NEXT: RandomizeLayout (SubjectMatchRule_record)
// CHECK-NEXT: ReleaseHandle (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function)
// CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)

View File

@ -25,7 +25,6 @@ add_clang_unittest(ASTTests
EvaluateAsRValueTest.cpp
ExternalASTSourceTest.cpp
NamedDeclPrinterTest.cpp
RandstructTest.cpp
RecursiveASTVisitorTest.cpp
SizelessTypesTest.cpp
SourceLocationTest.cpp

View File

@ -1,442 +0,0 @@
//===- 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=StructureLayoutRandomization*`
*/
#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 <vector>
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::randstruct;
using field_names = std::vector<std::string>;
namespace {
std::unique_ptr<ASTUnit> makeAST(const std::string &SourceCode) {
std::vector<std::string> Args = getCommandLineArgsForTesting(Lang_C99);
Args.push_back("-frandomize-layout-seed=1234567890abcdef");
IgnoringDiagConsumer IgnoringConsumer = IgnoringDiagConsumer();
return tooling::buildASTFromCodeWithArgs(
SourceCode, Args, "input.c", "clang-tool",
std::make_shared<PCHContainerOperations>(),
tooling::getClangStripDependencyFileAdjuster(),
tooling::FileContentMappings(), &IgnoringConsumer);
}
RecordDecl *getRecordDeclFromAST(const ASTContext &C, const std::string &Name) {
RecordDecl *RD = FirstDeclMatcher<RecordDecl>().match(
C.getTranslationUnitDecl(), recordDecl(hasName(Name)));
return RD;
}
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;
}
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;
}
} // end anonymous namespace
namespace clang {
namespace ast_matchers {
#define RANDSTRUCT_TEST_SUITE_TEST StructureLayoutRandomizationTestSuiteTest
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 StructureLayoutRandomization
TEST(RANDSTRUCT_TEST, UnmarkedStruct) {
const 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");
const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"};
EXPECT_FALSE(RD->hasAttr<RandomizeLayoutAttr>());
EXPECT_FALSE(RD->isRandomized());
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
TEST(RANDSTRUCT_TEST, MarkedNoRandomize) {
const 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");
const field_names Expected = {"bacon", "lettuce", "tomato", "mayonnaise"};
EXPECT_TRUE(RD->hasAttr<NoRandomizeLayoutAttr>());
EXPECT_FALSE(RD->isRandomized());
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
TEST(RANDSTRUCT_TEST, MarkedRandomize) {
const 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");
const field_names Expected = {"lettuce", "bacon", "mayonnaise", "tomato"};
EXPECT_TRUE(RD->hasAttr<RandomizeLayoutAttr>());
EXPECT_TRUE(RD->isRandomized());
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
TEST(RANDSTRUCT_TEST, MismatchedAttrsDeclVsDef) {
const 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");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
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) {
const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
struct test2 {
int bacon;
long lettuce;
long long tomato;
float mayonnaise;
} __attribute__((randomize_layout)) __attribute__((no_randomize_layout));
)c");
EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred());
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) {
const 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");
EXPECT_TRUE(AST->getDiagnostics().hasErrorOccurred());
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) {
const 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");
const field_names Expected = {"a", "b", "c", "x", "y", "z"};
const field_names Subseq = {"x", "y", "z"};
const field_names Actual = getFieldNamesFromRecord(RD);
EXPECT_TRUE(isSubsequence(Actual, Subseq));
EXPECT_EQ(Expected, Actual);
}
TEST(RANDSTRUCT_TEST, CheckVariableLengthArrayMemberRemainsAtEndOfStructure) {
const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
struct test {
int a;
double b;
short c;
char name[];
} __attribute__((randomize_layout));
)c");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
const RecordDecl *RD = getRecordDeclFromAST(AST->getASTContext(), "test");
const field_names Expected = {"c", "a", "name", "b"};
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
TEST(RANDSTRUCT_TEST, RandstructDoesNotOverrideThePackedAttr) {
const 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");
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);
const field_names Expected = {"b", "a", "c", "d"};
EXPECT_EQ(19, Layout->getSize().getQuantity());
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
{
const RecordDecl *RD =
getRecordDeclFromAST(AST->getASTContext(), "another_struct");
const ASTRecordLayout *Layout =
&AST->getASTContext().getASTRecordLayout(RD);
const field_names Expected = {"c", "b", "a"};
EXPECT_EQ(10, Layout->getSize().getQuantity());
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
{
const RecordDecl *RD =
getRecordDeclFromAST(AST->getASTContext(), "last_struct");
const ASTRecordLayout *Layout =
&AST->getASTContext().getASTRecordLayout(RD);
const field_names Expected = {"a", "c", "b"};
EXPECT_EQ(9, Layout->getSize().getQuantity());
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
}
TEST(RANDSTRUCT_TEST, ZeroWidthBitfieldsSeparateAllocationUnits) {
const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
struct test_struct {
int a : 1;
int : 0;
int b : 1;
} __attribute__((randomize_layout));
)c");
EXPECT_FALSE(AST->getDiagnostics().hasErrorOccurred());
const RecordDecl *RD =
getRecordDeclFromAST(AST->getASTContext(), "test_struct");
const field_names Expected = {"b", "a", ""};
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
TEST(RANDSTRUCT_TEST, RandstructDoesNotRandomizeUnionFieldOrder) {
const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
union test_union {
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_union");
const field_names Expected = {"a", "b", "c", "d", "e", "f"};
EXPECT_FALSE(RD->isRandomized());
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
TEST(RANDSTRUCT_TEST, AnonymousStructsAndUnionsRetainFieldOrder) {
const std::unique_ptr<ASTUnit> AST = makeAST(R"c(
struct test_struct {
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_struct");
const field_names Expected = {"", "l", "r", "f", "a", "s", ""};
EXPECT_EQ(Expected, getFieldNamesFromRecord(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()) {
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;
}
} else if (RD->isStruct()) {
const field_names Expected = {"c", "b", "e", "d", "f"};
EXPECT_EQ(Expected, getFieldNamesFromRecord(RD));
}
}
}
EXPECT_TRUE(AnonStructTested);
EXPECT_TRUE(AnonUnionTested);
}
} // namespace ast_matchers
} // namespace clang

View File

@ -126,7 +126,6 @@ static_library("AST") {
"ParentMapContext.cpp",
"PrintfFormatString.cpp",
"QualTypeNames.cpp",
"Randstruct.cpp",
"RawCommentList.cpp",
"RecordLayout.cpp",
"RecordLayoutBuilder.cpp",

View File

@ -34,7 +34,6 @@ unittest("ASTTests") {
"EvaluateAsRValueTest.cpp",
"ExternalASTSourceTest.cpp",
"NamedDeclPrinterTest.cpp",
"RandstructTest.cpp",
"RecursiveASTVisitorTest.cpp",
"SizelessTypesTest.cpp",
"SourceLocationTest.cpp",