Default implicit function pointer conversions diagnostic to be an error

Implicitly converting between incompatible function pointers in C is
currently a default-on warning (it is an error in C++). However, this
is very poor security posture. A mismatch in parameters or return
types, or a mismatch in calling conventions, etc can lead to
exploitable security vulnerabilities. Rather than allow this unsafe
practice with a warning, this patch strengthens the warning to be an
error (while still allowing users the ability to disable the error or
the warning entirely to ease migration). Users should either ensure the
signatures are correctly compatible or they should use an explicit cast
if they believe that's more reasonable.

Differential Revision: https://reviews.llvm.org/D131351
This commit is contained in:
Aaron Ballman 2022-08-10 13:52:33 -04:00
parent bc9617899c
commit af01f717c4
22 changed files with 53 additions and 58 deletions

View File

@ -13,7 +13,9 @@ void handler_bad1(int) {
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: standard function '_exit' may not be asynchronous-safe; calling it from a signal handler may be dangerous [bugprone-signal-handler]
}
void handler_bad2(void *dst, const void *src) {
void handler_bad2(int) {
void *dst;
const void *src;
memcpy(dst, src, 10);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: standard function 'memcpy' may not be asynchronous-safe; calling it from a signal handler may be dangerous [bugprone-signal-handler]
}

View File

@ -14,6 +14,7 @@ typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
void f_extern(void);
void f_extern_handler(int);
void handler_printf(int) {
printf("1234");
@ -185,8 +186,8 @@ void test_other(void) {
signal(SIGINT, _Exit);
signal(SIGINT, other_call);
// CHECK-NOTES: :[[@LINE-1]]:18: warning: standard function 'other_call' may not be asynchronous-safe; using it as a signal handler may be dangerous [bugprone-signal-handler]
signal(SIGINT, f_extern);
// CHECK-NOTES: :[[@LINE-1]]:18: warning: cannot verify that external function 'f_extern' is asynchronous-safe; using it as a signal handler may be dangerous [bugprone-signal-handler]
signal(SIGINT, f_extern_handler);
// CHECK-NOTES: :[[@LINE-1]]:18: warning: cannot verify that external function 'f_extern_handler' is asynchronous-safe; using it as a signal handler may be dangerous [bugprone-signal-handler]
signal(SIGINT, SIG_IGN);
signal(SIGINT, SIG_DFL);

View File

@ -85,6 +85,10 @@ Improvements to Clang's diagnostics
- ``-Wbitfield-constant-conversion`` now diagnoses implicit truncation when 1 is
assigned to a 1-bit signed integer bitfield. This fixes
`Issue 53253 <https://github.com/llvm/llvm-project/issues/53253>`_.
- ``-Wincompatible-function-pointer-types`` now defaults to an error in all C
language modes. It may be downgraded to a warning with
``-Wno-error=incompatible-function-pointer-types`` or disabled entirely with
``-Wno-implicit-function-pointer-types``.
Non-comprehensive list of changes in this release
-------------------------------------------------

View File

@ -8157,24 +8157,6 @@ def err_typecheck_convert_incompatible_pointer : Error<
"; take the address with &|"
"; remove *|"
"; remove &}3">;
def ext_typecheck_convert_incompatible_function_pointer : ExtWarn<
"incompatible function pointer types "
"%select{%diff{assigning to $ from $|assigning to different types}0,1"
"|%diff{passing $ to parameter of type $|"
"passing to parameter of different type}0,1"
"|%diff{returning $ from a function with result type $|"
"returning from function with different return type}0,1"
"|%diff{converting $ to type $|converting between types}0,1"
"|%diff{initializing $ with an expression of type $|"
"initializing with expression of different type}0,1"
"|%diff{sending $ to parameter of type $|"
"sending to parameter of different type}0,1"
"|%diff{casting $ to type $|casting between types}0,1}2"
"%select{|; dereference with *|"
"; take the address with &|"
"; remove *|"
"; remove &}3">,
InGroup<IncompatibleFunctionPointerTypes>;
def err_typecheck_convert_incompatible_function_pointer : Error<
"incompatible function pointer types "
"%select{%diff{assigning to $ from $|assigning to different types}0,1"
@ -8192,6 +8174,9 @@ def err_typecheck_convert_incompatible_function_pointer : Error<
"; take the address with &|"
"; remove *|"
"; remove &}3">;
def ext_typecheck_convert_incompatible_function_pointer : ExtWarn<
err_typecheck_convert_incompatible_function_pointer.Text>,
InGroup<IncompatibleFunctionPointerTypes>, DefaultError;
def ext_typecheck_convert_discards_qualifiers : ExtWarn<
"%select{%diff{assigning to $ from $|assigning to different types}0,1"
"|%diff{passing $ to parameter of type $|"

View File

@ -1,4 +1,4 @@
// RUN: %clang_cc1 -no-opaque-pointers -emit-llvm -Wno-strict-prototypes -fcf-protection=branch -triple i386-linux-gnu %s -o - | FileCheck %s
// RUN: %clang_cc1 -no-opaque-pointers -emit-llvm -Wno-strict-prototypes -Wno-incompatible-function-pointer-types -fcf-protection=branch -triple i386-linux-gnu %s -o - | FileCheck %s
// CHECK: @t5 = weak{{.*}} global i32 2
int t5 __attribute__((weak)) = 2;

View File

@ -1,4 +1,4 @@
// RUN: %clang_cc1 -no-opaque-pointers -triple %itanium_abi_triple -emit-llvm %s -o - | FileCheck %s
// RUN: %clang_cc1 -no-opaque-pointers -triple %itanium_abi_triple -Wno-incompatible-function-pointer-types -emit-llvm %s -o - | FileCheck %s
// CHECK: _Z1fPA10_1X
// CHECK: _Z1fPFvvE

View File

@ -15,5 +15,5 @@ void __attribute__((aarch64_sve_pcs)) foo3(void) {} // expected-error {{function
typedef int (*fn_ty)(void);
typedef int __attribute__((aarch64_sve_pcs)) (*aasvepcs_fn_ty)(void);
void foo4(fn_ty ptr1, aasvepcs_fn_ty ptr2) {
ptr1 = ptr2; // expected-warning {{incompatible function pointer types}}
ptr1 = ptr2; // expected-error {{incompatible function pointer types}}
}

View File

@ -15,5 +15,5 @@ void __attribute__((aarch64_vector_pcs)) foo3(void) {} // expected-error {{funct
typedef int (*fn_ty)(void);
typedef int __attribute__((aarch64_vector_pcs)) (*aavpcs_fn_ty)(void);
void foo4(fn_ty ptr1, aavpcs_fn_ty ptr2) {
ptr1 = ptr2; // expected-warning {{incompatible function pointer types}}
ptr1 = ptr2; // expected-error {{incompatible function pointer types}}
}

View File

@ -8,8 +8,8 @@ typedef void (*callback_2t)(void);
void foo(callback_ns_1t nsfptr, // expected-error{{functions may not be declared with 'cmse_nonsecure_call' attribute}}
callback_1t fptr) __attribute__((cmse_nonsecure_call))
{
callback_1t fp1 = nsfptr; // expected-warning{{incompatible function pointer types initializing 'callback_1t'}}
callback_ns_1t fp2 = fptr; // expected-warning{{incompatible function pointer types initializing 'callback_ns_1t'}}
callback_1t fp1 = nsfptr; // expected-error{{incompatible function pointer types initializing 'callback_1t'}}
callback_ns_1t fp2 = fptr; // expected-error{{incompatible function pointer types initializing 'callback_ns_1t'}}
callback_2t fp3 = fptr;
callback_ns_2t fp4 = nsfptr;
}

View File

@ -15,7 +15,7 @@ void testNoCfCheckImpl(double __attribute__((nocf_check)) i) {} // expected-warn
// Allow attributed function pointers as well as casting between attributed
// and non-attributed function pointers.
void testNoCfCheckMismatch(FuncPointer f) {
FuncPointerWithNoCfCheck fNoCfCheck = f; // expected-warning {{incompatible function pointer types}}
FuncPointerWithNoCfCheck fNoCfCheck = f; // expected-error {{incompatible function pointer types}}
(*fNoCfCheck)(); // no-warning
}

View File

@ -81,7 +81,7 @@ static int funk(char *s) {
void next(void);
void foo4(void) {
int (^xx)(const char *s) = ^(char *s) { return 1; }; // expected-error {{incompatible block pointer types initializing 'int (^)(const char *)' with an expression of type 'int (^)(char *)'}}
int (*yy)(const char *s) = funk; // expected-warning {{incompatible function pointer types initializing 'int (*)(const char *)' with an expression of type 'int (char *)'}}
int (*yy)(const char *s) = funk; // expected-error {{incompatible function pointer types initializing 'int (*)(const char *)' with an expression of type 'int (char *)'}}
int (^nested)(char *s) = ^(char *str) { void (^nest)(void) = ^(void) { printf("%s\n", str); }; next(); return 1; };
}

View File

@ -13,7 +13,7 @@ void call(void) {
fp call_me = func;
call_me(1, 2, 3); // c2x-error {{too many arguments to function call, expected 0, have 3}}
fp nope = other_func; // c2x-warning {{incompatible function pointer types initializing 'fp' (aka 'void (*)(void)') with an expression of type 'void (int)'}}
fp nope = other_func; // c2x-error {{incompatible function pointer types initializing 'fp' (aka 'void (*)(void)') with an expression of type 'void (int)'}}
}
// Ensure these function declarations do not merge in C2x.

View File

@ -4,6 +4,6 @@ void __attribute__((ms_abi)) foo(void);
void (*pfoo)(void) = foo;
void __attribute__((sysv_abi)) bar(void);
void (*pbar)(void) = bar; // expected-warning{{incompatible function pointer types}}
void (*pbar)(void) = bar; // expected-error{{incompatible function pointer types}}
void (__attribute__((sysv_abi)) *pfoo2)(void) = foo; // expected-warning{{incompatible function pointer types}}
void (__attribute__((sysv_abi)) *pfoo2)(void) = foo; // expected-error{{incompatible function pointer types}}

View File

@ -1,9 +1,9 @@
// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-pc-linux-gnu %s
void __attribute__((ms_abi)) foo(void);
void (*pfoo)(void) = foo; // expected-warning{{incompatible function pointer types}}
void (*pfoo)(void) = foo; // expected-error{{incompatible function pointer types}}
void __attribute__((sysv_abi)) bar(void);
void (*pbar)(void) = bar;
void (__attribute__((ms_abi)) *pbar2)(void) = bar; // expected-warning{{incompatible function pointer types}}
void (__attribute__((ms_abi)) *pbar2)(void) = bar; // expected-error{{incompatible function pointer types}}

View File

@ -31,7 +31,7 @@ void (__attribute__((fastcall)) *pfoo)(float*) = foo;
void (__attribute__((stdcall)) *pbar)(float*) = bar;
void (__attribute__((cdecl)) *ptest1)(void) = test1; // expected-warning {{incompatible function pointer types}}
void (__attribute__((cdecl)) *ptest1)(void) = test1; // expected-error {{incompatible function pointer types}}
void (*pctest0)() = ctest0;

View File

@ -1,5 +1,7 @@
// RUN: %clang_cc1 -fsyntax-only %s -Wincompatible-pointer-types -verify
// RUN: %clang_cc1 -fsyntax-only %s -Wincompatible-function-pointer-types -verify
// RUN: %clang_cc1 -fsyntax-only %s -Wincompatible-pointer-types -verify=hard,expected
// RUN: %clang_cc1 -fsyntax-only %s -Wno-error=incompatible-pointer-types -verify=soft,expected
// RUN: %clang_cc1 -fsyntax-only %s -Wincompatible-function-pointer-types -verify=hard,expected
// RUN: %clang_cc1 -fsyntax-only %s -Wno-error=incompatible-function-pointer-types -verify=soft,expected
// This test ensures that the subgroup of -Wincompatible-pointer-types warnings
// that concern function pointers can be promoted (or not promoted) to an error
@ -10,5 +12,6 @@ int bar(char *a, int *b) { return 0; }
int foo(MyFnTyA x) { return 0; } // expected-note {{passing argument to parameter 'x' here}}
void baz(void) {
foo(&bar); // expected-warning {{incompatible function pointer types passing 'int (*)(char *, int *)' to parameter of type 'MyFnTyA' (aka 'int (*)(int *, char *)')}}
foo(&bar); // soft-warning {{incompatible function pointer types passing 'int (*)(char *, int *)' to parameter of type 'MyFnTyA' (aka 'int (*)(int *, char *)')}} \
hard-error {{incompatible function pointer types passing 'int (*)(char *, int *)' to parameter of type 'MyFnTyA' (aka 'int (*)(int *, char *)')}}
}

View File

@ -14,14 +14,14 @@ void foo_noproto();
void foo_noret_noproto() __attribute__((noreturn));
void test() {
Fn_noret fn2 = &foo; // expected-warning {{incompatible function pointer types initializing 'Fn_noret'}}
Fn_noret fn3 = &foo_noret;
Fn_ret fn4 = &foo_noret;
Fn_noret fn2 = &foo; // expected-error {{incompatible function pointer types initializing 'Fn_noret'}}
Fn_noret fn3 = &foo_noret;
Fn_ret fn4 = &foo_noret;
Fn_ret fn5 = &foo;
Fn_noret_noproto fn6 = &foo_noproto; // expected-warning {{incompatible function pointer types initializing 'Fn_noret_noproto'}}
Fn_noret_noproto fn7 = &foo_noret_noproto;
Fn_ret_noproto fn8 = &foo_noret_noproto;
Fn_noret_noproto fn6 = &foo_noproto; // expected-error {{incompatible function pointer types initializing 'Fn_noret_noproto'}}
Fn_noret_noproto fn7 = &foo_noret_noproto;
Fn_ret_noproto fn8 = &foo_noret_noproto;
Fn_ret_noproto fn9 = &foo_noproto;
}

View File

@ -14,12 +14,12 @@ void (*funcptr_nn)(__attribute__((noescape)) int *, __attribute__((noescape)) in
void test0(int c) {
escapefuncptr = &escapefunc;
escapefuncptr = &noescapefunc;
noescapefuncptr = &escapefunc; // expected-warning {{incompatible function pointer types assigning to 'void (*)(__attribute__((noescape)) int *)' from 'void (*)(int *)'}}
noescapefuncptr = &escapefunc; // expected-error {{incompatible function pointer types assigning to 'void (*)(__attribute__((noescape)) int *)' from 'void (*)(int *)'}}
noescapefuncptr = &noescapefunc;
escapefuncptr = c ? &escapefunc : &noescapefunc;
noescapefuncptr = c ? &escapefunc : &noescapefunc; // expected-warning {{incompatible function pointer types assigning to 'void (*)(__attribute__((noescape)) int *)' from 'void (*)(int *)'}}
noescapefuncptr = c ? &escapefunc : &noescapefunc; // expected-error {{incompatible function pointer types assigning to 'void (*)(__attribute__((noescape)) int *)' from 'void (*)(int *)'}}
funcptr_ee = c ? &func_ne : &func_en;
funcptr_nn = c ? &func_ne : &func_en; // expected-warning {{incompatible function pointer types assigning to 'void (*)(__attribute__((noescape)) int *, __attribute__((noescape)) int *)' from 'void (*)(int *, int *)'}}
funcptr_nn = c ? &func_ne : &func_en; // expected-error {{incompatible function pointer types assigning to 'void (*)(__attribute__((noescape)) int *, __attribute__((noescape)) int *)' from 'void (*)(int *, int *)'}}
}

View File

@ -112,7 +112,7 @@ void fn_type_conversions() {
void (*ambiguous)(int *) = &foo; // expected-error{{initializing 'void (*)(int *)' with an expression of incompatible type '<overloaded function type>'}} expected-note@-4{{candidate function}} expected-note@-3{{candidate function}}
void *vp_ambiguous = &foo; // expected-error{{initializing 'void *' with an expression of incompatible type '<overloaded function type>'}} expected-note@-5{{candidate function}} expected-note@-4{{candidate function}}
void (*specific1)(int *) = (void (*)(void *))&foo; // expected-warning{{incompatible function pointer types initializing 'void (*)(int *)' with an expression of type 'void (*)(void *)'}}
void (*specific1)(int *) = (void (*)(void *))&foo; // expected-error{{incompatible function pointer types initializing 'void (*)(int *)' with an expression of type 'void (*)(void *)'}}
void *specific2 = (void (*)(void *))&foo;
void disabled(void *c) __attribute__((overloadable, enable_if(0, "")));
@ -120,8 +120,8 @@ void fn_type_conversions() {
void disabled(char *c) __attribute__((overloadable, enable_if(1, "The function name lies.")));
// To be clear, these should all point to the last overload of 'disabled'
void (*dptr1)(char *c) = &disabled;
void (*dptr2)(void *c) = &disabled; // expected-warning{{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type '<overloaded function type>'}} expected-note@-5{{candidate function made ineligible by enable_if}} expected-note@-4{{candidate function made ineligible by enable_if}} expected-note@-3{{candidate function has type mismatch at 1st parameter (expected 'void *' but has 'char *')}}
void (*dptr3)(int *c) = &disabled; // expected-warning{{incompatible function pointer types initializing 'void (*)(int *)' with an expression of type '<overloaded function type>'}} expected-note@-6{{candidate function made ineligible by enable_if}} expected-note@-5{{candidate function made ineligible by enable_if}} expected-note@-4{{candidate function has type mismatch at 1st parameter (expected 'int *' but has 'char *')}}
void (*dptr2)(void *c) = &disabled; // expected-error{{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type '<overloaded function type>'}} expected-note@-5{{candidate function made ineligible by enable_if}} expected-note@-4{{candidate function made ineligible by enable_if}} expected-note@-3{{candidate function has type mismatch at 1st parameter (expected 'void *' but has 'char *')}}
void (*dptr3)(int *c) = &disabled; // expected-error{{incompatible function pointer types initializing 'void (*)(int *)' with an expression of type '<overloaded function type>'}} expected-note@-6{{candidate function made ineligible by enable_if}} expected-note@-5{{candidate function made ineligible by enable_if}} expected-note@-4{{candidate function has type mismatch at 1st parameter (expected 'int *' but has 'char *')}}
void *specific_disabled = &disabled;
}

View File

@ -44,8 +44,8 @@ void FunctionPtrs(void) {
void (*p)(void *) = NotOverloaded; //expected-error{{cannot take address of function 'NotOverloaded' because parameter 1 has pass_object_size attribute}}
void (*p2)(void *) = &NotOverloaded; //expected-error{{cannot take address of function 'NotOverloaded' because parameter 1 has pass_object_size attribute}}
void (*p3)(void *) = IsOverloaded; //expected-warning{{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type '<overloaded function type>'}}
void (*p4)(void *) = &IsOverloaded; //expected-warning{{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type '<overloaded function type>'}}
void (*p3)(void *) = IsOverloaded; //expected-error{{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type '<overloaded function type>'}}
void (*p4)(void *) = &IsOverloaded; //expected-error{{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type '<overloaded function type>'}}
void (*p5)(char *) = IsOverloaded;
void (*p6)(char *) = &IsOverloaded;

View File

@ -14,8 +14,8 @@ void __attribute__((preserve_most(1))) foo1(void *ptr) { // expected-error {{'pr
void (__attribute__((preserve_most)) *pfoo1)(void *) = foo;
void (__attribute__((cdecl)) *pfoo2)(void *) = foo; // expected-warning {{incompatible function pointer types initializing 'void (*)(void *) __attribute__((cdecl))' with an expression of type 'void (void *) __attribute__((preserve_most))'}}
void (*pfoo3)(void *) = foo; // expected-warning {{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type 'void (void *) __attribute__((preserve_most))'}}
void (__attribute__((cdecl)) *pfoo2)(void *) = foo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *) __attribute__((cdecl))' with an expression of type 'void (void *) __attribute__((preserve_most))'}}
void (*pfoo3)(void *) = foo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type 'void (void *) __attribute__((preserve_most))'}}
typedef_fun_t typedef_fun_foo; // expected-note {{previous declaration is here}}
void __attribute__((preserve_most)) typedef_fun_foo(int x) { } // expected-error {{function declared 'preserve_most' here was previously declared without calling convention}}
@ -30,8 +30,8 @@ void __attribute__((preserve_all(1))) boo1(void *ptr) { // expected-error {{'pre
void (__attribute__((preserve_all)) *pboo1)(void *) = boo;
void (__attribute__((cdecl)) *pboo2)(void *) = boo; // expected-warning {{incompatible function pointer types initializing 'void (*)(void *) __attribute__((cdecl))' with an expression of type 'void (void *) __attribute__((preserve_all))'}}
void (*pboo3)(void *) = boo; // expected-warning {{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type 'void (void *) __attribute__((preserve_all))'}}
void (__attribute__((cdecl)) *pboo2)(void *) = boo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *) __attribute__((cdecl))' with an expression of type 'void (void *) __attribute__((preserve_all))'}}
void (*pboo3)(void *) = boo; // expected-error {{incompatible function pointer types initializing 'void (*)(void *)' with an expression of type 'void (void *) __attribute__((preserve_all))'}}
typedef_fun_t typedef_fun_boo; // expected-note {{previous declaration is here}}
void __attribute__((preserve_all)) typedef_fun_boo(int x) { } // expected-error {{function declared 'preserve_all' here was previously declared without calling convention}}

View File

@ -31,9 +31,9 @@ typedef id FuncSignature (NSObject *arg1, Derived *arg2);
void foo(void)
{
// GCC currently allows this (it has some fiarly new support for covariant return types and contravariant argument types).
// GCC currently allows this (it has some fairly new support for covariant return types and contravariant argument types).
// Since registerFunc: expects a Derived object as it's second argument, I don't know why this would be legal.
[Derived registerFunc: ExternFunc]; // expected-warning{{incompatible function pointer types sending 'NSObject *(NSObject *, NSObject *)' to parameter of type 'FuncSignature *' (aka 'id (*)(NSObject *, Derived *)')}}
[Derived registerFunc: ExternFunc]; // expected-error{{incompatible function pointer types sending 'NSObject *(NSObject *, NSObject *)' to parameter of type 'FuncSignature *' (aka 'id (*)(NSObject *, Derived *)')}}
}
// rdar://10751015