Skip to content

Commit e8e8c02

Browse files
authored
Merge pull request #4393 from cyphar/mkdirall-inroot-securejoin
utils: switch to securejoin.MkdirAllHandle
2 parents f9f5764 + dd827f7 commit e8e8c02

File tree

82 files changed

+10905
-4442
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+10905
-4442
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/cilium/ebpf v0.12.3
88
github.com/containerd/console v1.0.4
99
github.com/coreos/go-systemd/v22 v22.5.0
10-
github.com/cyphar/filepath-securejoin v0.2.4
10+
github.com/cyphar/filepath-securejoin v0.3.1
1111
github.com/docker/go-units v0.5.0
1212
github.com/godbus/dbus/v5 v5.1.0
1313
github.com/moby/sys/mountinfo v0.7.1
@@ -21,7 +21,7 @@ require (
2121
github.com/urfave/cli v1.22.14
2222
github.com/vishvananda/netlink v1.1.0
2323
golang.org/x/net v0.24.0
24-
golang.org/x/sys v0.19.0
24+
golang.org/x/sys v0.22.0
2525
google.golang.org/protobuf v1.33.0
2626
)
2727

go.sum

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8
99
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
1010
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
1111
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
12-
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
13-
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
12+
github.com/cyphar/filepath-securejoin v0.3.1 h1:1V7cHiaW+C+39wEfpH6XlLBQo3j/PciWFrgfCLS8XrE=
13+
github.com/cyphar/filepath-securejoin v0.3.1/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
1414
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1515
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1616
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -58,8 +58,9 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
5858
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
5959
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6060
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
61-
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
6261
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
62+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
63+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
6364
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
6465
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
6566
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
@@ -77,8 +78,8 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
7778
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7879
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7980
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
80-
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
81-
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
81+
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
82+
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
8283
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
8384
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
8485
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

libcontainer/system/linux.go

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import (
66
"fmt"
77
"io"
88
"os"
9-
"runtime"
109
"strconv"
11-
"strings"
1210
"syscall"
1311
"unsafe"
1412

@@ -216,42 +214,3 @@ func SetLinuxPersonality(personality int) error {
216214
}
217215
return nil
218216
}
219-
220-
func prepareAt(dir *os.File, path string) (int, string) {
221-
if dir == nil {
222-
return unix.AT_FDCWD, path
223-
}
224-
225-
// Rather than just filepath.Join-ing path here, do it manually so the
226-
// error and handle correctly indicate cases like path=".." as being
227-
// relative to the correct directory. The handle.Name() might end up being
228-
// wrong but because this is (currently) only used in MkdirAllInRoot, that
229-
// isn't a problem.
230-
dirName := dir.Name()
231-
if !strings.HasSuffix(dirName, "/") {
232-
dirName += "/"
233-
}
234-
fullPath := dirName + path
235-
236-
return int(dir.Fd()), fullPath
237-
}
238-
239-
func Openat(dir *os.File, path string, flags int, mode uint32) (*os.File, error) {
240-
dirFd, fullPath := prepareAt(dir, path)
241-
fd, err := unix.Openat(dirFd, path, flags, mode)
242-
if err != nil {
243-
return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
244-
}
245-
runtime.KeepAlive(dir)
246-
return os.NewFile(uintptr(fd), fullPath), nil
247-
}
248-
249-
func Mkdirat(dir *os.File, path string, mode uint32) error {
250-
dirFd, fullPath := prepareAt(dir, path)
251-
err := unix.Mkdirat(dirFd, path, mode)
252-
if err != nil {
253-
err = &os.PathError{Op: "mkdirat", Path: fullPath, Err: err}
254-
}
255-
runtime.KeepAlive(dir)
256-
return err
257-
}

libcontainer/utils/utils_unix.go

Lines changed: 16 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
package utils
44

