Skip to content

runtime: add check before using arguments with -buildmode=c-archive and -buildmode=c-shared on non glibc systems such as musl/uclinux #52541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
6 changes: 5 additions & 1 deletion misc/cgo/testcarchive/testdata/libgo/libgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ func DidMainRun() bool { return ranMain }

//export CheckArgs
func CheckArgs() {
if len(os.Args) != 3 || os.Args[1] != "arg1" || os.Args[2] != "arg2" {
// Dynamic linkers which supply the library initialization functions with the
// main program's argc / argc should have 3 args here, else they should have
// none.
valid := (len(os.Args) == 3 && os.Args[1] == "arg1" && os.Args[2] == "arg2") || (len(os.Args) == 0)
if !valid {
fmt.Printf("CheckArgs: want [_, arg1, arg2], got: %v\n", os.Args)
os.Exit(2)
}
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "unsafe"

//go:linkname _cgo_init _cgo_init
//go:linkname _cgo_thread_start _cgo_thread_start
//go:linkname _cgo_sys_lib_args_valid _cgo_sys_lib_args_valid
//go:linkname _cgo_sys_thread_create _cgo_sys_thread_create
//go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done
//go:linkname _cgo_callers _cgo_callers
Expand All @@ -21,6 +22,7 @@ import "unsafe"
var (
_cgo_init unsafe.Pointer
_cgo_thread_start unsafe.Pointer
_cgo_sys_lib_args_valid unsafe.Pointer
_cgo_sys_thread_create unsafe.Pointer
_cgo_notify_runtime_init_done unsafe.Pointer
_cgo_callers unsafe.Pointer
Expand Down
8 changes: 8 additions & 0 deletions src/runtime/cgo/callbacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ var _cgo_init = &x_cgo_init
var x_cgo_thread_start byte
var _cgo_thread_start = &x_cgo_thread_start

// Determines if the argc / argv passed to the library initialization functions
// are valid.
//go:cgo_import_static x_cgo_sys_lib_args_valid
//go:linkname x_cgo_sys_lib_args_valid x_cgo_sys_lib_args_valid
//go:linkname _cgo_sys_lib_args_valid _cgo_sys_lib_args_valid
var x_cgo_sys_lib_args_valid byte
var _cgo_sys_lib_args_valid = &x_cgo_sys_lib_args_valid

// Creates a new system thread without updating any Go state.
//
// This method is invoked during shared library loading to create a new OS
Expand Down
15 changes: 15 additions & 0 deletions src/runtime/cgo/gcc_libinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <pthread.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // strerror
Expand All @@ -21,6 +22,20 @@ static int runtime_init_done;
// The context function, used when tracing back C calls into Go.
static void (*cgo_context_function)(struct context_arg*);

// Detect if using glibc
int
x_cgo_sys_lib_args_valid()
{
// The ELF gABI doesn't require an argc / argv to be passed to the functions
// in the DT_INIT_ARRAY. However, glibc always does.
// Ignore uClibc masquerading as glibc.
#if defined(__GLIBC__) && !defined(__UCLIBC__)
return 1;
#else
return 0;
#endif
}

void
x_cgo_sys_thread_create(void* (*func)(void*), void* arg) {
pthread_t p;
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/cgo/gcc_libinit_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ _cgo_maybe_run_preinit() {
}
}

int
x_cgo_sys_lib_args_valid() {
return 1;
}

void
x_cgo_sys_thread_create(void (*func)(void*), void* arg) {
uintptr_t thandle;
Expand Down
34 changes: 22 additions & 12 deletions src/runtime/os_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,21 +216,31 @@ var addrspace_vec [1]byte
func mincore(addr unsafe.Pointer, n uintptr, dst *byte) int32

func sysargs(argc int32, argv **byte) {
n := argc + 1

// skip over argv, envp to get to auxv
for argv_index(argv, n) != nil {
n++
argsValid := true
if islibrary || isarchive {
if !sysLibArgsValid() {
argsValid = false
}
}

// skip NULL separator
n++
if argsValid {
n := argc + 1

// now argv+n is auxv
auxv := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*goarch.PtrSize))
if sysauxv(auxv[:]) != 0 {
return
// skip over argv, envp to get to auxv
for argv_index(argv, n) != nil {
n++
}

// skip NULL separator
n++

// now argv+n is auxv
auxv := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*goarch.PtrSize))
if sysauxv(auxv[:]) != 0 {
return
}
}

// In some situations we don't get a loader-provided
// auxv, such as when loaded as a library on Android.
// Fall back to /proc/self/auxv.
Expand Down Expand Up @@ -259,7 +269,7 @@ func sysargs(argc int32, argv **byte) {
return
}
var buf [128]uintptr
n = read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf)))
n := read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf)))
closefd(fd)
if n < 0 {
return
Expand Down
177 changes: 175 additions & 2 deletions src/runtime/runtime1.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,202 @@ var (
argv **byte
)

// when using -buildmode=c-archive or -buildmode=c-shared on linux
// we have to first make sure that glibc is being used or else
// we cannot rely on argc/argv/auxv to be accurate
func sysLibArgsValid() bool {
if _cgo_sys_lib_args_valid != nil {
ret := asmcgocall(_cgo_sys_lib_args_valid, nil)
if ret != 1 {
return false
}
}
return true
}

// nosplit for use in linux startup sysargs
//go:nosplit
func argv_index(argv **byte, i int32) *byte {
if islibrary || isarchive {
if !sysLibArgsValid() {
return nil
}
}
return *(**byte)(add(unsafe.Pointer(argv), uintptr(i)*goarch.PtrSize))
}

// when using -buildmode=c-archive or -buildmode=c-shared on linux
// we have to first make sure that glibc is being used or else
// we cannot rely on argc/argv/auxv to be accurate
func sysLibArgsValid() bool {
if _cgo_sys_lib_args_valid != nil {
ret := asmcgocall(_cgo_sys_lib_args_valid, nil)
if ret != 1 {
return false
}
}
return true
}

var procCmdline = []byte("/proc/self/cmdline\x00")
var procEnviron = []byte("/proc/self/environ\x00")

func args(c int32, v **byte) {
argc = c
argv = v
if sysLibArgsValid() {
argc = c
argv = v
} else if GOOS == "linux" {
argc = 0
argv = nil

// get argc and argv size
var argvSize int32 = 0
fd := open(&procCmdline[0], 0 /* O_RDONLY */, 0)
if fd >= 0 {
for {
var buf [128]byte
c := read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf)))
if c <= 0 {
break
}

argvSize += c

i := c
for i = 0; i < c; i++ {
if buf[i] == 0 {
argc++
}
}
}

closefd(fd)
}

