Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'internal/git/safecmd.go')
-rw-r--r--internal/git/safecmd.go213
1 files changed, 213 insertions, 0 deletions
diff --git a/internal/git/safecmd.go b/internal/git/safecmd.go
new file mode 100644
index 000000000..352ee2ffe
--- /dev/null
+++ b/internal/git/safecmd.go
@@ -0,0 +1,213 @@
+package git
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "gitlab.com/gitlab-org/gitaly/internal/command"
+ "gitlab.com/gitlab-org/gitaly/internal/git/repository"
+)
+
+var invalidationTotal = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "gitaly_invalid_commands_total",
+ Help: "Total number of invalid arguments tried to execute",
+ },
+ []string{"command"},
+)
+
+func init() {
+ prometheus.MustRegister(invalidationTotal)
+}
+
+func incrInvalidArg(subcmdName string) {
+ invalidationTotal.WithLabelValues(subcmdName).Inc()
+}
+
+// SubCmd represents a specific git command
+type SubCmd struct {
+ Name string // e.g. "log", or "cat-file", or "worktree"
+ Flags []Option // optional flags before the positional args
+ Args []string // positional args after all flags
+ PostSepArgs []string // post separator (i.e. "--") positional args
+}
+
+var subCmdNameRegex = regexp.MustCompile(`^[[:alnum:]]+(-[[:alnum:]]+)*$`)
+
+// ValidateArgs checks all arguments in the sub command and validates them
+func (sc SubCmd) ValidateArgs() ([]string, error) {
+ var safeArgs []string
+
+ if !subCmdNameRegex.MatchString(sc.Name) {
+ return nil, &invalidArgErr{
+ msg: fmt.Sprintf("invalid sub command name %q", sc.Name),
+ }
+ }
+ safeArgs = append(safeArgs, sc.Name)
+
+ for _, o := range sc.Flags {
+ args, err := o.ValidateArgs()
+ if err != nil {
+ return nil, err
+ }
+ safeArgs = append(safeArgs, args...)
+ }
+
+ for _, a := range sc.Args {
+ if err := validatePositionalArg(a); err != nil {
+ return nil, err
+ }
+ safeArgs = append(safeArgs, a)
+ }
+
+ if len(sc.PostSepArgs) > 0 {
+ safeArgs = append(safeArgs, "--")
+ }
+
+ // post separator args do not need any validation
+ safeArgs = append(safeArgs, sc.PostSepArgs...)
+
+ return safeArgs, nil
+}
+
+// Option is a git command line flag with validation logic
+type Option interface {
+ IsOption()
+ ValidateArgs() ([]string, error)
+}
+
+// Flag is a single token optional command line argument that enables or
+// disables functionality (e.g. "-L")
+type Flag struct {
+ Name string
+}
+
+// IsOption is a method present on all Flag interface implementations
+func (Flag) IsOption() {}
+
+// ValidateArgs returns an error if the flag is not sanitary
+func (f Flag) ValidateArgs() ([]string, error) {
+ if !flagRegex.MatchString(f.Name) {
+ return nil, &invalidArgErr{
+ msg: fmt.Sprintf("flag %q failed regex validation", f.Name),
+ }
+ }
+ return []string{f.Name}, nil
+}
+
+// ValueFlag is an optional command line argument that is comprised of pair of
+// tokens (e.g. "-n 50")
+type ValueFlag struct {
+ Name string
+ Value string
+}
+
+// IsOption is a method present on all Flag interface implementations
+func (ValueFlag) IsOption() {}
+
+// ValidateArgs returns an error if the flag is not sanitary
+func (vf ValueFlag) ValidateArgs() ([]string, error) {
+ if !flagRegex.MatchString(vf.Name) {
+ return nil, &invalidArgErr{
+ msg: fmt.Sprintf("value flag %q failed regex validation", vf.Name),
+ }
+ }
+ return []string{vf.Name, vf.Value}, nil
+}
+
+var flagRegex = regexp.MustCompile(`^(-|--)[[:alnum:]]`)
+
+type invalidArgErr struct {
+ msg string
+}
+
+func (iae *invalidArgErr) Error() string { return iae.msg }
+
+// IsInvalidArgErr relays if the error is due to an argument validation failure
+func IsInvalidArgErr(err error) bool {
+ _, ok := err.(*invalidArgErr)
+ return ok
+}
+
+func validatePositionalArg(arg string) error {
+ if strings.HasPrefix(arg, "-") {
+ return &invalidArgErr{
+ msg: fmt.Sprintf("positional arg %q cannot start with dash '-'", arg),
+ }
+ }
+ return nil
+}
+
+// SafeCmd creates a git.Command with the given args and Repository. It
+// validates the arguments in the command before executing.
+func SafeCmd(ctx context.Context, repo repository.GitRepo, globals []Option, sc SubCmd) (*command.Command, error) {
+ args, err := combineArgs(globals, sc)
+ if err != nil {
+ return nil, err
+ }
+
+ return Command(ctx, repo, args...)
+}
+
+// SafeBareCmd creates a git.Command with the given args, stdin/stdout/stderr,
+// and env. It validates the arguments in the command before executing.
+func SafeBareCmd(ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, env []string, globals []Option, sc SubCmd) (*command.Command, error) {
+ args, err := combineArgs(globals, sc)
+ if err != nil {
+ return nil, err
+ }
+
+ return BareCommand(ctx, stdin, stdout, stderr, env, args...)
+}
+
+// SafeStdinCmd creates a git.Command with the given args and Repository that is
+// suitable for Write()ing to. It validates the arguments in the command before
+// executing.
+func SafeStdinCmd(ctx context.Context, repo repository.GitRepo, globals []Option, sc SubCmd) (*command.Command, error) {
+ args, err := combineArgs(globals, sc)
+ if err != nil {
+ return nil, err
+ }
+
+ return StdinCommand(ctx, repo, args...)
+}
+
+// SafeCmdWithoutRepo works like Command but without a git repository. It
+// validates the arugments in the command before executing.
+func SafeCmdWithoutRepo(ctx context.Context, globals []Option, sc SubCmd) (*command.Command, error) {
+ args, err := combineArgs(globals, sc)
+ if err != nil {
+ return nil, err
+ }
+
+ return CommandWithoutRepo(ctx, args...)
+}
+
+func combineArgs(globals []Option, sc SubCmd) (_ []string, err error) {
+ defer func() {
+ if err != nil && IsInvalidArgErr(err) {
+ incrInvalidArg(sc.Name)
+ }
+ }()
+
+ var args []string
+
+ for _, g := range globals {
+ gargs, err := g.ValidateArgs()
+ if err != nil {
+ return nil, err
+ }
+ args = append(args, gargs...)
+ }
+
+ scArgs, err := sc.ValidateArgs()
+ if err != nil {
+ return nil, err
+ }
+
+ return append(args, scArgs...), nil
+}