Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The function `Rel` in `path/filepath` was incorrectly considered a sanitizer for `go/path-injection` and `go/zipslip`. This has now been fixed, which may lead to more results for those queries.
5 changes: 0 additions & 5 deletions go/ql/lib/ext/path.filepath.model.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
extensions:
- addsTo:
pack: codeql/go-all
extensible: barrierModel
data:
- ["path/filepath", "", False, "Rel", "", "", "ReturnValue", "path-injection", "manual"]
- addsTo:
pack: codeql/go-all
extensible: summaryModel
Expand Down
37 changes: 0 additions & 37 deletions go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll
Original file line number Diff line number Diff line change
Expand Up @@ -129,43 +129,6 @@ module TaintedPath {
DotDotReplaceAll() { this.getReplacedString() = ["..", "."] }
}

/**
* A node `nd` guarded by a check that ensures it is contained within some root folder,
* considered as a sanitizer for path traversal.
*
* We currently recognize checks of the following form:
*
* ```
* ..., err := filepath.Rel(base, path)
* if err == nil {
* // path is known to be contained in base
* }
* ```
*/
class PathContainmentCheck extends SanitizerGuard, DataFlow::EqualityTestNode {
DataFlow::Node path;
boolean outcome;

PathContainmentCheck() {
exists(Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p |
f.hasQualifiedName("path/filepath", "Rel") and
inp.isParameter(1) and
outp.isResult(1) and
p.isNil()
|
exists(DataFlow::Node call, DataFlow::Node res |
call = f.getACall() and
DataFlow::localFlow(outp.getNode(call), res)
|
p.checkOn(this, outcome, res) and
path = inp.getNode(call)
)
)
}

override predicate checks(Expr e, boolean branch) { e = path.asExpr() and branch = outcome }
}

