[attributes] Add a facility for enforcing a Trusted Computing Base.

Introduce a function attribute 'enforce_tcb' that prevents the function
from calling other functions without the same attribute. This allows
isolating code that's considered to be somehow privileged so that it could not
use its privileges to exhibit arbitrary behavior.

Introduce an on-by-default warning '-Wtcb-enforcement' that warns
about violations of the above rule.

Introduce a function attribute 'enforce_tcb_leaf' that suppresses
the new warning within the function it is attached to. Such leaf functions
may implement common functionality between the trusted and the untrusted code
but they require extra careful audit with respect to their capabilities.

Fixes after a revert in 419ef38a50293c58078f830517f5e305068dbee6:
Fix a test.
Add workaround for GCC bug (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67274).
Attribute the patch appropriately!

Differential Revision: https://reviews.llvm.org/D91898
This commit is contained in:
Sean Dooher 2021-01-11 06:57:08 -08:00 committed by Artem Dergachev
parent 072714b961
commit 35c9baa11e
11 changed files with 389 additions and 0 deletions

View File

@ -3653,3 +3653,19 @@ def Builtin : InheritableAttr {
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;
}

View File

@ -5725,3 +5725,28 @@ Attribute docs`_, and `the GCC Inline docs`_.
}];
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``
}];
}

View File

@ -11116,4 +11116,11 @@ def err_probability_not_constant_float : Error<
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<DiagGroup<"tcb-enforcement">>;
} // end of sema component.

View File

@ -3210,6 +3210,9 @@ public:
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 @@ private:
/// 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);

View File

@ -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 @@ bool Sema::CheckFunctionCall(FunctionDecl *FDecl, CallExpr *TheCall,
if (!FnInfo)
return false;
CheckTCBEnforcement(TheCall, FDecl);
CheckAbsoluteValueFunction(TheCall, FDecl);
CheckMaxUnsignedZero(TheCall, FDecl);
@ -16059,3 +16062,38 @@ ExprResult Sema::SemaBuiltinMatrixColumnMajorStore(CallExpr *TheCall,
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<EnforceTCBAttr>() ||
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<EnforceTCBAttr>(),
[&](const auto *A) { CalleeTCBs.insert(A->getTCBName()); });
for_each(Callee->specific_attrs<EnforceTCBLeafAttr>(),
[&](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<EnforceTCBAttr>(),
[&](const auto *A) {
StringRef CallerTCB = A->getTCBName();
if (CalleeTCBs.count(CallerTCB) == 0) {
this->Diag(TheCall->getExprLoc(),
diag::warn_tcb_enforcement_violation) << Callee
<< CallerTCB;
}
});
}

View File

@ -2612,6 +2612,10 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D,
NewAttr = S.mergeImportModuleAttr(D, *IMA);
else if (const auto *INA = dyn_cast<WebAssemblyImportNameAttr>(Attr))
NewAttr = S.mergeImportNameAttr(D, *INA);
else if (const auto *TCBA = dyn_cast<EnforceTCBAttr>(Attr))
NewAttr = S.mergeEnforceTCBAttr(D, *TCBA);
else if (const auto *TCBLA = dyn_cast<EnforceTCBLeafAttr>(Attr))
NewAttr = S.mergeEnforceTCBLeafAttr(D, *TCBLA);
else if (Attr->shouldInheritEvenIfAlreadyPresent() || !DeclHasAttr(D, Attr))
NewAttr = cast<InheritableAttr>(Attr->clone(S.Context));

View File

@ -7517,6 +7517,75 @@ static void handleCFGuardAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(::new (S.Context) CFGuardAttr(S.Context, AL, Arg));
}
template <typename AttrTy>
static const AttrTy *findEnforceTCBAttrByName(Decl *D, StringRef Name) {
auto Attrs = D->specific_attrs<AttrTy>();
auto I = llvm::find_if(Attrs,
[Name](const AttrTy *A) {
return A->getTCBName() == Name;
});
return I == Attrs.end() ? nullptr : *I;
}
template <typename AttrTy, typename ConflictingAttrTy>
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<ConflictingAttrTy>(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<EnforceTCBAttr>();
return;
}
D->addAttr(AttrTy::Create(S.Context, Argument, AL));
}
template <typename AttrTy, typename ConflictingAttrTy>
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<ConflictingAttrTy>(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<EnforceTCBAttr>();
return nullptr;
}
ASTContext &Context = S.getASTContext();
return ::new(Context) AttrTy(Context, AL, AL.getTCBName());
}
EnforceTCBAttr *Sema::mergeEnforceTCBAttr(Decl *D, const EnforceTCBAttr &AL) {
return mergeEnforceTCBAttrImpl<EnforceTCBAttr, EnforceTCBLeafAttr>(
*this, D, AL);
}
EnforceTCBLeafAttr *Sema::mergeEnforceTCBLeafAttr(
Decl *D, const EnforceTCBLeafAttr &AL) {
return mergeEnforceTCBAttrImpl<EnforceTCBLeafAttr, EnforceTCBAttr>(
*this, D, AL);
}
//===----------------------------------------------------------------------===//
// Top Level Sema Entry Points
//===----------------------------------------------------------------------===//
@ -8220,6 +8289,14 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_UseHandle:
handleHandleAttr<UseHandleAttr>(S, D, AL);
break;
case ParsedAttr::AT_EnforceTCB:
handleEnforceTCBAttr<EnforceTCBAttr, EnforceTCBLeafAttr>(S, D, AL);
break;
case ParsedAttr::AT_EnforceTCBLeaf:
handleEnforceTCBAttr<EnforceTCBLeafAttr, EnforceTCBAttr>(S, D, AL);
break;
}
}

View File

@ -58,6 +58,8 @@
// CHECK-NEXT: Destructor (SubjectMatchRule_function)
// CHECK-NEXT: DisableTailCalls (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: EnableIf (SubjectMatchRule_function)
// CHECK-NEXT: EnforceTCB (SubjectMatchRule_function)
// CHECK-NEXT: EnforceTCBLeaf (SubjectMatchRule_function)
// CHECK-NEXT: EnumExtensibility (SubjectMatchRule_enum)
// CHECK-NEXT: ExcludeFromExplicitInstantiation (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record)
// CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable))

View File

@ -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
}

View File

@ -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'}}
}

View File

@ -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 <typename Ty>
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 <typename Ty>
PLACE_IN_TCB("foo")
void foo_specialized();
template<>
void foo_specialized<int>() {
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<int>(); // no-warning
}
PLACE_IN_TCB("bar")
void call_template_bad() {
foo_specialized<int>(); // expected-warning{{calling 'foo_specialized<int>' is a violation of trusted computing base 'bar'}}
}
template<typename Ty>
void foo_specialization_in_tcb();
template<>
PLACE_IN_TCB("foo")
void foo_specialization_in_tcb<int>() {
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<double>() {
not_in_tcb(); // no-warning
in_tcb_foo(); // no-warning
}
PLACE_IN_TCB("foo")
void call_specialization_in_tcb() {
foo_specialization_in_tcb<int>(); // no-warning
foo_specialization_in_tcb<long>(); // expected-warning{{calling 'foo_specialization_in_tcb<long>' is a violation of trusted computing base 'foo'}}
foo_specialization_in_tcb<double>(); // expected-warning{{'foo_specialization_in_tcb<double>' is a violation of trusted computing base 'foo'}}
}