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

Extend safeTemplateFilepath to cover more cases. #1833

Merged
merged 6 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
* [BUGFIX] Multikv: Fix watching for runtime config changes in `multi` KV store in ruler and querier. #1665
* [BUGFIX] Memcached: allow to use CNAME DNS records for the memcached backend addresses. #1654
* [BUGFIX] Querier: fixed temporary partial query results when shuffle sharding is enabled and hash ring backend storage is flushed / reset. #1829
* [BUGFIX] Alertmanager: prevent more file traversal cases related to template names. #1833

### Mixin

Expand Down
28 changes: 27 additions & 1 deletion pkg/alertmanager/multitenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -1262,16 +1262,42 @@ func safeTemplateFilepath(dir, templateName string) (string, error) {
return "", err
}

// If actualPath is same as containerDir, it's likely that actualPath was empty, or just ".".
if containerDir == actualPath {
return "", fmt.Errorf("invalid template name %q", templateName)
}

// Ensure the actual path of the template is within the expected directory.
// This check is a counter-measure to make sure the tenant is not trying to
// escape its own directory on disk.
if !strings.HasPrefix(actualPath, containerDir) {
pstibrany marked this conversation as resolved.
Show resolved Hide resolved
if !isFilePathInsideDirectory(containerDir, actualPath) {
return "", fmt.Errorf("invalid template name %q: the template filepath is escaping the per-tenant local directory", templateName)
}

return actualPath, nil
}

// Returns true if file described by filePath is inside given directory. (Doesn't check for symbolic links)
pstibrany marked this conversation as resolved.
Show resolved Hide resolved
// Directory argument should not end with separator, ie. it must be filepath.Clean-ed.
pstibrany marked this conversation as resolved.
Show resolved Hide resolved
func isFilePathInsideDirectory(absDir string, absFilePath string) bool {
pstibrany marked this conversation as resolved.
Show resolved Hide resolved
if absFilePath == absDir {
// If filePath refers to dir itself, it's not a "file" path.
return false
}

d := filepath.Dir(absFilePath)
if d == absDir {
return true
}

// If path was empty, its Dir is ".".
// If path was "/", its Dir is just "/".
if d == "." || d == "/" {
pstibrany marked this conversation as resolved.
Show resolved Hide resolved
return false
}
return isFilePathInsideDirectory(absDir, d)
}

// storeTemplateFile stores template file at the given templateFilepath.
// Returns true, if file content has changed (new or updated file), false if file with the same name
// and content was already stored locally.
Expand Down
42 changes: 42 additions & 0 deletions pkg/alertmanager/multitenant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,27 @@ func TestSafeTemplateFilepath(t *testing.T) {
template: "../test.tmpl",
expectedErr: errors.New(`invalid template name "../test.tmpl": the template filepath is escaping the per-tenant local directory`),
},
"template name starting with /": {
dir: "/tmp",
template: "/file",
expectedErr: nil,
expectedPath: "/tmp/file",
},
"escaping template name that has prefix of dir (tmp is prefix of tmpfile)": {
dir: "/sub/tmp",
template: "../tmpfile",
expectedErr: errors.New(`invalid template name "../tmpfile": the template filepath is escaping the per-tenant local directory`),
},
"empty template name": {
dir: "/tmp",
template: "",
expectedErr: errors.New(`invalid template name ""`),
},
"dot template name": {
dir: "/tmp",
template: ".",
expectedErr: errors.New(`invalid template name "."`),
},
}

for testName, testData := range tests {
Expand All @@ -1980,6 +2001,27 @@ func TestSafeTemplateFilepath(t *testing.T) {
}
}

func TestIsFilePathInsideDirectory(t *testing.T) {
assert.False(t, isFilePathInsideDirectory("/", "/"))
assert.False(t, isFilePathInsideDirectory("/", ""))
assert.True(t, isFilePathInsideDirectory("/", "/test"))
pstibrany marked this conversation as resolved.
Show resolved Hide resolved
assert.False(t, isFilePathInsideDirectory("/", "random"))

assert.False(t, isFilePathInsideDirectory("/tmp", "/"))
assert.False(t, isFilePathInsideDirectory("/tmp", "//"))
assert.False(t, isFilePathInsideDirectory("/tmp", ""))
assert.False(t, isFilePathInsideDirectory("/tmp", "/tmp"))
assert.False(t, isFilePathInsideDirectory("/tmp", "/tmpfile"))
assert.True(t, isFilePathInsideDirectory("/tmp", "/tmp/test"))
assert.True(t, isFilePathInsideDirectory("/tmp", "/tmp/inner/dir/file"))
// Dir ending with / doesn't work.
assert.False(t, isFilePathInsideDirectory("/tmp/", "/tmp/inner/dir/file"))

assert.True(t, isFilePathInsideDirectory(".", "./test"))
// Dir ending with / doesn't work.
assert.False(t, isFilePathInsideDirectory("./", "./test"))
}

func TestStoreTemplateFile(t *testing.T) {
tempDir := t.TempDir()
testTemplateDir := filepath.Join(tempDir, templatesDir)
Expand Down