+176
-10
lines changedFilter options
+176
-10
lines changed Original file line number Diff line number Diff line change
@@ -253,7 +253,7 @@ func mountCgroupV1(m *configs.Mount, c *mountConfig) error {
253
253
if c.cgroupns {
254
254
subsystemPath := filepath.Join(c.root, b.Destination)
255
255
subsystemName := filepath.Base(b.Destination)
256
-
if err := os.MkdirAll(subsystemPath, 0o755); err != nil {
256
+
if err := utils.MkdirAllInRoot(c.root, subsystemPath, 0o755); err != nil {
257
257
return err
258
258
}
259
259
if err := utils.WithProcfd(c.root, b.Destination, func(procfd string) error {
@@ -406,15 +406,26 @@ func createMountpoint(rootfs string, m *configs.Mount, mountFd *int, source stri
406
406
return "", fmt.Errorf("%w: file bind mount over rootfs", errRootfsToFile)
407
407
}
408
408
// Make the parent directory.
409
-
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
409
+
destDir, destBase := filepath.Split(dest)
410
+
destDirFd, err := utils.MkdirAllInRootOpen(rootfs, destDir, 0o755)
411
+
if err != nil {
410
412
return "", fmt.Errorf("make parent dir of file bind-mount: %w", err)
411
413
}
412
-
// Make the target file.
413
-
f, err := os.OpenFile(dest, os.O_CREATE, 0o755)
414
-
if err != nil {
415
-
return "", fmt.Errorf("create target of file bind-mount: %w", err)
414
+
defer destDirFd.Close()
415
+
// Make the target file. We want to avoid opening any file that is
416
+
// already there because it could be a "bad" file like an invalid
417
+
// device or hung tty that might cause a DoS, so we use mknodat.
418
+
// destBase does not contain any "/" components, and mknodat does
419
+
// not follow trailing symlinks, so we can safely just call mknodat
420
+
// here.
421
+
if err := unix.Mknodat(int(destDirFd.Fd()), destBase, unix.S_IFREG|0o644, 0); err != nil {
422
+
// If we get EEXIST, there was already an inode there and
423
+
// we can consider that a success.
424
+
if !errors.Is(err, unix.EEXIST) {
425
+
err = &os.PathError{Op: "mknod regular file", Path: dest, Err: err}
426
+
return "", fmt.Errorf("create target of file bind-mount: %w", err)
427
+
}
416
428
}
417
-
_ = f.Close()
418
429
// Nothing left to do.
419
430
return dest, nil
420
431
}
@@ -433,7 +444,7 @@ func createMountpoint(rootfs string, m *configs.Mount, mountFd *int, source stri
433
444
}
434
445
}
435
446
436
-
if err := os.MkdirAll(dest, 0o755); err != nil {
447
+
if err := utils.MkdirAllInRoot(rootfs, dest, 0o755); err != nil {
437
448
return "", err
438
449
}
439
450
return dest, nil
@@ -463,7 +474,7 @@ func mountToRootfs(m *configs.Mount, c *mountConfig) error {
463
474
} else if !fi.IsDir() {
464
475
return fmt.Errorf("filesystem %q must be mounted on ordinary directory", m.Device)
465
476
}
466
-
if err := os.MkdirAll(dest, 0o755); err != nil {
477
+
if err := utils.MkdirAllInRoot(rootfs, dest, 0o755); err != nil {
467
478
return err
468
479
}
469
480
// Selinux kernels do not support labeling of /proc or /sys.
@@ -751,7 +762,7 @@ func createDeviceNode(rootfs string, node *devices.Device, bind bool) error {
751
762
if dest == rootfs {
752
763
return fmt.Errorf("%w: mknod over rootfs", errRootfsToFile)
753
764
}
754
-
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
765
+
if err := utils.MkdirAllInRoot(rootfs, filepath.Dir(dest), 0o755); err != nil {
755
766
return err
756
767
}
757
768
if bind {
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ package system
6
6
import (
7
7
"os"
8
8
"os/exec"
9
+
"runtime"
10
+
"strings"
9
11
"unsafe"
10
12
11
13
"golang.org/x/sys/unix"
@@ -102,3 +104,42 @@ func GetSubreaper() (int, error) {
102
104
103
105
return int(i), nil
104
106
}
107
+
108
+
func prepareAt(dir *os.File, path string) (int, string) {
109
+
if dir == nil {
110
+
return unix.AT_FDCWD, path
111
+
}
112
+
113
+
// Rather than just filepath.Join-ing path here, do it manually so the
114
+
// error and handle correctly indicate cases like path=".." as being
115
+
// relative to the correct directory. The handle.Name() might end up being
116
+
// wrong but because this is (currently) only used in MkdirAllInRoot, that
117
+
// isn't a problem.
118
+
dirName := dir.Name()
119
+
if !strings.HasSuffix(dirName, "/") {
120
+
dirName += "/"
121
+
}
122
+
fullPath := dirName + path
123
+
124
+
return int(dir.Fd()), fullPath
125
+
}
126
+
127
+
func Openat(dir *os.File, path string, flags int, mode uint32) (*os.File, error) {
128
+
dirFd, fullPath := prepareAt(dir, path)
129
+
fd, err := unix.Openat(dirFd, path, flags, mode)
130
+
if err != nil {
131
+
return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
132
+
}
133
+
runtime.KeepAlive(dir)
134
+
return os.NewFile(uintptr(fd), fullPath), nil
135
+
}
136
+
137
+
func Mkdirat(dir *os.File, path string, mode uint32) error {
138
+
dirFd, fullPath := prepareAt(dir, path)
139
+
err := unix.Mkdirat(dirFd, path, mode)
140
+
if err != nil {
141
+
err = &os.PathError{Op: "mkdirat", Path: fullPath, Err: err}
142
+
}
143
+
runtime.KeepAlive(dir)
144
+
return err
145
+
}
Original file line number Diff line number Diff line change
@@ -4,12 +4,17 @@
4
4
package utils
5
5
6
6
import (
7
+
"errors"
7
8
"fmt"
8
9
"os"
10
+
"path/filepath"
9
11
"strconv"
10
12
"strings"
11
13
_ "unsafe" // for go:linkname
12
14
15
+
"github.com/opencontainers/runc/libcontainer/system"
16
+
17
+
securejoin "github.com/cyphar/filepath-securejoin"
13
18
"golang.org/x/sys/unix"
14
19
)
15
20
@@ -130,3 +135,112 @@ func IsLexicallyInRoot(root, path string) bool {
130
135
}
131
136
return strings.HasPrefix(path, root)
132
137
}
138
+
139
+
// MkdirAllInRootOpen attempts to make
140
+
//
141
+
// path, _ := securejoin.SecureJoin(root, unsafePath)
142
+
// os.MkdirAll(path, mode)
143
+
// os.Open(path)
144
+
//
145
+
// safer against attacks where components in the path are changed between
146
+
// SecureJoin returning and MkdirAll (or Open) being called. In particular, we
147
+
// try to detect any symlink components in the path while we are doing the
148
+
// MkdirAll.
149
+
//
150
+
// NOTE: Unlike os.MkdirAll, mode is not Go's os.FileMode, it is the unix mode
151
+
// (the suid/sgid/sticky bits are not the same as for os.FileMode).
152
+
//
153
+
// NOTE: If unsafePath is a subpath of root, we assume that you have already
154
+
// called SecureJoin and so we use the provided path verbatim without resolving
155
+
// any symlinks (this is done in a way that avoids symlink-exchange races).
156
+
// This means that the path also must not contain ".." elements, otherwise an
157
+
// error will occur.
158
+
//
159
+
// This is a somewhat less safe alternative to
160
+
// <https://github.com/cyphar/filepath-securejoin/pull/13>, but it should
161
+
// detect attempts to trick us into creating directories outside of the root.
162
+
// We should migrate to securejoin.MkdirAll once it is merged.
163
+
func MkdirAllInRootOpen(root, unsafePath string, mode uint32) (_ *os.File, Err error) {
164
+
// If the path is already "within" the root, use it verbatim.
165
+
fullPath := unsafePath
166
+
if !IsLexicallyInRoot(root, unsafePath) {
167
+
var err error
168
+
fullPath, err = securejoin.SecureJoin(root, unsafePath)
169
+
if err != nil {
170
+
return nil, err
171
+
}
172
+
}
173
+
subPath, err := filepath.Rel(root, fullPath)
174
+
if err != nil {
175
+
return nil, err
176
+
}
177
+
178
+
// Check for any silly mode bits.
179
+
if mode&^0o7777 != 0 {
180
+
return nil, fmt.Errorf("tried to include non-mode bits in MkdirAll mode: 0o%.3o", mode)
181
+
}
182
+
183
+
currentDir, err := os.OpenFile(root, unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
184
+
if err != nil {
185
+
return nil, fmt.Errorf("open root handle: %w", err)
186
+
}
187
+
defer func() {
188
+
if Err != nil {
189
+
currentDir.Close()
190
+
}
191
+
}()
192
+
193
+
for _, part := range strings.Split(subPath, string(filepath.Separator)) {
194
+
switch part {
195
+
case "", ".":
196
+
// Skip over no-op components.
197
+
continue
198
+
case "..":
199
+
return nil, fmt.Errorf("possible breakout detected: found %q component in SecureJoin subpath %s", part, subPath)
200
+
}
201
+
202
+
nextDir, err := system.Openat(currentDir, part, unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
203
+
switch {
204
+
case err == nil:
205
+
// Update the currentDir.
206
+
_ = currentDir.Close()
207
+
currentDir = nextDir
208
+
209
+
case errors.Is(err, unix.ENOTDIR):
210
+
// This might be a symlink or some other random file. Either way,
211
+
// error out.
212
+
return nil, fmt.Errorf("cannot mkdir in %s/%s: %w", currentDir.Name(), part, unix.ENOTDIR)
213
+
214
+
case errors.Is(err, os.ErrNotExist):
215
+
// Luckily, mkdirat will not follow trailing symlinks, so this is
216
+
// safe to do as-is.
217
+
if err := system.Mkdirat(currentDir, part, mode); err != nil {
218
+
return nil, err
219
+
}
220
+
// Open the new directory. There is a race here where an attacker
221
+
// could swap the directory with a different directory, but
222
+
// MkdirAll's fuzzy semantics mean we don't care about that.
223
+
nextDir, err := system.Openat(currentDir, part, unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
224
+
if err != nil {
225
+
return nil, fmt.Errorf("open newly created directory: %w", err)
226
+
}
227
+
// Update the currentDir.
228
+
_ = currentDir.Close()
229
+
currentDir = nextDir
230
+
231
+
default:
232
+
return nil, err
233
+
}
234
+
}
235
+
return currentDir, nil
236
+
}
237
+
238
+
// MkdirAllInRoot is a wrapper around MkdirAllInRootOpen which closes the
239
+
// returned handle, for callers that don't need to use it.
240
+
func MkdirAllInRoot(root, unsafePath string, mode uint32) error {
241
+
f, err := MkdirAllInRootOpen(root, unsafePath, mode)
242
+
if err == nil {
243
+
_ = f.Close()
244
+
}
245
+
return err
246
+
}
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4