Skip to content

Commit

Permalink
os: Add support for long path names on unix RemoveAll
Browse files Browse the repository at this point in the history
On unix systems, long enough path names will fail when performing syscalls
like `Lstat`. The current RemoveAll uses several of these syscalls, and so
will fail for long paths. This can be risky, as it can let users "hide"
files from the system or otherwise make long enough paths for programs
to fail. By using `Unlinkat` and `Openat` syscalls instead, RemoveAll is
safer on unix systems. Initially implemented for linux and darwin.

Fixes golang#27029

Co-authored-by: Giuseppe Capizzi <gcapizzi@pivotal.io>
Co-authored-by: Julia Nedialkova <yulia.nedyalkova@sap.com>

Change-Id: I21730a61c70a38b2fcef8dbab6cae4eca3f95f05
  • Loading branch information
ostenbom committed Sep 26, 2018
1 parent 930ce09 commit 3043fe3
Show file tree
Hide file tree
Showing 17 changed files with 628 additions and 224 deletions.
10 changes: 10 additions & 0 deletions src/internal/syscall/unix/link_sysnum_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package unix

const unlinkatTrap = uintptr(472)
const openatTrap = uintptr(463)

const AT_REMOVEDIR = 0x80
10 changes: 10 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_386.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package unix

const unlinkatTrap uintptr = 301
const openatTrap uintptr = 295

const AT_REMOVEDIR = 0x200
10 changes: 10 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_amd64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package unix

const unlinkatTrap uintptr = 263
const openatTrap uintptr = 257

const AT_REMOVEDIR = 0x200
10 changes: 10 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_arm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package unix

const unlinkatTrap = uintptr(328)
const openatTrap = uintptr(322)

const AT_REMOVEDIR = 0x200
10 changes: 10 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_arm64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package unix

const unlinkatTrap = uintptr(35)
const openatTrap = uintptr(56)

const AT_REMOVEDIR = 0x200
12 changes: 12 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_mips64x.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build mips64 mips64le

package unix

const unlinkatTrap = uintptr(5253)
const openatTrap = uintptr(5247)

const AT_REMOVEDIR = 0x200
12 changes: 12 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_mipsx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build mips mipsle

package unix

const unlinkatTrap = uintptr(4294)
const openatTrap = uintptr(4288)

const AT_REMOVEDIR = 0x200
12 changes: 12 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_ppc64x.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build ppc64 ppc64le

package unix

const unlinkatTrap = uintptr(292)
const openatTrap = uintptr(286)

const AT_REMOVEDIR = 0x200
10 changes: 10 additions & 0 deletions src/internal/syscall/unix/link_sysnum_linux_s390x.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package unix

const unlinkatTrap = uintptr(294)
const openatTrap = uintptr(288)

const AT_REMOVEDIR = 0x200
28 changes: 28 additions & 0 deletions src/internal/syscall/unix/openat_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux darwin

package unix

import (
"syscall"
"unsafe"
)

func Openat(fdat int, path string, flags int, perm uint32) (int, error) {
var pathBytePointer *byte
pathBytePointer, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, err
}

fdPointer, _, errNo := syscall.Syscall6(openatTrap, uintptr(fdat), uintptr(unsafe.Pointer(pathBytePointer)), uintptr(flags), uintptr(perm), 0, 0)
fd := int(fdPointer)
if errNo != 0 {
return 0, errNo
}

return fd, nil
}
27 changes: 27 additions & 0 deletions src/internal/syscall/unix/unlinkat_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux darwin

package unix

import (
"syscall"
"unsafe"
)

func Unlinkat(dirfd int, path string, flags int) error {
var pathBytePointer *byte
pathBytePointer, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}

_, _, errNo := syscall.Syscall(unlinkatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(pathBytePointer)), uintptr(flags))
if errNo != 0 {
return errNo
}

return nil
}
99 changes: 0 additions & 99 deletions src/os/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package os

import (
"io"
"syscall"
)

Expand Down Expand Up @@ -58,101 +57,3 @@ func MkdirAll(path string, perm FileMode) error {
}
return nil
}

// RemoveAll removes path and any children it contains.
// It removes everything it can but returns the first error
// it encounters. If the path does not exist, RemoveAll
// returns nil (no error).
func RemoveAll(path string) error {
// Simple case: if Remove works, we're done.
err := Remove(path)
if err == nil || IsNotExist(err) {
return nil
}

// Otherwise, is this a directory we need to recurse into?
dir, serr := Lstat(path)
if serr != nil {
if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
return nil
}
return serr
}
if !dir.IsDir() {
// Not a directory; return the error from Remove.
return err
}

// Remove contents & return first error.
err = nil
for {
fd, err := Open(path)
if err != nil {
if IsNotExist(err) {
// Already deleted by someone else.
return nil
}
return err
}

const request = 1024
names, err1 := fd.Readdirnames(request)

// Removing files from the directory may have caused
// the OS to reshuffle it. Simply calling Readdirnames
// again may skip some entries. The only reliable way
// to avoid this is to close and re-open the
// directory. See issue 20841.
fd.Close()

for _, name := range names {
err1 := RemoveAll(path + string(PathSeparator) + name)
if err == nil {
err = err1
}
}

if err1 == io.EOF {
break
}
// If Readdirnames returned an error, use it.
if err == nil {
err = err1
}
if len(names) == 0 {
break
}

// We don't want to re-open unnecessarily, so if we
// got fewer than request names from Readdirnames, try
// simply removing the directory now. If that
// succeeds, we are done.
if len(names) < request {
err1 := Remove(path)
if err1 == nil || IsNotExist(err1) {
return nil
}

if err != nil {
// We got some error removing the
// directory contents, and since we
// read fewer names than we requested
// there probably aren't more files to
// remove. Don't loop around to read
// the directory again. We'll probably
// just get the same error.
return err
}
}
}

// Remove directory.
err1 := Remove(path)
if err1 == nil || IsNotExist(err1) {
return nil
}
if err == nil {
err = err1
}
return err
}
Loading

0 comments on commit 3043fe3

Please sign in to comment.