99// License, v. 2.0. If a copy of the MPL was not distributed with this
1010// file, You can obtain one at https://mozilla.org/MPL/2.0/.
1111
12- package pathrs
12+ package gopathrs
1313
1414import (
1515 "errors"
@@ -23,9 +23,12 @@ import (
2323 "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/fd"
2424 "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
2525 "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/linux"
26+ "github.com/cyphar/filepath-securejoin/pathrs-lite/internal/procfs"
2627)
2728
28- var errInvalidMode = errors .New ("invalid permission mode" )
29+ // ErrInvalidMode is returned from [MkdirAll] when the requested mode is
30+ // invalid.
31+ var ErrInvalidMode = errors .New ("invalid permission mode" )
2932
3033// modePermExt is like os.ModePerm except that it also includes the set[ug]id
3134// and sticky bits.
@@ -45,11 +48,11 @@ func toUnixMode(mode os.FileMode) (uint32, error) {
4548 }
4649 // We don't allow file type bits.
4750 if mode & os .ModeType != 0 {
48- return 0 , fmt .Errorf ("%w %+.3o (%s): type bits not permitted" , errInvalidMode , mode , mode )
51+ return 0 , fmt .Errorf ("%w %+.3o (%s): type bits not permitted" , ErrInvalidMode , mode , mode )
4952 }
5053 // We don't allow other unknown modes.
5154 if mode &^modePermExt != 0 || sysMode & unix .S_IFMT != 0 {
52- return 0 , fmt .Errorf ("%w %+.3o (%s): unknown mode bits" , errInvalidMode , mode , mode )
55+ return 0 , fmt .Errorf ("%w %+.3o (%s): unknown mode bits" , ErrInvalidMode , mode , mode )
5356 }
5457 return sysMode , nil
5558}
@@ -84,11 +87,11 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
8487 // users it seems more prudent to return an error so users notice that
8588 // these bits will not be set.
8689 if unixMode &^0o1777 != 0 {
87- return nil , fmt .Errorf ("%w for mkdir %+.3o: suid and sgid are ignored by mkdir" , errInvalidMode , mode )
90+ return nil , fmt .Errorf ("%w for mkdir %+.3o: suid and sgid are ignored by mkdir" , ErrInvalidMode , mode )
8891 }
8992
9093 // Try to open as much of the path as possible.
91- currentDir , remainingPath , err := partialLookupInRoot (root , unsafePath )
94+ currentDir , remainingPath , err := PartialLookupInRoot (root , unsafePath )
9295 defer func () {
9396 if Err != nil {
9497 _ = currentDir .Close ()
@@ -117,7 +120,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
117120 // Re-open the path to match the O_DIRECTORY reopen loop later (so that we
118121 // always return a non-O_PATH handle). We also check that we actually got a
119122 // directory.
120- if reopenDir , err := Reopen (currentDir , unix .O_DIRECTORY | unix .O_CLOEXEC ); errors .Is (err , unix .ENOTDIR ) {
123+ if reopenDir , err := procfs . ReopenFd (currentDir , unix .O_DIRECTORY | unix .O_CLOEXEC ); errors .Is (err , unix .ENOTDIR ) {
121124 return nil , fmt .Errorf ("cannot create subdirectories in %q: %w" , currentDir .Name (), unix .ENOTDIR )
122125 } else if err != nil {
123126 return nil , fmt .Errorf ("re-opening handle to %q: %w" , currentDir .Name (), err )
@@ -207,40 +210,3 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.F
207210 }
208211 return currentDir , nil
209212}
210-
211- // MkdirAll is a race-safe alternative to the [os.MkdirAll] function,
212- // where the new directory is guaranteed to be within the root directory (if an
213- // attacker can move directories from inside the root to outside the root, the
214- // created directory tree might be outside of the root but the key constraint
215- // is that at no point will we walk outside of the directory tree we are
216- // creating).
217- //
218- // Effectively, MkdirAll(root, unsafePath, mode) is equivalent to
219- //
220- // path, _ := securejoin.SecureJoin(root, unsafePath)
221- // err := os.MkdirAll(path, mode)
222- //
223- // But is much safer. The above implementation is unsafe because if an attacker
224- // can modify the filesystem tree between [SecureJoin] and [os.MkdirAll], it is
225- // possible for MkdirAll to resolve unsafe symlink components and create
226- // directories outside of the root.
227- //
228- // If you plan to open the directory after you have created it or want to use
229- // an open directory handle as the root, you should use [MkdirAllHandle] instead.
230- // This function is a wrapper around [MkdirAllHandle].
231- //
232- // [SecureJoin]: https://pkg.go.dev/github.com/cyphar/filepath-securejoin#SecureJoin
233- func MkdirAll (root , unsafePath string , mode os.FileMode ) error {
234- rootDir , err := os .OpenFile (root , unix .O_PATH | unix .O_DIRECTORY | unix .O_CLOEXEC , 0 )
235- if err != nil {
236- return err
237- }
238- defer rootDir .Close () //nolint:errcheck // close failures aren't critical here
239-
240- f , err := MkdirAllHandle (rootDir , unsafePath , mode )
241- if err != nil {
242- return err
243- }
244- _ = f .Close ()
245- return nil
246- }
0 commit comments