1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || wasip1
package os
import (
"runtime"
"slices"
"sync"
"syscall"
)
// root implementation for platforms with a function to open a file
// relative to a directory.
type root struct {
name string
// refs is incremented while an operation is using fd.
// closed is set when Close is called.
// fd is closed when closed is true and refs is 0.
mu sync.Mutex
fd sysfdType
refs int // number of active operations
closed bool // set when closed
}
func (r *root) Close() error {
r.mu.Lock()
defer r.mu.Unlock()
if !r.closed && r.refs == 0 {
syscall.Close(r.fd)
}
r.closed = true
runtime.SetFinalizer(r, nil) // no need for a finalizer any more
return nil
}
func (r *root) incref() error {
r.mu.Lock()
defer r.mu.Unlock()
if r.closed {
return ErrClosed
}
r.refs++
return nil
}
func (r *root) decref() {
r.mu.Lock()
defer r.mu.Unlock()
if r.refs <= 0 {
panic("bad Root refcount")
}
r.refs--
if r.closed && r.refs == 0 {
syscall.Close(r.fd)
}
}
func (r *root) Name() string {
return r.name
}
func rootMkdir(r *Root, name string, perm FileMode) error {
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, mkdirat(parent, name, perm)
})
if err != nil {
return &PathError{Op: "mkdirat", Path: name, Err: err}
}
return err
}
func rootRemove(r *Root, name string) error {
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, removeat(parent, name)
})
if err != nil {
return &PathError{Op: "removeat", Path: name, Err: err}
}
return err
}
// doInRoot performs an operation on a path in a Root.
//
// It opens the directory containing the final element of the path,
// and calls f with the directory FD and name of the final element.
//
// If the path refers to a symlink which should be followed,
// then f must return errSymlink.
// doInRoot will follow the symlink and call f again.
func doInRoot[T any](r *Root, name string, f func(parent sysfdType, name string) (T, error)) (ret T, err error) {
if err := r.root.incref(); err != nil {
return ret, err
}
defer r.root.decref()
parts, suffixSep, err := splitPathInRoot(name, nil, nil)
if err != nil {
return ret, err
}
rootfd := r.root.fd
dirfd := rootfd
defer func() {
if dirfd != rootfd {
syscall.Close(dirfd)
}
}()
// When resolving .. path components, we restart path resolution from the root.
// (We can't openat(dir, "..") to move up to the parent directory,
// because dir may have moved since we opened it.)
// To limit how many opens a malicious path can cause us to perform, we set
// a limit on the total number of path steps and the total number of restarts
// caused by .. components. If *both* limits are exceeded, we halt the operation.
const maxSteps = 255
const maxRestarts = 8
i := 0
steps := 0
restarts := 0
symlinks := 0
for {
steps++
if steps > maxSteps && restarts > maxRestarts {
return ret, syscall.ENAMETOOLONG
}
if parts[i] == ".." {
// Resolve one or more parent ("..") path components.
//
// Rewrite the original path,
// removing the elements eliminated by ".." components,
// and start over from the beginning.
restarts++
end := i + 1
for end < len(parts) && parts[end] == ".." {
end++
}
count := end - i
if count > i {
return ret, errPathEscapes
}
parts = slices.Delete(parts, i-count, end)
if len(parts) == 0 {
parts = []string{"."}
}
i = 0
if dirfd != rootfd {
syscall.Close(dirfd)
}
dirfd = rootfd
continue
}
if i == len(parts)-1 {
// This is the last path element.
// Call f to decide what to do with it.
// If f returns errSymlink, this element is a symlink
// which should be followed.
// suffixSep contains any trailing separator characters
// which we rejoin to the final part at this time.
ret, err = f(dirfd, parts[i]+suffixSep)
if _, ok := err.(errSymlink); !ok {
return ret, err
}
} else {
var fd sysfdType
fd, err = rootOpenDir(dirfd, parts[i])
if err == nil {
if dirfd != rootfd {
syscall.Close(dirfd)
}
dirfd = fd
} else if _, ok := err.(errSymlink); !ok {
return ret, err
}
}
if e, ok := err.(errSymlink); ok {
symlinks++
if symlinks > rootMaxSymlinks {
return ret, syscall.ELOOP
}
newparts, newSuffixSep, err := splitPathInRoot(string(e), parts[:i], parts[i+1:])
if err != nil {
return ret, err
}
if i == len(parts)-1 {
// suffixSep contains any trailing path separator characters
// in the link target.
// If we are replacing the remainder of the path, retain these.
// If we're replacing some intermediate component of the path,
// ignore them, since intermediate components must always be
// directories.
suffixSep = newSuffixSep
}
if len(newparts) < i || !slices.Equal(parts[:i], newparts[:i]) {
// Some component in the path which we have already traversed
// has changed. We need to restart parsing from the root.
i = 0
if dirfd != rootfd {
syscall.Close(dirfd)
}
dirfd = rootfd
}
parts = newparts
continue
}
i++
}
}
// errSymlink reports that a file being operated on is actually a symlink,
// and the target of that symlink.
type errSymlink string
func (errSymlink) Error() string { panic("errSymlink is not user-visible") }
|