149 lines
4.7 KiB
Go
149 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|
|
|
|
func TestOpenFileConfinedDoesNotFollowLeafReplacedBeforeTruncate(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 = "unrelated 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.WriteFile(leaf, []byte("old diagnostic"), 0o600); err != nil {
|
|
t.Fatalf("WriteFile(leaf) error = %v", err)
|
|
}
|
|
|
|
file, err := openFileConfined(leaf, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, WorkspaceFileMode, func() error {
|
|
if err := os.Remove(leaf); err != nil {
|
|
return err
|
|
}
|
|
return os.Symlink(filepath.Base(target), leaf)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("openFileConfined() error = %v", err)
|
|
}
|
|
if _, err := file.WriteString("new diagnostic"); err != nil {
|
|
_ = file.Close()
|
|
t.Fatalf("WriteString() error = %v", err)
|
|
}
|
|
if err := file.Close(); err != nil {
|
|
t.Fatalf("Close() error = %v", err)
|
|
}
|
|
|
|
targetData, err := os.ReadFile(target)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(target) error = %v", err)
|
|
}
|
|
if string(targetData) != original {
|
|
t.Fatalf("target content = %q, want %q", targetData, original)
|
|
}
|
|
leafData, err := os.ReadFile(leaf)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(leaf) error = %v", err)
|
|
}
|
|
if string(leafData) != "new diagnostic" {
|
|
t.Fatalf("leaf content = %q, want replacement diagnostic", leafData)
|
|
}
|
|
info, err := os.Lstat(leaf)
|
|
if err != nil {
|
|
t.Fatalf("Lstat(leaf) error = %v", err)
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
|
t.Fatalf("leaf mode = %v, want regular file", info.Mode())
|
|
}
|
|
}
|
|
|
|
func TestOpenFileConfinedRejectsLeafReplacedBeforeExistingFileOpen(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.lock")
|
|
const original = "unrelated lock content"
|
|
if err := os.WriteFile(target, []byte(original), 0o600); err != nil {
|
|
t.Fatalf("WriteFile(target) error = %v", err)
|
|
}
|
|
leaf := filepath.Join(dir, "session.lock")
|
|
if err := os.WriteFile(leaf, []byte("old lock"), 0o600); err != nil {
|
|
t.Fatalf("WriteFile(leaf) error = %v", err)
|
|
}
|
|
|
|
file, err := openFileConfined(leaf, os.O_RDWR|os.O_CREATE, WorkspaceFileMode, func() error {
|
|
if err := os.Remove(leaf); err != nil {
|
|
return err
|
|
}
|
|
return os.Symlink(filepath.Base(target), leaf)
|
|
})
|
|
if file != nil {
|
|
_ = file.Close()
|
|
t.Fatal("openFileConfined() returned a raced symbolic-link target")
|
|
}
|
|
if err == nil || !strings.Contains(err.Error(), "changed while being opened") {
|
|
t.Fatalf("openFileConfined() error = %v, want identity-change rejection", err)
|
|
}
|
|
targetData, readErr := os.ReadFile(target)
|
|
if readErr != nil {
|
|
t.Fatalf("ReadFile(target) error = %v", readErr)
|
|
}
|
|
if string(targetData) != original {
|
|
t.Fatalf("target content = %q, want %q", targetData, original)
|
|
}
|
|
}
|