Skip to content

Commit 1aad641

Browse files
committed
[Clang][Sema] Add -Wcast-function-type-strict
Clang supports indirect call Control-Flow Integrity (CFI) sanitizers (e.g. -fsanitize=cfi-icall), which enforce an exact type match between a function pointer and the target function. Unfortunately, Clang doesn't provide diagnostics that would help developers avoid function type casts that lead to runtime CFI failures. -Wcast-function-type, while helpful, only warns about ABI incompatibility, which isn't sufficient with CFI. Add -Wcast-function-type-strict, which checks for a strict type compatibility in function type casts and helps warn about casts that can potentially lead to CFI failures. Reviewed By: nickdesaulniers, aaron.ballman Differential Revision: https://reviews.llvm.org/D134831
1 parent 2234f58 commit 1aad641

File tree

8 files changed

+143
-30
lines changed

8 files changed

+143
-30
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,11 @@ Improvements to Clang's diagnostics
328328
- Clang now correctly points to the problematic parameter for the ``-Wnonnull``
329329
warning. This fixes
330330
`Issue 58273 <https://github.com/llvm/llvm-project/issues/58273>`_.
331+
- Introduced ``-Wcast-function-type-strict`` to warn about function type mismatches
332+
in casts that may result in runtime indirect call `Control-Flow Integrity (CFI)
333+
<https://clang.llvm.org/docs/ControlFlowIntegrity.html>`_ failures. This diagnostic
334+
is grouped under ``-Wcast-function-type`` as it identifies a more strict set of
335+
potentially problematic function type casts.
331336

332337
Non-comprehensive list of changes in this release
333338
-------------------------------------------------

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,8 @@ def PrivateExtern : DiagGroup<"private-extern">;
538538
def SelTypeCast : DiagGroup<"cast-of-sel-type">;
539539
def FunctionDefInObjCContainer : DiagGroup<"function-def-in-objc-container">;
540540
def BadFunctionCast : DiagGroup<"bad-function-cast">;
541-
def CastFunctionType : DiagGroup<"cast-function-type">;
541+
def CastFunctionTypeStrict : DiagGroup<"cast-function-type-strict">;
542+
def CastFunctionType : DiagGroup<"cast-function-type", [CastFunctionTypeStrict]>;
542543
def ObjCPropertyImpl : DiagGroup<"objc-property-implementation">;
543544
def ObjCPropertyNoAttribute : DiagGroup<"objc-property-no-attribute">;
544545
def ObjCPropertyAssignOnObjectType : DiagGroup<"objc-property-assign-on-object-type">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8694,6 +8694,8 @@ def warn_bad_function_cast : Warning<
86948694
def warn_cast_function_type : Warning<
86958695
"cast %diff{from $ to $ |}0,1converts to incompatible function type">,
86968696
InGroup<CastFunctionType>, DefaultIgnore;
8697+
def warn_cast_function_type_strict : Warning<warn_cast_function_type.Text>,
8698+
InGroup<CastFunctionTypeStrict>, DefaultIgnore;
86978699
def err_cast_pointer_to_non_pointer_int : Error<
86988700
"pointer cannot be cast to type %0">;
86998701
def err_nullptr_cast : Error<

clang/lib/Sema/SemaCast.cpp

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,11 +1059,19 @@ static bool argTypeIsABIEquivalent(QualType SrcType, QualType DestType,
10591059
return Context.hasSameUnqualifiedType(SrcType, DestType);
10601060
}
10611061

