返回 DeepSeek-Reasonix
secure_path_unix.go
根目录 / internal / checkpoint / secure_path_unix.go
1 //go:build !windows && !plan9
2
3 package checkpoint
4
5 import (
6 "errors"
7 "fmt"
8 "io"
9 "os"
10
11 "golang.org/x/sys/unix"
12 )
13
14 func secureOpenParent(root, abs string, create bool) (int, string, error) {
15 rel, err := workspaceRelative(root, abs)
16 if err != nil {
17 return -1, "", err
18 }
19 parts := splitLocalPath(rel)
20 if len(parts) == 0 {
21 return -1, "", fmt.Errorf("workspace root is not a file target")
22 }
23 // The configured workspace root is the trust boundary and may itself be a
24 // user-selected symlink. Every component below it is opened with O_NOFOLLOW.
25 fd, err := unix.Open(root, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
26 if err != nil {
27 return -1, "", fmt.Errorf("open workspace root: %w", err)
28 }
29 for _, part := range parts[:len(parts)-1] {
30 next, openErr := unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
31 if errors.Is(openErr, unix.ENOENT) && create {
32 if mkdirErr := unix.Mkdirat(fd, part, 0o755); mkdirErr != nil && !errors.Is(mkdirErr, unix.EEXIST) {
33 unix.Close(fd)
34 return -1, "", fmt.Errorf("create workspace directory %q: %w", part, mkdirErr)
35 }
36 next, openErr = unix.Openat(fd, part, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
37 }
38 if openErr != nil {
39 unix.Close(fd)
40 return -1, "", fmt.Errorf("open workspace directory %q: %w", part, openErr)
41 }
42 unix.Close(fd)
43 fd = next
44 }
45 return fd, parts[len(parts)-1], nil
46 }
47
48 func secureOpenWorkspaceFile(root, abs string) (*os.File, error) {
49 if root == "" {
50 return os.Open(abs)
51 }
52 parent, base, err := secureOpenParent(root, abs, false)
53 if err != nil {
54 return nil, err
55 }
56 defer unix.Close(parent)
57 fd, err := unix.Openat(parent, base, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
58 if err != nil {
59 return nil, &os.PathError{Op: "open", Path: abs, Err: err}
60 }
61 return os.NewFile(uintptr(fd), abs), nil
62 }
63
64 func secureWriteNew(root, abs string, data []byte, mode os.FileMode) error {
65 if root == "" {
66 return writeNewFile(abs, data, mode)
67 }
68 parent, base, err := secureOpenParent(root, abs, true)
69 if err != nil {
70 return err
71 }
72 defer unix.Close(parent)
73 fd, err := unix.Openat(parent, base, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_CLOEXEC|unix.O_NOFOLLOW, uint32(mode.Perm()))
74 if err != nil {
75 return &os.PathError{Op: "create", Path: abs, Err: err}
76 }
77 file := os.NewFile(uintptr(fd), abs)
78 remove := true
79 defer func() {
80 _ = file.Close()
81 if remove {
82 _ = unix.Unlinkat(parent, base, 0)
83 }
84 }()
85 if _, err := file.Write(data); err != nil {
86 return err
87 }
88 if err := file.Sync(); err != nil {
89 return err
90 }
91 if err := file.Close(); err != nil {
92 return err
93 }
94 remove = false
95 return nil
96 }
97
98 func secureRename(root, oldAbs, newAbs string) error {
99 if root == "" {
100 return os.Rename(oldAbs, newAbs)
101 }
102 oldParent, oldBase, err := secureOpenParent(root, oldAbs, false)
103 if err != nil {
104 return err
105 }
106 defer unix.Close(oldParent)
107 newParent, newBase, err := secureOpenParent(root, newAbs, true)
108 if err != nil {
109 return err
110 }
111 defer unix.Close(newParent)
112 if err := unix.Renameat(oldParent, oldBase, newParent, newBase); err != nil {
113 return &os.LinkError{Op: "rename", Old: oldAbs, New: newAbs, Err: err}
114 }
115 return nil
116 }
117
118 func secureRemove(root, abs string) error {
119 if root == "" {
120 return os.Remove(abs)
121 }
122 parent, base, err := secureOpenParent(root, abs, false)
123 if err != nil {
124 return err
125 }
126 defer unix.Close(parent)
127 if err := unix.Unlinkat(parent, base, 0); err != nil {
128 return &os.PathError{Op: "remove", Path: abs, Err: err}
129 }
130 return nil
131 }
132
133 func secureChmod(root, abs string, mode os.FileMode) error {
134 file, err := secureOpenWorkspaceFile(root, abs)
135 if err != nil {
136 return err
137 }
138 defer file.Close()
139 return file.Chmod(mode)
140 }
141
142 func securePathExists(root, abs string) (bool, error) {
143 file, err := secureOpenWorkspaceFile(root, abs)
144 if err == nil {
145 _ = file.Close()
146 return true, nil
147 }
148 if os.IsNotExist(err) {
149 return false, nil
150 }
151 return false, err
152 }
153
154 func secureReadFile(root, abs string) ([]byte, error) {
155 file, err := secureOpenWorkspaceFile(root, abs)
156 if err != nil {
157 return nil, err
158 }
159 defer file.Close()
160 return io.ReadAll(file)
161 }
162
162 lines GO