Skip to content

Commit 8e7b88f

Browse files
authored
Merge pull request #881 from github/michaelrfairhurst/implement-expressions2-package
Implement expressions2 package
2 parents 0f9f491 + e655ed8 commit 8e7b88f

12 files changed

+542
-18
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# EXP16-C: Do not compare function pointers to constant values
2+
3+
This query implements the CERT-C rule EXP16-C:
4+
5+
> Do not compare function pointers to constant values
6+
7+
8+
## Description
9+
10+
Comparing a function pointer to a value that is not a null function pointer of the same type will be diagnosed because it typically indicates programmer error and can result in [unexpected behavior](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-unexpectedbehavior). Implicit comparisons will be diagnosed, as well.
11+
12+
## Noncompliant Code Example
13+
14+
In this noncompliant code example, the addresses of the POSIX functions `getuid` and `geteuid` are compared for equality to 0. Because no function address shall be null, the first subexpression will always evaluate to false (0), and the second subexpression always to true (nonzero). Consequently, the entire expression will always evaluate to true, leading to a potential security vulnerability.
15+
16+
```cpp
17+
/* First the options that are allowed only for root */
18+
if (getuid == 0 || geteuid != 0) {
19+
/* ... */
20+
}
21+
22+
```
23+
24+
## Noncompliant Code Example
25+
26+
In this noncompliant code example, the function pointers `getuid` and `geteuid` are compared to 0. This example is from an actual [vulnerability](https://wiki.sei.cmu.edu/confluence/display/c/BB.+Definitions#BB.Definitions-vulnerability) ([VU\#837857](http://www.kb.cert.org/vuls/id/837857)) discovered in some versions of the X Window System server. The vulnerability exists because the programmer neglected to provide the open and close parentheses following the `geteuid()` function identifier. As a result, the `geteuid` token returns the address of the function, which is never equal to 0. Consequently, the `or` condition of this `if` statement is always true, and access is provided to the protected block for all users. Many compilers issue a warning noting such pointless expressions. Therefore, this coding error is normally detected by adherence to [MSC00-C. Compile cleanly at high warning levels](https://wiki.sei.cmu.edu/confluence/display/c/MSC00-C.+Compile+cleanly+at+high+warning+levels).
27+
28+
```cpp
29+
/* First the options that are allowed only for root */
30+
if (getuid() == 0 || geteuid != 0) {
31+
/* ... */
32+
}
33+
34+
```
35+
36+
## Compliant Solution
37+
38+
The solution is to provide the open and close parentheses following the `geteuid` token so that the function is properly invoked:
39+
40+
```cpp
41+
/* First the options that are allowed only for root */
42+
if (getuid() == 0 || geteuid() != 0) {
43+
/* ... */
44+
}
45+
46+
```
47+
48+
## Compliant Solution
49+
50+
A function pointer can be compared to a null function pointer of the same type:
51+
52+
```cpp
53+
/* First the options that are allowed only for root */
54+
if (getuid == (uid_t(*)(void))0 || geteuid != (uid_t(*)(void))0) {
55+
/* ... */
56+
}
57+
58+
```
59+
This code should not be diagnosed by an analyzer.
60+
61+
## Noncompliant Code Example
62+
63+
In this noncompliant code example, the function pointer `do_xyz` is implicitly compared unequal to 0:
64+
65+
```cpp
66+
int do_xyz(void);
67+
68+
int f(void) {
69+
/* ... */
70+
if (do_xyz) {
71+
return -1; /* Indicate failure */
72+
}
73+
/* ... */
74+
return 0;
75+
}
76+
77+
```
78+
79+
## Compliant Solution
80+
81+
In this compliant solution, the function `do_xyz()` is invoked and the return value is compared to 0:
82+
83+
```cpp
84+
int do_xyz(void);
85+
86+
int f(void) {
87+
/* ... */
88+
if (do_xyz()) {
89+
return -1; /* Indicate failure */
90+
}
91+
/* ... */
92+
return 0;
93+
}
94+
95+
```
96+
97+
## Risk Assessment
98+
99+
Errors of omission can result in unintended program flow.
100+
101+
<table> <tbody> <tr> <th> Recommendation </th> <th> Severity </th> <th> Likelihood </th> <th> Remediation Cost </th> <th> Priority </th> <th> Level </th> </tr> <tr> <td> EXP16-C </td> <td> Low </td> <td> Likely </td> <td> Medium </td> <td> <strong>P6</strong> </td> <td> <strong>L2</strong> </td> </tr> </tbody> </table>
102+
103+
104+
## Automated Detection
105+
106+
<table> <tbody> <tr> <th> Tool </th> <th> Version </th> <th> Checker </th> <th> Description </th> </tr> <tr> <td> <a> Astrée </a> </td> <td> 24.04 </td> <td> <strong>function-name-constant-comparison</strong> </td> <td> Partially checked </td> </tr> <tr> <td> <a> Coverity </a> </td> <td> 2017.07 </td> <td> <strong>BAD_COMPARE</strong> </td> <td> Can detect the specific instance where the address of a function is compared against 0, such as in the case of <code>geteuid</code> versus <code>getuid()</code> in the implementation-specific details </td> </tr> <tr> <td> <a> GCC </a> </td> <td> 4.3.5 </td> <td> </td> <td> Can detect violations of this recommendation when the <code>-Wall</code> flag is used </td> </tr> <tr> <td> <a> Helix QAC </a> </td> <td> 2024.4 </td> <td> <strong>C0428, C3004, C3344</strong> </td> <td> </td> </tr> <tr> <td> <a> Klocwork </a> </td> <td> 2024.4 </td> <td> <strong>CWARN.NULLCHECK.FUNCNAMECWARN.FUNCADDR</strong> </td> <td> </td> </tr> <tr> <td> <a> LDRA tool suite </a> </td> <td> 9.7.1 </td> <td> <strong>99 S</strong> </td> <td> Partially implemented </td> </tr> <tr> <td> <a> Parasoft C/C++test </a> </td> <td> 2024.2 </td> <td> <strong>CERT_C-EXP16-a</strong> </td> <td> Function address should not be compared to zero </td> </tr> <tr> <td> <a> PC-lint Plus </a> </td> <td> 1.4 </td> <td> <strong>2440, 2441</strong> </td> <td> Partially supported: reports address of function, array, or variable directly or indirectly compared to null </td> </tr> <tr> <td> <a> PVS-Studio </a> </td> <td> 7.35 </td> <td> <strong><a>V516</a>, <a>V1058</a></strong> </td> <td> </td> </tr> <tr> <td> <a> RuleChecker </a> </td> <td> 24.04 </td> <td> <strong>function-name-constant-comparison</strong> </td> <td> Partially checked </td> </tr> </tbody> </table>
107+
108+
109+
## Related Vulnerabilities
110+
111+
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+EXP16-C).
112+
113+
## Related Guidelines
114+
115+
<table> <tbody> <tr> <td> <a> SEI CERT C++ Coding Standard </a> </td> <td> <a> VOID EXP16-CPP. Avoid conversions using void pointers </a> </td> </tr> <tr> <td> <a> ISO/IEC TR 24772:2013 </a> </td> <td> Likely incorrect expressions \[KOA\] </td> </tr> <tr> <td> <a> ISO/IEC TS 17961 </a> </td> <td> Comparing function addresses to zero \[funcaddr\] </td> </tr> <tr> <td> <a> MITRE CWE </a> </td> <td> <a> CWE-480 </a> , Use of incorrect operator <a> CWE-482 </a> , Comparing instead of assigning </td> </tr> </tbody> </table>
116+
117+
118+
## Bibliography
119+
120+
<table> <tbody> <tr> <td> \[ <a> Hatton 1995 </a> \] </td> <td> Section 2.7.2, "Errors of Omission and Addition" </td> </tr> </tbody> </table>
121+
122+
123+
## Implementation notes
124+
125+
None
126+
127+
## References
128+
129+
* CERT-C: [EXP16-C: Do not compare function pointers to constant values](https://wiki.sei.cmu.edu/confluence/display/c)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* @id c/cert/do-not-compare-function-pointers-to-constant-values
3+
* @name EXP16-C: Do not compare function pointers to constant values
4+
* @description Comparing function pointers to a constant value is not reliable and likely indicates
5+
* a programmer error.
6+
* @kind problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/cert/id/exp16-c
10+
* correctness
11+
* external/cert/severity/low
12+
* external/cert/likelihood/likely
13+
* external/cert/remediation-cost/medium
14+
* external/cert/priority/p6
15+
* external/cert/level/l2
16+
* external/cert/obligation/recommendation
17+
*/
18+
19+
import cpp
20+
import semmle.code.cpp.controlflow.IRGuards
21+
import codingstandards.c.cert
22+
import codingstandards.cpp.types.FunctionType
23+
import codingstandards.cpp.exprs.FunctionExprs
24+
import codingstandards.cpp.exprs.Guards
25+
26+
abstract class EffectivelyComparison extends Element {
27+
abstract string getExplanation();
28+
29+
abstract FunctionExpr getFunctionExpr();
30+
}
31+
32+
class ExplicitComparison extends EffectivelyComparison, ComparisonOperation {
33+
Expr constantExpr;
34+
FunctionExpr funcExpr;
35+
36+
ExplicitComparison() {
37+
funcExpr = getAnOperand() and
38+
constantExpr = getAnOperand() and
39+
exists(constantExpr.getValue()) and
40+
not funcExpr = constantExpr and
41+
not constantExpr.getExplicitlyConverted().getUnderlyingType() =
42+
funcExpr.getExplicitlyConverted().getUnderlyingType()
43+
}
44+
45+
override string getExplanation() { result = "$@ compared to constant value." }
46+
47+
override FunctionExpr getFunctionExpr() { result = funcExpr }
48+
}
49+
50+
class ImplicitComparison extends EffectivelyComparison, GuardCondition {
51+
ImplicitComparison() {
52+
this instanceof FunctionExpr and
53+
not getParent() instanceof ComparisonOperation
54+
}
55+
56+
override string getExplanation() { result = "$@ undergoes implicit constant comparison." }
57+
58+
override FunctionExpr getFunctionExpr() { result = this }
59+
}
60+
61+
from EffectivelyComparison comparison, FunctionExpr funcExpr, Element function, string funcName
62+
where
63+
not isExcluded(comparison,
64+
Expressions2Package::doNotCompareFunctionPointersToConstantValuesQuery()) and
65+
funcExpr = comparison.getFunctionExpr() and
66+
not exists(NullFunctionCallGuard nullGuard | nullGuard.getFunctionExpr() = funcExpr) and
67+
function = funcExpr.getFunction() and
68+
funcName = funcExpr.describe()
69+
select comparison, comparison.getExplanation(), function, funcName
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
| test.c:17:7:17:13 | ... == ... | $@ compared to constant value. | test.c:3:5:3:6 | f1 | Address of function f1 |
2+
| test.c:20:7:20:12 | ... > ... | $@ compared to constant value. | test.c:3:5:3:6 | f1 | Address of function f1 |
3+
| test.c:29:7:29:13 | ... == ... | $@ compared to constant value. | test.c:4:8:4:9 | g1 | Function pointer variable g1 |
4+
| test.c:32:7:32:16 | ... == ... | $@ compared to constant value. | test.c:5:7:5:8 | g2 | Function pointer variable g2 |
5+
| test.c:35:7:35:15 | ... != ... | $@ compared to constant value. | test.c:4:8:4:9 | g1 | Function pointer variable g1 |
6+
| test.c:38:7:38:8 | f1 | $@ undergoes implicit constant comparison. | test.c:3:5:3:6 | f1 | Address of function f1 |
7+
| test.c:41:7:41:8 | g1 | $@ undergoes implicit constant comparison. | test.c:4:8:4:9 | g1 | Function pointer variable g1 |
8+
| test.c:68:7:68:27 | ... == ... | $@ compared to constant value. | test.c:3:5:3:6 | f1 | Address of function f1 |
9+
| test.c:71:7:71:18 | ... == ... | $@ compared to constant value. | test.c:3:5:3:6 | f1 | Address of function f1 |
10+
| test.c:74:7:76:14 | ... == ... | $@ compared to constant value. | test.c:3:5:3:6 | f1 | Address of function f1 |
11+
| test.c:83:3:83:9 | ... == ... | $@ compared to constant value. | test.c:82:10:82:11 | l1 | Function pointer variable l1 |
12+
| test.c:84:3:84:12 | ... == ... | $@ compared to constant value. | test.c:82:10:82:11 | l1 | Function pointer variable l1 |
13+
| test.c:91:3:91:4 | g1 | $@ undergoes implicit constant comparison. | test.c:4:8:4:9 | g1 | Function pointer variable g1 |
14+
| test.c:96:7:96:18 | ... == ... | $@ compared to constant value. | test.c:9:9:9:10 | fp | Function pointer variable fp |
15+
| test.c:102:7:102:22 | ... == ... | $@ compared to constant value. | test.c:14:11:14:21 | get_handler | Address of function get_handler |
16+
| test.c:105:7:105:24 | ... == ... | $@ compared to constant value. | test.c:105:7:105:17 | call to get_handler | Expression with function pointer type |
17+
| test.c:121:7:121:13 | ... != ... | $@ compared to constant value. | test.c:3:5:3:6 | f1 | Address of function f1 |
18+
| test.c:133:7:133:13 | ... != ... | $@ compared to constant value. | test.c:4:8:4:9 | g1 | Function pointer variable g1 |
19+
| test.c:139:7:139:13 | ... == ... | $@ compared to constant value. | test.c:4:8:4:9 | g1 | Function pointer variable g1 |
20+
| test.c:149:8:149:9 | g1 | $@ undergoes implicit constant comparison. | test.c:4:8:4:9 | g1 | Function pointer variable g1 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/EXP16-C/DoNotCompareFunctionPointersToConstantValues.ql

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

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#include <stdlib.h>
2+
3+
int f1();
4+
void (*g1)(void);
5+
int (*g2)(int);
6+
void *g3 = NULL;
7+
8+
struct S {
9+
int (*fp)(void);
10+
int x;
11+
};
12+
13+
typedef int (*handler_t)(void);
14+
handler_t get_handler(void);
15+
16+
void f2(void) {
17+
if (f1 == 0) // NON-COMPLIANT
18+
return;
19+
20+
if (f1 > 0) // NON-COMPLIANT
21+
return;
22+
23+
if (f1() == 0) // COMPLIANT
24+
return;
25+
26+
if (f1() > 0) // COMPLIANT
27+
return;
28+
29+
if (g1 == 0) // NON-COMPLIANT
30+
return;
31+
32+
if (g2 == NULL) // NON-COMPLIANT
33+
return;
34+
35+
if (g1 != 0x0) // NON-COMPLIANT
36+
return;
37+
38+
if (f1) // NON-COMPLIANT - implicit comparison
39+
return;
40+
41+
if (g1) // NON-COMPLIANT - implicit comparison
42+
return;
43+
}
44+
45+
void f3(void *p1) {
46+
if (g1 == p1) // COMPLIANT - comparing to variable
47+
return;
48+
49+
if (g2 == g3) // COMPLIANT - comparing to variable
50+
return;
51+
}
52+
53+
void f4(void) {
54+
int (*l1)(void) = 0;
55+
56+
if (f1 == f1) // COMPLIANT - comparing to constant value of same type
57+
return;
58+
59+
if (f1 == l1) // COMPLIANT - comparing to constant value of same type
60+
return;
61+
62+
if (f1 == (int (*)(void))0) // COMPLIANT - explicit cast
63+
return;
64+
65+
if (f1 == (int (*)(void))0) // COMPLIANT - explicit cast
66+
return;
67+
68+
if (f1 == (int (*)(int))0) // NON-COMPLIANT - explicit cast to wrong type
69+
return;
70+
71+
if (f1 == (int)0) // NON-COMPLIANT - cast to non-function pointer type
72+
return;
73+
74+
if (f1 ==
75+
(int)(int (*)(void))
76+
NULL) // NON-COMPLIANT - compliant cast subsumed by non-compliant cast
77+
return;
78+
}
79+
80+
typedef void (*func_t)(void);
81+
void f5(void) {
82+
func_t l1 = g1;
83+
l1 == 0; // NON-COMPLIANT
84+
l1 == NULL; // NON-COMPLIANT
85+
l1 == (func_t)0; // COMPLIANT - cast to function pointer type
86+
}
87+
88+
void f6(void) {
89+
g1 + 0; // COMPLIANT - not a comparison
90+
g1 == g2; // COMPLIANT - not comparing to constant
91+
g1 ? 1 : 0; // NON-COMPLIANT - implicit comparison
92+
}
93+
94+
void f7(void) {
95+
struct S s;
96+
if (s.fp == NULL) // NON-COMPLIANT
97+
f1();
98+
99+
if (s.fp() == NULL) // COMPLIANT
100+
return;
101+
102+
if (get_handler == 0) // NON-COMPLIANT - missing parentheses
103+
return;
104+
105+
if (get_handler() == 0) // NON-COMPLIANT
106+
return;
107+
108+
if (get_handler() == (handler_t)0) // COMPLIANT
109+
return;
110+
111+
if (get_handler()() == 0) // COMPLIANT
112+
return;
113+
}
114+
115+
void f8(void) {
116+
// Test instances of where the function pointer check is used to guard calls
117+
// to that function.
118+
119+
// Technically, this function may perhaps be set to NULL by the linker. But
120+
// it is not a variable that should need to be null-checked at runtime.
121+
if (f1 != 0) // NON-COMPLIANT
122+
{
123+
f1();
124+
}
125+
126+
// Check guards a call, so it is compliant.
127+
if (g1 != 0) // COMPLIANT
128+
{
129+
g1();
130+
}
131+
132+
// Incorrect check, not compliant.
133+
if (g1 != 0) // NON-COMPLIANT
134+
{
135+
f1();
136+
}
137+
138+
// Incorrect check, not compliant.
139+
if (g1 == 0) // NON-COMPLIANT
140+
{
141+
g1();
142+
}
143+
144+
if (g1) // COMPLIANT
145+
{
146+
g1();
147+
}
148+
149+
if (!g1) // NON-COMPLIANT
150+
{
151+
g1();
152+
}
153+
}

0 commit comments

Comments
 (0)