From b36b622cc1229e6c9ee84f6559daba20e7a06988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vesa=20Poikaj=C3=A4rvi?= Date: Thu, 6 Jun 2024 13:13:54 +0300 Subject: [PATCH] feat(saver): test sauce file is not escaping destination --- pkg/recipe/saver.go | 26 +++++++++++++++++++++++++- pkg/recipe/saver_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/pkg/recipe/saver.go b/pkg/recipe/saver.go index d45dfe96..419ac555 100644 --- a/pkg/recipe/saver.go +++ b/pkg/recipe/saver.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "gopkg.in/yaml.v3" ) @@ -13,6 +14,24 @@ const ( yamlIndent int = 2 ) +func IsSafePath(basePath, filePath string) error { + absBasePath, err := filepath.Abs(basePath) + if err != nil { + return fmt.Errorf("invalid base path %q: %v", basePath, err) + } + + absFilePath, err := filepath.Abs(filePath) + if err != nil { + return fmt.Errorf("invalid file path %q: %v", basePath, err) + } + + if !strings.HasPrefix(absFilePath, absBasePath) { + return fmt.Errorf("file path escapes destination: %q outside %q", absFilePath, absBasePath) + } + + return nil +} + // Save saves recipe to given destination func (re *Recipe) Save(dest string) error { err := os.MkdirAll(dest, defaultFileMode) @@ -197,8 +216,13 @@ func saveFileMap(files map[string]File, dest string) error { for path, file := range files { destPath := filepath.Join(dest, path) + err := IsSafePath(dest, destPath) + if err != nil { + return err + } + // Create file's parent directories (if not already exist) - err := os.MkdirAll(filepath.Dir(destPath), 0700) + err = os.MkdirAll(filepath.Dir(destPath), 0700) if err != nil { return err } diff --git a/pkg/recipe/saver_test.go b/pkg/recipe/saver_test.go index 4a17fd61..7a2f653c 100644 --- a/pkg/recipe/saver_test.go +++ b/pkg/recipe/saver_test.go @@ -3,6 +3,7 @@ package recipe import ( "os" "path/filepath" + "strings" "testing" "github.com/futurice/jalapeno/pkg/engine" @@ -117,3 +118,37 @@ func TestSaveSauce(t *testing.T) { } } } + +func TestSaveSauceDoesNotWriteOutsideDest(t *testing.T) { + dir, err := os.MkdirTemp("", "jalapeno-test-saver") + if err != nil { + t.Fatalf("cannot create temp dir: %s", err) + } + defer os.RemoveAll(dir) + + re := NewRecipe() + re.Name = "Test" + re.Version = "v0.0.1" + re.Templates = map[string]File{ + "../foo.md": NewFile([]byte("foo")), + } + + err = re.Validate() + if err != nil { + t.Fatalf("test recipe was not valid: %s", err) + } + + sauce, err := re.Execute(engine.New(), nil, uuid.Must(uuid.NewV4())) + if err != nil { + t.Fatalf("recipe execution failed: %s", err) + } + + err = sauce.Save(dir) + if err == nil { + t.Fatalf("should not have saved sauce") + } + + if !strings.Contains(err.Error(), "file path escapes destination") { + t.Fatalf("error received was not expected: %s", err) + } +}