Skip to content

Commit 80faff6

Browse files
committed
add better barrier, and fix up test
1 parent 0bb1b87 commit 80faff6

2 files changed

Lines changed: 39 additions & 11 deletions

File tree

go/src/security/UnboundedIORead/UnboundedIORead.ql

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class UnboundedReadCall extends DataFlow::CallNode {
2020
UnboundedReadCall() {
2121
this.getTarget().hasQualifiedName("io", "ReadAll")
2222
or
23-
this.getTarget().hasQualifiedName("io/ioutil", "ReadAll") // deprecated but still used
23+
this.getTarget().hasQualifiedName("io/ioutil", "ReadAll")
2424
or
2525
this.getTarget().hasQualifiedName("ioutil", "ReadAll")
2626
}
@@ -29,13 +29,10 @@ class UnboundedReadCall extends DataFlow::CallNode {
2929
/**
3030
* A source node: an HTTP request body that can be controlled by an attacker.
3131
*
32-
* Matches:
33-
* - `r.Body` where `r` is an `*http.Request`
34-
* - `r.GetBody()` calls
32+
* Matches `r.Body` where `r` is an `*http.Request`.
3533
*/
3634
class HTTPRequestBodySource extends DataFlow::Node {
3735
HTTPRequestBodySource() {
38-
// r.Body field read
3936
exists(Field f, SelectorExpr sel |
4037
f.hasQualifiedName("net/http", "Request", "Body") and
4138
sel.getSelector().refersTo(f) and
@@ -46,10 +43,6 @@ class HTTPRequestBodySource extends DataFlow::Node {
4643

4744
/**
4845
* A call that wraps a reader with a size limit, acting as a sanitizer.
49-
*
50-
* - `http.MaxBytesReader(w, r, n)`
51-
* - `io.LimitReader(r, n)`
52-
* - `io.LimitedReader{R: r, N: n}`
5346
*/
5447
class SizeLimitingCall extends DataFlow::CallNode {
5548
SizeLimitingCall() {
@@ -59,15 +52,38 @@ class SizeLimitingCall extends DataFlow::CallNode {
5952
}
6053
}
6154

55+
/**
56+
* Holds if `r.Body` is reassigned from a size-limiting call in the same function
57+
* that `bodyRead` is in, on the same request variable `r`.
58+
*/
59+
predicate bodyLimitedByReassignment(SelectorExpr bodyRead) {
60+
exists(SizeLimitingCall limiter, Assignment assign, SelectorExpr lhs, Variable v |
61+
// The assignment target is `v.Body`
62+
assign.getLhs() = lhs and
63+
lhs.getSelector().getName() = "Body" and
64+
lhs.getBase().(Ident).refersTo(v) and
65+
// The RHS is a MaxBytesReader/LimitReader call
66+
assign.getRhs() = limiter.asExpr() and
67+
// The body read is on the same variable: `v.Body`
68+
bodyRead.getBase().(Ident).refersTo(v) and
69+
// Both in the same function
70+
assign.getEnclosingFunction() = bodyRead.getEnclosingFunction()
71+
)
72+
}
73+
6274
module UnboundedReadConfig implements DataFlow::ConfigSig {
63-
predicate isSource(DataFlow::Node source) { source instanceof HTTPRequestBodySource }
75+
predicate isSource(DataFlow::Node source) {
76+
source instanceof HTTPRequestBodySource and
77+
// Exclude r.Body reads where r.Body was reassigned from a size limiter
78+
not bodyLimitedByReassignment(source.asExpr())
79+
}
6480

6581
predicate isSink(DataFlow::Node sink) {
6682
exists(UnboundedReadCall readAll | sink = readAll.getArgument(0))
6783
}
6884

6985
predicate isBarrier(DataFlow::Node node) {
70-
// If the body passes through a size-limiting wrapper, it is safe
86+
// If the body passes through a size-limiting wrapper (io.LimitReader pattern), it is safe
7187
node = any(SizeLimitingCall c).getResult(0)
7288
or
7389
node = any(SizeLimitingCall c).getResult()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
edges
2+
| UnboundedIORead.go:24:12:24:17 | selection of Body | UnboundedIORead.go:25:24:25:29 | reader | provenance | Src:MaD:1919 |
3+
nodes
4+
| UnboundedIORead.go:12:24:12:29 | selection of Body | semmle.label | selection of Body |
5+
| UnboundedIORead.go:18:28:18:33 | selection of Body | semmle.label | selection of Body |
6+
| UnboundedIORead.go:24:12:24:17 | selection of Body | semmle.label | selection of Body |
7+
| UnboundedIORead.go:25:24:25:29 | reader | semmle.label | reader |
8+
subpaths
9+
#select
10+
| UnboundedIORead.go:12:24:12:29 | selection of Body | UnboundedIORead.go:12:24:12:29 | selection of Body | UnboundedIORead.go:12:24:12:29 | selection of Body | Unbounded read of HTTP request $@ with no size limit allows denial of service via memory exhaustion. | UnboundedIORead.go:12:24:12:29 | selection of Body | body |
11+
| UnboundedIORead.go:18:28:18:33 | selection of Body | UnboundedIORead.go:18:28:18:33 | selection of Body | UnboundedIORead.go:18:28:18:33 | selection of Body | Unbounded read of HTTP request $@ with no size limit allows denial of service via memory exhaustion. | UnboundedIORead.go:18:28:18:33 | selection of Body | body |
12+
| UnboundedIORead.go:25:24:25:29 | reader | UnboundedIORead.go:24:12:24:17 | selection of Body | UnboundedIORead.go:25:24:25:29 | reader | Unbounded read of HTTP request $@ with no size limit allows denial of service via memory exhaustion. | UnboundedIORead.go:24:12:24:17 | selection of Body | body |

0 commit comments

Comments
 (0)