If control reaches the end closing curly brace (}
) of a non-void
function without evaluating a return
statement, using the return value of the function call results in is undefined behavior. (See also undefined behavior 7 of Appendix J88.
This rule is related to recommendation MSC01-C. Strive for logical completeness because both rules involve ensuring that programs properly handle all possible conditions.
)
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. This leads to , resulting in undefined behavior, and various . Many compilers will generate code equivalent to for the checkpass()
function, returning various values when along the execution path where no return
statement is executed in checkpass()
defined.
Code Block | ||||
---|---|---|---|---|
| ||||
#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 recommendation MSC00-C. Compile cleanly at high warning levels.)
...
This compliant solution ensures that the checkpass()
function always returns a value.:
Code Block | ||||
---|---|---|---|---|
| ||||
#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 noncomplaint noncompliant code example, control reaches the end of the the getlen()
function when input
does not contain the integer delim
. As Because the potentially undefined return value of getlen()
is later used as an index into an array, this can lead to a buffer overflow may occur.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stddef.h> size_t getlen(const int *input, size_t maxlen, int delim) { for (size_t i; for (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
...
When the following is compiled with -Wall
on most versions of the GCC compiler, the following warning is generated
Code Block |
---|
example.c: In function âgetlenâ:
example.c:12: warning: control reaches end of non-void function
|
(GCC)
Violating this rule can have unexpected consequences, as in the following example:
When the program was compiled and run with GCC 4.4.3 on Linux, the getlen()
function returned 5, causing an out-of-bounds write to the data
array:
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stdio.h> #include <stdlib.h> #include <string.h> size_t getlen(const int *input, size_t maxlen, int delim) { for (size_t i; for (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: %d%zu\n", i); data[i] = 0; return EXIT_SUCCESS0; } |
When this program is compiled with -Wall
on most versions of the GCC compiler, the following warning is generated:
Code Block |
---|
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 return an error code to indicate any error conditionsreturns 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 recommendation ERR00-C. Adopt and implement a consistent and comprehensive error-handling policy for more on error handling.)
Code Block | ||||
---|---|---|---|---|
| ||||
#include <stddef.h> int getlen(const int *input, size_t maxlen, int delim, size size_t *result) { if (result == NULL) { size_t i return -1; } for (size_t i = 0; i < maxlen; ++i) { if (input[i] == delim) { if (result != NULL) { *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.3.4, paragraph 1 [ISO/IEC 9899:2024], "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:
Code Block | ||||
---|---|---|---|---|
| ||||
#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 potentially lead to a buffer overflow vulnerability, vulnerabilities as well as other unexpected program behaviors.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
MSC37-C |
High |
Unlikely |
Low | P9 | L2 |
Related
...
Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Automated Detection
Tool | Version | Checker | Description | ||||||
---|---|---|---|---|---|---|---|---|---|
Astrée |
| return-implicit | Fully checked | ||||||
Axivion Bauhaus Suite |
| CertC-MSC37 | |||||||
CodeSonar |
| LANG.STRUCT.MRS | Missing return statement | ||||||
Coverity |
| MISSING_RETURN | Implemented | ||||||
Cppcheck |
| missingReturn | Fully implemented | ||||||
Cppcheck Premium |
| missingReturn | Fully implemented | ||||||
Helix QAC |
| C++4022 DF2888 | |||||||
Klocwork |
| FUNCRET.GEN FUNCRET.IMPLICIT | |||||||
LDRA tool suite |
| 2 D, 36 S, 66 S | Fully implemented | ||||||
Parasoft C/C++test |
| CERT_C-MSC37-a | All exit paths from a function, except main(), with non-void return type shall have an explicit return statement with an expression | ||||||
PC-lint Plus |
| 533 | Fully supported | ||||||
Polyspace Bug Finder |
| CERT C: Rule MSC37-C | Checks for missing return statement (rule fully covered) | ||||||
RuleChecker |
| return-implicit | Fully checked | ||||||
SonarQube C/C++ Plugin |
| S935 | |||||||
TrustInSoft Analyzer |
| Body of function falls-through | Exhaustively verified. |
Related Guidelines
Key here (explains table format and definitions)
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 |
CERT-CWE Mapping Notes
Key here for mapping notes
CWE-758 and MSC37-C
Independent( INT34-C, INT36-C, MEM30-C, MSC37-C, FLP32-C, EXP33-C, EXP30-C, ERR34-C, ARR32-C)
CWE-758 = Union( MSC37-C, list) where list =
Undefined behavior that results from anything other than failing to return a value from a function that expects one
Bibliography
[ISO/IEC 9899:2024] | 5.1.2.3.4, "Program Termination" |
...
ISO/IEC 9899:1999 Section 6.9.1, "Function definitions"
Bibliography
MSC36-C. Check for alignment of memory space before calling realloc() function 49. Miscellaneous (MSC) MSC38-C. Do not treat as an object any predefined identifier that might be implemented as a macro