diff --git a/go/ql/lib/change-notes/2024-08-12-add-environment-models.md b/go/ql/lib/change-notes/2024-08-12-add-environment-models.md new file mode 100644 index 000000000000..c511718475d5 --- /dev/null +++ b/go/ql/lib/change-notes/2024-08-12-add-environment-models.md @@ -0,0 +1,11 @@ +--- +category: minorAnalysis +--- +* Local source models for reading and parsing environment variables have been added for the following libraries: + - os + - syscall + - github.com/caarlos0/env + - github.com/gobuffalo/envy + - github.com/hashicorp/go-envparse + - github.com/joho/godotenv + - github.com/kelseyhightower/envconfig diff --git a/go/ql/lib/ext/github.com.caarlos0.env.model.yml b/go/ql/lib/ext/github.com.caarlos0.env.model.yml new file mode 100644 index 000000000000..42f6380c3fa9 --- /dev/null +++ b/go/ql/lib/ext/github.com.caarlos0.env.model.yml @@ -0,0 +1,16 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["github.com/caarlos0/env", "", False, "Parse", "", "", "Argument[0]", "environment", "manual"] + - ["github.com/caarlos0/env", "", False, "ParseAs", "", "", "ReturnValue[0]", "environment", "manual"] + - ["github.com/caarlos0/env", "", False, "ParseAsWithOptions", "", "", "ReturnValue[0]", "environment", "manual"] + - ["github.com/caarlos0/env", "", False, "ParseWithFuncs", "", "", "Argument[0]", "environment", "manual"] + - ["github.com/caarlos0/env", "", False, "ParseWithOptions", "", "", "Argument[0]", "environment", "manual"] + - addsTo: + pack: codeql/go-all + extensible: summaryModel + data: + - ["github.com/caarlos0/env", "", False, "Must", "", "", "Argument[0]", "ReturnValue", "value", "manual"] + - ["github.com/caarlos0/env", "", False, "ToMap", "", "", "Argument[0]", "ReturnValue", "taint", "manual"] diff --git a/go/ql/lib/ext/github.com.gobuffalo.envy.model.yml b/go/ql/lib/ext/github.com.gobuffalo.envy.model.yml new file mode 100644 index 000000000000..1d0d890560d9 --- /dev/null +++ b/go/ql/lib/ext/github.com.gobuffalo.envy.model.yml @@ -0,0 +1,12 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["github.com/gobuffalo/envy", "", False, "Environ", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/gobuffalo/envy", "", False, "Get", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/gobuffalo/envy", "", False, "GoBin", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/gobuffalo/envy", "", False, "GoPath", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/gobuffalo/envy", "", False, "GoPaths", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/gobuffalo/envy", "", False, "Map", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/gobuffalo/envy", "", False, "MustGet", "", "", "ReturnValue[0]", "environment", "manual"] diff --git a/go/ql/lib/ext/github.com.hashicorp.go-envparse.model.yml b/go/ql/lib/ext/github.com.hashicorp.go-envparse.model.yml new file mode 100644 index 000000000000..73a178fbdcc8 --- /dev/null +++ b/go/ql/lib/ext/github.com.hashicorp.go-envparse.model.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["github.com/hashicorp/go-envparse", "", False, "Parse", "", "", "ReturnValue", "environment", "manual"] diff --git a/go/ql/lib/ext/github.com.joho.godotenv.model.yml b/go/ql/lib/ext/github.com.joho.godotenv.model.yml new file mode 100644 index 000000000000..8bd62c5dd0b1 --- /dev/null +++ b/go/ql/lib/ext/github.com.joho.godotenv.model.yml @@ -0,0 +1,9 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["github.com/joho/godotenv", "", False, "Parse", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/joho/godotenv", "", False, "Read", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/joho/godotenv", "", False, "Unmarshal", "", "", "ReturnValue", "environment", "manual"] + - ["github.com/joho/godotenv", "", False, "UnmarshalBytes", "", "", "ReturnValue", "environment", "manual"] diff --git a/go/ql/lib/ext/github.com.kelseyhightower.envconfig.model.yml b/go/ql/lib/ext/github.com.kelseyhightower.envconfig.model.yml new file mode 100644 index 000000000000..71d032a18e1b --- /dev/null +++ b/go/ql/lib/ext/github.com.kelseyhightower.envconfig.model.yml @@ -0,0 +1,11 @@ +extensions: + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["github.com/kelseyhightower/envconfig", "", False, "CheckDisallowed", "", "", "Argument[1]", "environment", "manual"] + - ["github.com/kelseyhightower/envconfig", "", False, "MustProcess", "", "", "Argument[1]", "environment", "manual"] + - ["github.com/kelseyhightower/envconfig", "", False, "Process", "", "", "Argument[1]", "environment", "manual"] + - ["github.com/kelseyhightower/envconfig", "", False, "Usage", "", "", "Argument[1]", "environment", "manual"] + - ["github.com/kelseyhightower/envconfig", "", False, "Usagef", "", "", "Argument[1]", "environment", "manual"] + - ["github.com/kelseyhightower/envconfig", "", False, "Usaget", "", "", "Argument[1]", "environment", "manual"] \ No newline at end of file diff --git a/go/ql/lib/ext/os.model.yml b/go/ql/lib/ext/os.model.yml index 31034541b6df..3d87eefe43f7 100644 --- a/go/ql/lib/ext/os.model.yml +++ b/go/ql/lib/ext/os.model.yml @@ -46,6 +46,13 @@ extensions: pack: codeql/go-all extensible: sourceModel data: + - ["os", "", False, "Environ", "", "", "ReturnValue", "environment", "manual"] # TODO: when sources can have access paths, use .ArrayElement + - ["os", "", False, "ExpandEnv", "", "", "ReturnValue", "environment", "manual"] + - ["os", "", False, "Getenv", "", "", "ReturnValue", "environment", "manual"] + - ["os", "", False, "LookupEnv", "", "", "ReturnValue[0]", "environment", "manual"] - ["os", "", False, "Open", "", "", "ReturnValue[0]", "file", "manual"] - ["os", "", False, "OpenFile", "", "", "ReturnValue[0]", "file", "manual"] - - ["os", "", False, "ReadFile", "", "", "ReturnValue[0]", "file", "manual"] \ No newline at end of file + - ["os", "", False, "ReadFile", "", "", "ReturnValue[0]", "file", "manual"] + - ["os", "", False, "UserCacheDir", "", "", "ReturnValue[0]", "environment", "manual"] + - ["os", "", False, "UserConfigDir", "", "", "ReturnValue[0]", "environment", "manual"] + - ["os", "", False, "UserHomeDir", "", "", "ReturnValue[0]", "environment", "manual"] diff --git a/go/ql/lib/ext/syscall.model.yml b/go/ql/lib/ext/syscall.model.yml index 14ddd68b4021..9d65f2bedbd3 100644 --- a/go/ql/lib/ext/syscall.model.yml +++ b/go/ql/lib/ext/syscall.model.yml @@ -20,3 +20,9 @@ extensions: - ["syscall", "Conn", True, "SyscallConn", "", "", "Argument[receiver]", "ReturnValue[0]", "taint", "manual"] - ["syscall", "RawConn", True, "Read", "", "", "Argument[receiver]", "Argument[0]", "taint", "manual"] - ["syscall", "RawConn", True, "Write", "", "", "Argument[0]", "Argument[receiver]", "taint", "manual"] + - addsTo: + pack: codeql/go-all + extensible: sourceModel + data: + - ["syscall", "", False, "Environ", "", "", "ReturnValue", "environment", "manual"] + - ["syscall", "", False, "Getenv", "", "", "ReturnValue[0]", "environment", "manual"] \ No newline at end of file diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/go.mod b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/go.mod new file mode 100644 index 000000000000..90c1c9f56600 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/go.mod @@ -0,0 +1,9 @@ +module test + +go 1.22.5 + +require ( + github.com/hashicorp/go-envparse v0.1.0 + github.com/joho/godotenv v1.5.1 + github.com/kelseyhightower/envconfig v1.4.0 +) diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.expected b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.expected new file mode 100644 index 000000000000..db33d6d2504a --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.expected @@ -0,0 +1,3 @@ +testFailures +invalidModelRow +failures diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.ext.yml b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.ext.yml new file mode 100644 index 000000000000..b7075d8e7ae9 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.ext.yml @@ -0,0 +1,6 @@ +extensions: + - addsTo: + pack: codeql/threat-models + extensible: threatModelConfiguration + data: + - ["environment", true, 0] \ No newline at end of file diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.go new file mode 100644 index 000000000000..515888f570ed --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.go @@ -0,0 +1,116 @@ +package test + +import ( + "fmt" + "github.com/caarlos0/env" + "github.com/gobuffalo/envy" + "github.com/hashicorp/go-envparse" + "github.com/joho/godotenv" + "github.com/kelseyhightower/envconfig" + "os" + "syscall" +) + +func osEnvironmentVariables() { + home := os.Getenv("HOME") // $ source + + port, ok := os.LookupEnv("PORT") // $ source + if !ok { + port = "3000" + } + + for _, e := range os.Environ() { // $ source + _ = e + } + + fmt.Printf("HOME: %s\n", home) + fmt.Printf("PORT: %s\n", port) +} + +type ServerConfig struct { + Port int `envconfig:"PORT"` + Host string `envconfig:"HOST"` +} + +func envconfigEnvironmentVariables() { + var cfg ServerConfig + envconfig.Process("myapp", &cfg) // $ source +} + +func godotenvEnvironmentVariables() { + var err error + var username, greeting string + + users, err := godotenv.Read("user.env") // $ source + if err != nil { + return + } + + username = users["USERNAME"] + + greetings, err := godotenv.Unmarshal("HELLO=hello") // $ source + if err != nil { + return + } + + greeting = greetings["HELLO"] + + fmt.Printf("%s, %s!\n", greeting, username) +} + +func envparseEnvironmentVariables() { + f, err := os.Open("file.txt") + if err != nil { + return + } + defer f.Close() + envVars, err := envparse.Parse(f) // $ source + + if err != nil { + return + } + + fmt.Printf("HOME: %s\n", envVars["HOME"]) +} + +func caarlos0EnvironmentVariables() { + type config struct { + Home string `env:"HOME"` + Port int `env:"PORT"` + } + + cfg := config{} + err := env.Parse(&cfg) // $ source + + fmt.Printf("HOME: %s\n", cfg.Home) + + cfg, err = env.ParseAs[config]() // $ source + + if err != nil { + return + } + + fmt.Printf("HOME: %s\n", cfg.Home) +} + +func envyEnvironmentVariables() { + goPath := envy.GoPath() // $ source + + fmt.Printf("GOPATH: %s\n", goPath) + + homeDir := envy.MustGet("HOME") // $ source + + fmt.Printf("HOME: %s\n", homeDir) +} + +func syscallEnvironmentVariables() { + for _, envVar := range syscall.Environ() { // $ source + fmt.Println("%s", envVar) + } + + home, found := syscall.Getenv("HOME") // $ source + if !found { + return + } + fmt.Println("HOME: %s", home) +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.ql b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.ql new file mode 100644 index 000000000000..db6bbb1a2d16 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/test.ql @@ -0,0 +1,19 @@ +import go +import ModelValidation +import TestUtilities.InlineExpectationsTest + +module SourceTest implements TestSig { + string getARelevantTag() { result = "source" } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(ThreatModelFlowSource s | + s.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(), + location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and + element = s.toString() and + value = "" and + tag = "source" + ) + } +} + +import MakeTest diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/caarlos0/env/stub.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/caarlos0/env/stub.go new file mode 100644 index 000000000000..9223a3f4677f --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/caarlos0/env/stub.go @@ -0,0 +1,32 @@ +package env + +type Options struct{} + +func Must[T any](t T, err error) T { + if err != nil { + panic(err) + } + return t +} + +func Parse(v interface{}) error { + return nil +} + +func ParseAs[T any]() (T, error) { + var t T + return t, nil +} + +func ParseAsWithOptions[T any](opts Options) (T, error) { + var t T + return t, nil +} + +func ParseWithOptions(v interface{}, opts Options) error { + return nil +} + +func ToMap(env []string) map[string]string { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/gobuffalo/envy/stub.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/gobuffalo/envy/stub.go new file mode 100644 index 000000000000..895b82e9a10c --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/gobuffalo/envy/stub.go @@ -0,0 +1,29 @@ +package envy + +func Environ() []string { + return nil +} + +func Get(key string) string { + return "" +} + +func GoPath() string { + return "" +} + +func GoPaths() []string { + return nil +} + +func GoBin() string { + return "" +} + +func Map() map[string]string { + return nil +} + +func MustGet(key string) string { + return "" +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/hashicorp/go-envparse/stub.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/hashicorp/go-envparse/stub.go new file mode 100644 index 000000000000..0ce38bd01735 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/hashicorp/go-envparse/stub.go @@ -0,0 +1,7 @@ +package envparse + +import "io" + +func Parse(r io.Reader) (map[string]string, error) { + return nil, nil +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/joho/godotenv/stub.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/joho/godotenv/stub.go new file mode 100644 index 000000000000..eba88b5050c7 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/joho/godotenv/stub.go @@ -0,0 +1,39 @@ +package godotenv + +import "io" + +func Exec(filenames []string, cmd string, cmdArgs []string, overload bool) error { + return nil +} + +func Load(filenames ...string) (err error) { + return nil +} + +func Marshal(envMap map[string]string) (string, error) { + return "", nil +} + +func Overload(filenames ...string) (err error) { + return nil +} + +func Parse(r io.Reader) (map[string]string, error) { + return nil, nil +} + +func Read(filenames ...string) (envMap map[string]string, err error) { + return nil, nil +} + +func Unmarshal(str string) (envMap map[string]string, err error) { + return nil, nil +} + +func UnmarshalBytes(src []byte) (map[string]string, error) { + return nil, nil +} + +func Write(envMap map[string]string, filename string) error { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/kelseyhightower/envconfig/stub.go b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/kelseyhightower/envconfig/stub.go new file mode 100644 index 000000000000..856b8ebb6ef0 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/github.com/kelseyhightower/envconfig/stub.go @@ -0,0 +1,30 @@ +package envconfig + +import ( + "io" + "text/template" +) + +func CheckDisallowed(prefix string, cfg interface{}) error { + return nil +} + +func MustProcess(prefix string, cfg interface{}) { + +} + +func Process(prefix string, cfg interface{}) error { + return nil +} + +func Usage(prefix string, spec interface{}) error { + return nil +} + +func Usagef(prefix string, spec interface{}, out io.Writer, format string) error { + return nil +} + +func Usaget(prefix string, spec interface{}, out io.Writer, tmpl *template.Template) error { + return nil +} diff --git a/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/modules.txt b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/modules.txt new file mode 100644 index 000000000000..0b7968eaec90 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/dataflow/flowsources/local/environment/vendor/modules.txt @@ -0,0 +1,9 @@ +# github.com/hashicorp/go-envparse v0.1.0 +## explicit +github.com/hashicorp/go-envparse +# github.com/joho/godotenv v1.5.1 +## explicit +github.com/joho/godotenv +# github.com/kelseyhightower/envconfig v1.4.0 +## explicit +github.com/kelseyhightower/envconfig