Skip to content

Commit e410d4a

Browse files
committed
merge #44 into cyphar/filepath-securejoin:main
Aleksa Sarai (2): gha: add GOARCH=386 build check mkdirall: switch to os.FileMode argument LGTMs: cyphar kolyshkin
2 parents f3a512c + ea4e5b6 commit e410d4a

File tree

4 files changed

+77
-29
lines changed

4 files changed

+77
-29
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
- windows-latest
2020
- ubuntu-latest
2121
- macos-latest
22+
go-arch:
23+
- amd64
2224
go-version:
2325
- "1.18"
2426
- "1.19"
@@ -27,13 +29,19 @@ jobs:
2729
- "1.22"
2830
- "1.23"
2931
- "^1"
32+
include:
33+
- os: ubuntu-latest
34+
go-arch: "386"
35+
go-version: "^1"
3036
runs-on: ${{ matrix.os }}
3137
steps:
3238
- uses: actions/checkout@v4
3339
- uses: actions/setup-go@v5
3440
with:
3541
go-version: ${{ matrix.go-version }}
3642
check-latest: true
43+
- name: set GOOARCH
44+
run: echo "GOARCH=${{ matrix.go-arch }}" >>"$GITHUB_ENV"
3745
- name: go build check
3846
run: go build ./...
3947
- name: go test build check

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2222
necessarily a breaking API change (though we expect no real users to be
2323
affected by it).
2424

25+
- `MkdirAll` and `MkdirHandle` now take an `os.FileMode`-style mode argument
26+
instead of a raw `unix.S_*`-style mode argument, which may cause compile-time
27+
type errors depending on how you use `filepath-securejoin`. For most users,
28+
there will be no change in behaviour aside from the type change (as the
29+
bottom `0o777` bits are the same in both formats, and most users are probably
30+
only using those bits).
31+
32+
However, if you were using `unix.S_ISVTX` to set the sticky bit with
33+
`MkdirAll(Handle)` you will need to switch to `os.ModeSticky` otherwise you
34+
will get a runtime error with this update. In addition, the error message you
35+
will get from passing `unix.S_ISUID` and `unix.S_ISGID` will be different as
36+
they are treated as invalid bits now (note that previously passing said bits
37+
was also an error).
38+
2539
## [0.3.6] - 2024-12-17 ##
2640

2741
### Compatibility ###

mkdir_linux.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,33 @@ var (
2121
errPossibleAttack = errors.New("possible attack detected")
2222
)
2323