/**
* A call of the form `strings.HasPrefix(path, ...)` considered as a sanitizer guard
* for `path`.
Expand Down
15 changes: 13 additions & 2 deletions go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
#select
| TaintedPath.go:18:29:18:40 | tainted_path | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:18:29:18:40 | tainted_path | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value |
| TaintedPath.go:22:28:22:69 | call to Join | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:22:28:22:69 | call to Join | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value |
| TaintedPath.go:27:28:27:45 | sanitized_filepath | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:27:28:27:45 | sanitized_filepath | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value |
| TaintedPath.go:43:29:43:40 | tainted_path | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:43:29:43:40 | tainted_path | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value |
| TaintedPath.go:74:28:74:57 | call to Clean | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:74:28:74:57 | call to Clean | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value |
edges
| TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:15:18:15:30 | call to Query | provenance | Src:MaD:2 MaD:3 |
| TaintedPath.go:15:18:15:30 | call to Query | TaintedPath.go:18:29:18:40 | tainted_path | provenance | Sink:MaD:1 |
| TaintedPath.go:15:18:15:30 | call to Query | TaintedPath.go:22:57:22:68 | tainted_path | provenance | |
| TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:22:28:22:69 | call to Join | provenance | FunctionModel Sink:MaD:1 |
| TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:26:63:26:74 | tainted_path | provenance | |
| TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:43:29:43:40 | tainted_path | provenance | Sink:MaD:1 |
| TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:74:39:74:56 | ...+... | provenance | |
| TaintedPath.go:74:39:74:56 | ...+... | TaintedPath.go:74:28:74:57 | call to Clean | provenance | MaD:4 Sink:MaD:1 |
| TaintedPath.go:26:2:26:75 | ... := ...[0] | TaintedPath.go:27:28:27:45 | sanitized_filepath | provenance | Sink:MaD:1 |
| TaintedPath.go:26:63:26:74 | tainted_path | TaintedPath.go:26:2:26:75 | ... := ...[0] | provenance | MaD:4 |
| TaintedPath.go:74:39:74:56 | ...+... | TaintedPath.go:74:28:74:57 | call to Clean | provenance | MaD:5 Sink:MaD:1 |
models
| 1 | Sink: io/ioutil; ; false; ReadFile; ; ; Argument[0]; path-injection; manual |
| 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual |
| 3 | Summary: net/url; URL; true; Query; ; ; Argument[receiver]; ReturnValue; taint; manual |
| 4 | Summary: path; ; false; Clean; ; ; Argument[0]; ReturnValue; taint; manual |
| 4 | Summary: path/filepath; ; false; Rel; ; ; Argument[0..1]; ReturnValue[0]; taint; manual |
| 5 | Summary: path; ; false; Clean; ; ; Argument[0]; ReturnValue; taint; manual |
nodes
| TaintedPath.go:15:18:15:22 | selection of URL | semmle.label | selection of URL |
| TaintedPath.go:15:18:15:30 | call to Query | semmle.label | call to Query |
| TaintedPath.go:18:29:18:40 | tainted_path | semmle.label | tainted_path |
| TaintedPath.go:22:28:22:69 | call to Join | semmle.label | call to Join |
| TaintedPath.go:22:57:22:68 | tainted_path | semmle.label | tainted_path |
| TaintedPath.go:26:2:26:75 | ... := ...[0] | semmle.label | ... := ...[0] |
| TaintedPath.go:26:63:26:74 | tainted_path | semmle.label | tainted_path |
| TaintedPath.go:27:28:27:45 | sanitized_filepath | semmle.label | sanitized_filepath |
| TaintedPath.go:43:29:43:40 | tainted_path | semmle.label | tainted_path |
| TaintedPath.go:74:28:74:57 | call to Clean | semmle.label | call to Clean |
| TaintedPath.go:74:39:74:56 | ...+... | semmle.label | ...+... |
subpaths
8 changes: 4 additions & 4 deletions go/ql/test/query-tests/Security/CWE-022/TaintedPath.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ func handler(w http.ResponseWriter, r *http.Request) {
data, _ = ioutil.ReadFile(filepath.Join("/home/user/", tainted_path)) // $ Alert[go/path-injection]
w.Write(data)

// GOOD: This can only read inside the provided safe path
// BAD: This could still read any file on the file system
sanitized_filepath, _ := filepath.Rel("/home/user/safepath", tainted_path)
data, _ = ioutil.ReadFile(sanitized_filepath)
data, _ = ioutil.ReadFile(sanitized_filepath) // $ Alert[go/path-injection]
w.Write(data)

// GOOD: This can only read inside the provided safe path
Expand All @@ -37,10 +37,10 @@ func handler(w http.ResponseWriter, r *http.Request) {
data, _ = ioutil.ReadFile(strings.ReplaceAll(tainted_path, "..", ""))
w.Write(data)

// GOOD: This can only read inside the provided safe path
// BAD: This could still read any file on the file system
_, err := filepath.Rel("/home/user/safepath", tainted_path)
if err == nil {
data, _ = ioutil.ReadFile(tainted_path)
data, _ = ioutil.ReadFile(tainted_path) // $ Alert[go/path-injection]
w.Write(data)
}

Expand Down
17 changes: 15 additions & 2 deletions go/ql/test/query-tests/Security/CWE-022/ZipSlip.expected
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
| UnsafeUnzipSymlinkGood.go:72:3:72:25 | ... := ...[0] | UnsafeUnzipSymlinkGood.go:72:3:72:25 | ... := ...[0] | UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | Unsanitized archive entry, which may contain '..', is used in a $@. | UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | file system operation |
| ZipSlip.go:11:2:15:2 | range statement[1] | ZipSlip.go:11:2:15:2 | range statement[1] | ZipSlip.go:14:20:14:20 | p | Unsanitized archive entry, which may contain '..', is used in a $@. | ZipSlip.go:14:20:14:20 | p | file system operation |
| tarslip.go:15:2:15:30 | ... := ...[0] | tarslip.go:15:2:15:30 | ... := ...[0] | tarslip.go:16:14:16:34 | call to Dir | Unsanitized archive entry, which may contain '..', is used in a $@. | tarslip.go:16:14:16:34 | call to Dir | file system operation |
| tst.go:23:2:43:2 | range statement[1] | tst.go:23:2:43:2 | range statement[1] | tst.go:27:21:27:48 | call to Join | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:27:21:27:48 | call to Join | file system operation |
| tst.go:23:2:43:2 | range statement[1] | tst.go:23:2:43:2 | range statement[1] | tst.go:29:20:29:23 | path | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:29:20:29:23 | path | file system operation |
| tst.go:23:2:43:2 | range statement[1] | tst.go:23:2:43:2 | range statement[1] | tst.go:31:21:31:24 | path | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:31:21:31:24 | path | file system operation |
edges
| UnsafeUnzipSymlinkGood.go:52:24:52:32 | SSA def(candidate) | UnsafeUnzipSymlinkGood.go:61:53:61:61 | candidate | provenance | |
| UnsafeUnzipSymlinkGood.go:61:53:61:61 | candidate | UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | provenance | FunctionModel Sink:MaD:3 |
Expand All @@ -14,14 +16,20 @@ edges
| ZipSlip.go:12:3:12:30 | ... := ...[0] | ZipSlip.go:14:20:14:20 | p | provenance | Sink:MaD:1 |
| ZipSlip.go:12:24:12:29 | selection of Name | ZipSlip.go:12:3:12:30 | ... := ...[0] | provenance | MaD:4 |
| tarslip.go:15:2:15:30 | ... := ...[0] | tarslip.go:16:23:16:33 | selection of Name | provenance | |
| tarslip.go:16:23:16:33 | selection of Name | tarslip.go:16:14:16:34 | call to Dir | provenance | MaD:5 Sink:MaD:2 |
| tarslip.go:16:23:16:33 | selection of Name | tarslip.go:16:14:16:34 | call to Dir | provenance | MaD:6 Sink:MaD:2 |
| tst.go:23:2:43:2 | range statement[1] | tst.go:25:38:25:41 | path | provenance | |
| tst.go:23:2:43:2 | range statement[1] | tst.go:29:20:29:23 | path | provenance | Sink:MaD:1 |
| tst.go:23:2:43:2 | range statement[1] | tst.go:31:21:31:24 | path | provenance | Sink:MaD:1 |
| tst.go:25:3:25:42 | ... := ...[0] | tst.go:27:41:27:47 | relpath | provenance | |
| tst.go:25:38:25:41 | path | tst.go:25:3:25:42 | ... := ...[0] | provenance | MaD:5 |
| tst.go:27:41:27:47 | relpath | tst.go:27:21:27:48 | call to Join | provenance | FunctionModel Sink:MaD:1 |
models
| 1 | Sink: io/ioutil; ; false; WriteFile; ; ; Argument[0]; path-injection; manual |
| 2 | Sink: os; ; false; MkdirAll; ; ; Argument[0]; path-injection; manual |
| 3 | Sink: os; ; false; Readlink; ; ; Argument[0]; path-injection; manual |
| 4 | Summary: path/filepath; ; false; Abs; ; ; Argument[0]; ReturnValue[0]; taint; manual |
| 5 | Summary: path; ; false; Dir; ; ; Argument[0]; ReturnValue; taint; manual |
| 5 | Summary: path/filepath; ; false; Rel; ; ; Argument[0..1]; ReturnValue[0]; taint; manual |
| 6 | Summary: path; ; false; Dir; ; ; Argument[0]; ReturnValue; taint; manual |
nodes
| UnsafeUnzipSymlinkGood.go:52:24:52:32 | SSA def(candidate) | semmle.label | SSA def(candidate) |
| UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | semmle.label | call to Join |
Expand All @@ -37,5 +45,10 @@ nodes
| tarslip.go:16:14:16:34 | call to Dir | semmle.label | call to Dir |
| tarslip.go:16:23:16:33 | selection of Name | semmle.label | selection of Name |
| tst.go:23:2:43:2 | range statement[1] | semmle.label | range statement[1] |
| tst.go:25:3:25:42 | ... := ...[0] | semmle.label | ... := ...[0] |
| tst.go:25:38:25:41 | path | semmle.label | path |
| tst.go:27:21:27:48 | call to Join | semmle.label | call to Join |
| tst.go:27:41:27:47 | relpath | semmle.label | relpath |
| tst.go:29:20:29:23 | path | semmle.label | path |
| tst.go:31:21:31:24 | path | semmle.label | path |
subpaths
8 changes: 4 additions & 4 deletions go/ql/test/query-tests/Security/CWE-022/tst.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func uploadFile(w http.ResponseWriter, r *http.Request) {
file, handler, _ := r.FormFile("file")
// err handling
defer file.Close()
tempFile, _ := ioutil.TempFile("/tmp", handler.Filename) // NOT OK
tempFile, _ := ioutil.TempFile("/tmp", handler.Filename) // OK
use(tempFile)
}

Expand All @@ -24,11 +24,11 @@ func unzip2(f string, root string) {
path := f.Name
relpath, err := filepath.Rel(root, path)
if err == nil {
ioutil.WriteFile(filepath.Join(root, relpath), []byte("present"), 0666) // OK
ioutil.WriteFile(filepath.Join(root, relpath), []byte("present"), 0666) // $ Sink[go/zipslip]
}
ioutil.WriteFile(path, []byte("present"), 0666) // $ Sink[go/zipslip] // NOT OK
ioutil.WriteFile(path, []byte("present"), 0666) // $ Sink[go/zipslip]
if containedIn(path, root) {
ioutil.WriteFile(path, []byte("present"), 0666) // OK
ioutil.WriteFile(path, []byte("present"), 0666) // $ Sink[go/zipslip]
}
if ok, _ := regexp.MatchString("^[a-z]*$", path); ok {
ioutil.WriteFile(path, []byte("present"), 0666) // OK
Expand Down
Loading