Skip to content

Commit

Permalink
runtime: separate error result for mmap
Browse files Browse the repository at this point in the history
Currently mmap returns an unsafe.Pointer that encodes OS errors as
values less than 4096. In practice this is okay, but it borders on
being really unsafe: for example, the value has to be checked
immediately after return and if stack copying were ever to observe
such a value, it would panic. It's also not remotely idiomatic.

Fix this by making mmap return a separate pointer value and error,
like a normal Go function.

Updates #22218.

Change-Id: Iefd965095ffc82cc91118872753a5d39d785c3a6
Reviewed-on: https://go-review.googlesource.com/71270
Run-TryBot: Austin Clements <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
aclements committed Oct 18, 2017
1 parent 3ba818c commit 193088b
Show file tree
Hide file tree
Showing 35 changed files with 237 additions and 103 deletions.
6 changes: 3 additions & 3 deletions src/runtime/cgo/gcc_mmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

#include "libcgo.h"

void *
uintptr_t
x_cgo_mmap(void *addr, uintptr_t length, int32_t prot, int32_t flags, int32_t fd, uint32_t offset) {
void *p;

Expand All @@ -20,9 +20,9 @@ x_cgo_mmap(void *addr, uintptr_t length, int32_t prot, int32_t flags, int32_t fd
_cgo_tsan_release();
if (p == MAP_FAILED) {
/* This is what the Go code expects on failure. */
p = (void *) (uintptr_t) errno;
return (uintptr_t)errno;
}
return p;
return (uintptr_t)p;
}

void
Expand Down
10 changes: 6 additions & 4 deletions src/runtime/cgo_mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ var _cgo_mmap unsafe.Pointer
//go:linkname _cgo_munmap _cgo_munmap
var _cgo_munmap unsafe.Pointer

func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer {
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) {
if _cgo_mmap != nil {
// Make ret a uintptr so that writing to it in the
// function literal does not trigger a write barrier.
// A write barrier here could break because of the way
// that mmap uses the same value both as a pointer and
// an errno value.
// TODO: Fix mmap to return two values.
var ret uintptr
systemstack(func() {
ret = callCgoMmap(addr, n, prot, flags, fd, off)
})
return unsafe.Pointer(ret)
if ret < 4096 {
return nil, int(ret)
}
return unsafe.Pointer(ret), 0
}
return sysMmap(addr, n, prot, flags, fd, off)
}
Expand All @@ -46,7 +48,7 @@ func munmap(addr unsafe.Pointer, n uintptr) {
}

// sysMmap calls the mmap system call. It is implemented in assembly.
func sysMmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer
func sysMmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (p unsafe.Pointer, err int)

// callCgoMmap calls the mmap function in the runtime/cgo package
// using the GCC calling convention. It is implemented in assembly.
Expand Down
22 changes: 11 additions & 11 deletions src/runtime/mem_bsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
// which prevents us from allocating more stack.
//go:nosplit
func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
v := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(v) < 4096 {
v, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
return nil
}
mSysStatInc(sysStat, n)
Expand Down Expand Up @@ -51,8 +51,8 @@ func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
return v
}

p := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(p) < 4096 {
p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
return nil
}
*reserved = true
Expand All @@ -76,22 +76,22 @@ func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
// to do this - we do not on other platforms.
flags |= _MAP_FIXED
}
p := mmap(v, n, _PROT_READ|_PROT_WRITE, flags, -1, 0)
if uintptr(p) == _ENOMEM || (GOOS == "solaris" && uintptr(p) == _sunosEAGAIN) {
p, err := mmap(v, n, _PROT_READ|_PROT_WRITE, flags, -1, 0)
if err == _ENOMEM || (GOOS == "solaris" && err == _sunosEAGAIN) {
throw("runtime: out of memory")
}
if p != v {
print("runtime: address space conflict: map(", v, ") = ", p, "\n")
if p != v || err != 0 {
print("runtime: address space conflict: map(", v, ") = ", p, "(err ", err, ")\n")
throw("runtime: address space conflict")
}
return
}

p := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
if uintptr(p) == _ENOMEM || (GOOS == "solaris" && uintptr(p) == _sunosEAGAIN) {
p, err := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
if err == _ENOMEM || (GOOS == "solaris" && err == _sunosEAGAIN) {
throw("runtime: out of memory")
}
if p != v {
if p != v || err != 0 {
throw("runtime: cannot map pages in arena address space")
}
}
14 changes: 7 additions & 7 deletions src/runtime/mem_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import "unsafe"
// which prevents us from allocating more stack.
//go:nosplit
func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
v := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(v) < 4096 {
v, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
return nil
}
mSysStatInc(sysStat, n)
Expand Down Expand Up @@ -40,8 +40,8 @@ func sysFault(v unsafe.Pointer, n uintptr) {

func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
*reserved = true
p := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(p) < 4096 {
p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
return nil
}
return p
Expand All @@ -53,11 +53,11 @@ const (

func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
mSysStatInc(sysStat, n)
p := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
if uintptr(p) == _ENOMEM {
p, err := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
if err == _ENOMEM {
throw("runtime: out of memory")
}
if p != v {
if p != v || err != 0 {
throw("runtime: cannot map pages in arena address space")
}
}
42 changes: 21 additions & 21 deletions src/runtime/mem_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,30 @@ func addrspace_free(v unsafe.Pointer, n uintptr) bool {
return true
}

func mmap_fixed(v unsafe.Pointer, n uintptr, prot, flags, fd int32, offset uint32) unsafe.Pointer {
p := mmap(v, n, prot, flags, fd, offset)
func mmap_fixed(v unsafe.Pointer, n uintptr, prot, flags, fd int32, offset uint32) (unsafe.Pointer, int) {
p, err := mmap(v, n, prot, flags, fd, offset)
// On some systems, mmap ignores v without
// MAP_FIXED, so retry if the address space is free.
if p != v && addrspace_free(v, n) {
if uintptr(p) > 4096 {
if err == 0 {
munmap(p, n)
}
p = mmap(v, n, prot, flags|_MAP_FIXED, fd, offset)
p, err = mmap(v, n, prot, flags|_MAP_FIXED, fd, offset)
}
return p
return p, err
}

// Don't split the stack as this method may be invoked without a valid G, which
// prevents us from allocating more stack.
//go:nosplit
func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
p := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(p) < 4096 {
if uintptr(p) == _EACCES {
p, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
if err == _EACCES {
print("runtime: mmap: access denied\n")
exit(2)
}
if uintptr(p) == _EAGAIN {
if err == _EAGAIN {
print("runtime: mmap: too much locked memory (check 'ulimit -l').\n")
exit(2)
}
Expand Down Expand Up @@ -186,9 +186,9 @@ func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
// if we can reserve at least 64K and check the assumption in SysMap.
// Only user-mode Linux (UML) rejects these requests.
if sys.PtrSize == 8 && uint64(n) > 1<<32 {
p := mmap_fixed(v, 64<<10, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if p != v {
if uintptr(p) >= 4096 {
p, err := mmap_fixed(v, 64<<10, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if p != v || err != 0 {
if err == 0 {
munmap(p, 64<<10)
}
return nil
Expand All @@ -198,8 +198,8 @@ func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
return v
}

p := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(p) < 4096 {
p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
return nil
}
*reserved = true
Expand All @@ -211,22 +211,22 @@ func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {

// On 64-bit, we don't actually have v reserved, so tread carefully.
if !reserved {
p := mmap_fixed(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(p) == _ENOMEM {
p, err := mmap_fixed(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err == _ENOMEM {
throw("runtime: out of memory")
}
if p != v {
print("runtime: address space conflict: map(", v, ") = ", p, "\n")
if p != v || err != 0 {
print("runtime: address space conflict: map(", v, ") = ", p, " (err ", err, ")\n")
throw("runtime: address space conflict")
}
return
}

p := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
if uintptr(p) == _ENOMEM {
p, err := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
if err == _ENOMEM {
throw("runtime: out of memory")
}
if p != v {
if p != v || err != 0 {
throw("runtime: cannot map pages in arena address space")
}
}
3 changes: 2 additions & 1 deletion src/runtime/mmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import "unsafe"
// We only pass the lower 32 bits of file offset to the
// assembly routine; the higher bits (if required), should be provided
// by the assembly routine as 0.
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer
// The err result is an OS error code such as ENOMEM.
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (p unsafe.Pointer, err int)

// munmap calls the munmap system call. It is implemented in assembly.
func munmap(addr unsafe.Pointer, n uintptr)
6 changes: 3 additions & 3 deletions src/runtime/os3_solaris.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,12 +402,12 @@ func madvise(addr unsafe.Pointer, n uintptr, flags int32) {
}

//go:nosplit
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer {
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) {
p, err := doMmap(uintptr(addr), n, uintptr(prot), uintptr(flags), uintptr(fd), uintptr(off))
if p == ^uintptr(0) {
return unsafe.Pointer(err)
return nil, int(err)
}
return unsafe.Pointer(p)
return unsafe.Pointer(p), 0
}

//go:nosplit
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/os_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,8 @@ func sysargs(argc int32, argv **byte) {
// try using mincore to detect the physical page size.
// mincore should return EINVAL when address is not a multiple of system page size.
const size = 256 << 10 // size of memory region to allocate
p := mmap(nil, size, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if uintptr(p) < 4096 {
p, err := mmap(nil, size, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0)
if err != 0 {
return
}
var n uintptr
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/os_nacl.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func nacl_thread_create(fn uintptr, stk, tls, xx unsafe.Pointer) int32
//go:noescape
func nacl_nanosleep(ts, extra *timespec) int32
func nanotime() int64
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (p unsafe.Pointer, err int)
func exit(code int32)
func osyield()

Expand Down
26 changes: 10 additions & 16 deletions src/runtime/runtime_mmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,10 @@ import (
// what the code in mem_bsd.go, mem_darwin.go, and mem_linux.go expects.
// See the uses of ENOMEM in sysMap in those files.
func TestMmapErrorSign(t *testing.T) {
p := runtime.Mmap(nil, ^uintptr(0)&^(runtime.GetPhysPageSize()-1), 0, runtime.MAP_ANON|runtime.MAP_PRIVATE, -1, 0)
p, err := runtime.Mmap(nil, ^uintptr(0)&^(runtime.GetPhysPageSize()-1), 0, runtime.MAP_ANON|runtime.MAP_PRIVATE, -1, 0)

// The runtime.mmap function is nosplit, but t.Errorf is not.
// Reset the pointer so that we don't get an "invalid stack
// pointer" error from t.Errorf if we call it.
v := uintptr(p)
p = nil

if v != runtime.ENOMEM {
t.Errorf("mmap = %v, want %v", v, runtime.ENOMEM)
if p != nil || err != runtime.ENOMEM {
t.Errorf("mmap = %v, %v, want nil, %v", p, err, runtime.ENOMEM)
}
}

Expand All @@ -35,20 +29,20 @@ func TestPhysPageSize(t *testing.T) {
ps := runtime.GetPhysPageSize()

// Get a region of memory to play with. This should be page-aligned.
b := uintptr(runtime.Mmap(nil, 2*ps, 0, runtime.MAP_ANON|runtime.MAP_PRIVATE, -1, 0))
if b < 4096 {
t.Fatalf("Mmap: %v", b)
b, err := runtime.Mmap(nil, 2*ps, 0, runtime.MAP_ANON|runtime.MAP_PRIVATE, -1, 0)
if err != 0 {
t.Fatalf("Mmap: %v", err)
}

// Mmap should fail at a half page into the buffer.
err := uintptr(runtime.Mmap(unsafe.Pointer(uintptr(b)+ps/2), ps, 0, runtime.MAP_ANON|runtime.MAP_PRIVATE|runtime.MAP_FIXED, -1, 0))
if err >= 4096 {
_, err = runtime.Mmap(unsafe.Pointer(uintptr(b)+ps/2), ps, 0, runtime.MAP_ANON|runtime.MAP_PRIVATE|runtime.MAP_FIXED, -1, 0)
if err == 0 {
t.Errorf("Mmap should have failed with half-page alignment %d, but succeeded: %v", ps/2, err)
}

// Mmap should succeed at a full page into the buffer.
err = uintptr(runtime.Mmap(unsafe.Pointer(uintptr(b)+ps), ps, 0, runtime.MAP_ANON|runtime.MAP_PRIVATE|runtime.MAP_FIXED, -1, 0))
if err < 4096 {
_, err = runtime.Mmap(unsafe.Pointer(uintptr(b)+ps), ps, 0, runtime.MAP_ANON|runtime.MAP_PRIVATE|runtime.MAP_FIXED, -1, 0)
if err != 0 {
t.Errorf("Mmap at full-page alignment %d failed: %v", ps, err)
}
}
8 changes: 7 additions & 1 deletion src/runtime/sys_darwin_386.s
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ TEXT runtime·raiseproc(SB),NOSPLIT,$16
TEXT runtime·mmap(SB),NOSPLIT,$0
MOVL $197, AX
INT $0x80
MOVL AX, ret+24(FP)
JAE ok
MOVL $0, p+24(FP)
MOVL AX, err+28(FP)
RET
ok:
MOVL AX, p+24(FP)
MOVL $0, err+28(FP)
RET

TEXT runtime·madvise(SB),NOSPLIT,$0
Expand Down
8 changes: 7 additions & 1 deletion src/runtime/sys_darwin_amd64.s
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,13 @@ TEXT runtime·mmap(SB),NOSPLIT,$0
MOVL off+28(FP), R9 // arg 6 offset
MOVL $(0x2000000+197), AX // syscall entry
SYSCALL
MOVQ AX, ret+32(FP)
JCC ok
MOVQ $0, p+32(FP)
MOVQ AX, err+40(FP)
RET
ok:
MOVQ AX, p+32(FP)
MOVQ $0, err+40(FP)
RET

TEXT runtime·munmap(SB),NOSPLIT,$0
Expand Down
9 changes: 8 additions & 1 deletion src/runtime/sys_darwin_arm.s
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,14 @@ TEXT runtime·mmap(SB),NOSPLIT,$0
MOVW $0, R6 // off_t is uint64_t
MOVW $SYS_mmap, R12
SWI $0x80
MOVW R0, ret+24(FP)
MOVW $0, R1
BCC ok
MOVW R1, p+24(FP)
MOVW R0, err+28(FP)
RET
ok:
MOVW R0, p+24(FP)
MOVW R1, err+28(FP)
RET

TEXT runtime·munmap(SB),NOSPLIT,$0
Expand Down
8 changes: 7 additions & 1 deletion src/runtime/sys_darwin_arm64.s
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,13 @@ TEXT runtime·mmap(SB),NOSPLIT,$0
MOVW off+28(FP), R5
MOVW $SYS_mmap, R16
SVC $0x80
MOVD R0, ret+32(FP)
BCC ok
MOVD $0, p+32(FP)
MOVD R0, err+40(FP)
RET
ok:
MOVD R0, p+32(FP)
MOVD $0, err+40(FP)
RET

TEXT runtime·munmap(SB),NOSPLIT,$0
Expand Down
Loading

0 comments on commit 193088b

Please sign in to comment.