Files
nomad/client/allocdir/alloc_dir_nonlinux_test.go
Tim Gross b7595c646d alloc fs: use case-insensitive check for reads of secret/private dir (#24125)
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>
2024-10-03 14:20:24 -04:00

61 lines
1.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
//go:build !linux
// +build !linux
package allocdir
import (
"os"
"path/filepath"
"testing"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/plugins/drivers/fsisolation"
"github.com/shoenig/test/must"
)
func TestAllocDir_ReadAt_CaseInsensitiveSecretDir(t *testing.T) {
ci.Parallel(t)
// On macOS, os.TempDir returns a symlinked path under /var which
// is outside of the directories shared into the VM used for Docker.
// Expand the symlink to get the real path in /private, which is ok.
tmp, err := filepath.EvalSymlinks(t.TempDir())
must.NoError(t, err)
d := NewAllocDir(testlog.HCLogger(t), tmp, tmp, "test")
must.NoError(t, d.Build())
defer func() { _ = d.Destroy() }()
td := d.NewTaskDir(t1Windows)
must.NoError(t, td.Build(fsisolation.None, nil, "nobody"))
target := filepath.Join(t1Windows.Name, TaskSecrets, "test_file")
full := filepath.Join(d.AllocDir, target)
must.NoError(t, os.WriteFile(full, []byte("hi"), 0o600))
targetCaseInsensitive := filepath.Join(t1Windows.Name, "sEcReTs", "test_file")
_, err = d.ReadAt(targetCaseInsensitive, 0)
must.EqError(t, err, "Reading secret file prohibited: "+targetCaseInsensitive)
}
var (
t1Windows = &structs.Task{
Name: "web",
Driver: "exec",
Config: map[string]interface{}{
"command": "/bin/date",
"args": "+%s",
},
Resources: &structs.Resources{
DiskMB: 1,
},
}
)