1062-
static bool checkCastFunctionType(Sema &Self, const ExprResult &SrcExpr,
1063-
QualType DestType) {
1064-
if (Self.Diags.isIgnored(diag::warn_cast_function_type,
1065-
SrcExpr.get()->getExprLoc()))
1066-
return true;
1062+
static unsigned int checkCastFunctionType(Sema &Self, const ExprResult &SrcExpr,
1063+
QualType DestType) {
1064+
unsigned int DiagID = 0;
1065+
const unsigned int DiagList[] = {diag::warn_cast_function_type_strict,
1066+
diag::warn_cast_function_type};
1067+
for (auto ID : DiagList) {
1068+
if (!Self.Diags.isIgnored(ID, SrcExpr.get()->getExprLoc())) {
1069+
DiagID = ID;
1070+
break;
1071+
}
1072+
}
1073+
if (!DiagID)
1074+
return 0;
10671075

10681076
QualType SrcType = SrcExpr.get()->getType();
10691077
const FunctionType *SrcFTy = nullptr;
@@ -1078,10 +1086,17 @@ static bool checkCastFunctionType(Sema &Self, const ExprResult &SrcExpr,
10781086
SrcFTy = SrcType->castAs<FunctionType>();
10791087
DstFTy = DestType.getNonReferenceType()->castAs<FunctionType>();
10801088
} else {
1081-
return true;
1089+
return 0;
10821090
}
10831091
assert(SrcFTy && DstFTy);
10841092

1093+
if (Self.Context.hasSameType(SrcFTy, DstFTy))
1094+
return 0;
1095+
1096+
// For strict checks, ensure we have an exact match.
1097+
if (DiagID == diag::warn_cast_function_type_strict)
1098+
return DiagID;
1099+
10851100
auto IsVoidVoid = [](const FunctionType *T) {
10861101
if (!T->getReturnType()->isVoidType())
10871102
return false;
@@ -1092,16 +1107,16 @@ static bool checkCastFunctionType(Sema &Self, const ExprResult &SrcExpr,
10921107

10931108
// Skip if either function type is void(*)(void)
10941109
if (IsVoidVoid(SrcFTy) || IsVoidVoid(DstFTy))
1095-
return true;
1110+
return 0;
10961111

10971112
// Check return type.
10981113
if (!argTypeIsABIEquivalent(SrcFTy->getReturnType(), DstFTy->getReturnType(),
10991114
Self.Context))
1100-
return false;
1115+
return DiagID;
11011116

11021117
// Check if either has unspecified number of parameters
11031118
if (SrcFTy->isFunctionNoProtoType() || DstFTy->isFunctionNoProtoType())
1104-
return true;
1119+
return 0;
11051120

11061121
// Check parameter types.
11071122

@@ -1114,19 +1129,19 @@ static bool checkCastFunctionType(Sema &Self, const ExprResult &SrcExpr,
11141129
unsigned DstNumParams = DstFPTy->getNumParams();
11151130
if (NumParams > DstNumParams) {
11161131
if (!DstFPTy->isVariadic())
1117-
return false;
1132+
return DiagID;
11181133
NumParams = DstNumParams;
11191134
} else if (NumParams < DstNumParams) {
11201135
if (!SrcFPTy->isVariadic())
1121-
return false;
1136+
return DiagID;
11221137
}
11231138

11241139
for (unsigned i = 0; i < NumParams; ++i)
11251140
if (!argTypeIsABIEquivalent(SrcFPTy->getParamType(i),
11261141
DstFPTy->getParamType(i), Self.Context))
1127-
return false;
1142+
return DiagID;
11281143

1129-
return true;
1144+
return 0;
11301145
}
11311146

11321147
/// CheckReinterpretCast - Check that a reinterpret_cast\<DestType\>(SrcExpr) is
@@ -1167,8 +1182,8 @@ void CastOperation::CheckReinterpretCast() {
11671182
checkObjCConversion(Sema::CCK_OtherCast);
11681183
DiagnoseReinterpretUpDownCast(Self, SrcExpr.get(), DestType, OpRange);
11691184

1170-
if (!checkCastFunctionType(Self, SrcExpr, DestType))
1171-
Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type)
1185+
if (unsigned DiagID = checkCastFunctionType(Self, SrcExpr, DestType))
1186+
Self.Diag(OpRange.getBegin(), DiagID)
11721187
<< SrcExpr.get()->getType() << DestType << OpRange;
11731188
} else {
11741189
SrcExpr = ExprError();
@@ -2797,8 +2812,8 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
27972812
if (Kind == CK_BitCast)
27982813
checkCastAlign();
27992814

2800-
if (!checkCastFunctionType(Self, SrcExpr, DestType))
2801-
Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type)
2815+
if (unsigned DiagID = checkCastFunctionType(Self, SrcExpr, DestType))
2816+
Self.Diag(OpRange.getBegin(), DiagID)
28022817
<< SrcExpr.get()->getType() << DestType << OpRange;
28032818

28042819
} else {
@@ -3156,9 +3171,8 @@ void CastOperation::CheckCStyleCast() {
31563171
}
31573172
}
31583173

3159-
if (!checkCastFunctionType(Self, SrcExpr, DestType))
3160-
Self.Diag(OpRange.getBegin(), diag::warn_cast_function_type)
3161-
<< SrcType << DestType << OpRange;
3174+
if (unsigned DiagID = checkCastFunctionType(Self, SrcExpr, DestType))
3175+
Self.Diag(OpRange.getBegin(), DiagID) << SrcType << DestType << OpRange;
31623176

31633177
if (isa<PointerType>(SrcType) && isa<PointerType>(DestType)) {
31643178
QualType SrcTy = cast<PointerType>(SrcType)->getPointeeType();
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %clang_cc1 %s -fsyntax-only -Wcast-function-type -verify
2+
// RUN: %clang_cc1 %s -fsyntax-only -Wcast-function-type-strict -verify
3+
4+
5+
int t(int array[static 12]);
6+
int u(int i);
7+
const int v(int i);
8+
int x(long);
9+
10+
typedef int (f1)(long);
11+
typedef int (f2)(void*);
12+
typedef int (f3)();
13+
typedef void (f4)();
14+
typedef void (f5)(void);
15+
typedef int (f6)(long, int);
16+
typedef int (f7)(long,...);
17+
typedef int (f8)(int *);
18+
typedef int (f9)(const int);
19+
typedef int (f10)(int);
20+
21+
f1 *a;
22+
f2 *b;
23+
f3 *c;
24+
f4 *d;
25+
f5 *e;
26+
f6 *f;
27+
f7 *g;
28+
f8 *h;
29+
f9 *i;
30+
f10 *j;
31+
32+
void foo(void) {
33+
a = (f1 *)x;
34+
b = (f2 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)') converts to incompatible function type}} */
35+
c = (f3 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f3 *' (aka 'int (*)()') converts to incompatible function type}} */
36+
d = (f4 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f4 *' (aka 'void (*)()') converts to incompatible function type}} */
37+
e = (f5 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f5 *' (aka 'void (*)(void)') converts to incompatible function type}} */
38+
f = (f6 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f6 *' (aka 'int (*)(long, int)') converts to incompatible function type}} */
39+
g = (f7 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f7 *' (aka 'int (*)(long, ...)') converts to incompatible function type}} */
40+
h = (f8 *)t;
41+
i = (f9 *)u;
42+
j = (f10 *)v; /* expected-warning {{cast from 'const int (*)(int)' to 'f10 *' (aka 'int (*)(int)') converts to incompatible function type}} */
43+
}

clang/test/Sema/warn-cast-function-type.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -x c %s -fsyntax-only -Wcast-function-type -triple x86_64-- -verify
1+
// RUN: %clang_cc1 %s -fsyntax-only -Wcast-function-type -Wno-cast-function-type-strict -verify
22

33
int x(long);
44

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// RUN: %clang_cc1 %s -fblocks -fsyntax-only -Wcast-function-type -verify
2+
// RUN: %clang_cc1 %s -fblocks -fsyntax-only -Wcast-function-type-strict -verify
3+
4+
int x(long);
5+
6+
typedef int (f1)(long);
7+
typedef int (f2)(void*);
8+
typedef int (f3)(...);
9+
typedef void (f4)(...);
10+
typedef void (f5)(void);
11+
typedef int (f6)(long, int);
12+
typedef int (f7)(long,...);
13+
typedef int (&f8)(long, int);
14+
15+
f1 *a;
16+
f2 *b;
17+
f3 *c;
18+
f4 *d;
19+
f5 *e;
20+
f6 *f;
21+
f7 *g;
22+
23+
struct S
24+
{
25+
void foo (int*);
26+
void bar (int);
27+
};
28+
29+
typedef void (S::*mf)(int);
30+
31+
void foo() {
32+
a = (f1 *)x;
33+
b = (f2 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)') converts to incompatible function type}}
34+
b = reinterpret_cast<f2 *>(x); // expected-warning {{cast from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)') converts to incompatible function type}}
35+
c = (f3 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f3 *' (aka 'int (*)(...)') converts to incompatible function type}}
36+
d = (f4 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f4 *' (aka 'void (*)(...)') converts to incompatible function type}}
37+
e = (f5 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f5 *' (aka 'void (*)()') converts to incompatible function type}}
38+
f = (f6 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f6 *' (aka 'int (*)(long, int)') converts to incompatible function type}}
39+
g = (f7 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f7 *' (aka 'int (*)(long, ...)') converts to incompatible function type}}
40+
41+
mf p1 = (mf)&S::foo; // expected-warning {{cast from 'void (S::*)(int *)' to 'mf' (aka 'void (S::*)(int)') converts to incompatible function type}}
42+
43+
f8 f2 = (f8)x; // expected-warning {{cast from 'int (long)' to 'f8' (aka 'int (&)(long, int)') converts to incompatible function type}}
44+
(void)f2;
45+
46+
int (^y)(long);
47+
f = (f6 *)y; // expected-warning {{cast from 'int (^)(long)' to 'f6 *' (aka 'int (*)(long, int)') converts to incompatible function type}}
48+
}

clang/test/SemaCXX/warn-cast-function-type.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -x c++ %s -fblocks -fsyntax-only -Wcast-function-type -triple x86_64-- -verify
1+
// RUN: %clang_cc1 %s -fblocks -fsyntax-only -Wcast-function-type -Wno-cast-function-type-strict -verify
22

33
int x(long);
44

@@ -29,19 +29,19 @@ typedef void (S::*mf)(int);
2929

3030
void foo() {
3131
a = (f1 *)x;
32-
b = (f2 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)') converts to incompatible function type}} */
33-
b = reinterpret_cast<f2 *>(x); /* expected-warning {{cast from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)') converts to incompatible function type}} */
32+
b = (f2 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)') converts to incompatible function type}}
33+
b = reinterpret_cast<f2 *>(x); // expected-warning {{cast from 'int (*)(long)' to 'f2 *' (aka 'int (*)(void *)') converts to incompatible function type}}
3434
c = (f3 *)x;
35-
d = (f4 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f4 *' (aka 'void (*)(...)') converts to incompatible function type}} */
35+
d = (f4 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f4 *' (aka 'void (*)(...)') converts to incompatible function type}}
3636
e = (f5 *)x;
37-
f = (f6 *)x; /* expected-warning {{cast from 'int (*)(long)' to 'f6 *' (aka 'int (*)(long, int)') converts to incompatible function type}} */
37+
f = (f6 *)x; // expected-warning {{cast from 'int (*)(long)' to 'f6 *' (aka 'int (*)(long, int)') converts to incompatible function type}}
3838
g = (f7 *)x;
3939

40-
mf p1 = (mf)&S::foo; /* expected-warning {{cast from 'void (S::*)(int *)' to 'mf' (aka 'void (S::*)(int)') converts to incompatible function type}} */
40+
mf p1 = (mf)&S::foo; // expected-warning {{cast from 'void (S::*)(int *)' to 'mf' (aka 'void (S::*)(int)') converts to incompatible function type}}
4141

42-
f8 f2 = (f8)x; /* expected-warning {{cast from 'int (long)' to 'f8' (aka 'int (&)(long, int)') converts to incompatible function type}} */
42+
f8 f2 = (f8)x; // expected-warning {{cast from 'int (long)' to 'f8' (aka 'int (&)(long, int)') converts to incompatible function type}}
4343
(void)f2;
4444

4545
int (^y)(long);
46-
f = (f6 *)y; /* expected-warning {{cast from 'int (^)(long)' to 'f6 *' (aka 'int (*)(long, int)') converts to incompatible function type}} */
46+
f = (f6 *)y; // expected-warning {{cast from 'int (^)(long)' to 'f6 *' (aka 'int (*)(long, int)') converts to incompatible function type}}
4747
}

0 commit comments

Comments
 (0)