55
import (
6-
"errors"
76
"fmt"
87
"math"
98
"os"
@@ -14,8 +13,6 @@ import (
1413
"sync"
1514
_ "unsafe" // for go:linkname
1615

17-
"github.com/opencontainers/runc/libcontainer/system"
18-
1916
securejoin "github.com/cyphar/filepath-securejoin"
2017
"github.com/sirupsen/logrus"
2118
"golang.org/x/sys/unix"
@@ -299,83 +296,37 @@ func IsLexicallyInRoot(root, path string) bool {
299296
// This means that the path also must not contain ".." elements, otherwise an
300297
// error will occur.
301298
//
302-
// This is a somewhat less safe alternative to
303-
// <https://github.com/cyphar/filepath-securejoin/pull/13>, but it should
304-
// detect attempts to trick us into creating directories outside of the root.
305-
// We should migrate to securejoin.MkdirAll once it is merged.
299+
// This uses securejoin.MkdirAllHandle under the hood, but it has special
300+
// handling if unsafePath has already been scoped within the rootfs (this is
301+
// needed for a lot of runc callers and fixing this would require reworking a
302+
// lot of path logic).
306303
func MkdirAllInRootOpen(root, unsafePath string, mode uint32) (_ *os.File, Err error) {
307-
// If the path is already "within" the root, use it verbatim.
308-
fullPath := unsafePath
309-
if !IsLexicallyInRoot(root, unsafePath) {
310-
var err error
311-
fullPath, err = securejoin.SecureJoin(root, unsafePath)
304+
// If the path is already "within" the root, get the path relative to the
305+
// root and use that as the unsafe path. This is necessary because a lot of
306+
// MkdirAllInRootOpen callers have already done SecureJoin, and refactoring
307+
// all of them to stop using these SecureJoin'd paths would require a fair
308+
// amount of work.
309+
// TODO(cyphar): Do the refactor to libpathrs once it's ready.
310+
if IsLexicallyInRoot(root, unsafePath) {
311+
subPath, err := filepath.Rel(root, unsafePath)
312312
if err != nil {
313313
return nil, err
314314
}
315-
}
316-
subPath, err := filepath.Rel(root, fullPath)
317-
if err != nil {
318-
return nil, err
315+
unsafePath = subPath
319316
}
320317

321318
// Check for any silly mode bits.
322319
if mode&^0o7777 != 0 {
323320
return nil, fmt.Errorf("tried to include non-mode bits in MkdirAll mode: 0o%.3o", mode)
324321
}
325322

326-
currentDir, err := os.OpenFile(root, unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
323+
rootDir, err := os.OpenFile(root, unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
327324
if err != nil {
328325
return nil, fmt.Errorf("open root handle: %w", err)
329326
}
330-
defer func() {
331-
if Err != nil {
332-
currentDir.Close()
333-
}
334-
}()
335-
336-
for _, part := range strings.Split(subPath, string(filepath.Separator)) {
337-
switch part {
338-
case "", ".":
339-
// Skip over no-op components.
340-
continue
341-
case "..":
342-
return nil, fmt.Errorf("possible breakout detected: found %q component in SecureJoin subpath %s", part, subPath)
343-
}
327+
defer rootDir.Close()
344328

345-
nextDir, err := system.Openat(currentDir, part, unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
346-
switch {
347-
case err == nil:
348-
// Update the currentDir.
349-
_ = currentDir.Close()
350-
currentDir = nextDir
351-
352-
case errors.Is(err, unix.ENOTDIR):
353-
// This might be a symlink or some other random file. Either way,
354-
// error out.
355-
return nil, fmt.Errorf("cannot mkdir in %s/%s: %w", currentDir.Name(), part, unix.ENOTDIR)
356-
357-
case errors.Is(err, os.ErrNotExist):
358-
// Luckily, mkdirat will not follow trailing symlinks, so this is
359-
// safe to do as-is.
360-
if err := system.Mkdirat(currentDir, part, mode); err != nil {
361-
return nil, err
362-
}
363-
// Open the new directory. There is a race here where an attacker
364-
// could swap the directory with a different directory, but
365-
// MkdirAll's fuzzy semantics mean we don't care about that.
366-
nextDir, err := system.Openat(currentDir, part, unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
367-
if err != nil {
368-
return nil, fmt.Errorf("open newly created directory: %w", err)
369-
}
370-
// Update the currentDir.
371-
_ = currentDir.Close()
372-
currentDir = nextDir
373-
374-
default:
375-
return nil, err
376-
}
377-
}
378-
return currentDir, nil
329+
return securejoin.MkdirAllHandle(rootDir, unsafePath, int(mode))
379330
}
380331

381332
// MkdirAllInRoot is a wrapper around MkdirAllInRootOpen which closes the

vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md

Lines changed: 138 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/cyphar/filepath-securejoin/LICENSE

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)