Skip to content

Commit f1115af

Browse files
authoredMar 6, 2024
Merge pull request #15130 from Malayke/main
Go: new query for detect DOS vulnerability
2 parents f4c2e65 + 02bab4c commit f1115af

File tree

8 files changed

+288
-0
lines changed

8 files changed

+288
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
3+
<qhelp>
4+
<overview>
5+
<p>Using untrusted input to created with the built-in make function
6+
could lead to excessive memory allocation and potentially cause the program to crash due
7+
to running out of memory. This vulnerability could be exploited to perform a DoS attack by consuming all available server resources.</p>
8+
</overview>
9+
10+
<recommendation>
11+
<p>Implement a maximum allowed value for creates a slice with the built-in make function to prevent excessively large allocations.
12+
For instance, you could restrict it to a reasonable upper limit.</p>
13+
</recommendation>
14+
15+
<example>
16+
<p>In the following example snippet, the <code>n</code> field is user-controlled.</p>
17+
<p> The server trusts that n has an acceptable value, however when using a maliciously large value,
18+
it allocates a slice of <code>n</code> of strings before filling the slice with data.</p>
19+
20+
<sample src="DenialOfServiceBad.go" />
21+
22+
<p>One way to prevent this vulnerability is by implementing a maximum allowed value for the user-controlled input:</p>
23+
24+
<sample src="DenialOfServiceGood.go" />
25+
</example>
26+
27+
<references>
28+
<li>
29+
OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html">Denial of Service Cheat Sheet</a>
30+
</li>
31+
</references>
32+
</qhelp>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @name Denial Of Service
3+
* @description slices created with the built-in make function from user-controlled sources using a
4+
* maliciously large value possibly leading to a denial of service.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 9
8+
* @precision high
9+
* @id go/denial-of-service
10+
* @tags security
11+
* experimental
12+
* external/cwe/cwe-770
13+
*/
14+
15+
import go
16+
17+
/**
18+
* Holds if the guard `g` on its branch `branch` checks that `e` is not constant and is less than some other value.
19+
*/
20+
predicate denialOfServiceSanitizerGuard(DataFlow::Node g, Expr e, boolean branch) {
21+
exists(DataFlow::Node lesser |
22+
e = lesser.asExpr() and
23+
g.(DataFlow::RelationalComparisonNode).leq(branch, lesser, _, _) and
24+
not e.isConst()
25+
)
26+
}
27+
28+
/**
29+
* Module for defining predicates and tracking taint flow related to denial of service issues.
30+
*/
31+
module Config implements DataFlow::ConfigSig {
32+
predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
33+
34+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
35+
exists(Function f, DataFlow::CallNode cn | cn = f.getACall() |
36+
f.hasQualifiedName("strconv", ["Atoi", "ParseInt", "ParseUint", "ParseFloat"]) and
37+
node1 = cn.getArgument(0) and
38+
node2 = cn.getResult(0)
39+
)
40+
}
41+
42+
predicate isBarrier(DataFlow::Node node) {
43+
node = DataFlow::BarrierGuard<denialOfServiceSanitizerGuard/3>::getABarrierNode()
44+
}
45+
46+
predicate isSink(DataFlow::Node sink) { sink = Builtin::make().getACall().getArgument(0) }
47+
}
48+
49+
/**
50+
* Tracks taint flow for reasoning about denial of service, where source is
51+
* user-controlled and unchecked.
52+
*/
53+
module Flow = TaintTracking::Global<Config>;
54+
55+
import Flow::PathGraph
56+
57+
from Flow::PathNode source, Flow::PathNode sink
58+
where Flow::flowPath(source, sink)
59+
select sink, source, sink, "This variable might be leading to denial of service."
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"strconv"
8+
)
9+
10+
func OutOfMemoryBad(w http.ResponseWriter, r *http.Request) {
11+
query := r.URL.Query()
12+
13+
queryStr := query.Get("n")
14+
collectionSize, err := strconv.Atoi(queryStr)
15+
if err != nil {
16+
http.Error(w, err.Error(), http.StatusBadRequest)
17+
return
18+
}
19+
20+
result := make([]string, collectionSize)
21+
for i := 0; i < collectionSize; i++ {
22+
result[i] = fmt.Sprintf("Item %d", i+1)
23+
}
24+
25+
w.Header().Set("Content-Type", "application/json")
26+
json.NewEncoder(w).Encode(result)
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"strconv"
8+
)
9+
10+
func OutOfMemoryGood(w http.ResponseWriter, r *http.Request) {
11+
query := r.URL.Query()
12+
MaxValue := 6
13+
queryStr := query.Get("n")
14+
collectionSize, err := strconv.Atoi(queryStr)
15+
if err != nil {
16+
http.Error(w, err.Error(), http.StatusBadRequest)
17+
return
18+
}
19+
if collectionSize < 0 || collectionSize > MaxValue {
20+
http.Error(w, "Bad request", http.StatusBadRequest)
21+
return
22+
}
23+
result := make([]string, collectionSize)
24+
for i := 0; i < collectionSize; i++ {
25+
result[i] = fmt.Sprintf("Item %d", i+1)
26+
}
27+
28+
w.Header().Set("Content-Type", "application/json")
29+
json.NewEncoder(w).Encode(result)
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
edges
2+
| DenialOfServiceBad.go:11:12:11:16 | selection of URL | DenialOfServiceBad.go:11:12:11:24 | call to Query | provenance | |
3+
| DenialOfServiceBad.go:11:12:11:24 | call to Query | DenialOfServiceBad.go:13:15:13:20 | source | provenance | |
4+
| DenialOfServiceBad.go:13:15:13:20 | source | DenialOfServiceBad.go:13:15:13:29 | call to Get | provenance | |
5+
| DenialOfServiceBad.go:13:15:13:29 | call to Get | DenialOfServiceBad.go:14:28:14:36 | sourceStr | provenance | |
6+
| DenialOfServiceBad.go:14:2:14:37 | ... := ...[0] | DenialOfServiceBad.go:20:27:20:30 | sink | provenance | |
7+
| DenialOfServiceBad.go:14:28:14:36 | sourceStr | DenialOfServiceBad.go:14:2:14:37 | ... := ...[0] | provenance | |
8+
nodes
9+
| DenialOfServiceBad.go:11:12:11:16 | selection of URL | semmle.label | selection of URL |
10+
| DenialOfServiceBad.go:11:12:11:24 | call to Query | semmle.label | call to Query |
11+
| DenialOfServiceBad.go:13:15:13:20 | source | semmle.label | source |
12+
| DenialOfServiceBad.go:13:15:13:29 | call to Get | semmle.label | call to Get |
13+
| DenialOfServiceBad.go:14:2:14:37 | ... := ...[0] | semmle.label | ... := ...[0] |
14+
| DenialOfServiceBad.go:14:28:14:36 | sourceStr | semmle.label | sourceStr |
15+
| DenialOfServiceBad.go:20:27:20:30 | sink | semmle.label | sink |
16+
subpaths
17+
#select
18+
| DenialOfServiceBad.go:20:27:20:30 | sink | DenialOfServiceBad.go:11:12:11:16 | selection of URL | DenialOfServiceBad.go:20:27:20:30 | sink | This variable might be leading to denial of service. |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/CWE-770/DenialOfService.ql
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"strconv"
8+
)
9+
10+
func OutOfMemoryBad(w http.ResponseWriter, r *http.Request) {
11+
source := r.URL.Query()
12+
13+
sourceStr := source.Get("n")
14+
sink, err := strconv.Atoi(sourceStr)
15+
if err != nil {
16+
http.Error(w, err.Error(), http.StatusBadRequest)
17+
return
18+
}
19+
20+
result := make([]string, sink)
21+
for i := 0; i < sink; i++ {
22+
result[i] = fmt.Sprintf("Item %d", i+1)
23+
}
24+
25+
w.Header().Set("Content-Type", "application/json")
26+
json.NewEncoder(w).Encode(result)
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"strconv"
8+
)
9+
10+
func OutOfMemoryGood1(w http.ResponseWriter, r *http.Request) {
11+
source := r.URL.Query()
12+
MaxValue := 6
13+
sourceStr := source.Get("n")
14+
sink, err := strconv.Atoi(sourceStr)
15+
if err != nil || sink < 0 {
16+
http.Error(w, "Bad request", http.StatusBadRequest)
17+
return
18+
}
19+
if sink > MaxValue {
20+
return
21+
}
22+
result := make([]string, sink)
23+
for i := 0; i < sink; i++ {
24+
result[i] = fmt.Sprintf("Item %d", i+1)
25+
}
26+
27+
w.Header().Set("Content-Type", "application/json")
28+
json.NewEncoder(w).Encode(result)
29+
}
30+
31+
func OutOfMemoryGood2(w http.ResponseWriter, r *http.Request) {
32+
source := r.URL.Query()
33+
MaxValue := 6
34+
sourceStr := source.Get("n")
35+
sink, err := strconv.Atoi(sourceStr)
36+
if err != nil || sink < 0 {
37+
http.Error(w, "Bad request", http.StatusBadRequest)
38+
return
39+
}
40+
if sink <= MaxValue {
41+
result := make([]string, sink)
42+
for i := 0; i < sink; i++ {
43+
result[i] = fmt.Sprintf("Item %d", i+1)
44+
}
45+
46+
w.Header().Set("Content-Type", "application/json")
47+
json.NewEncoder(w).Encode(result)
48+
}
49+
}
50+
51+
func OutOfMemoryGood3(w http.ResponseWriter, r *http.Request) {
52+
source := r.URL.Query()
53+
MaxValue := 6
54+
sourceStr := source.Get("n")
55+
sink, err := strconv.Atoi(sourceStr)
56+
if err != nil || sink < 0 {
57+
http.Error(w, "Bad request", http.StatusBadRequest)
58+
return
59+
}
60+
if sink > MaxValue {
61+
sink = MaxValue
62+
result := make([]string, sink)
63+
for i := 0; i < sink; i++ {
64+
result[i] = fmt.Sprintf("Item %d", i+1)
65+
}
66+
67+
w.Header().Set("Content-Type", "application/json")
68+
json.NewEncoder(w).Encode(result)
69+
}
70+
}
71+
72+
func OutOfMemoryGood4(w http.ResponseWriter, r *http.Request) {
73+
source := r.URL.Query()
74+
MaxValue := 6
75+
sourceStr := source.Get("n")
76+
sink, err := strconv.Atoi(sourceStr)
77+
if err != nil || sink < 0 {
78+
http.Error(w, "Bad request", http.StatusBadRequest)
79+
return
80+
}
81+
if sink > MaxValue {
82+
sink = MaxValue
83+
} else {
84+
tmp := sink
85+
sink = tmp
86+
}
87+
result := make([]string, sink)
88+
for i := 0; i < sink; i++ {
89+
result[i] = fmt.Sprintf("Item %d", i+1)
90+
}
91+
92+
w.Header().Set("Content-Type", "application/json")
93+
json.NewEncoder(w).Encode(result)
94+
}

0 commit comments

Comments
 (0)
Please sign in to comment.