Skip to content
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

runtime: fix segfault due to missing argv on musl-linux c-archive #69325

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
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
17 changes: 17 additions & 0 deletions src/runtime/cgo/callbacks_musl_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build linux

package cgo

import _ "unsafe" // for go:linkname

// x_cgo_is_musl returns 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
14 changes: 14 additions & 0 deletions src/runtime/cgo/gcc_musl_linux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build linux

// 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
}
35 changes: 24 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.
if !libmusl {
// 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 Expand Up @@ -356,16 +360,25 @@ func readRandom(r []byte) int {
}

func goenvs() {
if libmusl {
// Read envs from /proc/self/environ instead
envs = readNullTerminatedStringsFromFile(procEnviron)
return
}
goenvs_unix()
}

//go:linkname _cgo_is_musl _cgo_is_musl
var _cgo_is_musl unsafe.Pointer

// Called to do synchronous initialization of Go code built with
// -buildmode=c-archive or -buildmode=c-shared.
// None of the Go runtime is initialized.
//
//go:nosplit
//go:nowritebarrierrec
func libpreinit() {
libmusl = asmcgocall(_cgo_is_musl, nil) == 1
initsig(true)
}

Expand Down
14 changes: 14 additions & 0 deletions src/runtime/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,20 @@ 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 the binary is an archive or a library, the operating system is Linux,
// and the system uses Musl, then read the environment variables from the
// /proc/self/environ file. Iterate over each null-terminated string read
// from the file. If any string has the specified prefix, return that string.
if libmusl {
for _, value := range readNullTerminatedStringsFromFile(procEnviron) {
if stringslite.HasPrefix(value, prefix) {
return value
}
}
return env
}

n := int32(0)
for argv_index(argv, argc+1+n) != nil {
n++
Expand Down
55 changes: 55 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 @@ -73,6 +76,13 @@ func goargs() {
if GOOS == "windows" {
return
}

// musl-linux library: Read argv from /proc/self/cmdline instead
if libmusl {
argslice = readNullTerminatedStringsFromFile(procCmdline)
return
}

argslice = make([]string, argc)
for i := int32(0); i < argc; i++ {
argslice[i] = gostringnocopy(argv_index(argv, i))
Expand All @@ -94,6 +104,51 @@ func goenvs_unix() {
}
}

// readNullTerminatedStringsFromFile reads a file specified by the given path
// and returns a slice of strings. Each string in the slice is null-terminated
// in the file.
//
// Parameters:
// - path: A null-terminated byte slice representing the file path.
//
// Returns:
// - A slice of strings read from the file, where each string is null-terminated.
//
// It opens the file, reads its contents in chunks,
// and parses the data into a slice of strings based on null-termination.
//
// Note: This function will return nil if the file cannot be opened.
func readNullTerminatedStringsFromFile(path []byte) []string {
fd := open(&path[0], _O_RDONLY, 0)
if fd <= 0 {
return nil
}

// Read the file.
var data []byte
var buf [1024]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
3 changes: 3 additions & 0 deletions src/runtime/runtime2.go
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,9 @@ var (
isarchive bool // -buildmode=c-archive
)

// libmusl is set if the Go archive/library is linked against linux musl libc.
var libmusl bool // set by os_linux libpreinit

// Must agree with internal/buildcfg.FramePointerEnabled.
const framepointer_enabled = GOARCH == "amd64" || GOARCH == "arm64"

Expand Down