Skip to content

Commit d9dfdca

Browse files
authored
Merge pull request #254 from mbaluda-org/Contracts6
Contracts7
2 parents 4a2f35f + b5cf930 commit d9dfdca

28 files changed

+1031
-7
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# MSC33-C: Do not pass invalid data to the asctime() function
2+
3+
This query implements the CERT-C rule MSC33-C:
4+
5+
> Do not pass invalid data to the asctime() function
6+
7+
8+
## Description
9+
10+
The C Standard, 7.27.3.1 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\], provides the following sample implementation of the `asctime()` function:
11+
12+
```cpp
13+
char *asctime(const struct tm *timeptr) {
14+
static const char wday_name[7][3] = {
15+
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
16+
};
17+
static const char mon_name[12][3] = {
18+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
19+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
20+
};
21+
static char result[26];
22+
sprintf(
23+
result,
24+
"%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
25+
wday_name[timeptr->tm_wday],
26+
mon_name[timeptr->tm_mon],
27+
timeptr->tm_mday, timeptr->tm_hour,
28+
timeptr->tm_min, timeptr->tm_sec,
29+
1900 + timeptr->tm_year
30+
);
31+
return result;
32+
}
33+
34+
```
35+
This function is supposed to output a character string of 26 characters at most, including the terminating null character. If we count the length indicated by the format directives, we arrive at 25. Taking into account the terminating null character, the array size of the string appears sufficient.
36+
37+
However, this [implementation](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) assumes that the values of the `struct tm` data are within normal ranges and does nothing to enforce the range limit. If any of the values print more characters than expected, the `sprintf()` function may overflow the `result` array. For example, if `tm_year` has the value `12345,` then 27 characters (including the terminating null character) are printed, resulting in a buffer overflow.
38+
39+
The* POSIX<sup>®</sup> Base Specifications* \[[IEEE Std 1003.1:2013](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-IEEEStd1003.1-2013)\] says the following about the `asctime()` and `asctime_r()` functions:
40+
41+
> These functions are included only for compatibility with older implementations. They have undefined behavior if the resulting string would be too long, so the use of these functions should be discouraged. On implementations that do not detect output string length overflow, it is possible to overflow the output buffers in such a way as to cause applications to fail, or possible system security violations. Also, these functions do not support localized date and time formats. To avoid these problems, applications should use `strftime()` to generate strings from broken-down times.
42+
43+
44+
The C Standard, Annex K, also defines `asctime_s()`, which can be used as a secure substitute for `asctime()`.
45+
46+
The `asctime()` function appears in the list of obsolescent functions in [MSC24-C. Do not use deprecated or obsolescent functions](https://wiki.sei.cmu.edu/confluence/display/c/MSC24-C.+Do+not+use+deprecated+or+obsolescent+functions).
47+
48+
## Noncompliant Code Example
49+
50+
This noncompliant code example invokes the `asctime()` function with potentially unsanitized data:
51+
52+
```cpp
53+
#include <time.h>
54+
55+
void func(struct tm *time_tm) {
56+
char *time = asctime(time_tm);
57+
/* ... */
58+
}
59+
```
60+
61+
## Compliant Solution (strftime())
62+
63+
The `strftime()` function allows the programmer to specify a more rigorous format and also to specify the maximum size of the resulting time string:
64+
65+
```cpp
66+
#include <time.h>
67+
68+
enum { maxsize = 26 };
69+
70+
void func(struct tm *time) {
71+
char s[maxsize];
72+
/* Current time representation for locale */
73+
const char *format = "%c";
74+
75+
size_t size = strftime(s, maxsize, format, time);
76+
}
77+
```
78+
This call has the same effects as `asctime()` but also ensures that no more than `maxsize` characters are printed, preventing buffer overflow.
79+
80+
## Compliant Solution (asctime_s())
81+
82+
The C Standard, Annex K, defines the `asctime_s()` function, which serves as a close replacement for the `asctime()` function but requires an additional argument that specifies the maximum size of the resulting time string:
83+
84+
```cpp
85+
#define __STDC_WANT_LIB_EXT1__ 1
86+
#include <time.h>
87+
88+
enum { maxsize = 26 };
89+
90+
void func(struct tm *time_tm) {
91+
char buffer[maxsize];
92+
93+
if (asctime_s(buffer, maxsize, &time_tm)) {
94+
/* Handle error */
95+
}
96+
}
97+
```
98+
99+
## Risk Assessment
100+
101+
On [implementations](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-implementation) that do not detect output-string-length overflow, it is possible to overflow the output buffers.
102+
103+
<table> <tbody> <tr> <th> Rule </th> <th> Severity </th> <th> Likelihood </th> <th> Remediation Cost </th> <th> Priority </th> <th> Level </th> </tr> <tr> <td> MSC33-C </td> <td> High </td> <td> Likely </td> <td> Low </td> <td> <strong>P27</strong> </td> <td> <strong>L1</strong> </td> </tr> </tbody> </table>
104+
105+
106+
## Automated Detection
107+
108+
<table> <tbody> <tr> <th> Tool </th> <th> Version </th> <th> Checker </th> <th> Description </th> </tr> <tr> <td> <a> Astrée </a> </td> <td> 22.04 </td> <td> </td> <td> Supported, but no explicit checker </td> </tr> <tr> <td> <a> Axivion Bauhaus Suite </a> </td> <td> 7.2.0 </td> <td> <strong>CertC-MSC33</strong> </td> <td> </td> </tr> <tr> <td> <a> CodeSonar </a> </td> <td> 7.2p0 </td> <td> <strong>BADFUNC.TIME_H</strong> </td> <td> Use of &lt;time.h&gt; Time/Date Function </td> </tr> <tr> <td> <a> Helix QAC </a> </td> <td> 2022.4 </td> <td> <strong>C5032</strong> <strong>C++5030</strong> </td> <td> </td> </tr> <tr> <td> <a> Klocwork </a> </td> <td> 2022.4 </td> <td> <strong>CERT.MSC.ASCTIME</strong> </td> <td> </td> </tr> <tr> <td> <a> LDRA tool suite </a> </td> <td> 9.7.1 </td> <td> <strong>44 S</strong> </td> <td> Enhanced Enforcement </td> </tr> <tr> <td> <a> Parasoft C/C++test </a> </td> <td> 2022.2 </td> <td> <strong>CERT_C-MSC33-a</strong> </td> <td> The 'asctime()' and 'asctime_r()' functions should not be used </td> </tr> <tr> <td> <a> PC-lint Plus </a> </td> <td> 1.4 </td> <td> <strong>586</strong> </td> <td> Fully supported </td> </tr> <tr> <td> <a> Polyspace Bug Finder </a> </td> <td> R2022b </td> <td> <a> CERT C: Rule MSC33-C </a> </td> <td> Checks for use of obsolete standard function (rule fully covered) </td> </tr> <tr> <td> <a> PRQA QA-C </a> </td> <td> 9.7 </td> <td> <strong>5032 </strong> </td> <td> </td> </tr> <tr> <td> <a> PRQA QA-C++ </a> </td> <td> 4.4 </td> <td> <strong>5030</strong> </td> <td> </td> </tr> <tr> <td> <a> RuleChecker </a> </td> <td> 22.04 </td> <td> </td> <td> Supported, but no explicit checker </td> </tr> </tbody> </table>
109+
110+
111+
## Related Vulnerabilities
112+
113+
Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+MSC33-C).
114+
115+
## Related Guidelines
116+
117+
[Key here](https://wiki.sei.cmu.edu/confluence/display/c/How+this+Coding+Standard+is+Organized#HowthisCodingStandardisOrganized-RelatedGuidelines) (explains table format and definitions)
118+
119+
<table> <tbody> <tr> <th> Taxonomy </th> <th> Taxonomy item </th> <th> Relationship </th> </tr> <tr> <td> <a> CERT C Secure Coding Standard </a> </td> <td> <a> MSC24-C. Do not use deprecated or obsolescent functions </a> </td> <td> Prior to 2018-01-12: CERT: Unspecified Relationship </td> </tr> </tbody> </table>
120+
121+
122+
## Bibliography
123+
124+
<table> <tbody> <tr> <td> \[ <a> IEEE Std 1003.1:2013 </a> \] </td> <td> XSH, System Interfaces, <code>asctime</code> </td> </tr> <tr> <td> \[ <a> ISO/IEC 9899:2011 </a> \] </td> <td> 7.27.3.1, "The <code>asctime</code> Function" </td> </tr> </tbody> </table>
125+
126+
127+
## Implementation notes
128+
129+
None
130+
131+
## References
132+
133+
* CERT-C: [MSC33-C: Do not pass invalid data to the asctime() function](https://wiki.sei.cmu.edu/confluence/display/c)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @id c/cert/do-not-pass-invalid-data-to-the-asctime-function
3+
* @name MSC33-C: Do not pass invalid data to the asctime() function
4+
* @description The data passed to the asctime() function is invalid. This can lead to buffer
5+
* overflow.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/cert/id/msc33-c
10+
* security
11+
* correctness
12+
* external/cert/obligation/rule
13+
*/
14+
15+
import cpp
16+
import codingstandards.c.cert
17+
import semmle.code.cpp.dataflow.DataFlow
18+
19+
/**
20+
* The argument of a call to `asctime`
21+
*/
22+
class AsctimeArg extends Expr {
23+
AsctimeArg() {
24+
this =
25+
any(FunctionCall f | f.getTarget().hasGlobalName(["asctime", "asctime_r"])).getArgument(0)
26+
}
27+
}
28+
29+
/**
30+
* Dataflow configuration for flow from a library function
31+
* to a call of function `asctime`
32+
*/
33+
class TmStructSafeConfig extends DataFlow::Configuration {
34+
TmStructSafeConfig() { this = "TmStructSafeConfig" }
35+
36+
override predicate isSource(DataFlow::Node src) {
37+
src.asExpr()
38+
.(FunctionCall)
39+
.getTarget()
40+
.hasGlobalName(["localtime", "localtime_r", "localtime_s", "gmtime", "gmtime_r", "gmtime_s"])
41+
}
42+
43+
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof AsctimeArg }
44+
}
45+
46+
from AsctimeArg fc, TmStructSafeConfig config
47+
where
48+
not isExcluded(fc, Contracts7Package::doNotPassInvalidDataToTheAsctimeFunctionQuery()) and
49+
not config.hasFlowToExpr(fc)
50+
select fc,
51+
"The function `asctime` and `asctime_r` should be discouraged. Unsanitized input can overflow the output buffer."
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# MSC39-C: Do not call va_arg() on a va_list that has an indeterminate value
2+
3+
This query implements the CERT-C rule MSC39-C:
4+
5+
> Do not call va_arg() on a va_list that has an indeterminate value
6+
7+
8+
## Description
9+
10+
Variadic functions access their variable arguments by using `va_start()` to initialize an object of type `va_list`, iteratively invoking the `va_arg()` macro, and finally calling `va_end()`. The `va_list` may be passed as an argument to another function, but calling `va_arg()` within that function causes the `va_list` to have an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue) in the calling function. As a result, attempting to read variable arguments without reinitializing the `va_list` can have [unexpected behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unexpectedbehavior). According to the C Standard, 7.16, paragraph 3 \[[ISO/IEC 9899:2011](https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-ISO-IEC9899-2011)\],
11+
12+
> If access to the varying arguments is desired, the called function shall declare an object (generally referred to as `ap` in this subclause) having type `va_list`. The object `ap` may be passed as an argument to another function; if that function invokes the `va_arg` macro with parameter `ap`, the value of `ap` in the calling function is indeterminate and shall be passed to the `va_end` macro prior to any further reference to `ap`.<sup>253</sup>253) It is permitted to create a pointer to a `va_list` and pass that pointer to another function, in which case the original function may take further use of the original list after the other function returns.
13+
14+
15+
## Noncompliant Code Example
16+
17+
This noncompliant code example attempts to check that none of its variable arguments are zero by passing a `va_list` to helper function `contains_zero()`. After the call to `contains_zero()`, the value of `ap` is [indeterminate](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue).
18+
19+
```cpp
20+
#include <stdarg.h>
21+
#include <stdio.h>
22+
23+
int contains_zero(size_t count, va_list ap) {
24+
for (size_t i = 1; i < count; ++i) {
25+
if (va_arg(ap, double) == 0.0) {
26+
return 1;
27+
}
28+
}
29+
return 0;
30+
}
31+
32+
int print_reciprocals(size_t count, ...) {
33+
va_list ap;
34+
va_start(ap, count);
35+
36+
if (contains_zero(count, ap)) {
37+
va_end(ap);
38+
return 1;
39+
}
40+
41+
for (size_t i = 0; i < count; ++i) {
42+
printf("%f ", 1.0 / va_arg(ap, double));
43+
}
44+
45+
va_end(ap);
46+
return 0;
47+
}
48+
49+
```
50+
51+
## Compliant Solution
52+
53+
The compliant solution modifies `contains_zero()` to take a pointer to a `va_list`. It then uses the `va_copy` macro to make a copy of the list, traverses the copy, and cleans it up. Consequently, the `print_reciprocals()` function is free to traverse the original `va_list`.
54+
55+
```cpp
56+
#include <stdarg.h>
57+
#include <stdio.h>
58+
59+
int contains_zero(size_t count, va_list *ap) {
60+
va_list ap1;
61+
va_copy(ap1, *ap);
62+
for (size_t i = 1; i < count; ++i) {
63+
if (va_arg(ap1, double) == 0.0) {
64+
return 1;
65+
}
66+
}
67+
va_end(ap1);
68+
return 0;
69+
}
70+
71+
int print_reciprocals(size_t count, ...) {
72+
int status;
73+
va_list ap;
74+
va_start(ap, count);
75+
76+
if (contains_zero(count, &ap)) {
77+
printf("0 in arguments!\n");
78+
status = 1;
79+
} else {
80+
for (size_t i = 0; i < count; i++) {
81+
printf("%f ", 1.0 / va_arg(ap, double));
82+
}
83+
printf("\n");
84+
status = 0;
85+
}
86+
87+
va_end(ap);
88+
return status;
89+
}
90+
91+
```
92+
93+
## Risk Assessment
94+
95+
Reading variable arguments using a `va_list` that has an [indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-indeterminatevalue) can have unexpected results.
96+
97+
<table> <tbody> <tr> <th> Rule </th> <th> Severity </th> <th> Likelihood </th> <th> Remediation Cost </th> <th> Priority </th> <th> Level </th> </tr> <tr> <td> MSC39-C </td> <td> Low </td> <td> Unlikely </td> <td> Low </td> <td> <strong>P3</strong> </td> <td> <strong>L3</strong> </td> </tr> </tbody> </table>
98+
99+
100+
## Automated Detection
101+
102+
<table> <tbody> <tr> <th> Tool </th> <th> Version </th> <th> Checker </th> <th> Description </th> </tr> <tr> <td> <a> CodeSonar </a> </td> <td> 7.2p0 </td> <td> <strong>BADMACRO.STDARG_H</strong> </td> <td> Use of &lt;stdarg.h&gt; Feature </td> </tr> <tr> <td> <a> Helix QAC </a> </td> <td> 2022.4 </td> <td> <strong>C3497</strong> <strong>C++3146, C++3147, C++3148, C++3149, C++3167</strong> </td> <td> </td> </tr> <tr> <td> <a> Klocwork </a> </td> <td> 2022.4 </td> <td> <strong>VA.LIST.INDETERMINATE</strong> </td> <td> </td> </tr> <tr> <td> <a> Parasoft C/C++test </a> </td> <td> 2022.2 </td> <td> <strong>CERT_C-MSC39-a</strong> </td> <td> Use macros for variable arguments correctly </td> </tr> <tr> <td> <a> Polyspace Bug Finder </a> </td> <td> R2022b </td> <td> <a> CERT C: Rule MSC39-C </a> </td> <td> Checks for: Invalid va_list argumentnvalid va_list argument, too many va_arg calls for current argument listoo many va_arg calls for current argument list. Rule partially covered. </td> </tr> <tr> <td> <a> PRQA QA-C </a> </td> <td> 9.7 </td> <td> <strong>3497</strong> </td> <td> Enforced by QAC </td> </tr> <tr> <td> <a> TrustInSoft Analyzer </a> </td> <td> 1.38 </td> <td> <strong>variadic</strong> </td> <td> Exhaustively verified. </td> </tr> </tbody> </table>
103+
104+
105+
## Related Vulnerabilities
106+
107+
Search for [vulnerabilities](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) resulting from the violation of this rule on the [CERT website](https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+MSC39-C).
108+
109+
## Bibliography
110+
111+
<table> <tbody> <tr> <td> \[ <a> ISO/IEC 9899:2011 </a> \] </td> <td> Subclause 7.16, "Variable Arguments <code>&lt;stdarg.h&gt;</code> " </td> </tr> </tbody> </table>
112+
113+
114+
## Implementation notes
115+
116+
None
117+
118+
## References
119+
120+
* CERT-C: [MSC39-C: Do not call va_arg() on a va_list that has an indeterminate value](https://wiki.sei.cmu.edu/confluence/display/c)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @id c/cert/do-not-call-va-arg-on-a-va-list-that-has-an-indeterminate-value
3+
* @name MSC39-C: Do not call va_arg() on a va_list that has an indeterminate value
4+
* @description Do not call va_arg() on a va_list that has an indeterminate value.
5+
* @kind problem
6+
* @precision high
7+
* @problem.severity error
8+
* @tags external/cert/id/msc39-c
9+
* correctness
10+
* external/cert/obligation/rule
11+
*/
12+
13+
import cpp
14+
import codingstandards.c.cert
15+
import codingstandards.cpp.Macro
16+
import semmle.code.cpp.dataflow.DataFlow
17+
18+
abstract class VaAccess extends Expr { }
19+
20+
/**
21+
* The argument of a call to `va_arg`
22+
*/
23+
class VaArgArg extends VaAccess {
24+
VaArgArg() { this = any(MacroInvocation m | m.getMacroName() = ["va_arg"]).getExpr().getChild(0) }
25+
}
26+
27+
/**
28+
* The argument of a call to `va_end`
29+
*/
30+
class VaEndArg extends VaAccess {
31+
VaEndArg() { this = any(MacroInvocation m | m.getMacroName() = ["va_end"]).getExpr().getChild(0) }
32+
}
33+
34+
/**
35+
* Dataflow configuration for flow from a library function
36+
* to a call of function `asctime`
37+
*/
38+
class VaArgConfig extends DataFlow::Configuration {
39+
VaArgConfig() { this = "VaArgConfig" }
40+
41+
override predicate isSource(DataFlow::Node src) {
42+
src.asUninitialized() =
43+
any(VariableDeclarationEntry m | m.getType().hasName("va_list")).getVariable()
44+
}
45+
46+
override predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof VaAccess }
47+
}
48+
49+
/**
50+
* Controlflow nodes preceeding a call to `va_arg`
51+
*/
52+
ControlFlowNode preceedsFC(VaAccess va_arg) {
53+
result = va_arg
54+
or
55+
exists(ControlFlowNode mid |
56+
result = mid.getAPredecessor() and
57+
mid = preceedsFC(va_arg) and
58+
// stop recursion on va_end on the same object
59+
not result =
60+
any(MacroInvocation m |
61+
m.getMacroName() = ["va_start"] and
62+
m.getExpr().getChild(0).(VariableAccess).getTarget() = va_arg.(VariableAccess).getTarget()
63+
).getExpr()
64+
)
65+
}
66+
67+
predicate sameSource(VaAccess e1, VaAccess e2) {
68+
exists(VaArgConfig config, DataFlow::Node source |
69+
config.hasFlow(source, DataFlow::exprNode(e1)) and
70+
config.hasFlow(source, DataFlow::exprNode(e2))
71+
)
72+
}
73+
74+
from VaAccess va_acc, VaArgArg va_arg, FunctionCall fc
75+
where
76+
not isExcluded(va_acc,
77+
Contracts7Package::doNotCallVaArgOnAVaListThatHasAnIndeterminateValueQuery()) and
78+
sameSource(va_acc, va_arg) and
79+
fc = preceedsFC(va_acc) and
80+
fc.getTarget().calls*(va_arg.getEnclosingFunction())
81+
select va_acc, "The value of " + va_acc.toString() + " is indeterminate after the $@.", fc,
82+
fc.toString()

c/cert/test/rules/FIO42-C/test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ int closing_helper(FILE *g) {
8282
return 0;
8383
}
8484
int f2inter(const char *filename) {
85-
FILE *f = fopen(filename, "r"); // COMPLIANT (FALSE_POSITIVE)
85+
FILE *f = fopen(filename, "r"); // COMPLIANT[FALSE_POSITIVE]
8686
if (NULL == f) {
8787
return -1;
8888
}

0 commit comments

Comments
 (0)