...
The C Standard identifies five distinct situations in which undefined behavior (UB) may arise as a result of invoking a function using a declaration that is incompatible with its definition or with by supplying incorrect types or numbers of arguments:
UB | Description |
A pointer is used to call a function whose type is not compatible with the referenced type (6.3.2.3). | |
For a call to a function without a function prototype in scope, the number of arguments does not equal the number of parameters (6.5.2.2). | |
For a call to a function without a function prototype in scope where the function is defined with a function prototype, either the prototype ends with an ellipsis or the types of the arguments after promotion are not compatible with the types of the parameters (6.5.2.2). | |
For a call to a function without a function prototype in scope where the function is not defined with a function prototype, the types of the arguments after promotion are not compatible with those of the parameters after promotion (with certain exceptions) (6.5.2.2). | |
A function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function (6.5.2.2). |
Functions that are appropriately declared (as in DCL07DCL40-C. Include the appropriate type information in function declarators) will typically generate a compiler error if Do not create incompatible declarations of the same function or object) will typically generate a compiler diagnostic message if they are supplied with the wrong number or types of arguments. However, there are cases in which supplying the incorrect arguments to a function will, at best, generate compiler warnings. While Although such warnings should be resolved, they do not prevent program compilation. (See MSC00-C. Compile cleanly at high warning levels.)
Noncompliant Code Example
In this noncompliant example, the C Standard Library function strchr()
is called through the function pointer fp
with incorrectly typed arguments. According to the C Standard, subclause 6.3.2.3 paragraph 8 [ISO/IEC 9899:2011],
A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
(See also undefined behavior 26 in Annex J of the C Standard.)
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
char *(*fp)();
int main(void) {
const char *c;
fp = strchr;
c = fp('e', "Hello");
printf("%s\n", c);
return 0;
} |
Noncompliant Code Example
In this noncompliant example, the C Standard Library function strchr()
is declared with the correct arguments. This code still exhibits the same undefined behavior, but most compilers will warn that the arguments passed to fp
do not match its declaration.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
char *(*fp)(const char *, int);
int main(void) {
const char *c;
fp = strchr;
c = fp('e', "Hello");
printf("%s\n", c);
return 0;
}
|
Compliant Solution
In this compliant solution, the function pointer fp
is invoked with the correct number and type of arguments:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
char *(*fp)(const char *, int);
int main(void) {
const char *c;
fp = strchr;
c = fp("Hello",'e');
printf("%s\n", c);
return 0;
}
|
Noncompliant Code Example
In this noncompliant example, the function copy()
is defined to take three arguments but is called with two arguments:
Code Block | ||||
---|---|---|---|---|
| ||||
/* In another source file */
#include <string.h>
void copy(char *dst, const char *src, size_t size) {
if (!strncpy(dst, src, size)) {
/* Report error */
}
}
/* In this source file, no copy prototype in scope */
void copy();
void g(const char *s) {
enum { BUFFERSIZE = 20 };
char buf[BUFFERSIZE];
copy(buf, s);
} |
Compliant Solution
In this compliant solution, the prototype for the copy()
function is included in the scope in the source file where it is used, and the copy()
function is passed the correct number and type of arguments:
The header <tgmath.h>
provides type-generic macros for math functions. Although most functions from the <math.h>
header have a complex counterpart in <complex.h>
, several functions do not. Calling any of the following type-generic functions with complex values is undefined behavior.
Functions That Should Not Be Called with Complex Values
atan2() | erf | fdim | fmin | ilogb | llround | logb | nextafter | rint | tgamma |
cbrt | erfc | floor | fmod | ldexp | log10 | lrint | nexttoward | round | trunc |
ceil | exp2 | fma | frexp | lgamma | log1p | lround | remainder | scalbn | |
copysign | expm1 | fmax | hypot | llrint | log2 | nearbyint | remquo | scalbln |
This noncompliant code example attempts to take the base-2 logarithm of a complex number, resulting in undefined behavior:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <tgmath.h>
void func(void) {
double complex c = 2.0 + 4.0 * I;
double complex result = log2(c);
} |
Compliant Solution (Complex Number)
If the clog2()
function is not available for an implementation as an extension, the programmer can take the base-2 logarithm of a complex number, using log()
instead of log2()
, because log()
can be used on complex arguments, as shown in this compliant solution:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <tgmath.h>
void func(void) {
double complex c = 2.0 + 4.0 * I;
double complex result = log(c)/log(2);
} |
Compliant Solution (Real Number)
The programmer can use this compliant solution if the intent is to take the base-2 logarithm of the real part of the complex number:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <tgmath.h>
void func(void) {
double complex c = 2.0 + 4.0 * I;
double complex result = log2(creal(c));
} |
Noncompliant Code Example
In this noncompliant example, the C standard library function strchr()
is called through the function pointer fp
declared with a prototype with incorrectly typed arguments. According to the C Standard, 6.3.2.3, paragraph 8 [ISO/IEC 9899:2024]
A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
char *(*fp)();
int main(void) {
const char *c;
fp = strchr;
c = fp('e', "Hello");
printf("%s\n", c);
return 0;
} |
Compliant Solution
In this compliant solution, the function pointer fp
, which points to the C standard library function strchr()
, is declared with the correct parameters and is invoked with the correct number and type of arguments:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h>
#include <string.h>
char *(*fp)(const char *, int);
int main(void) {
const char *c;
fp = strchr;
c = fp("Hello",'e');
printf("%s\n", c);
return 0;
| ||||
Code Block | ||||
| ||||
/* In another source file */
#include <string.h>
void copy(char *dst, const char *src, size_t size) {
if (strncpy(dst, src, size) == 0) {
/* Report error */
}
}
/* Copy prototype in scope in this source file */
void copy(char *dst, const char *src, size_t size);
void g(const char *s) {
enum { BUFFERSIZE = 20 };
char buf[BUFFERSIZE];
copy(buf, s, BUFFERSIZE);
}
|
Noncompliant Code Example
In this noncompliant example, the function buginff()
is defined to take a variable number of arguments and expects them all to be signed integers, with a sentinel value of -1
an argument of type long
but f()
is called from another file with an argument of type int
:
Code Block | ||||
---|---|---|---|---|
| ||||
/* In another source file */ voidlong buginff(const char *fmt, ...long x) { return x < /* ... */0 ? -x : x; } /* In this source file, no buginff prototype in scope */ voidlong buginff(); voidlong hg(voidint x) { buginf("bug in function %s, line %d\n", "h", __LINE__return f(x); } |
While this code appears to be well-defined due to the prototype-less declaration of buginf()
, this code exhibits undefined behavior per subclause 6.5.2.2 paragraph 6 [ISO/IEC 9899:2011]:
If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.
Compliant Solution
In this compliant solution, the prototype for the function buginf()
is included in the scope in the source file where it is used:
Compliant Solution
In this compliant solution, the prototype for the function f()
is included in the source file in the scope of where it is called, and the function f()
is correctly called with an argument of type long
:
Code Block | ||||
---|---|---|---|---|
| ||||
/* In | ||||
Code Block | ||||
| ||||
/* In another source file */ voidlong buginff(constlong char *fmt, ...) { /* ... */x) { return x < 0 ? -x : x; } /* buginff prototype in scope in this source file */ voidlong buginff(const char *fmt, ...long x); voidlong hg(voidint x) { buginf("bug in function %s, line %d\n", "h", __LINE__); /* ... */return f((long)x); } |
Noncompliant Code Example
...
In this noncompliant example, the function f()
is defined to take an argument of type long
, but f()
is called from another file with an argument of type int
:
Code Block | ||||
---|---|---|---|---|
| ||||
/* In another source file */
long f(long x) {
return x < 0 ? -x : x;
}
/* In this source file, no f prototype in scope */
long f();
long g(int x) {
return f(x);
} |
Compliant Solution
In this compliant solution, the prototype for the function f()
is included in the scope in the source file where it is used, and the function f()
is correctly called with an argument of type long
:
Code Block | ||||
---|---|---|---|---|
| ||||
/* In another source file */
long f(long x) {
return x < 0 ? -x : x;
}
/* f prototype in scope in this source file */
long f(long x);
long g(int x) {
return f((long)x);
}
|
Noncompliant Code Example (POSIX)
The POSIX function open()
[Open Group 2004] is a variadic function with the following prototype:
Code Block |
---|
int open(const char *path, int oflag, ... );
|
The open()
function accepts a third argument to determine a newly created file's access mode. If open()
is used to create a new file, and the third argument is omitted, the file may be created with unintended access permissions. (See FIO06-C. Create files with appropriate access permissions.)
In this noncompliant code example from a vulnerability in the useradd()
function of the shadow-utils
package CVE-2006-1174, the third argument to open()
has been accidentally omitted:
Code Block | ||||
---|---|---|---|---|
| ||||
fd = open(ms, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC);
|
Note that, technically, it is also incorrect to pass a third argument to open()
when not creating a new file (that is, with the O_CREAT
flag not set). A POSIX implementation could, if it wished, return an EINVAL error in this case. However, in practice, it is unlikely to cause a problem.
Compliant Solution (POSIX)
To correct this example, a third argument is specified in the call to open()
:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <fcntl.h>
void func(const char *ms, mode_t perms) {
/* ... */
int fd;
fd = open(ms, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC, perms);
if (fd == -1) {
/* Handle error */
}
} |
Risk Assessment
Calling a function with incorrect arguments can result in unexpected or unintended program behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
EXP37-C | Medium | Probable | High | P4 | L3 |
Automated Detection
...
Can detect some violations of this rule. In particular, it ensures that all calls to open()
supply exactly two arguments if the second argument does not involve O_CREAT
, and exactly three arguments if the second argument does involve O_CREAT
...
...
CC2.EXP37
...
Partially implemented
...
Can detect violation of this rule when the -Wstrict-prototypes
flag is used. However, it cannot detect violations involving variadic functions, such as the open()
example described earlier
(POSIX)
The POSIX function open()
[IEEE Std 1003.1:2013] is a variadic function with the following prototype:
Code Block |
---|
int open(const char *path, int oflag, ... );
|
The open()
function accepts a third argument to determine a newly created file's access mode. If open()
is used to create a new file and the third argument is omitted, the file may be created with unintended access permissions. (See FIO06-C. Create files with appropriate access permissions.)
In this noncompliant code example from a vulnerability in the useradd()
function of the shadow-utils
package CVE-2006-1174, the third argument to open()
is accidentally omitted:
Code Block | ||||
---|---|---|---|---|
| ||||
fd = open(ms, O_CREAT | O_EXCL | O_WRONLY | O_TRUNC);
|
Technically, it is incorrect to pass a third argument to open() when not creating a new file (that is, with the O_CREAT flag not set).
Compliant Solution (POSIX)
In this compliant solution, a third argument is specified in the call to open()
:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <fcntl.h>
void func(const char *ms, mode_t perms) {
/* ... */
int fd;
fd = open(ms, O_CREAT | O_EXCL | O_WRONLY | O_TRUNC, perms);
if (fd == -1) {
/* Handle error */
}
} |
Risk Assessment
Calling a function with incorrect arguments can result in unexpected or unintended program behavior.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
EXP37-C | Medium | Probable | High | P4 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| incompatible-argument-type parameter-match parameter-match-computed parameter-match-type | Fully checked | ||||||
Axivion Bauhaus Suite |
| CertC-EXP37 | |||||||
CodeSonar |
| LANG.FUNCS.APM | Array parameter mismatch | ||||||
Compass/ROSE | Can detect some violations of this rule. In particular, it ensures that all calls to | ||||||||
Coverity |
| MISRA C 2012 Rule 8.2 MISRA C 2012 Rule 17.3 | Implemented Relies on functions declared with prototypes, allow compiler to check | ||||||
Cppcheck Premium |
| premium-cert-exp37-c | Fully implemented | ||||||
| CC2.EXP37 | Partially implemented | |||||||
EDG | |||||||||
GCC |
| Can detect violation of this rule when the | |||||||
Helix QAC |
| C1331, C1332, C1333, C3002, C3320, C3335 C++0403 | |||||||
Klocwork |
| MISRA.FUNC.UNMATCHED.PARAMS | |||||||
LDRA tool suite |
| 41 D, 21 S, 98 S, 170 S, 496 S, 576 S | Partially implemented | ||||||
Parasoft C/C++test |
| CERT_C-EXP37-a | Conversions shall not be performed between non compatible pointer to a function types | ||||||
Polyspace Bug Finder |
| Checks for:
Rule partially covered. | |||||||
PVS-Studio |
| V540, V541, V549, V575, V632, V639, V666, V671, V742, V743, V764, V1004 | |||||||
SonarQube C/C++ Plugin |
| S930 | Detects incorrect argument count | ||||||
RuleChecker |
| parameter-match parameter-match-type | Partially checked | ||||||
TrustInSoft Analyzer |
| unclassified ("function type matches") | Partially verified (see one compliant and one non-compliant example). |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
---|---|---|
CERT C Secure Coding Standard | DCL07-C. Include the appropriate type information in function declarators | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C Secure Coding Standard | MSC00-C. Compile cleanly at high warning levels | Prior to 2018-01-12: CERT: Unspecified Relationship |
CERT C Secure Coding Standard | FIO06-C. Create files with appropriate access permissions | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TR 24772:2013 | Subprogram Signature Mismatch [OTR] | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TS 17961 | Calling functions with incorrect arguments [argcomp] | Prior to 2018-01-12: CERT: Unspecified Relationship |
MISRA C:2012 | Rule 8.2 (required) | Prior to 2018-01-12: CERT: Unspecified Relationship |
MISRA C:2012 | Rule 17.3 (mandatory) | Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-628, Function Call with Incorrectly Specified Arguments | 2017-07-05: CERT: Rule subset of CWE |
CERT-CWE Mapping Notes
Key here for mapping notes
CWE-685 and EXP37-C
EXP37-C = Union( CWE-685, CWE-686) Intersection( CWE-685, CWE-686) = Ø
CWE-686 and EXP37-C
Intersection( EXP37-C, FIO47-C) =
- Invalid argument types passed to format I/O function
EXP37-C – FIO47-C =
- Invalid argument types passed to non-format I/O function
FIO47-C – EXP37-C =
- Invalid format string, but correctly matches arguments in number and type
EXP37-C = Union( CWE-685, CWE-686)
Intersection( CWE-685, CWE-686) = Ø
CWE-628 and EXP37-C
CWE-628 = Union( EXP37-C, list) where list =
- Improper ordering of function arguments (that does not violate argument types)
- Wrong argument values or references
...
...
41 D
98 S
170 S
496 S
576 S
...
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
...
DCL07-C. Include the appropriate type information in function declarators
MSC00-C. Compile cleanly at high warning levels
FIO06-C. Create files with appropriate access permissions
...
Bibliography
[CVE] | CVE-2006-1174 |
[ISO/IEC 9899:2011] |
6. |
5.2. |
2, " |
Subclause 6.5
Function Calls" | |
[ISO/IEC 9899:2024] | 6.3.2. |
3, " |
Pointers" | |
[IEEE Std 1003.1:2013] | open() |
[Spinellis 2006] | Section 2.6.1, "Incorrect Routine or Arguments" |
...
...