mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
When using the Client FS APIs, we check to ensure that reads don't traverse into the allocation's secret dir and private dir. But this check can be bypassed on case-insensitive file systems (ex. Windows, macOS, and Linux with obscure ext4 options enabled). This allows a user with `read-fs` permissions but not `alloc-exec` permissions to read from the secrets dir. This changeset updates the check so that it's case-insensitive. This risks false positives for escape (see linked Go issue), but only if a task without filesystem isolation deliberately writes into the task working directory to do so, which is a fail-safe failure mode. Ref: https://github.com/golang/go/issues/18358 Co-authored-by: dduzgun-security <deniz.duzgun@hashicorp.com>
148 lines
3.6 KiB
Go
148 lines
3.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
//go:build !windows
|
|
// +build !windows
|
|
|
|
package allocdir
|
|
|
|
import (
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/testlog"
|
|
"github.com/hashicorp/nomad/plugins/drivers/fsisolation"
|
|
"github.com/shoenig/test/must"
|
|
)
|
|
|
|
// Test that building a chroot will skip nonexistent directories.
|
|
func TestTaskDir_EmbedNonexistent(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tmp := t.TempDir()
|
|
|
|
d := NewAllocDir(testlog.HCLogger(t), tmp, tmp, "test")
|
|
defer d.Destroy()
|
|
td := d.NewTaskDir(t1)
|
|
must.NoError(t, d.Build())
|
|
|
|
fakeDir := "/foobarbaz"
|
|
mapping := map[string]string{fakeDir: fakeDir}
|
|
must.NoError(t, td.embedDirs(mapping))
|
|
}
|
|
|
|
// Test that building a chroot copies files from the host into the task dir.
|
|
func TestTaskDir_EmbedDirs(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
tmp := t.TempDir()
|
|
|
|
d := NewAllocDir(testlog.HCLogger(t), tmp, tmp, "test")
|
|
defer d.Destroy()
|
|
td := d.NewTaskDir(t1)
|
|
must.NoError(t, d.Build())
|
|
|
|
// Create a fake host directory, with a file, and a subfolder that contains
|
|
// a file.
|
|
host := t.TempDir()
|
|
|
|
subDirName := "subdir"
|
|
subDir := filepath.Join(host, subDirName)
|
|
must.NoError(t, os.MkdirAll(subDir, 0o777))
|
|
|
|
file := "foo"
|
|
subFile := "bar"
|
|
must.NoError(t, os.WriteFile(filepath.Join(host, file), []byte{'a'}, 0o777))
|
|
must.NoError(t, os.WriteFile(filepath.Join(subDir, subFile), []byte{'a'}, 0o777))
|
|
|
|
// Create mapping from host dir to task dir.
|
|
taskDest := "bin/test/"
|
|
mapping := map[string]string{host: taskDest}
|
|
must.NoError(t, td.embedDirs(mapping))
|
|
|
|
exp := []string{filepath.Join(td.Dir, taskDest, file), filepath.Join(td.Dir, taskDest, subDirName, subFile)}
|
|
for _, f := range exp {
|
|
if _, err := os.Stat(f); os.IsNotExist(err) {
|
|
t.Fatalf("File %v not embedded: %v", f, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that task dirs for image based isolation don't require root.
|
|
func TestTaskDir_NonRoot_Image(t *testing.T) {
|
|
requireNonRoot(t)
|
|
|
|
ci.Parallel(t)
|
|
|
|
tmp := t.TempDir()
|
|
|
|
d := NewAllocDir(testlog.HCLogger(t), tmp, tmp, "test")
|
|
defer d.Destroy()
|
|
td := d.NewTaskDir(t1)
|
|
must.NoError(t, d.Build())
|
|
must.NoError(t, td.Build(fsisolation.Image, nil, "nobody"))
|
|
}
|
|
|
|
// Test that task dirs with no isolation don't require root.
|
|
func TestTaskDir_NonRoot(t *testing.T) {
|
|
requireNonRoot(t)
|
|
|
|
ci.Parallel(t)
|
|
|
|
tmp := t.TempDir()
|
|
|
|
d := NewAllocDir(testlog.HCLogger(t), tmp, tmp, "test")
|
|
defer d.Destroy()
|
|
td := d.NewTaskDir(t1)
|
|
must.NoError(t, d.Build())
|
|
must.NoError(t, td.Build(fsisolation.None, nil, "nobody"))
|
|
|
|
// ${TASK_DIR}/alloc should not exist!
|
|
if _, err := os.Stat(td.SharedTaskDir); !os.IsNotExist(err) {
|
|
t.Fatalf("Expected a NotExist error for shared alloc dir in task dir: %q", td.SharedTaskDir)
|
|
}
|
|
}
|
|
|
|
func TestTaskDir_NonRoot_Unveil(t *testing.T) {
|
|
requireNonRoot(t)
|
|
|
|
ci.Parallel(t)
|
|
|
|
tmp := t.TempDir()
|
|
|
|
// non-root, should still work for tasks running as the same user as the
|
|
// nomad client agent
|
|
u, err := user.Current()
|
|
must.NoError(t, err)
|
|
|
|
d := NewAllocDir(testlog.HCLogger(t), tmp, tmp, "test")
|
|
defer d.Destroy()
|
|
td := d.NewTaskDir(t1)
|
|
must.NoError(t, d.Build())
|
|
must.NoError(t, td.Build(fsisolation.Unveil, nil, u.Username))
|
|
fi, err := os.Stat(td.MountsTaskDir)
|
|
must.NoError(t, err)
|
|
must.NotNil(t, fi)
|
|
}
|
|
|
|
func TestTaskDir_Root_Unveil(t *testing.T) {
|
|
requireRoot(t)
|
|
|
|
ci.Parallel(t)
|
|
|
|
tmp := t.TempDir()
|
|
|
|
// root, can build task dirs for another user
|
|
d := NewAllocDir(testlog.HCLogger(t), tmp, tmp, "test")
|
|
defer d.Destroy()
|
|
td := d.NewTaskDir(t1)
|
|
must.NoError(t, d.Build())
|
|
must.NoError(t, td.Build(fsisolation.Unveil, nil, "nobody"))
|
|
fi, err := os.Stat(td.MountsTaskDir)
|
|
must.NoError(t, err)
|
|
must.NotNil(t, fi)
|
|
}
|