Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions internal/fsutil/getattrlist_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//go:build darwin

package fsutil

import (
"syscall"
"unsafe"

"golang.org/x/sys/unix"
)

var getattrlistZero byte

var libc_getattrlist_trampoline_addr uintptr

//go:linkname syscall_syscall6 syscall.syscall6
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)

//go:cgo_import_dynamic libc_getattrlist getattrlist "/usr/lib/libSystem.B.dylib"

func getattrlist(path string, attrList *unix.Attrlist, attrBuf []byte, options uint32) error {
p, err := unix.BytePtrFromString(path)
if err != nil {
return err
}
bufPtr := unsafe.Pointer(&getattrlistZero)
if len(attrBuf) > 0 {
bufPtr = unsafe.Pointer(&attrBuf[0])
}
_, _, e := syscall_syscall6(libc_getattrlist_trampoline_addr,
uintptr(unsafe.Pointer(p)),
uintptr(unsafe.Pointer(attrList)),
uintptr(bufPtr),
uintptr(len(attrBuf)),
uintptr(options),
0)
if e != 0 {
return e
}
return nil
}
18 changes: 18 additions & 0 deletions internal/fsutil/getattrlist_darwin.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//go:build darwin

#include "textflag.h"

TEXT libc_getattrlist_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_getattrlist(SB)
GLOBL ·libc_getattrlist_trampoline_addr(SB), RODATA, $8
DATA ·libc_getattrlist_trampoline_addr(SB)/8, $libc_getattrlist_trampoline<>(SB)

TEXT libc_open_extended_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_open_extended(SB)
GLOBL ·libc_open_extended_trampoline_addr(SB), RODATA, $8
DATA ·libc_open_extended_trampoline_addr(SB)/8, $libc_open_extended_trampoline<>(SB)

TEXT libc_mkdir_extended_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_mkdir_extended(SB)
GLOBL ·libc_mkdir_extended_trampoline_addr(SB), RODATA, $8
DATA ·libc_mkdir_extended_trampoline_addr(SB)/8, $libc_mkdir_extended_trampoline<>(SB)
56 changes: 56 additions & 0 deletions internal/fsutil/private_temp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package fsutil

import (
"crypto/rand"
"errors"
"os"
"path/filepath"
"strings"
)

// privateCreationObserver observes the object immediately after the creation
// syscall, before any metadata changes or content writes. Tests are serial.
var privateCreationObserver func(string)

// CreatePrivateTemp creates an owner-only staging file, suppressing effective
// inherited grants in the creation syscall. The caller must close and remove it.
func CreatePrivateTemp(dir, pattern string) (*os.File, error) {
var file *os.File
_, err := createPrivateTemp(dir, pattern, func(path string) error {
var err error
file, err = createPrivateFile(path)
return err
})
return file, err
}

// CreatePrivateTempDir isolates formatter rewrites and auxiliary files as well
// as the initial copy. The caller must remove the directory after the child exits.
func CreatePrivateTempDir(dir, pattern string) (string, error) {
return createPrivateTemp(dir, pattern, createPrivateDir)
}

