Skip to content

Commit 16d0fb0

Browse files
authored
Merge pull request #10 from kevinbackhouse/libssh2_eating_error_codes
Libssh2 eating error codes
2 parents 2411d50 + e59f6fa commit 16d0fb0

7 files changed

Lines changed: 108 additions & 0 deletions

File tree

ql_demos/cpp/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[snapshot](https://downloads.lgtm.com/snapshots/cpp/libssh2/libssh2_libssh2_C_C++_38bf7ce.zip)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @name 00_error_codes
3+
*/
4+
5+
import cpp
6+
7+
// Look for return statements that return a negative integer constant.
8+
// For example:
9+
//
10+
// return -1;
11+
//
12+
// The negative return value might be an error code.
13+
from ReturnStmt ret
14+
where ret.getExpr().getValue().toInt() < 0
15+
select ret
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @name 01_error_codes_call
3+
*/
4+
5+
import cpp
6+
7+
// Extend the previous query to also find calls to functions that sometimes
8+
// return a negative integer constant.
9+
from Function f, FunctionCall call, ReturnStmt ret
10+
where
11+
call.getTarget() = f and
12+
ret.getEnclosingFunction() = f and
13+
ret.getExpr().getValue().toInt() < 0
14+
select ret, call
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @name 02_eating_error_codes
3+
*/
4+
5+
import cpp
6+
7+
// Look for calls that are cast to unsigned, which means that the error
8+
// code might be accidentally ignored.
9+
from Function f, FunctionCall call, ReturnStmt ret
10+
where
11+
call.getTarget() = f and
12+
ret.getEnclosingFunction() = f and
13+
ret.getExpr().getValue().toInt() < 0 and
14+
call.getFullyConverted().getType().getUnderlyingType().(IntegralType).isUnsigned()
15+
select call, ret
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name 03_eating_error_codes_localflow
3+
*/
4+
5+
import cpp
6+
import semmle.code.cpp.dataflow.DataFlow
7+
8+
// The previous query only handled cases where the result of the function
9+
// call is immediately cast to unsigned. So it will fail to detect examples
10+
// like this, where the cast doesn't happen immediately:
11+
//
12+
// int r = f();
13+
// unsigned int x = r;
14+
//
15+
// In this query, we add local dataflow so that we can also handle such
16+
// cases.
17+
from Function f, FunctionCall call, ReturnStmt ret, DataFlow::Node source, DataFlow::Node sink
18+
where call.getTarget() = f
19+
and ret.getEnclosingFunction() = f
20+
and ret.getExpr().getValue().toInt() < 0
21+
and source.asExpr() = call
22+
and DataFlow::localFlow(source, sink)
23+
and sink.asExpr().getFullyConverted().getType().getUnderlyingType().(IntegralType).isUnsigned()
24+
select source, sink
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @name 04_eating_error_codes_localflow_rangeanalysis
3+
*/
4+
5+
import cpp
6+
import semmle.code.cpp.dataflow.DataFlow
7+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
8+
9+
// The previous query produced some weird results. The problem is that it
10+
// treats any expression with an unsigned type as a potential sink. What we
11+
// really want is to find where the cast from signed to unsigned happens,
12+
// because that's where the integer overflow occurs. So we want the sink to
13+
// be a potentially negative expression that gets cast to unsigned.
14+
//
15+
// Note that by using range analysis, we can avoid producing false positive
16+
// results for examples like this:
17+
//
18+
// int r = f();
19+
// if (r < 0) return -1;
20+
// unsigned int x = r;
21+
from Function f, FunctionCall call, ReturnStmt ret, DataFlow::Node source, DataFlow::Node sink
22+
where
23+
call.getTarget() = f and
24+
ret.getEnclosingFunction() = f and
25+
ret.getExpr().getValue().toInt() < 0 and
26+
source.asExpr() = call and
27+
DataFlow::localFlow(source, sink) and
28+
sink.asExpr().getFullyConverted().getType().getUnderlyingType().(IntegralType).isUnsigned() and
29+
lowerBound(sink.asExpr()) < 0
30+
select source, sink
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Eating error codes in libssh2
2+
3+
Download this [snapshot](https://downloads.lgtm.com/snapshots/cpp/libssh2/libssh2_libssh2_C_C++_38bf7ce.zip) for the demo.
4+
5+
This demo shows how to develop, step-by-step, the query from the [blog post](https://blog.semmle.com/libssh2-integer-overflow/) about libssh2 CVE-2019-13115. This query did not find the bug that caused the CVE. It is instead about doing variant analysis on a bug that we noticed on the development branch of libssh2. We sent the query results to the libssh2 development team and they were able to fix all the variants before the next version of libssh2 was released.
6+
7+
[This](https://lgtm.com/projects/g/libssh2/libssh2/snapshot/6e2f5563c80521b3cde72a6fcdb675c2e085f9cf/files/src/hostkey.c?sort=name&dir=ASC&mode=heatmap&__hstc=70225743.5fa8704c8874c6eafaef219923a26734.1534954774206.1564532078978.1564925733575.72&__hssc=70225743.2.1565139962633&__hsfp=997709570#L677) is an example of the bug. The problem is that `_libssh2_get_c_string` returns a negative integer as an error code, but the type of `r_len` is `unsigned int`, so the error code is accidentally ignored.
8+
9+
For a shorter demo, stop at step 02. Steps 03 and 04 make the query more sophisticated by adding local data flow and range analysis.

0 commit comments

Comments
 (0)