Skip to content

Commit 477d8f8

Browse files
authored
Merge pull request github#14064 from amammad/amammad-go-NewFileSystemAccess
Go: New File System Access Sinks
2 parents 96543b8 + b6968d9 commit 477d8f8

File tree

48 files changed

+5993
-665
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+5993
-665
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Support has been added for file system access sinks in the following libraries: [net/http](https://pkg.go.dev/net/http), [Afero](https://github.com/spf13/afero), [beego](https://pkg.go.dev/github.com/astaxie/beego), [Echo](https://pkg.go.dev/github.com/labstack/echo), [Fiber](https://github.com/kataras/iris), [Gin](https://pkg.go.dev/github.com/gin-gonic/gin), [Iris](https://github.com/kataras/iris).

go/ql/lib/go.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import semmle.go.dataflow.GlobalValueNumbering
3030
import semmle.go.dataflow.SSA
3131
import semmle.go.dataflow.TaintTracking
3232
import semmle.go.dataflow.TaintTracking2
33+
import semmle.go.frameworks.Afero
3334
import semmle.go.frameworks.Beego
3435
import semmle.go.frameworks.BeegoOrm
3536
import semmle.go.frameworks.Chi
@@ -38,11 +39,13 @@ import semmle.go.frameworks.Echo
3839
import semmle.go.frameworks.ElazarlGoproxy
3940
import semmle.go.frameworks.Email
4041
import semmle.go.frameworks.Encoding
42+
import semmle.go.frameworks.Fiber
4143
import semmle.go.frameworks.Gin
4244
import semmle.go.frameworks.Glog
4345
import semmle.go.frameworks.GoMicro
4446
import semmle.go.frameworks.GoRestfulHttp
4547
import semmle.go.frameworks.Gqlgen
48+
import semmle.go.frameworks.Iris
4649
import semmle.go.frameworks.K8sIoApimachineryPkgRuntime
4750
import semmle.go.frameworks.K8sIoApiCoreV1
4851
import semmle.go.frameworks.K8sIoClientGo
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* Provides classes for working with sinks and taint propagators
3+
* from the `github.com/spf13/afero` package.
4+
*/
5+
6+
import go
7+
8+
/**
9+
* Provide File system access sinks of [afero](https://github.com/spf13/afero) framework
10+
*/
11+
module Afero {
12+
/**
13+
* Gets all versions of `github.com/spf13/afero`
14+
*/
15+
string aferoPackage() { result = package("github.com/spf13/afero", "") }
16+
17+
/**
18+
* The File system access sinks of [afero](https://github.com/spf13/afero) framework methods
19+
*/
20+
class AferoSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
21+
AferoSystemAccess() {
22+
exists(Method m |
23+
m.hasQualifiedName(aferoPackage(), "HttpFs",
24+
["Create", "Open", "OpenFile", "Remove", "RemoveAll"]) and
25+
this = m.getACall()
26+
or
27+
m.hasQualifiedName(aferoPackage(), "RegexpFs",
28+
["Create", "Open", "OpenFile", "Remove", "RemoveAll", "Mkdir", "MkdirAll"]) and
29+
this = m.getACall()
30+
or
31+
m.hasQualifiedName(aferoPackage(), "ReadOnlyFs",
32+
["Create", "Open", "OpenFile", "ReadDir", "ReadlinkIfPossible", "Mkdir", "MkdirAll"]) and
33+
this = m.getACall()
34+
or
35+
m.hasQualifiedName(aferoPackage(), "OsFs",
36+
[
37+
"Create", "Open", "OpenFile", "ReadlinkIfPossible", "Remove", "RemoveAll", "Mkdir",
38+
"MkdirAll"
39+
]) and
40+
this = m.getACall()
41+
or
42+
m.hasQualifiedName(aferoPackage(), "MemMapFs",
43+
["Create", "Open", "OpenFile", "Remove", "RemoveAll", "Mkdir", "MkdirAll"]) and
44+
this = m.getACall()
45+
)
46+
}
47+
48+
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
49+
}
50+
51+
/**
52+
* The File system access sinks of [afero](https://github.com/spf13/afero) framework utility functions
53+
*
54+
* Afero Type is basically is an wrapper around utility functions which make them like a method, look at [here](https://github.com/spf13/afero/blob/cf95922e71986c0116204b6eeb3b345a01ffd842/ioutil.go#L61)
55+
*
56+
* The Types that are not vulnerable: `afero.BasePathFs` and `afero.IOFS`
57+
*/
58+
class AferoUtilityFunctionSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
59+
int pathArg;
60+
61+
AferoUtilityFunctionSystemAccess() {
62+
// utility functions
63+
exists(Function f |
64+
f.hasQualifiedName(aferoPackage(),
65+
["WriteReader", "SafeWriteReader", "WriteFile", "ReadFile", "ReadDir"]) and
66+
this = f.getACall() and
67+
pathArg = 1 and
68+
not aferoSanitizer(this.getArgument(0))
69+
)
70+
or
71+
exists(Method m |
72+
m.hasQualifiedName(aferoPackage(), "Afero",
73+
["WriteReader", "SafeWriteReader", "WriteFile", "ReadFile", "ReadDir"]) and
74+
this = m.getACall() and
75+
pathArg = 0 and
76+
not aferoSanitizer(this.getReceiver())
77+
)
78+
}
79+
80+
override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
81+
}
82+
83+
/**
84+
* Holds if the Afero utility function has a first argument of a safe type like `NewBasePathFs`.
85+
*
86+
* e.g.
87+
* ```
88+
* basePathFs := afero.NewBasePathFs(osFS, "tmp")
89+
* afero.ReadFile(basePathFs, filepath)
90+
* ```
91+
*/
92+
predicate aferoSanitizer(DataFlow::Node n) {
93+
exists(Function f |
94+
f.hasQualifiedName(aferoPackage(), ["NewBasePathFs", "NewIOFS"]) and
95+
TaintTracking::localTaint(f.getACall(), n)
96+
)
97+
}
98+
99+
/**
100+
* Holds if there is a dataflow node from n1 to n2 when initializing the Afero instance
101+
*
102+
* A helper for `aferoSanitizer` for when the Afero instance is initialized with one of the safe FS types like IOFS
103+
*
104+
* e.g.`n2 := &afero.Afero{Fs: afero.NewBasePathFs(osFS, "./")}` n1 is `afero.NewBasePathFs(osFS, "./")`
105+
*/
106+
class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
107+
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
108+
exists(StructLit st | st.getType().hasQualifiedName(aferoPackage(), "Afero") |
109+
n1.asExpr() = st.getAnElement().(KeyValueExpr).getAChildExpr() and
110+
n2.asExpr() = st
111+
)
112+
}
113+
}
114+
}

