Skip to content

Commit 883ff4c

Browse files
committed
init DecOverflowWhenComparing query
1 parent a906346 commit 883ff4c

File tree

6 files changed

+220
-0
lines changed

6 files changed

+220
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <stdio.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
// from https://github.com/apple-oss-distributions/Libinfo/blob/9fce29e5c5edc15d3ecea55116ca17d3f6350603/lookup.subproj/mdns_module.c#L1033C1-L1079C2
7+
char* _mdns_parse_domain_name(const uint8_t *data, uint32_t datalen)
8+
{
9+
int i = 0, j = 0;
10+
uint32_t len;
11+
uint32_t domainlen = 0;
12+
char *domain = NULL;
13+
14+
if ((data == NULL) || (datalen == 0)) return NULL;
15+
16+
/*
17+
* i: index into input data
18+
* j: index into output string
19+
*/
20+
while (datalen-- > 0)
21+
{
22+
printf("%d\n", len);
23+
len = data[i++];
24+
domainlen += (len + 1);
25+
domain = reallocf(domain, domainlen);
26+
27+
if (domain == NULL) return NULL;
28+
29+
if (len == 0) break; // DNS root (NUL)
30+
31+
if (j > 0)
32+
{
33+
domain[j++] = datalen ? '.' : '\0';
34+
}
35+
36+
while ((len-- > 0) && (0 != datalen--))
37+
{
38+
if (data[i] == '.')
39+
{
40+
/* special case: escape the '.' with a '\' */
41+
domain = reallocf(domain, ++domainlen);
42+
if (domain == NULL) return NULL;
43+
44+
domain[j++] = '\\';
45+
}
46+
47+
domain[j++] = data[i++];
48+
}
49+
}
50+
51+
domain[j] = '\0';
52+
53+
return domain;
54+
}
55+
56+
int main() {
57+
const uint16_t datalen = 128;
58+
uint8_t data[datalen] = {};
59+
memcpy(data, "\x04quildu\x03xyz\x00", 11);
60+
_mdns_parse_domain_name(data, datalen);
61+
}
62+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Finds unsigned integer overflow issues with the following heuristic:
8+
* variable is compared to 0 and decremented
9+
* variable is used after the comparison and decrementation
10+
11+
In such cases it is likely that the decrementation was not expected.
12+
</p>
13+
14+
<p>
15+
You can read about a real-world vulnerability here: https://github.com/trailofbits/exploits/tree/main/obts-2025-macos-lpe
16+
</p>
17+
</overview>
18+
19+
<recommendation>
20+
<p>
21+
Move the decrementation outside of comparison and/or add explicit checks for overflows.
22+
</p>
23+
</recommendation>
24+
25+
<example>
26+
<sample src="DecOverflowWhenComparing.c" />
27+
28+
<p>
29+
The <code>datalen</code> variable may overflow to UINT_MAX given a specific input.
30+
</p>
31+
</example>
32+
33+
</qhelp>
34+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @name Decrementation overflow when comparing
3+
* @id tob/cpp/dec-overflow-when-comparing
4+
* @description This query finds unsigned integer overflows resulting from unchecked decrementation during comparison.
5+
* @kind problem
6+
* @tags security
7+
* @problem.severity error
8+
* @precision high
9+
* @security-severity 5.0
10+
* @group security
11+
*/
12+
13+
import cpp
14+
import semmle.code.cpp.ir.IR
15+
16+
from Variable var, VariableAccess varAcc, DecrementOperation dec,
17+
VariableAccess varAccAfterOverflow, ComparisonOperation cmp
18+
where
19+
// get unsigned variable that is decremented
20+
varAcc = var.getAnAccess() and
21+
varAccAfterOverflow = var.getAnAccess() and
22+
varAcc != varAccAfterOverflow and
23+
dec.getOperand() = varAcc.getExplicitlyConverted() and
24+
varAcc.getUnderlyingType().(IntegralType).isUnsigned() and
25+
26+
// only decrementations inside comparisons
27+
cmp.getAnOperand().getAChild*() = varAcc and
28+
cmp.getAnOperand() instanceof Zero and
29+
30+
// only if the variable is used after the comparison
31+
cmp.getASuccessor+() = varAccAfterOverflow and
32+
cmp.getAnOperand().getAChild*() != varAccAfterOverflow and
33+
34+
// skip if the variable is overwritten
35+
// TODO: handle loops correctly
36+
// not exists(VariableAccess varAccLV | varAccLV.isUsedAsLValue() |
37+
// varAccLV = var.getAnAccess() and
38+
// varAccLV != varAcc and
39+
// varAccLV != varAccAfterOverflow and
40+
// cmp.getASuccessor+() = varAccLV and
41+
// varAccAfterOverflow.getAPredecessor+() = varAccLV
42+
// ) and
43+
44+
// var-- > 0 (0 < var--) then accesses only in false branch
45+
// var-- >= 0 then accesses in all branches
46+
// var-- == 0 then accesses in all branches
47+
// var-- != 0 then accesses in all branches
48+
if (
49+
(cmp instanceof GTExpr and cmp.getRightOperand() instanceof Zero)
50+
or
51+
(cmp instanceof LTExpr and cmp.getLeftOperand() instanceof Zero)
52+
)
53+
then
54+
cmp.getAFalseSuccessor().getASuccessor*() = varAccAfterOverflow
55+
else
56+
any()
57+
select cmp, varAccAfterOverflow
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#ifndef USE_HEADERS
2+
#include "../../../include/libc/string_stubs.h"
3+
4+
#else
5+
#include <stdio.h>
6+
#include <stdint.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
#endif
10+
11+
// from https://github.com/apple-oss-distributions/Libinfo/blob/9fce29e5c5edc15d3ecea55116ca17d3f6350603/lookup.subproj/mdns_module.c#L1033C1-L1079C2
12+
char* _mdns_parse_domain_name(const uint8_t *data, uint32_t datalen)
13+
{
14+
int i = 0, j = 0;
15+
uint32_t len;
16+
uint32_t domainlen = 0;
17+
char *domain = NULL;
18+
19+
if ((data == NULL) || (datalen == 0)) return NULL;
20+
21+
/*
22+
* i: index into input data
23+
* j: index into output string
24+
*/
25+
while (datalen-- > 0)
26+
{
27+
len = data[i++];
28+
domainlen += (len + 1);
29+
domain = reallocf(domain, domainlen);
30+
31+
if (domain == NULL) return NULL;
32+
33+
if (len == 0) break; // DNS root (NUL)
34+
35+
if (j > 0)
36+
{
37+
domain[j++] = datalen ? '.' : '\0';
38+
}
39+
40+
while ((len-- > 0) && (0 != datalen--))
41+
{
42+
if (data[i] == '.')
43+
{
44+
/* special case: escape the '.' with a '\' */
45+
domain = reallocf(domain, ++domainlen);
46+
if (domain == NULL) return NULL;
47+
48+
domain[j++] = '\\';
49+
}
50+
51+
domain[j++] = data[i++];
52+
}
53+
}
54+
55+
domain[j] = '\0';
56+
57+
return domain;
58+
}
59+
60+
int main() {
61+
const uint16_t datalen = 128;
62+
uint8_t data[datalen] = {};
63+
memcpy(data, "\x04quildu\x03xyz\x00", 11);
64+
_mdns_parse_domain_name(data, datalen);
65+
}
66+

cpp/test/query-tests/security/DecOverflowWhenComparing/DecOverflowWhenComparing.expected

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
security/DecOverflowWhenComparing/DecOverflowWhenComparing.ql

0 commit comments

Comments
 (0)