var environSize int32 = 0
var envc int32 = 0
fd = open(&procEnviron[0], 0 /* O_RDONLY */, 0)
if fd >= 0 {
for {
var buf [128]byte
c := read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf)))
if c <= 0 {
break
}

environSize += c

i := c
for i = 0; i < c; i++ {
if buf[i] == 0 {
envc++
}
}
}

closefd(fd)
}

argv = (**byte)(unsafe.Pointer(persistentalloc(goarch.PtrSize*(uintptr(argc)+uintptr(envc)+1), 0, &memstats.other_sys)))
argvPtr := (**byte)(add(unsafe.Pointer(argv), goarch.PtrSize*(uintptr(argc)+uintptr(envc)+1)))
*argvPtr = (*byte)(nil) //null terminate array

if argvSize > 0 {
argvBuf := unsafe.Pointer(persistentalloc(uintptr(argvSize), 0, &memstats.other_sys))
fd := open(&procCmdline[0], 0 /* O_RDONLY */, 0)
if fd >= 0 {
c := read(fd, noescape(argvBuf), int32(argvSize))
if c < 0 {
throw("failed to read arguments")
return
}

if c != int32(argvSize) {
throw("short read arguments")
return
}

strStart := int32(0)
strNum := 0

i := c
var b *byte
for i = 0; i < c; i++ {
b = (*byte)(add(argvBuf, uintptr(i)))
if *b == 0 {
argvPtr := (**byte)(add(unsafe.Pointer(argv), goarch.PtrSize*uintptr(strNum)))
*argvPtr = (*byte)(add(argvBuf, uintptr(strStart)))
strStart = i + 1
strNum++
}
}

closefd(fd)
}
}

if environSize > 0 {
environBuf := unsafe.Pointer(persistentalloc(uintptr(environSize), 0, &memstats.other_sys))
fd := open(&procEnviron[0], 0 /* O_RDONLY */, 0)
if fd >= 0 {
c := read(fd, noescape(environBuf), int32(environSize))
if c < 0 {
throw("failed to read environment")
return
}

if c != int32(environSize) {
throw("short read environment")
return
}

strStart := int32(0)
strNum := 0

i := c
var b *byte
for i = 0; i < c; i++ {
b = (*byte)(add(environBuf, uintptr(i)))
if *b == 0 {
argvPtr := (**byte)(add(unsafe.Pointer(argv), goarch.PtrSize*(uintptr(argc)+uintptr(strNum))))
*argvPtr = (*byte)(add(environBuf, uintptr(strStart)))
strStart = i + 1
strNum++
}
}

closefd(fd)
}
}
}
sysargs(c, v)
}

func goargs() {
if GOOS == "windows" {
return
}

if islibrary || isarchive {
if !sysLibArgsValid() {
return
}
}

argslice = make([]string, argc)
for i := int32(0); i < argc; i++ {
argslice[i] = gostringnocopy(argv_index(argv, i))
}
}

func goenvs_unix() {
if islibrary || isarchive {
if !sysLibArgsValid() {
envs = make([]string, 0)
return
}
}

// TODO(austin): ppc64 in dynamic linking mode doesn't
// guarantee env[] will immediately follow argv. Might cause
// problems.
Expand Down