carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package safefile
2
3import (
4 "io"
5 "os"
6 "path/filepath"
7 "testing"
8
9 "golang.org/x/sys/unix"
10)
11
12func TestRefuseUnsafeOpen(t *testing.T) {
13 for _, kind := range []string{"symlink", "hardlink", "fifo", "parent"} {
14 t.Run(kind, func(t *testing.T) {
15 dir := t.TempDir()
16 victim := filepath.Join(dir, "victim")
17 if err := os.WriteFile(victim, []byte("untouched"), 0o600); err != nil {
18 t.Fatal(err)
19 }
20 path := filepath.Join(dir, "unsafe")
21 var err error
22 switch kind {
23 case "symlink":
24 err = os.Symlink(victim, path)
25 case "hardlink":
26 err = os.Link(victim, path)
27 case "fifo":
28 err = unix.Mkfifo(path, 0o600)
29 case "parent":
30 err = os.Symlink(dir, path)
31 path = filepath.Join(path, "victim")
32 }
33 if err != nil {
34 t.Fatal(err)
35 }
36 f, err := OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o644)
37 if err == nil {
38 f.Close()
39 t.Fatal("unsafe path opened")
40 }
41 got, err := os.ReadFile(victim)
42 if err != nil || string(got) != "untouched" {
43 t.Fatalf("victim changed: %q %v", got, err)
44 }
45 })
46 }
47}
48
49func TestWritePinsParentAndTemporaryFile(t *testing.T) {
50 dir := t.TempDir()
51 parent := filepath.Join(dir, "state")
52 outside := filepath.Join(dir, "outside")
53 for _, p := range []string{parent, outside} {
54 if err := os.Mkdir(p, 0o755); err != nil {
55 t.Fatal(err)
56 }
57 }
58 victim := filepath.Join(outside, "data")
59 if err := os.WriteFile(victim, []byte("untouched"), 0o600); err != nil {
60 t.Fatal(err)
61 }
62 moved := filepath.Join(dir, "moved")
63 err := Write(filepath.Join(parent, "result"), 0o644, func(w io.Writer) error {
64 entries, err := os.ReadDir(parent)
65 if err != nil {
66 return err
67 }
68 stage := filepath.Join(parent, entries[0].Name())
69 if err := os.Rename(stage, stage+"-moved"); err != nil {
70 return err
71 }
72 if err := os.Symlink(outside, stage); err != nil {
73 return err
74 }
75 if err := os.Rename(parent, moved); err != nil {
76 return err
77 }
78 if err := os.Symlink(outside, parent); err != nil {
79 return err
80 }
81 _, err = io.WriteString(w, "safe")
82 return err
83 })
84 if err != nil {
85 t.Fatal(err)
86 }
87 got, err := os.ReadFile(filepath.Join(moved, "result"))
88 if err != nil || string(got) != "safe" {
89 t.Fatalf("anchored result: %q %v", got, err)
90 }
91 got, err = os.ReadFile(victim)
92 if err != nil || string(got) != "untouched" {
93 t.Fatalf("victim changed: %q %v", got, err)
94 }
95 st, err := os.Stat(victim)
96 if err != nil || st.Mode().Perm() != 0o600 {
97 t.Fatalf("victim permissions changed: %v %v", st, err)
98 }
99 if _, err := os.Stat(filepath.Join(outside, "result")); !os.IsNotExist(err) {
100 t.Fatalf("published outside pinned parent: %v", err)
101 }
102}
103