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

cmd/cgo: memory sanitizer error due to uninitialized memory in C.CString when using cgo with sanitizers enabled #60912

Closed
bc-lee opened this issue Jun 21, 2023 · 4 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Milestone

Comments

@bc-lee
Copy link

bc-lee commented Jun 21, 2023

What version of Go are you using (go version)?

$ go version
go version go1.20.5 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/builder/.cache/go-build"
GOENV="/home/builder/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/builder/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/builder/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/builder/.local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/builder/.local/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.20.5"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="/home/builder/src/test-golang-cgo-sanitizers/third_party/llvm-build/Release+Asserts/bin/clang"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/builder/src/test-golang-cgo-sanitizers/go.mod"
GOWORK=""
CGO_CFLAGS="--target=x86_64-linux-gnu -nostdinc -isysroot /home/builder/src/test-golang-cgo-sanitizers/build/linux/debian_bullseye_amd64-sysroot -isystem /home/builder/src/test-golang-cgo-sanitizers/third_party/llvm-build/Release+Asserts/lib/clang/17/include -isystem /home/builder/src/test-golang-cgo-sanitizers/third_party/build/linux/debian_bullseye_amd64-sysroot/usr/include -isystem /home/builder/src/test-golang-cgo-sanitizers/third_party/build/linux/debian_bullseye_amd64-sysroot/usr/include/x86_64-linux-gnu -fsanitize=memory"
CGO_CPPFLAGS="-fsanitize=memory"
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-fuse-ld=lld --target=x86_64-linux-gnu --sysroot /home/builder/src/test-golang-cgo-sanitizers/third_party/build/linux/debian_bullseye_amd64-sysroot -L /home/builder/src/test-golang-cgo-sanitizers/third_party/build/linux/debian_bullseye_amd64-sysroot/usr/lib/x86_64-linux-gnu -rdynamic -L /home/builder/src/test-golang-cgo-sanitizers/native -fsanitize=memory /home/builder/src/test-golang-cgo-sanitizers/third_party/llvm-build/Release+Asserts/lib/clang/17/lib/x86_64-unknown-linux-gnu/libclang_rt.msan.a /home/builder/src/test-golang-cgo-sanitizers/third_party/llvm-build/Release+Asserts/lib/clang/17/lib/x86_64-unknown-linux-gnu/libclang_rt.msan_cxx.a"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3455317176=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Sample repo: https://github.com/bc-lee/test-golang-cgo-sanitizers
To reproduce, run the following commands on linux x86_64:

$ ./prepare.sh
$ ./build.sh --sanitizer=msan

Basically, I'm making a executable with cgo, with address/memory sanitizers enabled C libraries.
I checked out Chromium's LLVM build and sysroot to build the executable.

The main code is as follows:

package main

/*
#include <stdlib.h>

extern int foo(const char*);
*/
// #cgo CPPFLAGS: -I${SRCDIR}/native
// #cgo CFLAGS: -std=c11 -Wall
// #cgo LDFLAGS: -Lnative -lfoo
import "C"
import (
	"time"
	"fmt"
	"unsafe"
)

func main() {
	defer doLeakSanitizerCheck()
	currentTime := time.Now()
	currentTimeStr := currentTime.Format("2006-01-02 15:04:05")
	cStr := C.CString(currentTimeStr)
	defer C.free(unsafe.Pointer(cStr))
	result := C.foo(cStr)
	fmt.Printf("Result: %d\n", result)
}

From my understanding, C.Cstring allocates a memory(using malloc) and returns a pointer to it. So I'm calling C.free to free the memory.
However when I run the executable, I get the following error:

$ ./build.sh --sanitizer=msan
Build a native library for msan on linux/x86_64
~/src/test-golang-cgo-sanitizers/native ~/src/test-golang-cgo-sanitizers
~/src/test-golang-cgo-sanitizers
Build a Go library for msan on linux/x86_64
Run the Go binary
Uninitialized bytes in __interceptor_strlen at offset 0 inside [0x702000000080, 20)
==3993935==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0x4059d4 in foo (/home/builder/src/test-golang-cgo-sanitizers/main+0x4059d4)
    #1 0x3ffccf in _cgo_26aadca938df_Cfunc_foo (/home/builder/src/test-golang-cgo-sanitizers/main+0x3ffccf)
    #2 0x3cf843 in runtime.asmcgocall.abi0 /home/builder/.local/go/src/runtime/asm_amd64.s:848

SUMMARY: MemorySanitizer: use-of-uninitialized-value (/home/builder/src/test-golang-cgo-sanitizers/main+0x4059d4) in foo
Exiting

It seems that msan treats the memory allocated by C.CString as uninitialized.
I'm not sure if this is a bug, or golang currently does not support this kind of use case, or I'm doing something wrong.

What did you expect to see?