go/ql/lib/semmle/go/frameworks/Beego.qll

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -278,21 +278,31 @@ module Beego {
278278
}
279279
}
280280

281+
/**
282+
* The File system access sinks
283+
*/
281284
private class FsOperations extends FileSystemAccess::Range, DataFlow::CallNode {
285+
int pathArg;
286+
282287
FsOperations() {
283-
this.getTarget().hasQualifiedName(packagePath(), "Walk")
288+
this.getTarget().hasQualifiedName(packagePath(), "Walk") and pathArg = 1
284289
or
285290
exists(Method m | this = m.getACall() |
286-
m.hasQualifiedName(packagePath(), "FileSystem", "Open") or
287-
m.hasQualifiedName(packagePath(), "Controller", "SaveToFile")
291+
m.hasQualifiedName(packagePath(), "FileSystem", "Open") and pathArg = 0
292+
or
293+
m.hasQualifiedName(packagePath(), "Controller", "SaveToFile") and pathArg = 1
294+
or
295+
m.hasQualifiedName(contextPackagePath(), "BeegoOutput", "Download") and
296+
pathArg = 0
297+
or
298+
// SaveToFileWithBuffer only available in v2
299+
m.hasQualifiedName("github.com/beego/beego/v2/server/web", "Controller",
300+
"SaveToFileWithBuffer") and
301+
pathArg = 1
288302
)
289303
}
290304

291-
override DataFlow::Node getAPathArgument() {
292-
this.getTarget().getName() = ["Walk", "SaveToFile"] and result = this.getArgument(1)
293-
or
294-
this.getTarget().getName() = "Open" and result = this.getArgument(0)
295-
}
305+
override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
296306
}
297307

