Skip to content

Commit 02d605c

Browse files
authored
Inconsistent return value handling rule (#10)
* initial work for InconsistentReturnValueHandling * InconsistentReturnValueHandling v1 working * fix string stubs * InconsistentReturnValueHandling v2 working * InconsistentReturnValueHandling - reduced FPs * InconsistentReturnValueHandling - small changes * fix tests * finalize * metadata done * fix string stubs
1 parent 3dd9440 commit 02d605c

File tree

10 files changed

+1296
-3
lines changed

10 files changed

+1296
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ codeql database analyze database.db --format=sarif-latest --output=./tob.sarif -
5050
|[Async unsafe signal handler](./cpp/src/docs/security/AsyncUnsafeSignalHandler/AsyncUnsafeSignalHandler.md)|Async unsafe signal handler (like the one used in CVE-2024-6387)|warning|high|
5151
|[Decrementation overflow when comparing](./cpp/src/docs/security/DecOverflowWhenComparing/DecOverflowWhenComparing.md)|This query finds unsigned integer overflows resulting from unchecked decrementation during comparison.|error|high|
5252
|[Find all problematic implicit casts](./cpp/src/docs/security/UnsafeImplicitConversions/UnsafeImplicitConversions.md)|Find all implicit casts that may be problematic. That is, casts that may result in unexpected truncation, reinterpretation or widening of values.|error|high|
53+
|[Inconsistent handling of return values from a specific function](./cpp/src/docs/security/InconsistentReturnValueHandling/InconsistentReturnValueHandling.md)|Detects functions whose return values are compared inconsistently across call sites, which may indicate bugs|warning|medium|
5354
|[Invalid string size passed to string manipulation function](./cpp/src/docs/security/CStrnFinder/CStrnFinder.md)|Finds calls to functions that take as input a string and its size as separate arguments (e.g., `strncmp`, `strncat`, ...) and the size argument is wrong|error|low|
5455
|[Missing null terminator](./cpp/src/docs/security/NoNullTerminator/NoNullTerminator.md)|This query finds incorrectly initialized strings that are passed to functions expecting null-byte-terminated strings|error|high|
5556

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Inconsistent handling of return values from a specific function
2+
When a function's return value is checked in `if` statements across multiple call sites, the comparisons typically fall into a consistent pattern (e.g., compared against a numeric literal, `NULL`, or `sizeof`). If a small number of call sites compare the return value in a different way than the majority, these inconsistent comparisons may indicate a bug.
3+
4+
The query categorizes each comparison into one of the following categories:
5+
6+
* Numeric literal (e.g., `ret != -1`)
7+
* Boolean (e.g., `ret == true`)
8+
* Null pointer (e.g., `ret != NULL`)
9+
* Pointer
10+
* `sizeof` expression (e.g., `ret > sizeof(buf)`)
11+
* Another function's return value (e.g., `ret != other_func()`)
12+
* Passed as argument to another function (e.g., `if (check(ret))`)
13+
* Arithmetic expression
14+
15+
When at least 75% of a function's return value comparisons fall into one category, the remaining comparisons in a different category are flagged as potentially incorrect.
16+
17+
18+
## Recommendation
19+
Review each flagged call site and verify that the comparison matches the function's return value semantics. If the function returns an error code or count, all call sites should compare it consistently. Fix any comparisons that use the wrong type of operand (e.g., comparing an integer return value against `sizeof` when all other sites compare against a numeric literal).
20+
21+
22+
## Example
23+
24+
```c
25+
struct header {
26+
int type;
27+
int length;
28+
};
29+
30+
// Returns number of items processed, or -1 on error
31+
int process_items(int *items, int count) {
32+
int processed = 0;
33+
for (int i = 0; i < count; i++) {
34+
if (items[i] < 0)
35+
return -1;
36+
processed++;
37+
}
38+
return processed;
39+
}
40+
41+
void example() {
42+
int items[10];
43+
int result;
44+
45+
// Typical: return value compared with a numeric literal
46+
result = process_items(items, 10);
47+
if (result > 0) { /* success */ }
48+
49+
result = process_items(items, 5);
50+
if (result != -1) { /* no error */ }
51+
52+
result = process_items(items, 3);
53+
if (result == 0) { /* empty */ }
54+
55+
result = process_items(items, 8);
56+
if (result >= 1) { /* at least one */ }
57+
58+
// BAD: comparing with sizeof instead of a numeric literal.
59+
// This is inconsistent with all other call sites and likely a bug.
60+
result = process_items(items, 7);
61+
if (result > sizeof(struct header)) { /* wrong comparison */ }
62+
}
63+
```
64+
In this example, `process_items` returns the number of items processed or `-1` on error. Most call sites correctly compare the return value with a numeric literal. However, one call site mistakenly compares it with `sizeof(struct header)`, which is inconsistent with how the return value is used everywhere else.
65+
66+
67+
## References
68+
* [CWE-252: Unchecked Return Value](https://cwe.mitre.org/data/definitions/252.html)
69+
* [CWE-253: Incorrect Check of Function Return Value](https://cwe.mitre.org/data/definitions/253.html)

cpp/src/security/DecOverflowWhenComparing/DecOverflowWhenComparing.ql

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ where
139139
)
140140
then cmp.getAFalseSuccessor().getASuccessor*() = varAccAfterOverflow
141141
else any()
142-
) and
143-
// skip vendor code
144-
not dec.getFile().getAbsolutePath().toLowerCase().matches(["%vendor%", "%third_party%"])
142+
)
143+
145144
select dec, "Unsigned decrementation in comparison ($@) - $@", cmp, cmp.toString(),
146145
varAccAfterOverflow, varAccAfterOverflow.toString()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
struct header {
2+
int type;
3+
int length;
4+
};
5+
6+
// Returns number of items processed, or -1 on error
7+
int process_items(int *items, int count) {
8+
int processed = 0;
9+
for (int i = 0; i < count; i++) {
10+
if (items[i] < 0)
11+
return -1;
12+
processed++;
13+
}
14+
return processed;
15+
}
16+
17+
void example() {
18+
int items[10];
19+
int result;
20+
21+
// Typical: return value compared with a numeric literal
22+
result = process_items(items, 10);
23+
if (result > 0) { /* success */ }
24+
25+
result = process_items(items, 5);
26+
if (result != -1) { /* no error */ }
27+
28+
result = process_items(items, 3);
29+
if (result == 0) { /* empty */ }
30+
31+
result = process_items(items, 8);
32+
if (result >= 1) { /* at least one */ }
33+
34+
// BAD: comparing with sizeof instead of a numeric literal.
35+
// This is inconsistent with all other call sites and likely a bug.
36+
result = process_items(items, 7);
37+
if (result > sizeof(struct header)) { /* wrong comparison */ }
38+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
When a function's return value is checked in <code>if</code> statements across multiple call sites,
8+
the comparisons typically fall into a consistent pattern (e.g., compared against a numeric literal,
9+
<code>NULL</code>, or <code>sizeof</code>). If a small number of call sites compare the return value
10+
in a different way than the majority, these inconsistent comparisons may indicate a bug.
11+
</p>
12+
13+
<p>
14+
The query categorizes each comparison into one of the following categories:
15+
</p>
16+
<ul>
17+
<li>Numeric literal (e.g., <code>ret != -1</code>)</li>
18+
<li>Boolean (e.g., <code>ret == true</code>)</li>
19+
<li>Null pointer (e.g., <code>ret != NULL</code>)</li>
20+
<li>Pointer</li>
21+
<li><code>sizeof</code> expression (e.g., <code>ret > sizeof(buf)</code>)</li>
22+
<li>Another function's return value (e.g., <code>ret != other_func()</code>)</li>
23+
<li>Passed as argument to another function (e.g., <code>if (check(ret))</code>)</li>
24+
<li>Arithmetic expression</li>
25+
</ul>
26+
27+
<p>
28+
When at least 75% of a function's return value comparisons fall into one category,
29+
the remaining comparisons in a different category are flagged as potentially incorrect.
30+
</p>
31+
</overview>
32+
33+
<recommendation>
34+
<p>
35+
Review each flagged call site and verify that the comparison matches the function's return value semantics.
36+
If the function returns an error code or count, all call sites should compare it consistently.
37+
Fix any comparisons that use the wrong type of operand (e.g., comparing an integer return value against
38+
<code>sizeof</code> when all other sites compare against a numeric literal).
39+
</p>
40+
</recommendation>
41+
42+
<example>
43+
<p>
44+
In this example, <code>process_items</code> returns the number of items processed or <code>-1</code>
45+
on error. Most call sites correctly compare the return value with a numeric literal. However, one
46+
call site mistakenly compares it with <code>sizeof(struct header)</code>, which is inconsistent
47+
with how the return value is used everywhere else.
48+
</p>
49+
50+
<sample src="InconsistentReturnValueHandling.c" />
51+
</example>
52+
53+
<references>
54+
<li>
55+
<a href="https://cwe.mitre.org/data/definitions/252.html">CWE-252: Unchecked Return Value</a>
56+
</li>
57+
<li>
58+
<a href="https://cwe.mitre.org/data/definitions/253.html">CWE-253: Incorrect Check of Function Return Value</a>
59+
</li>
60+
</references>
61+
62+
</qhelp>

0 commit comments

Comments
 (0)