No error.

What did you see instead?

Memory sanitizer error.

@bcmills bcmills added the compiler/runtime Issues related to the Go compiler and/or runtime. label Jun 21, 2023
@dmitshur dmitshur changed the title cgo: Memory sanitizer error due to uninitialized memory in C.CString when using cgo with sanitizers enabled cmd/cgo: memory sanitizer error due to uninitialized memory in C.CString when using cgo with sanitizers enabled Jun 22, 2023
@dmitshur dmitshur added this to the Backlog milestone Jun 22, 2023
@dmitshur dmitshur added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jun 22, 2023
@dmitshur
Copy link
Contributor

CC @golang/compiler.

@bc-lee
Copy link
Author

bc-lee commented Jun 23, 2023

I'm trying to understand why MSAN reports an uninitialized value in the above code.

I put a gdb script to understand what's going on:

Click to expand
break malloc
commands
silent
printf "malloc(%d)\n", $rdi
continue
end
break foo
commands
silent
printf "foo(%p)\n", $rdi
continue
end
break __msan_unpoison
commands
silent
printf "__msan_unpoison(%p, %d)\n", $rdi, $rsi
continue
end

And I get the following output:

...

malloc(20)
__msan_unpoison(0x2fffffffdc88, 255)
__msan_unpoison(0x7ffff7ca0748, 0)
__msan_unpoison(0x7ffff7fbb680, 0)
__msan_unpoison(0x2fffffffdc88, 255)
__msan_unpoison(0x7ffff7ca0748, 0)
__msan_unpoison(0x7ffff7fbb680, 0)
__msan_unpoison(0x2fffffffdc88, 255)
__msan_unpoison(0x7ffff7ca0748, 0)
__msan_unpoison(0x7ffff7fbb680, 0)
__msan_unpoison(0x2fffffffdc80, 255)
__msan_unpoison(0x7ffff7ca0748, 0)
__msan_unpoison(0x7ffff7fbb680, 0)
__msan_unpoison(0xc000108230, 16)
__msan_unpoison(0x7ffff7ca0748, 0)
__msan_unpoison(0x7ffff7fbb680, 0)
__msan_unpoison(0xc000108230, 16)
__msan_unpoison(0x2fffffffdc48, 255)
__msan_unpoison(0x2fffffffdc40, 255)
__msan_unpoison(0x2fffffffdc38, 255)
__msan_unpoison(0x2fffffffdc34, 255)
__msan_unpoison(0x2fffffffdbd8, 255)
foo(0x702000000080)
__msan_unpoison(0x702000000080, 255)
Uninitialized bytes in strlen at offset 0 inside [0x702000000080, 20)
__msan_unpoison(0x7ffff7ea3331, 195)
__msan_unpoison(0x7ffff7ea3331, 3)
__msan_unpoison(0x2cdd99, 712)
__msan_unpoison(0x2cdd99, 3)
__msan_unpoison(0x2cdd99, 602)
__msan_unpoison(0x2cdd99, 3)
__msan_unpoison(0x2cdd99, 825)
__msan_unpoison(0x2cdd99, 3)
__msan_unpoison(0x2cdd99, 819)
__msan_unpoison(0x2cdd99, 3)
__msan_unpoison(0x2cdd99, 1694)
__msan_unpoison(0x2cdd99, 3)
__msan_unpoison(0x2cdd99, 2952)
__msan_unpoison(0x2cdd99, 3)
__msan_unpoison(0x2cdd99, 2900)
__msan_unpoison(0x2cdd99, 3)
__msan_unpoison(0x2cdd99, 2900)
==414583==WARNING: MemorySanitizer: use-of-uninitialized-value

...

It seems that the memory allocated by malloc isn't unpoisoned by __msan_unpoison. (__msan_unpoison for 0x702000000080 is called after the call to foo)

I thought the copy held in https://github.com/golang/go/blob/go1.20.5/src/cmd/cgo/out.go#L1670 will let MSAN know that the memory is initialized, but apparently it doesn't.

Any idea why?

@mknyszek
Copy link
Contributor

mknyszek commented Jul 5, 2023

In triage, we think that maybe your build.sh isn't passing everything needed to go build. We think it's missing -msan, which might be why it's this C string copy code isn't getting instrumented correctly. (-tags msan is passed, but that's insufficient; it doesn't actually do the instrumentation. Also, -msan implies -tags msan.)

@mknyszek mknyszek added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Jul 5, 2023
@bc-lee
Copy link
Author

bc-lee commented Jul 6, 2023

Aha, I checked that there are no error messages after I replaced '-tags msan' with '-msan' in build.sh, and the binary is working fine. Thanks!
I'm closing this issue.

@bc-lee bc-lee closed this as completed Jul 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Projects
None yet
Development

No branches or pull requests

4 participants