24+
// modePermExt is like os.ModePerm except that it also includes the set[ug]id
25+
// and sticky bits.
26+
const modePermExt = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky
27+
28+
//nolint:cyclop // this function needs to handle a lot of cases
29+
func toUnixMode(mode os.FileMode) (uint32, error) {
30+
sysMode := uint32(mode.Perm())
31+
if mode&os.ModeSetuid != 0 {
32+
sysMode |= unix.S_ISUID
33+
}
34+
if mode&os.ModeSetgid != 0 {
35+
sysMode |= unix.S_ISGID
36+
}
37+
if mode&os.ModeSticky != 0 {
38+
sysMode |= unix.S_ISVTX
39+
}
40+
// We don't allow file type bits.
41+
if mode&os.ModeType != 0 {
42+
return 0, fmt.Errorf("%w %+.3o (%s): type bits not permitted", errInvalidMode, mode, mode)
43+
}
44+
// We don't allow other unknown modes.
45+
if mode&^modePermExt != 0 || sysMode&unix.S_IFMT != 0 {
46+
return 0, fmt.Errorf("%w %+.3o (%s): unknown mode bits", errInvalidMode, mode, mode)
47+
}
48+
return sysMode, nil
49+
}
50+
2451
// MkdirAllHandle is equivalent to [MkdirAll], except that it is safer to use
2552
// in two respects:
2653
//
@@ -39,17 +66,17 @@ var (
3966
// a brand new lookup of unsafePath (such as with [SecureJoin] or openat2) after
4067
// doing [MkdirAll]. If you intend to open the directory after creating it, you
4168
// should use MkdirAllHandle.
42-
func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err error) {
43-
// Make sure there are no os.FileMode bits set.
44-
if mode&^0o7777 != 0 {
45-
return nil, fmt.Errorf("%w for mkdir 0o%.3o", errInvalidMode, mode)
69+
func MkdirAllHandle(root *os.File, unsafePath string, mode os.FileMode) (_ *os.File, Err error) {
70+
unixMode, err := toUnixMode(mode)
71+
if err != nil {
72+
return nil, err
4673
}
4774
// On Linux, mkdirat(2) (and os.Mkdir) silently ignore the suid and sgid
4875
// bits. We could also silently ignore them but since we have very few
4976
// users it seems more prudent to return an error so users notice that
5077
// these bits will not be set.
51-
if mode&^0o1777 != 0 {
52-
return nil, fmt.Errorf("%w for mkdir 0o%.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode)
78+
if unixMode&^0o1777 != 0 {
79+
return nil, fmt.Errorf("%w for mkdir %+.3o: suid and sgid are ignored by mkdir", errInvalidMode, mode)
5380
}
5481

5582
// Try to open as much of the path as possible.
@@ -104,9 +131,6 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err
104131
return nil, fmt.Errorf("%w: yet-to-be-created path %q contains '..' components", unix.ENOENT, remainingPath)
105132
}
106133

107-
// Make sure the mode doesn't have any type bits.
108-
mode &^= unix.S_IFMT
109-
110134
// Create the remaining components.
111135
for _, part := range remainingParts {
112136
switch part {
@@ -123,7 +147,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err
123147
// directory at the same time as us. In that case, just continue on as
124148
// if we created it (if the created inode is not a directory, the
125149
// following open call will fail).
126-
if err := unix.Mkdirat(int(currentDir.Fd()), part, uint32(mode)); err != nil && !errors.Is(err, unix.EEXIST) {
150+
if err := unix.Mkdirat(int(currentDir.Fd()), part, unixMode); err != nil && !errors.Is(err, unix.EEXIST) {
127151
err = &os.PathError{Op: "mkdirat", Path: currentDir.Name() + "/" + part, Err: err}
128152
// Make the error a bit nicer if the directory is dead.
129153
if deadErr := isDeadInode(currentDir); deadErr != nil {
@@ -196,10 +220,7 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err
196220
// If you plan to open the directory after you have created it or want to use
197221
// an open directory handle as the root, you should use [MkdirAllHandle] instead.
198222
// This function is a wrapper around [MkdirAllHandle].
199-
//
200-
// NOTE: The mode argument must be set the unix mode bits (unix.S_I...), not
201-
// the Go generic mode bits ([os.FileMode]...).
202-
func MkdirAll(root, unsafePath string, mode int) error {
223+
func MkdirAll(root, unsafePath string, mode os.FileMode) error {
203224
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
204225
if err != nil {
205226
return err

mkdir_linux_test.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ import (
2020
"golang.org/x/sys/unix"
2121
)
2222

23-
type mkdirAllFunc func(t *testing.T, root, unsafePath string, mode int) error
23+
type mkdirAllFunc func(t *testing.T, root, unsafePath string, mode os.FileMode) error
2424

25-
var mkdirAll_MkdirAll mkdirAllFunc = func(t *testing.T, root, unsafePath string, mode int) error {
25+
var mkdirAll_MkdirAll mkdirAllFunc = func(t *testing.T, root, unsafePath string, mode os.FileMode) error {
2626
// We can't check expectedPath here.
2727
return MkdirAll(root, unsafePath, mode)
2828
}
2929

30-
var mkdirAll_MkdirAllHandle mkdirAllFunc = func(t *testing.T, root, unsafePath string, mode int) error {
30+
var mkdirAll_MkdirAllHandle mkdirAllFunc = func(t *testing.T, root, unsafePath string, mode os.FileMode) error {
3131
// Same logic as MkdirAll.
3232
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
3333
if err != nil {
@@ -55,7 +55,7 @@ var mkdirAll_MkdirAllHandle mkdirAllFunc = func(t *testing.T, root, unsafePath s
5555
return nil
5656
}
5757

58-
func checkMkdirAll(t *testing.T, mkdirAll mkdirAllFunc, root, unsafePath string, mode, expectedMode int, expectedErr error) {
58+
func checkMkdirAll(t *testing.T, mkdirAll mkdirAllFunc, root, unsafePath string, mode os.FileMode, expectedMode int, expectedErr error) {
5959
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
6060
require.NoError(t, err)
6161
defer rootDir.Close()
@@ -248,31 +248,36 @@ func TestMkdirAllHandle_AsRoot(t *testing.T) {
248248

249249
func testMkdirAll_InvalidMode(t *testing.T, mkdirAll mkdirAllFunc) {
250250
for _, test := range []struct {
251-
mode int
251+
mode os.FileMode
252252
expectedErr error
253253
}{
254-
// os.FileMode bits are invalid.
255-
{int(os.ModeDir | 0o777), errInvalidMode},
256-
{int(os.ModeSticky | 0o777), errInvalidMode},
257-
{int(os.ModeIrregular | 0o777), errInvalidMode},
254+
// unix.S_IS* bits are invalid.
255+
{unix.S_ISUID | 0o777, errInvalidMode},
256+
{unix.S_ISGID | 0o777, errInvalidMode},
257+
{unix.S_ISVTX | 0o777, errInvalidMode},
258+
{unix.S_ISUID | unix.S_ISGID | unix.S_ISVTX | 0o777, errInvalidMode},
258259
// unix.S_IFMT bits are also invalid.
259260
{unix.S_IFDIR | 0o777, errInvalidMode},
260261
{unix.S_IFREG | 0o777, errInvalidMode},
261262
{unix.S_IFIFO | 0o777, errInvalidMode},
263+
// os.FileType bits are also invalid.
264+
{os.ModeDir | 0o777, errInvalidMode},
265+
{os.ModeNamedPipe | 0o777, errInvalidMode},
266+
{os.ModeIrregular | 0o777, errInvalidMode},
262267
// suid/sgid bits are silently ignored by mkdirat and so we return an
263268
// error explicitly.
264-
{unix.S_ISUID | 0o777, errInvalidMode},
265-
{unix.S_ISGID | 0o777, errInvalidMode},
266-
{unix.S_ISUID | unix.S_ISGID | unix.S_ISVTX | 0o777, errInvalidMode},
269+
{os.ModeSetuid | 0o777, errInvalidMode},
270+
{os.ModeSetgid | 0o777, errInvalidMode},
271+
{os.ModeSetuid | os.ModeSetgid | os.ModeSticky | 0o777, errInvalidMode},
267272
// Proper sticky bit should work.
268-
{unix.S_ISVTX | 0o777, nil},
273+
{os.ModeSticky | 0o777, nil},
269274
// Regular mode bits.
270275
{0o777, nil},
271276
{0o711, nil},
272277
} {
273278
root := t.TempDir()
274279
err := mkdirAll(t, root, "a/b/c", test.mode)
275-
assert.ErrorIsf(t, err, test.expectedErr, "mkdirall 0o%.3o", test.mode)
280+
assert.ErrorIsf(t, err, test.expectedErr, "mkdirall %+.3o (%s)", test.mode, test.mode)
276281
}
277282
}
278283

@@ -295,7 +300,7 @@ func newRacingMkdirMeta() *racingMkdirMeta {
295300
}
296301
}
297302

298-
func (m *racingMkdirMeta) checkMkdirAllHandle_Racing(t *testing.T, root, unsafePath string, mode int, allowedErrs []error) {
303+
func (m *racingMkdirMeta) checkMkdirAllHandle_Racing(t *testing.T, root, unsafePath string, mode os.FileMode, allowedErrs []error) {
299304
rootDir, err := os.OpenFile(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
300305
require.NoError(t, err, "open root")
301306
defer rootDir.Close()

0 commit comments

Comments
 (0)