33package utils
44
55import (
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) .
306303func 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
0 commit comments