func createPrivateTemp(dir, pattern string, create func(string) error) (string, error) {
if strings.ContainsAny(pattern, `/\`) {
return "", errors.New("fsutil: invalid temporary pattern")
}
prefix, suffix := pattern, ""
if index := strings.LastIndexByte(pattern, '*'); index >= 0 {
prefix, suffix = pattern[:index], pattern[index+1:]
}
for range 10000 {
name := filepath.Join(dir, prefix+rand.Text()+suffix)
err := create(name)
if errors.Is(err, os.ErrExist) {
continue
}
if err != nil {
return "", err
}
if privateCreationObserver != nil {
privateCreationObserver(name)
}
return name, nil
}
return "", errors.New("fsutil: temporary name collisions")
}
61 changes: 61 additions & 0 deletions internal/fsutil/private_temp_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//go:build darwin

package fsutil

import (
"encoding/binary"
"os"
"runtime"
"unsafe"

"golang.org/x/sys/unix"
)

var libc_open_extended_trampoline_addr uintptr
var libc_mkdir_extended_trampoline_addr uintptr

// These are the libSystem primitives underlying openx_np/mkdirx_np since 10.4.
//go:cgo_import_dynamic libc_open_extended __open_extended "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_mkdir_extended __mkdir_extended "/usr/lib/libSystem.B.dylib"

// kauth_filesec: magic, owner/group GUIDs, ACL entry count and ACL flags.
// An empty ACL with NO_INHERIT prevents the parent from granting access at birth.
func privateFilesec() []byte {
blob := make([]byte, 44)
binary.LittleEndian.PutUint32(blob, 0x012cc16d)
binary.LittleEndian.PutUint32(blob[40:], 1<<17)
return blob
}
func createPrivateFile(path string) (*os.File, error) {
name, err := unix.BytePtrFromString(path)
if err != nil {
return nil, err
}
security := privateFilesec()
const noID = uintptr(0xffffff9b) // KAUTH_UID_NONE / KAUTH_GID_NONE
fd, _, errno := syscall_syscall6(libc_open_extended_trampoline_addr,
uintptr(unsafe.Pointer(name)), unix.O_RDWR|unix.O_CREAT|unix.O_EXCL|unix.O_CLOEXEC,
noID, noID, 0o600, uintptr(unsafe.Pointer(&security[0])))
runtime.KeepAlive(name)
runtime.KeepAlive(security)
if errno != 0 {
return nil, &os.PathError{Op: "create private staging", Path: path, Err: errno}
}
return os.NewFile(fd, path), nil
}
func createPrivateDir(path string) error {
name, err := unix.BytePtrFromString(path)
if err != nil {
return err
}
security := privateFilesec()
const noID = uintptr(0xffffff9b)
_, _, errno := syscall_syscall6(libc_mkdir_extended_trampoline_addr,
uintptr(unsafe.Pointer(name)), noID, noID, 0o700, uintptr(unsafe.Pointer(&security[0])), 0)
runtime.KeepAlive(name)
runtime.KeepAlive(security)
if errno != 0 {
return &os.PathError{Op: "mkdir private staging", Path: path, Err: errno}
}
return nil
}
15 changes: 15 additions & 0 deletions internal/fsutil/private_temp_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build !linux && !darwin && !windows

package fsutil

import (
"errors"
"os"
)

func createPrivateFile(string) (*os.File, error) {
return nil, errors.New("fsutil: private staging creation is unsupported on this platform")
}
func createPrivateDir(string) error {
return errors.New("fsutil: private staging creation is unsupported on this platform")
}
12 changes: 12 additions & 0 deletions internal/fsutil/private_temp_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build linux

package fsutil

import "os"

// POSIX ACL inheritance intersects the ACL mask with the requested group bits.
// 0600/0700 therefore disable every inherited named-user and group grant.
func createPrivateFile(path string) (*os.File, error) {
return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
}
func createPrivateDir(path string) error { return os.Mkdir(path, 0o700) }
56 changes: 56 additions & 0 deletions internal/fsutil/private_temp_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build windows

package fsutil

import (
"fmt"
"os"
"unsafe"

"golang.org/x/sys/windows"
)

// An explicit protected current-user DACL is installed by CreateFile/CreateDirectory,
// not by a second call after an inheritable object has become visible.
func privateSecurityAttributes() (*windows.SecurityAttributes, error) {
token := windows.GetCurrentProcessToken()
user, err := token.GetTokenUser()
if err != nil {
return nil, err
}
descriptor, err := windows.SecurityDescriptorFromString(fmt.Sprintf("D:P(A;OICI;FA;;;%s)", user.User.Sid.String()))
if err != nil {
return nil, err
}
return &windows.SecurityAttributes{Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})), SecurityDescriptor: descriptor}, nil
}
func createPrivateFile(path string) (*os.File, error) {
name, err := windows.UTF16PtrFromString(path)
if err != nil {
return nil, err
}
security, err := privateSecurityAttributes()
if err != nil {
return nil, err
}
handle, err := windows.CreateFile(name, windows.GENERIC_READ|windows.GENERIC_WRITE|windows.READ_CONTROL|windows.WRITE_DAC,
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, security, windows.CREATE_NEW, windows.FILE_ATTRIBUTE_NORMAL, 0)
if err != nil {
return nil, &os.PathError{Op: "create private staging", Path: path, Err: err}
}
return os.NewFile(uintptr(handle), path), nil
}
func createPrivateDir(path string) error {
name, err := windows.UTF16PtrFromString(path)
if err != nil {
return err
}
security, err := privateSecurityAttributes()
if err != nil {
return err
}
if err := windows.CreateDirectory(name, security); err != nil {
return &os.PathError{Op: "mkdir private staging", Path: path, Err: err}
}
return nil
}
Loading
Loading