CertC++-MSC37¶
Ensure that control never reaches the end of a non-void function
Required inputs: IR
If control reaches the closing curly brace (
}) of a non-
void function without evaluating a
return statement, using the return value of the function call is
undefined
behavior.(See
undefined
behavior 88.)
Noncompliant Code Example
In this noncompliant code example, control reaches the end of the
checkpass() function when the two strings passed to
strcmp() are not equal, resulting in undefined behavior. Many
compilers will generate code for the
checkpass() function, returning various values along the execution
path where no
return statement is defined.
#include <string.h>
#include <stdio.h>
int checkpass(const char *password) {
if (strcmp(password, "pass") == 0) {
return 1;
}
}
void func(const char *userinput) {
if (checkpass(userinput)) {
printf("Success\n");
}
}
This error is frequently diagnosed by compilers. (See MSC00-C. Compile cleanly at high warning levels.)
Compliant Solution
This compliant solution ensures that the
checkpass() function always returns a value:
#include <string.h>
#include <stdio.h>
int checkpass(const char *password) {
if (strcmp(password, "pass") == 0) {
return 1;
}
return 0;
}
void func(const char *userinput) {
if (checkpass(userinput)) {
printf("Success!\n");
}
}
Noncompliant Code Example
In this noncompliant code example, control reaches the end of the
getlen() function when
input does not contain the integer
delim. Because the potentially undefined return value of
getlen() is later used as an index into an array, a buffer
overflow may occur.
#include <stddef.h>
size_t getlen(const int *input, size_t maxlen, int delim) {
for (size_t i = 0; i < maxlen; ++i) {
if (input[i] == delim) {
return i;
}
}
}
void func(int userdata) {
size_t i;
int data[] = { 1, 1, 1 };
i = getlen(data, sizeof(data), 0);
data[i] = userdata;
}
Implementation Details (GCC)
Violating this rule can have unexpected consequences, as in the following example:
#include <stdio.h>
size_t getlen(const int *input, size_t maxlen, int delim) {
for (size_t i = 0; i < maxlen; ++i) {
if (input[i] == delim) {
return i;
}
}
}
int main(int argc, char **argv) {
size_t i;
int data[] = { 1, 1, 1 };
i = getlen(data, sizeof(data), 0);
printf("Returned: %zu\n", i);
data[i] = 0;
return 0;
}
When this program is compiled with
-Wall on most versions of the GCC compiler, the following warning
is generated:
example.c: In function 'getlen': example.c:12: warning: control reaches end of non-void function
None of the inputs to the function equal the delimiter, so when run with
GCC 5.3 on Linux, control reaches the end of the
getlen() function, which is undefined behavior and in this test
returns
3, causing an out-of-bounds write to the
data array.
Compliant Solution
This compliant solution changes the interface of
getlen() to store the result in a user-provided pointer and
returns a status indicator to report success or failure. The best method for
handling this type of error is specific to the application and the type of
error. (See
ERR00-C.
Adopt and implement a consistent and comprehensive error-handling policy
for more on error handling.)
#include <stddef.h>
int getlen(const int *input, size_t maxlen, int delim,
size_t *result) {
if (result == NULL) {
return -1;
}
for (size_t i = 0; i < maxlen; ++i) {
if (input[i] == delim) {
*result = i;
return 0;
}
}
return -1;
}
void func(int userdata) {
size_t i;
int data[] = {1, 1, 1};
if (getlen(data, sizeof(data), 0, &i) != 0) {
/* Handle error */
} else {
data[i] = userdata;
}
}
Exceptions
MSC37-C-EX1: According to the C Standard, 5.1.2.2.3, paragraph
1 [
ISO/IEC
9899:2011], "Reaching the
} that terminates the main function returns a
value of 0." As a result, it is permissible for control to reach the end
of the
main() function without executing a return statement.
MSC37-C-EX2: It is permissible for a control path to not
return a value if that code path is never taken and a function marked
_Noreturn is called as part of that code path. For example:
#include <stdio.h>
#include <stdlib.h>
_Noreturn void unreachable(const char *msg) {
printf("Unreachable code reached: %s\n", msg);
exit(1);
}
enum E {
One,
Two,
Three
};
int f(enum E e) {
switch (e) {
case One: return 1;
case Two: return 2;
case Three: return 3;
}
unreachable("Can never get here");
}
Risk Assessment
Using the return value from a non-
void function where control reaches the end of the function
without evaluating a
return statement can lead to buffer overflow
vulnerabilities
as well as other
unexpected
program behaviors.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| MSC37-C | High | Unlikely | Low | P9 | L2 |
Related Guidelines
| Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CERT C Secure Coding Standard | MSC01-C. Strive for logical completeness | Prior to 2018-01-12: CERT: Unspecified Relationship |
| CWE 2.11 | CWE-758 | 2017-07-07: CERT: Rule subset of CWE |
Bibliography
| [ ISO/IEC 9899:2011] | 5.1.2.2.3, "Program Termination" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
lambda_return_missing_value |
Return without value in non-void lambda expression |
None |
False |
missing_return |
Ensure that control never reaches the end of a non-void function. |
None |
False |
missing_return_in_lambda |
Non-void lambda expression needs return with value at end. |
None |
False |
return_missing_value |
Return without value in non-void function |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
exclude_missing_return_for_main¶
exclude_missing_return_for_main : bool = False