diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3653,3 +3653,19 @@ let SemaHandler = 0; let Documentation = [Undocumented]; } + +def EnforceTCB : InheritableAttr { + let Spellings = [Clang<"enforce_tcb">]; + let Subjects = SubjectList<[Function]>; + let Args = [StringArgument<"TCBName">]; + let Documentation = [EnforceTCBDocs]; + bit InheritEvenIfAlreadyPresent = 1; +} + +def EnforceTCBLeaf : InheritableAttr { + let Spellings = [Clang<"enforce_tcb_leaf">]; + let Subjects = SubjectList<[Function]>; + let Args = [StringArgument<"TCBName">]; + let Documentation = [EnforceTCBLeafDocs]; + bit InheritEvenIfAlreadyPresent = 1; +} diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5725,3 +5725,28 @@ }]; let Heading = "always_inline, __force_inline"; } + +def EnforceTCBDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ + The ``enforce_tcb`` attribute can be placed on functions to enforce that a + trusted compute base (TCB) does not call out of the TCB. This generates a + warning every time a function not marked with an ``enforce_tcb`` attribute is + called from a function with the ``enforce_tcb`` attribute. A function may be a + part of multiple TCBs. Invocations through function pointers are currently + not checked. Builtins are considered to a part of every TCB. + + - ``enforce_tcb(Name)`` indicates that this function is a part of the TCB named ``Name`` + }]; +} + +def EnforceTCBLeafDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ + The ``enforce_tcb_leaf`` attribute satisfies the requirement enforced by + ``enforce_tcb`` for the marked function to be in the named TCB but does not + continue to check the functions called from within the leaf function. + + - ``enforce_tcb_leaf(Name)`` indicates that this function is a part of the TCB named ``Name`` + }]; +} diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11116,4 +11116,11 @@ def err_probability_out_of_range : Error< "probability argument to __builtin_expect_with_probability is outside the " "range [0.0, 1.0]">; + +// TCB warnings +def err_tcb_conflicting_attributes : Error< + "attributes '%0(\"%2\")' and '%1(\"%2\")' are mutually exclusive">; +def warn_tcb_enforcement_violation : Warning< + "calling %0 is a violation of trusted computing base '%1'">, + InGroup>; } // end of sema component. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3210,6 +3210,9 @@ Decl *D, const WebAssemblyImportNameAttr &AL); WebAssemblyImportModuleAttr *mergeImportModuleAttr( Decl *D, const WebAssemblyImportModuleAttr &AL); + EnforceTCBAttr *mergeEnforceTCBAttr(Decl *D, const EnforceTCBAttr &AL); + EnforceTCBLeafAttr *mergeEnforceTCBLeafAttr(Decl *D, + const EnforceTCBLeafAttr &AL); void mergeDeclAttributes(NamedDecl *New, Decl *Old, AvailabilityMergeKind AMK = AMK_Redeclaration); @@ -12427,6 +12430,8 @@ /// attempts to add itself into the container void CheckObjCCircularContainer(ObjCMessageExpr *Message); + void CheckTCBEnforcement(const CallExpr *TheCall, const FunctionDecl *Callee); + void AnalyzeDeleteExprMismatch(const CXXDeleteExpr *DE); void AnalyzeDeleteExprMismatch(FieldDecl *Field, SourceLocation DeleteLoc, bool DeleteWasArrayForm); diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -75,6 +75,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Triple.h" #include "llvm/Support/AtomicOrdering.h" @@ -4569,6 +4570,8 @@ if (!FnInfo) return false; + CheckTCBEnforcement(TheCall, FDecl); + CheckAbsoluteValueFunction(TheCall, FDecl); CheckMaxUnsignedZero(TheCall, FDecl); @@ -16059,3 +16062,37 @@ return CallResult; } + +/// \brief Enforce the bounds of a TCB +/// CheckTCBEnforcement - Enforces that every function in a named TCB only +/// directly calls other functions in the same TCB as marked by the enforce_tcb +/// and enforce_tcb_leaf attributes. +void Sema::CheckTCBEnforcement(const CallExpr *TheCall, + const FunctionDecl *Callee) { + const FunctionDecl *Caller = getCurFunctionDecl(); + + // Calls to builtins are not enforced. + if (!Caller || !Caller->hasAttr() || + Callee->getBuiltinID() != 0) + return; + + // Search through the enforce_tcb and enforce_tcb_leaf attributes to find + // all TCBs the callee is a part of. + llvm::StringSet<> CalleeTCBs; + for_each(Callee->specific_attrs(), + [&](const auto *A) { CalleeTCBs.insert(A->getTCBName()); }); + for_each(Callee->specific_attrs(), + [&](const auto *A) { CalleeTCBs.insert(A->getTCBName()); }); + + // Go through the TCBs the caller is a part of and emit warnings if Caller + // is in a TCB that the Callee is not. + for_each( + Caller->specific_attrs(), + [&](const auto *A) { + StringRef CallerTCB = A->getTCBName(); + if (CalleeTCBs.count(CallerTCB) == 0) { + Diag(TheCall->getExprLoc(), diag::warn_tcb_enforcement_violation) + << Callee << CallerTCB; + } + }); +} diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2612,6 +2612,10 @@ NewAttr = S.mergeImportModuleAttr(D, *IMA); else if (const auto *INA = dyn_cast(Attr)) NewAttr = S.mergeImportNameAttr(D, *INA); + else if (const auto *TCBA = dyn_cast(Attr)) + NewAttr = S.mergeEnforceTCBAttr(D, *TCBA); + else if (const auto *TCBLA = dyn_cast(Attr)) + NewAttr = S.mergeEnforceTCBLeafAttr(D, *TCBLA); else if (Attr->shouldInheritEvenIfAlreadyPresent() || !DeclHasAttr(D, Attr)) NewAttr = cast(Attr->clone(S.Context)); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7517,6 +7517,75 @@ D->addAttr(::new (S.Context) CFGuardAttr(S.Context, AL, Arg)); } + +template +static const AttrTy *findEnforceTCBAttrByName(Decl *D, StringRef Name) { + auto Attrs = D->specific_attrs(); + auto I = llvm::find_if(Attrs, + [Name](const AttrTy *A) { + return A->getTCBName() == Name; + }); + return I == Attrs.end() ? nullptr : *I; +} + +template +static void handleEnforceTCBAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + StringRef Argument; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Argument)) + return; + + // A function cannot be have both regular and leaf membership in the same TCB. + if (const ConflictingAttrTy *ConflictingAttr = + findEnforceTCBAttrByName(D, Argument)) { + // We could attach a note to the other attribute but in this case + // there's no need given how the two are very close to each other. + S.Diag(AL.getLoc(), diag::err_tcb_conflicting_attributes) + << AL.getAttrName()->getName() << ConflictingAttr->getAttrName()->getName() + << Argument; + + // Error recovery: drop the non-leaf attribute so that to suppress + // all future warnings caused by erroneous attributes. The leaf attribute + // needs to be kept because it can only suppresses warnings, not cause them. + D->dropAttr(); + return; + } + + D->addAttr(AttrTy::Create(S.Context, Argument, AL)); +} + +template +static AttrTy *mergeEnforceTCBAttrImpl(Sema &S, Decl *D, const AttrTy &AL) { + // Check if the new redeclaration has different leaf-ness in the same TCB. + StringRef TCBName = AL.getTCBName(); + if (const ConflictingAttrTy *ConflictingAttr = + findEnforceTCBAttrByName(D, TCBName)) { + S.Diag(ConflictingAttr->getLoc(), diag::err_tcb_conflicting_attributes) + << ConflictingAttr->getAttrName()->getName() + << AL.getAttrName()->getName() << TCBName; + + // Add a note so that the user could easily find the conflicting attribute. + S.Diag(AL.getLoc(), diag::note_conflicting_attribute); + + // More error recovery. + D->dropAttr(); + return nullptr; + } + + ASTContext &Context = S.getASTContext(); + return ::new(Context) AttrTy(Context, AL, AL.getTCBName()); +} + +EnforceTCBAttr *Sema::mergeEnforceTCBAttr(Decl *D, const EnforceTCBAttr &AL) { + return mergeEnforceTCBAttrImpl( + *this, D, AL); +} + +EnforceTCBLeafAttr *Sema::mergeEnforceTCBLeafAttr( + Decl *D, const EnforceTCBLeafAttr &AL) { + return mergeEnforceTCBAttrImpl( + *this, D, AL); +} + //===----------------------------------------------------------------------===// // Top Level Sema Entry Points //===----------------------------------------------------------------------===// @@ -8220,6 +8289,14 @@ case ParsedAttr::AT_UseHandle: handleHandleAttr(S, D, AL); break; + + case ParsedAttr::AT_EnforceTCB: + handleEnforceTCBAttr(S, D, AL); + break; + + case ParsedAttr::AT_EnforceTCBLeaf: + handleEnforceTCBAttr(S, D, AL); + break; } } diff --git a/clang/test/Sema/attr-enforce-tcb-errors.cpp b/clang/test/Sema/attr-enforce-tcb-errors.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-enforce-tcb-errors.cpp @@ -0,0 +1,80 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +[[clang::enforce_tcb("oops")]] int wrong_subject_type; // expected-warning{{'enforce_tcb' attribute only applies to functions}} + +void no_arguments() __attribute__((enforce_tcb)); // expected-error{{'enforce_tcb' attribute takes one argument}} + +void too_many_arguments() __attribute__((enforce_tcb("test", 12))); // expected-error{{'enforce_tcb' attribute takes one argument}} + +void wrong_argument_type() __attribute__((enforce_tcb(12))); // expected-error{{'enforce_tcb' attribute requires a string}} + +[[clang::enforce_tcb_leaf("oops")]] int wrong_subject_type_leaf; // expected-warning{{'enforce_tcb_leaf' attribute only applies to functions}} + +void no_arguments_leaf() __attribute__((enforce_tcb_leaf)); // expected-error{{'enforce_tcb_leaf' attribute takes one argument}} + +void too_many_arguments_leaf() __attribute__((enforce_tcb_leaf("test", 12))); // expected-error{{'enforce_tcb_leaf' attribute takes one argument}} +void wrong_argument_type_leaf() __attribute__((enforce_tcb_leaf(12))); // expected-error{{'enforce_tcb_leaf' attribute requires a string}} + +void foo(); + +__attribute__((enforce_tcb("x"))) +__attribute__((enforce_tcb_leaf("x"))) // expected-error{{attributes 'enforce_tcb_leaf("x")' and 'enforce_tcb("x")' are mutually exclusive}} +void both_tcb_and_tcb_leaf() { + foo(); // no-warning +} + +__attribute__((enforce_tcb_leaf("x"))) // expected-note{{conflicting attribute is here}} +void both_tcb_and_tcb_leaf_on_separate_redeclarations(); +__attribute__((enforce_tcb("x"))) // expected-error{{attributes 'enforce_tcb("x")' and 'enforce_tcb_leaf("x")' are mutually exclusive}} +void both_tcb_and_tcb_leaf_on_separate_redeclarations() { + // Error recovery: no need to emit a warning when we didn't + // figure out our attributes to begin with. + foo(); // no-warning +} + +__attribute__((enforce_tcb_leaf("x"))) +__attribute__((enforce_tcb("x"))) // expected-error{{attributes 'enforce_tcb("x")' and 'enforce_tcb_leaf("x")' are mutually exclusive}} +void both_tcb_and_tcb_leaf_opposite_order() { + foo(); // no-warning +} + +__attribute__((enforce_tcb("x"))) // expected-note{{conflicting attribute is here}} +void both_tcb_and_tcb_leaf_on_separate_redeclarations_opposite_order(); +__attribute__((enforce_tcb_leaf("x"))) // expected-error{{attributes 'enforce_tcb_leaf("x")' and 'enforce_tcb("x")' are mutually exclusive}} +void both_tcb_and_tcb_leaf_on_separate_redeclarations_opposite_order() { + foo(); // no-warning +} + +__attribute__((enforce_tcb("x"))) +__attribute__((enforce_tcb_leaf("y"))) // no-error +void both_tcb_and_tcb_leaf_but_different_identifiers() { + foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'x'}} +} +__attribute__((enforce_tcb_leaf("x"))) +__attribute__((enforce_tcb("y"))) // no-error +void both_tcb_and_tcb_leaf_but_different_identifiers_opposite_order() { + foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'y'}} +} + +__attribute__((enforce_tcb("x"))) +void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations(); +__attribute__((enforce_tcb_leaf("y"))) // no-error +void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations() { + foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'x'}} +} + +__attribute__((enforce_tcb_leaf("x"))) +void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations_opposite_order(); +__attribute__((enforce_tcb("y"))) +void both_tcb_and_tcb_leaf_but_different_identifiers_on_separate_redeclarations_opposite_order() { + foo(); // expected-warning{{calling 'foo' is a violation of trusted computing base 'y'}} +} + +__attribute__((enforce_tcb("y"))) +__attribute__((enforce_tcb("x"))) +__attribute__((enforce_tcb_leaf("x"))) // expected-error{{attributes 'enforce_tcb_leaf("x")' and 'enforce_tcb("x")' are mutually exclusive}} +void error_recovery_over_individual_tcbs() { + // FIXME: Ideally this should warn. The conflict between attributes + // for TCB "x" shouldn't affect the warning about TCB "y". + foo(); // no-warning +} diff --git a/clang/test/Sema/attr-enforce-tcb.c b/clang/test/Sema/attr-enforce-tcb.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-enforce-tcb.c @@ -0,0 +1,65 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +#define PLACE_IN_TCB(NAME) __attribute__ ((enforce_tcb(NAME))) +#define PLACE_IN_TCB_LEAF(NAME) __attribute__ ((enforce_tcb_leaf(NAME))) + +void foo1 (void) PLACE_IN_TCB("bar"); +void foo2 (void) PLACE_IN_TCB("bar"); +void foo3 (void); // not in any TCB +void foo4 (void) PLACE_IN_TCB("bar2"); +void foo5 (void) PLACE_IN_TCB_LEAF("bar"); +void foo6 (void) PLACE_IN_TCB("bar2") PLACE_IN_TCB("bar"); +void foo7 (void) PLACE_IN_TCB("bar3"); +void foo8 (void) PLACE_IN_TCB("bar") PLACE_IN_TCB("bar2"); +void foo9 (void); + +void foo1() { + foo2(); // OK - function in same TCB + foo3(); // expected-warning {{calling 'foo3' is a violation of trusted computing base 'bar'}} + foo4(); // expected-warning {{calling 'foo4' is a violation of trusted computing base 'bar'}} + foo5(); // OK - in leaf node + foo6(); // OK - in multiple TCBs, one of which is the same + foo7(); // expected-warning {{calling 'foo7' is a violation of trusted computing base 'bar'}} + (void) __builtin_clz(5); // OK - builtins are excluded +} + +// Normal use without any attributes works +void foo3() { + foo9(); // no-warning +} + +void foo5() { + // all calls should be okay, function in TCB leaf + foo2(); // no-warning + foo3(); // no-warning + foo4(); // no-warning +} + +void foo6() { + foo1(); // expected-warning {{calling 'foo1' is a violation of trusted computing base 'bar2'}} + foo4(); // expected-warning {{calling 'foo4' is a violation of trusted computing base 'bar'}} + foo8(); // no-warning + foo7(); // #1 + // expected-warning@#1 {{calling 'foo7' is a violation of trusted computing base 'bar2'}} + // expected-warning@#1 {{calling 'foo7' is a violation of trusted computing base 'bar'}} +} + +// Ensure that attribute merging works as expected across redeclarations. +void foo10() PLACE_IN_TCB("bar"); +void foo10() PLACE_IN_TCB("bar2"); +void foo10() PLACE_IN_TCB("bar3"); +void foo10() { + foo1(); // #2 + // expected-warning@#2 {{calling 'foo1' is a violation of trusted computing base 'bar2'}} + // expected-warning@#2 {{calling 'foo1' is a violation of trusted computing base 'bar3'}} + foo3(); // #3 + // expected-warning@#3 {{calling 'foo3' is a violation of trusted computing base 'bar'}} + // expected-warning@#3 {{calling 'foo3' is a violation of trusted computing base 'bar2'}} + // expected-warning@#3 {{calling 'foo3' is a violation of trusted computing base 'bar3'}} + foo4(); // #4 + // expected-warning@#4 {{calling 'foo4' is a violation of trusted computing base 'bar'}} + // expected-warning@#4 {{calling 'foo4' is a violation of trusted computing base 'bar3'}} + foo7(); // #5 + // expected-warning@#5 {{calling 'foo7' is a violation of trusted computing base 'bar'}} + // expected-warning@#5 {{calling 'foo7' is a violation of trusted computing base 'bar2'}} +} diff --git a/clang/test/Sema/attr-enforce-tcb.cpp b/clang/test/Sema/attr-enforce-tcb.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-enforce-tcb.cpp @@ -0,0 +1,70 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +#define PLACE_IN_TCB(NAME) [[clang::enforce_tcb(NAME)]] +#define PLACE_IN_TCB_LEAF(NAME) [[clang::enforce_tcb_leaf(NAME)]] + +PLACE_IN_TCB("foo") void in_tcb_foo(); +void not_in_tcb(); + +// Test behavior on classes and methods. +class C { + void bar(); + + PLACE_IN_TCB("foo") + void foo() { + // TODO: Figure out if we want to support methods at all. + // Does it even make sense to isolate individual methods into a TCB? + // Maybe a per-class attribute would make more sense? + bar(); // expected-warning{{calling 'bar' is a violation of trusted computing base 'foo'}} + } +}; + +// Test behavior on templates. +template +PLACE_IN_TCB("foo") +void foo_never_instantiated() { + not_in_tcb(); // expected-warning{{calling 'not_in_tcb' is a violation of trusted computing base 'foo'}} + in_tcb_foo(); // no-warning +} + +template +PLACE_IN_TCB("foo") +void foo_specialized(); + +template<> +void foo_specialized() { + not_in_tcb(); // expected-warning{{calling 'not_in_tcb' is a violation of trusted computing base 'foo'}} + in_tcb_foo(); // no-warning +} + +PLACE_IN_TCB("foo") +void call_template_good() { + foo_specialized(); // no-warning +} +PLACE_IN_TCB("bar") +void call_template_bad() { + foo_specialized(); // expected-warning{{calling 'foo_specialized' is a violation of trusted computing base 'bar'}} +} + +template +void foo_specialization_in_tcb(); + +template<> +PLACE_IN_TCB("foo") +void foo_specialization_in_tcb() { + not_in_tcb(); //expected-warning{{calling 'not_in_tcb' is a violation of trusted computing base 'foo'}} + in_tcb_foo(); // no-warning +} + +template<> +void foo_specialization_in_tcb() { + not_in_tcb(); // no-warning + in_tcb_foo(); // no-warning +} + +PLACE_IN_TCB("foo") +void call_specialization_in_tcb() { + foo_specialization_in_tcb(); // no-warning + foo_specialization_in_tcb(); // expected-warning{{calling 'foo_specialization_in_tcb' is a violation of trusted computing base 'foo'}} + foo_specialization_in_tcb(); // expected-warning{{'foo_specialization_in_tcb' is a violation of trusted computing base 'foo'}} +}