Confine local file installation paths

This commit is contained in:
2026-08-10 17:59:29 +00:00
parent 59f3fe3d1d
commit 18ddf00d3d
20 changed files with 694 additions and 153 deletions

View File

@@ -115,6 +115,82 @@ func TestInstallDownloadedTempFileSetsPermissions(t *testing.T) {
}
}
func TestWriteFileAtomicRejectsSymlinkedDestinationAncestor(t *testing.T) {
root := t.TempDir()
outside := t.TempDir()
sentinel := filepath.Join(outside, "sentinel.txt")
if err := os.WriteFile(sentinel, []byte("unchanged"), 0o600); err != nil {
t.Fatalf("WriteFile(sentinel) error = %v", err)
}
if err := os.Symlink(outside, filepath.Join(root, "redirect")); err != nil {
t.Skipf("Symlink unavailable: %v", err)
}
err := WriteFileAtomic(filepath.Join(root, "redirect", "output.txt"), []byte("new"), 0o640)
if err == nil {
t.Fatal("WriteFileAtomic() error = nil, want symlink ancestor rejection")
}
data, err := os.ReadFile(sentinel)
if err != nil {
t.Fatalf("ReadFile(sentinel) error = %v", err)
}
if string(data) != "unchanged" {
t.Fatalf("outside sentinel = %q, want unchanged", data)
}
if _, err := os.Stat(filepath.Join(outside, "output.txt")); !os.IsNotExist(err) {
t.Fatalf("outside output exists: stat err = %v", err)
}
}
func TestWriteFileAtomicReplacesLeafSymlinkWithoutFollowingIt(t *testing.T) {
root := t.TempDir()
outside := filepath.Join(t.TempDir(), "outside.txt")
if err := os.WriteFile(outside, []byte("outside"), 0o600); err != nil {
t.Fatalf("WriteFile(outside) error = %v", err)
}
destination := filepath.Join(root, "output.txt")
if err := os.Symlink(outside, destination); err != nil {
t.Skipf("Symlink unavailable: %v", err)
}
if err := WriteFileAtomic(destination, []byte("inside"), 0o640); err != nil {
t.Fatalf("WriteFileAtomic() error = %v", err)
}
outsideData, err := os.ReadFile(outside)
if err != nil {
t.Fatalf("ReadFile(outside) error = %v", err)
}
if string(outsideData) != "outside" {
t.Fatalf("outside file = %q, want unchanged", outsideData)
}
info, err := os.Lstat(destination)
if err != nil || info.Mode()&os.ModeSymlink != 0 {
t.Fatalf("destination was not replaced with a regular file: info=%v err=%v", info, err)
}
}
func TestDownloadAndInstallRejectsSymlinkedDestinationAncestor(t *testing.T) {
root := t.TempDir()
outside := t.TempDir()
if err := os.Symlink(outside, filepath.Join(root, "redirect")); err != nil {
t.Skipf("Symlink unavailable: %v", err)
}
err := DownloadAndInstall(filepath.Join(root, "redirect", "output.txt"), 0o640, func(io.Writer) error {
return nil
})
if err == nil {
t.Fatal("DownloadAndInstall() error = nil, want symlink ancestor rejection")
}
entries, err := os.ReadDir(outside)
if err != nil {
t.Fatalf("ReadDir(outside) error = %v", err)
}
if len(entries) != 0 {
t.Fatalf("outside destination received entries: %v", entries)
}
}
func TestReplaceFileAtomicOrdersDurableOperations(t *testing.T) {
events := make([]string, 0, 8)
tmp := &recordingTemporaryFile{name: "/work/.result.tmp-1", events: &events}