Skip to content

Commit

Permalink
unix: use libc stubs for OpenBSD pledge+unveil
Browse files Browse the repository at this point in the history
For both compatibility and security concerns, it is preferrable to
perform system calls through the libc stubs rather than syscall(2).
Besides making programs resilient to changing system call numbers, it
removes an unnecessary use of the syscall(2) call which, if found by
an attacker, could be abused to perform any system call.

As OpenBSD 6.2 is the oldest supported version of OpenBSD, remove the
handling of pledge on versions prior to this release.

PledgeExecpromises is no longer usable on 6.2, as the execpromises is
treated as required (unlike Pledge, where on 6.2 it is allowed but
must be empty).  The empty execpromises string is no longer converted
to a nil pointer on 6.2.  This fixes an out-of-bounds read where, on
6.2, an empty string would be passed to the deprecated pledge(2) API,
which would interpret the pointer as an array of strings.
  • Loading branch information
jrick committed Oct 22, 2023
1 parent 1bfbee0 commit 3317d7f
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 76 deletions.
97 changes: 36 additions & 61 deletions unix/pledge_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"errors"
"fmt"
"strconv"
"syscall"
"unsafe"
)

// Pledge implements the pledge syscall.
Expand All @@ -20,42 +18,48 @@ import (
// execpromises must be empty when Pledge is called on OpenBSD
// releases predating 6.3, otherwise an error will be returned.
//
// On OpenBSD 6.3 and later, an empty execpromises removes all
// execpromises from the process. Use PledgePromises to only set
// promises without setting execpromises.
//
// For more information see pledge(2).
func Pledge(promises, execpromises string) error {
maj, min, err := majmin()
if err != nil {
return err
}

err = pledgeAvailable(maj, min, execpromises)
if err != nil {
return err
// OS support for execpromises is required only when execpromises is not
// the empty string.
if execpromises != "" {
err = supportsExecpromises(maj, min)
if err != nil {
return err
}
}

pptr, err := syscall.BytePtrFromString(promises)
promisesBytes, err := BytePtrFromString(promises)
if err != nil {
return err
}

// This variable will hold either a nil unsafe.Pointer or
// an unsafe.Pointer to a string (execpromises).
var expr unsafe.Pointer
// This variable will hold either a nil pointer or a pointer to the
// NUL-terminated execpromises string.
var execpromisesBytes *byte

// If we're running on OpenBSD > 6.2, pass execpromises to the syscall.
if maj > 6 || (maj == 6 && min > 2) {
exptr, err := syscall.BytePtrFromString(execpromises)
// If we're running on OpenBSD >= 6.3, pass execpromises to the syscall.
// While an empty execpromises string is required by this API on
// OpenBSD <= 6.2 and has no effect, the empty execpromises string
// removes all execpromises on OpenBSD >= 6.3.
if maj > 6 || (maj == 6 && min >= 3) {
exptr, err := BytePtrFromString(execpromises)
if err != nil {
return err
}
expr = unsafe.Pointer(exptr)
}

_, _, e := syscall.Syscall(SYS_PLEDGE, uintptr(unsafe.Pointer(pptr)), uintptr(expr), 0)
if e != 0 {
return e
execpromisesBytes = exptr
}

return nil
return pledge(promisesBytes, execpromisesBytes)
}

// PledgePromises implements the pledge syscall.
Expand All @@ -64,62 +68,39 @@ func Pledge(promises, execpromises string) error {
//
// For more information see pledge(2).
func PledgePromises(promises string) error {
maj, min, err := majmin()
if err != nil {
return err
}

err = pledgeAvailable(maj, min, "")
promisesBytes, err := BytePtrFromString(promises)
if err != nil {
return err
}

// This variable holds the execpromises and is always nil.
var expr unsafe.Pointer

pptr, err := syscall.BytePtrFromString(promises)
if err != nil {
return err
}

_, _, e := syscall.Syscall(SYS_PLEDGE, uintptr(unsafe.Pointer(pptr)), uintptr(expr), 0)
if e != 0 {
return e
}

return nil
return pledge(promisesBytes, nil)
}

// PledgeExecpromises implements the pledge syscall.
//
// This changes the execpromises and leaves the promises untouched.
//
// The pledge syscall does not accept execpromises on OpenBSD releases
// before 6.3.
//
// For more information see pledge(2).
func PledgeExecpromises(execpromises string) error {
maj, min, err := majmin()
if err != nil {
return err
}

err = pledgeAvailable(maj, min, execpromises)
err = supportsExecpromises(maj, min)
if err != nil {
return err
}

// This variable holds the promises and is always nil.
var pptr unsafe.Pointer

exptr, err := syscall.BytePtrFromString(execpromises)
execpromisesBytes, err := BytePtrFromString(execpromises)
if err != nil {
return err
}

_, _, e := syscall.Syscall(SYS_PLEDGE, uintptr(pptr), uintptr(unsafe.Pointer(exptr)), 0)
if e != 0 {
return e
}

return nil
return pledge(nil, execpromisesBytes)
}

// majmin returns major and minor version number for an OpenBSD system.
Expand All @@ -145,17 +126,11 @@ func majmin() (major int, minor int, err error) {
return
}

// pledgeAvailable checks for availability of the pledge(2) syscall
// based on the running OpenBSD version.
func pledgeAvailable(maj, min int, execpromises string) error {
// If OpenBSD <= 5.9, pledge is not available.
if (maj == 5 && min != 9) || maj < 5 {
return fmt.Errorf("pledge syscall is not available on OpenBSD %d.%d", maj, min)
}

// If OpenBSD <= 6.2 and execpromises is not empty,
// return an error - execpromises is not available before 6.3
if (maj < 6 || (maj == 6 && min <= 2)) && execpromises != "" {
// supportsExecpromises checks for availability of the execpromises argument to
// the pledge(2) syscall based on the running OpenBSD version.
func supportsExecpromises(maj, min int) error {
// execpromises is not available before 6.3
if maj < 6 || (maj == 6 && min <= 2) {
return fmt.Errorf("cannot use execpromises on OpenBSD %d.%d", maj, min)
}

Expand Down
2 changes: 2 additions & 0 deletions unix/syscall_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,5 @@ func Uname(uname *Utsname) error {
//sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error)
//sys munmap(addr uintptr, length uintptr) (err error)
//sys utimensat(dirfd int, path string, times *[2]Timespec, flags int) (err error)
//sys pledge(promises *byte, execpromises *byte) (err error)
//sys unveil(path *byte, flags *byte) (err error)
45 changes: 30 additions & 15 deletions unix/unveil_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,54 @@

package unix

import (
"syscall"
"unsafe"
)
import "fmt"

// Unveil implements the unveil syscall.
// For more information see unveil(2).
// Note that the special case of blocking further
// unveil calls is handled by UnveilBlock.
//
// Unveil requires OpenBSD 6.4 or later.
func Unveil(path string, flags string) error {
pathPtr, err := syscall.BytePtrFromString(path)
err := supportsUnveil()
if err != nil {
return err
}
flagsPtr, err := syscall.BytePtrFromString(flags)
pathBytes, err := BytePtrFromString(path)
if err != nil {
return err
}
_, _, e := syscall.Syscall(SYS_UNVEIL, uintptr(unsafe.Pointer(pathPtr)), uintptr(unsafe.Pointer(flagsPtr)), 0)
if e != 0 {
return e
flagsBytes, err := BytePtrFromString(flags)
if err != nil {
return err
}
return nil
return unveil(pathBytes, flagsBytes)
}

// UnveilBlock blocks future unveil calls.
// For more information see unveil(2).
//
// Unveil requires OpenBSD 6.4 or later.
func UnveilBlock() error {
// Both pointers must be nil.
var pathUnsafe, flagsUnsafe unsafe.Pointer
_, _, e := syscall.Syscall(SYS_UNVEIL, uintptr(pathUnsafe), uintptr(flagsUnsafe), 0)
if e != 0 {
return e
err := supportsUnveil()
if err != nil {
return err
}
return unveil(nil, nil)
}

// supportsUnveil checks for availability of the unveil(2) system call based
// on the running OpenBSD version.
func supportsUnveil() error {
maj, min, err := majmin()
if err != nil {
return err
}

// unveil is not available before 6.4
if maj < 6 || (maj == 6 && min <= 3) {
return fmt.Errorf("cannot use unveil on OpenBSD %d.%d", maj, min)
}

return nil
}
28 changes: 28 additions & 0 deletions unix/zsyscall_openbsd_386.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions unix/zsyscall_openbsd_386.s
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,13 @@ TEXT libc_utimensat_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_utimensat(SB)
GLOBL ·libc_utimensat_trampoline_addr(SB), RODATA, $4
DATA ·libc_utimensat_trampoline_addr(SB)/4, $libc_utimensat_trampoline<>(SB)

TEXT libc_pledge_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_pledge(SB)
GLOBL ·libc_pledge_trampoline_addr(SB), RODATA, $4
DATA ·libc_pledge_trampoline_addr(SB)/4, $libc_pledge_trampoline<>(SB)

TEXT libc_unveil_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_unveil(SB)
GLOBL ·libc_unveil_trampoline_addr(SB), RODATA, $4
DATA ·libc_unveil_trampoline_addr(SB)/4, $libc_unveil_trampoline<>(SB)
28 changes: 28 additions & 0 deletions unix/zsyscall_openbsd_amd64.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions unix/zsyscall_openbsd_amd64.s
Original file line number Diff line number Diff line change
Expand Up @@ -672,3 +672,13 @@ TEXT libc_utimensat_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_utimensat(SB)
GLOBL ·libc_utimensat_trampoline_addr(SB), RODATA, $8
DATA ·libc_utimensat_trampoline_addr(SB)/8, $libc_utimensat_trampoline<>(SB)

TEXT libc_pledge_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_pledge(SB)
GLOBL ·libc_pledge_trampoline_addr(SB), RODATA, $8
DATA ·libc_pledge_trampoline_addr(SB)/8, $libc_pledge_trampoline<>(SB)

TEXT libc_unveil_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_unveil(SB)
GLOBL ·libc_unveil_trampoline_addr(SB), RODATA, $8
DATA ·libc_unveil_trampoline_addr(SB)/8, $libc_unveil_trampoline<>(SB)

0 comments on commit 3317d7f

Please sign in to comment.