Skip to content

Commit

Permalink
runtime: fix segfault due to missing argv on musl-linux c-archive
Browse files Browse the repository at this point in the history
This change fixes a segmentation fault that occurs on musl-based systems
when using c-archive (or c-shared with LD_PRELOAD). The issue
was caused by uninitialized argv and envp, which led to crashes at runtime.

This fix ensures that both argv and envp are correctly initialized,
preventing the segmentation fault.

While this fix addresses crashes due to missing argv and envp, it does
not address the error described in issue golang#54805, where a c-shared library
loaded with dlopen triggers an error related initial-exec access to
TLS objects [1] in a dynamically loaded library.

[1]: http://git.musl-libc.org/cgit/musl/commit/?id=5c2f46a214fceeee3c3e41700c51415e0a4f1acd

Fixes golang#13492
  • Loading branch information
adambenhassen committed Sep 6, 2024
1 parent 42d1f08 commit cbc1cd8
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/runtime/cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import "unsafe"
//go:linkname _cgo_pthread_key_created _cgo_pthread_key_created
//go:linkname _cgo_bindm _cgo_bindm
//go:linkname _cgo_getstackbound _cgo_getstackbound
//go:linkname _cgo_is_musl _cgo_is_musl

var (
_cgo_init unsafe.Pointer
Expand All @@ -32,6 +33,7 @@ var (
_cgo_pthread_key_created unsafe.Pointer
_cgo_bindm unsafe.Pointer
_cgo_getstackbound unsafe.Pointer
_cgo_is_musl unsafe.Pointer
)

// iscgo is set to true by the runtime/cgo package
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 @@ -150,3 +150,11 @@ var _cgo_yield unsafe.Pointer
//go:linkname _cgo_getstackbound _cgo_getstackbound
var x_cgo_getstackbound byte
var _cgo_getstackbound = &x_cgo_getstackbound

// x_cgo_is_musl is set to 1 if the C library is musl.

//go:cgo_import_static x_cgo_is_musl
//go:linkname x_cgo_is_musl x_cgo_is_musl
//go:linkname _cgo_is_musl _cgo_is_musl
var x_cgo_is_musl byte
var _cgo_is_musl = &x_cgo_is_musl
11 changes: 11 additions & 0 deletions src/runtime/cgo/gcc_libinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <stdlib.h>
#include <string.h> // strerror
#include <time.h>
#include <limits.h>
#include "libcgo.h"
#include "libcgo_unix.h"

Expand Down Expand Up @@ -179,3 +180,13 @@ pthread_key_destructor(void* g) {
x_crosscall2_ptr(NULL, g, 0, 0);
}
}

// x_cgo_is_musl reports whether the C library is musl.
int
x_cgo_is_musl() {
#if defined(__GLIBC__) || defined(__UCLIBC__)
return 0;
#else
return 1;
#endif
}
26 changes: 15 additions & 11 deletions src/runtime/os_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,21 +233,25 @@ var auxvreadbuf [128]uintptr
func sysargs(argc int32, argv **byte) {
n := argc + 1

// skip over argv, envp to get to auxv
for argv_index(argv, n) != nil {
n++
}
// auxv on argv is not available on musl library/archive. avoid for musl.
if !((islibrary || isarchive) && isMusl()) {
// skip over argv, envp to get to auxv
for argv_index(argv, n) != nil {
n++
}

// skip NULL separator
n++
// skip NULL separator
n++

// now argv+n is auxv
auxvp := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*goarch.PtrSize))
// now argv+n is auxv
auxvp := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*goarch.PtrSize))

if pairs := sysauxv(auxvp[:]); pairs != 0 {
auxv = auxvp[: pairs*2 : pairs*2]
return
if pairs := sysauxv(auxvp[:]); pairs != 0 {
auxv = auxvp[: pairs*2 : pairs*2]
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
8 changes: 8 additions & 0 deletions src/runtime/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,14 @@ func getGodebugEarly() string {
// Similar to goenv_unix but extracts the environment value for
// GODEBUG directly.
// TODO(moehrmann): remove when general goenvs() can be called before cpuinit()
if (isarchive || islibrary) && isMusl() {
for _, value := range fetch_from_fd(procEnviron) {
if hasPrefix(value, prefix) {
return value
}
}
}

n := int32(0)
for argv_index(argv, argc+1+n) != nil {
n++
Expand Down
50 changes: 50 additions & 0 deletions src/runtime/runtime1.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const (
var traceback_cache uint32 = 2 << tracebackShift
var traceback_env uint32

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

// gotraceback returns the current traceback settings.
//
// If level is 0, suppress all tracebacks.
Expand Down Expand Up @@ -56,6 +59,11 @@ var (
argv **byte
)

// isMusl reports whether the Go program is linked with musl libc.
func isMusl() bool {
return asmcgocall(_cgo_is_musl, nil) == 1
}

// nosplit for use in linux startup sysargs.
//
//go:nosplit
Expand All @@ -73,13 +81,24 @@ func goargs() {
if GOOS == "windows" {
return
}

if (isarchive || islibrary) && isMusl() {
argslice = fetch_from_fd(procCmdline)
return
}

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

func goenvs_unix() {
if (isarchive || islibrary) && isMusl() {
envs = fetch_from_fd(procEnviron)
return
}

// TODO(austin): ppc64 in dynamic linking mode doesn't
// guarantee env[] will immediately follow argv. Might cause
// problems.
Expand All @@ -94,6 +113,37 @@ func goenvs_unix() {
}
}

func fetch_from_fd(path []byte) []string {
fd := open(&path[0], 0 /* O_RDONLY */, 0)
if fd <= 0 {
return nil
}

// Read the file in 16-byte chunks.
var data []byte
var buf [16]byte
for {
n := read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf)))
if n <= 0 { // EOF
break
}
data = append(data, buf[:n]...)
}

// Parse the data into a slice of strings.
var start int
var result = make([]string, 0, 8)
for i := 0; i < len(data); i++ {
if data[i] == 0 { // null-termination
result = append(result, gostring(&data[start:i][0]))
start = i + 1
}
}

closefd(fd)
return result
}

func environ() []string {
return envs
}
Expand Down

0 comments on commit cbc1cd8

Please sign in to comment.