Confine subprocess diagnostics and retain redacted tails
This commit is contained in:
@@ -184,15 +184,21 @@ func syncOpenedDirectory(parent *os.Root) error {
|
||||
}
|
||||
|
||||
// OpenFileConfined opens a file after verifying its parent hierarchy without
|
||||
// following symbolic links. Existing symbolic-link leaves are rejected.
|
||||
// following symbolic links. Existing symbolic-link and non-regular leaves are
|
||||
// rejected.
|
||||
func OpenFileConfined(path string, flags int, mode os.FileMode) (*os.File, error) {
|
||||
parent, name, err := openConfinedParent(path, false, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = parent.Close() }()
|
||||
if info, err := parent.Lstat(name); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, fmt.Errorf("destination file %q is a symbolic link", name)
|
||||
if info, err := parent.Lstat(name); err == nil {
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
return nil, fmt.Errorf("destination file %q is a symbolic link", name)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil, fmt.Errorf("destination file %q is not a regular file", name)
|
||||
}
|
||||
} else if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("inspect destination file %q: %w", name, err)
|
||||
}
|
||||
@@ -206,7 +212,7 @@ func OpenFileConfined(path string, flags int, mode os.FileMode) (*os.File, error
|
||||
return nil, fmt.Errorf("inspect opened destination file %q: %w", name, err)
|
||||
}
|
||||
current, err := parent.Lstat(name)
|
||||
if err != nil || current.Mode()&os.ModeSymlink != 0 || !os.SameFile(opened, current) {
|
||||
if err != nil || !opened.Mode().IsRegular() || current.Mode()&os.ModeSymlink != 0 || !current.Mode().IsRegular() || !os.SameFile(opened, current) {
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reinspect destination file %q: %w", name, err)
|
||||
|
||||
56
internal/fileops/confined_open_test.go
Normal file
56
internal/fileops/confined_open_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package fileops
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpenFileConfinedRejectsNonRegularLeaf(t *testing.T) {
|
||||
leaf := filepath.Join(t.TempDir(), "diagnostic.log")
|
||||
if err := os.Mkdir(leaf, 0o700); err != nil {
|
||||
t.Fatalf("Mkdir() error = %v", err)
|
||||
}
|
||||
file, err := OpenFileConfined(leaf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, WorkspaceFileMode)
|
||||
if file != nil {
|
||||
_ = file.Close()
|
||||
t.Fatal("OpenFileConfined() returned a file for a directory")
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), "not a regular file") {
|
||||
t.Fatalf("OpenFileConfined() error = %v, want regular-file rejection", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenFileConfinedRejectsSymlinkWithoutTruncatingTarget(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("creating symlinks requires privileges that are not available on every Windows runner")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
target := filepath.Join(dir, "target.log")
|
||||
const original = "outside content"
|
||||
if err := os.WriteFile(target, []byte(original), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(target) error = %v", err)
|
||||
}
|
||||
leaf := filepath.Join(dir, "diagnostic.log")
|
||||
if err := os.Symlink(target, leaf); err != nil {
|
||||
t.Fatalf("Symlink() error = %v", err)
|
||||
}
|
||||
|
||||
file, err := OpenFileConfined(leaf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, WorkspaceFileMode)
|
||||
if file != nil {
|
||||
_ = file.Close()
|
||||
t.Fatal("OpenFileConfined() returned a file for a symbolic link")
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), "symbolic link") {
|
||||
t.Fatalf("OpenFileConfined() error = %v, want symbolic-link rejection", err)
|
||||
}
|
||||
data, readErr := os.ReadFile(target)
|
||||
if readErr != nil {
|
||||
t.Fatalf("ReadFile(target) error = %v", readErr)
|
||||
}
|
||||
if string(data) != original {
|
||||
t.Fatalf("target content = %q, want %q", data, original)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user