298308
private class RedirectMethods extends Http::Redirect::Range, DataFlow::CallNode {

go/ql/lib/semmle/go/frameworks/Echo.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,18 @@ private module Echo {
9898

9999
override Http::ResponseWriter getResponseWriter() { none() }
100100
}
101+
102+
/**
103+
* The File system access sinks
104+
*/
105+
class FsOperations extends FileSystemAccess::Range, DataFlow::CallNode {
106+
FsOperations() {
107+
exists(Method m |
108+
m.hasQualifiedName(packagePath(), "Context", ["Attachment", "File"]) and
109+
this = m.getACall()
110+
)
111+
}
112+
113+
override DataFlow::Node getAPathArgument() { result = this.getArgument(0) }
114+
}
101115
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Provides classes for working the `github.com/gofiber/fiber` package.
3+
*/
4+
5+
import go
6+
7+
private module Gin {
8+
/** Gets the package name `github.com/gofiber/fiber`. */
9+
string packagePath() { result = package("github.com/gofiber/fiber", "") }
10+
11+
/** Gets the v2 module path `github.com/gofiber/fiber/v2` */
12+
string v2modulePath() { result = "github.com/gofiber/fiber/v2" }
13+
14+
/**
15+
* The File system access sinks
16+
*/
17+
class FsOperations extends FileSystemAccess::Range, DataFlow::CallNode {
18+
int pathArg;
19+
20+
FsOperations() {
21+
exists(Method m |
22+
(
23+
m.hasQualifiedName(packagePath(), "Ctx", ["SendFile", "Download"]) and
24+
pathArg = 0
25+
or
26+
m.hasQualifiedName(packagePath(), "Ctx", "SaveFile") and
27+
pathArg = 1
28+
or
29+
m.hasQualifiedName(v2modulePath(), "Ctx", "SaveFileToStorage") and
30+
pathArg = 1
31+
) and
32+
this = m.getACall()
33+
)
34+
}
35+
36+
override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
37+
}
38+
}

go/ql/lib/semmle/go/frameworks/Gin.qll

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,26 @@ private module Gin {
5353
)
5454
}
5555
}
56+
57+
/**
58+
* The File system access sinks
59+
*/
60+
class FsOperations extends FileSystemAccess::Range, DataFlow::CallNode {
61+
int pathArg;
62+
63+
FsOperations() {
64+
exists(Method m |
65+
(
66+
m.hasQualifiedName(packagePath(), "Context", ["File", "FileAttachment"]) and
67+
pathArg = 0
68+
or
69+
m.hasQualifiedName(packagePath(), "Context", "SaveUploadedFile") and
70+
pathArg = 1
71+
) and
72+
this = m.getACall()
73+
)
74+
}
75+
76+
override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
77+
}
5678
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Provides classes for working the `github.com/kataras/iris` package.
3+
*/
4+
5+
import go
6+
7+
private module Iris {
8+
/** Gets the v1 module path `github.com/kataras/iris`. */
9+
string v1modulePath() { result = "github.com/kataras/iris" }
10+
11+
/** Gets the v12 module path `github.com/kataras/iris/v12` */
12+
string v12modulePath() { result = "github.com/kataras/iris/v12" }
13+
14+
/** Gets the path for the context package of all versions of beego. */
15+
string contextPackagePath() {
16+
result = v12contextPackagePath()
17+
or
18+
result = v1contextPackagePath()
19+
}
20+
21+
/** Gets the path for the context package of beego v12. */
22+
string v12contextPackagePath() { result = v12modulePath() + "/context" }
23+
24+
/** Gets the path for the context package of beego v1. */
25+
string v1contextPackagePath() { result = v1modulePath() + "/server/web/context" }
26+
27+
/**
28+
* The File system access sinks
29+
*/
30+
class FsOperations extends FileSystemAccess::Range, DataFlow::CallNode {
31+
int pathArg;
32+
33+
FsOperations() {
34+
exists(Method m |
35+
(
36+
m.hasQualifiedName(contextPackagePath(), "Context",
37+
["SendFile", "ServeFile", "SendFileWithRate", "ServeFileWithRate", "UploadFormFiles"]) and
38+
pathArg = 0
39+
or
40+
m.hasQualifiedName(v12contextPackagePath(), "Context", "SaveFormFile") and
41+
pathArg = 1
42+
) and
43+
this = m.getACall()
44+
)
45+
}
46+
47+
override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
48+
}
49+
}

go/ql/lib/semmle/go/frameworks/stdlib/NetHttp.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,18 @@ module NetHttp {
288288

289289
override predicate guardedBy(DataFlow::Node check) { check = handlerReg.getArgument(0) }
290290
}
291+
292+
/**
293+
* The File system access sinks
294+
*/
295+
class HttpServeFile extends FileSystemAccess::Range, DataFlow::CallNode {
296+
HttpServeFile() {
297+
exists(Function f |
298+
f.hasQualifiedName("net/http", "ServeFile") and
299+
this = f.getACall()
300+
)
301+
}
302+
303+
override DataFlow::Node getAPathArgument() { result = this.getArgument(2) }
304+
}
291305
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
testFailures
2+
failures

0 commit comments

Comments
 (0)