Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add taint command #12

Merged
merged 12 commits into from
Dec 2, 2023
Merged

Add taint command #12

merged 12 commits into from
Dec 2, 2023

Conversation

picatz
Copy link
Owner

@picatz picatz commented Nov 22, 2023

This PR aims to introduce the taint command to provide an interactive shell (REPL) to explore a program with, like its call graph or dependencies, and identify potential taint analysis issues.

$ go run ./cmd/taint/main.go
... clears screen ...
Commands (tab complete)

- clear to clear screen.
- exit to quit.

> ...

Note
There are commands like callpath, check, pkgs, amongst a few others that are currently not shown in that top level help. That can be figured out later, maybe using cobra.

// ./xss/testdata/src/b
package main

import (
	"net/http"
)

func mirror(w http.ResponseWriter, r *http.Request) {
	input := r.URL.Query().Get("input")

	b := []byte(input)

	w.Write(b) // want "potential XSS"
}

func main() {
	http.HandleFunc("/", mirror)

	http.ListenAndServe(":8080", nil)
}
> load ./xss/testdata/src/b
loaded 1 packages
> pkgs
github.com/picatz/taint/xss/testdata/src/b 1 imports
> nodes
n0:github.com/picatz/taint/xss/testdata/src/b.main
n1:github.com/picatz/taint/xss/testdata/src/b.mirror
n2:(*net/url.URL).Query
n3:(net/url.Values).Get
n4:(net/http.ResponseWriter).Write
n5:net/http.HandleFunc
n6:net/http.ListenAndServe
> callpath (net/http.ResponseWriter).Write
n0:github.com/picatz/taint/xss/testdata/src/b.main → n5:net/http.HandleFunc → n1:github.com/picatz/taint/xss/testdata/src/b.mirror → n4:(net/http.ResponseWriter).Write
> check *net/http.Request (net/http.ResponseWriter).Write 
n5:net/http.HandleFunc → n1:github.com/picatz/taint/xss/testdata/src/b.mirror → n4:(net/http.ResponseWriter).Write
> cg
n4:(net/http.ResponseWriter).Write

n5:net/http.HandleFunc
        → n1:github.com/picatz/taint/xss/testdata/src/b.mirror

n6:net/http.ListenAndServe

n0:github.com/picatz/taint/xss/testdata/src/b.main
        → n5:net/http.HandleFunc
        → n6:net/http.ListenAndServe

n1:github.com/picatz/taint/xss/testdata/src/b.mirror
        → n2:(*net/url.URL).Query
        → n3:(net/url.Values).Get
        → n4:(net/http.ResponseWriter).Write

n2:(*net/url.URL).Query

n3:(net/url.Values).Get

demo


It also acts as a nice standalone example for how to use the package's provided in this module directly, without needing to go through go/analysis.

In hopes my future self (and perhaps others?) will benefit from this breakdown:

  1. First, we need to determine the package "patterns" we want to use:

    taint/cmd/taint/main.go

    Lines 159 to 161 in b728a26

    // patterns := []string{dir}
    patterns := []string{"./..."}
    // patterns := []string{"all"}

  2. Next, we need to load our packages, applying our load pattern with a given configuration:

    Here, we set the directory we're working with, amongst other information, like environment variables. We also provide a custom file parsing function. In the future, we can optimize our package loading using these fields. We also are excluding tests, which might need to be configurable in the future.

    taint/cmd/taint/main.go

    Lines 163 to 172 in b728a26

    pkgs, err = packages.Load(&packages.Config{
    Mode: loadMode,
    Context: ctx,
    Env: os.Environ(),
    Dir: dir,
    Tests: false,
    ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
    return parser.ParseFile(fset, filename, src, parseMode)
    },
    }, patterns...)

    Note

    We use a loadMode and parseMode with the works, but we can also make this configurable to make stuff faster in the future:

    taint/cmd/taint/main.go

    Lines 150 to 153 in b728a26

    loadMode := packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
    packages.NeedTypes | packages.NeedTypesSizes | packages.NeedImports |
    packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps |
    packages.NeedExportFile | packages.NeedDeps | packages.NeedEmbedPatterns | packages.NeedModule

    For example, by skipping comment parsing, you can save a lot of time/memory:
    parseMode := parser.ParseComments

  3. Once we have loaded packages, we can build the complete SSA program information:

    taint/cmd/taint/main.go

    Lines 180 to 186 in b728a26

    ssaProg, ssaPkgs = ssautil.Packages(pkgs, ssaMode)
    ssaProg.Build()
    for _, pkg := range ssaPkgs {
    pkg.Build()
    }

    Note

    We use ssaMode which instantiates generics and sanity checks functions, but there are other modes to consider like ssa.BareInits|ssa.GlobalDebug:

    ssaMode := ssa.InstantiateGenerics | ssa.SanityCheckFunctions

  4. Now we have the SSA value graph, we can create a call graph from that information. We're assuming there's only one logical program loaded (which isn't always true, to be clear), and create a call graph rooted in that program's main function:

    taint/cmd/taint/main.go

    Lines 188 to 211 in b728a26

    mainPkgs := ssautil.MainPackages(ssaPkgs)
    mainFn := mainPkgs[0].Members["main"].(*ssa.Function)
    var srcFns []*ssa.Function
    for _, pkg := range ssaPkgs {
    for _, fn := range pkg.Members {
    if fn.Object() == nil {
    continue
    }
    if fn.Object().Name() == "_" {
    continue
    }
    pngFn := pkg.Func(fn.Object().Name())
    if pngFn == nil {
    continue
    }
    srcFns = append(srcFns, pngFn)
    }
    }

    cg, err = callgraph.New(mainFn, srcFns...)

@picatz picatz marked this pull request as ready for review December 2, 2023 01:25
@picatz picatz merged commit 69c4202 into main Dec 2, 2023
1 check passed
@picatz picatz deleted the taint-cmd branch December 2, 2023 01:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant