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/go: dylib on macos with rpath fails to run with go run/test #36572

Open
edaniels opened this issue Jan 15, 2020 · 20 comments
Open

cmd/go: dylib on macos with rpath fails to run with go run/test #36572

edaniels opened this issue Jan 15, 2020 · 20 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Darwin
Milestone

Comments

@edaniels
Copy link
Contributor

edaniels commented Jan 15, 2020

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

$ go version
go version go1.13.6 darwin/amd64

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

$ sw_vers -productVersion
10.15.2

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="auto"
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/eric/Library/Caches/go-build"
GOENV="/Users/eric/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/eric/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/v4/m6f6w8h54j58bp8q4h_138180000gn/T/go-build216040480=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

When trying to run a simple go source file that links against an rpath based dylib, it fails to run with either go run or go test on 1.13.6. However, doing go build followed by running the executable works fine. It also works fine when I build go and test it from the go1.13.6 tag. It also works in go 1.13.5 but from looking at the commits between the two, I cannot find an issue. This leads me to believe that however the 1.13.6 was built on google servers introduced some kind of issue.

A repro with a makefile is located at https://github.com/edaniels/go1136dylibissue.

What did you expect to see?

I expected to see a log output of 2 from:

LD_LIBRARY_PATH=`pwd`/dylib/lib go run github.com/edaniels/go1136dylibissue/cmd

What did you see instead?

dyld: Library not loaded: @rpath/libfoo.dylib
  Referenced from: /var/folders/v4/m6f6w8h54j58bp8q4h_138180000gn/T/go-build330381377/b001/exe/cmd
  Reason: image not found
signal: abort trap
@adamchel
Copy link

I have encountered the same exact problem. Downgrading to 1.13.5 fixed it for me.

@cagedmantis cagedmantis changed the title dylib on macos with rpath fails to run with go run/test cmd/go: dylib on macos with rpath fails to run with go run/test Jan 15, 2020
@randall77
Copy link
Contributor

randall77 commented Jan 15, 2020

I can't reproduce this problem.
I downloaded your test case, and edited the makefile to remove the GOROOT assignment (not needed) and changed the instance of /usr/local/gogit/bin/go to go.
When I run make with Go 1.13.6 on OSX 10.15.2, the makefile runs to completion with no errors.

A possible theory: your GOROOT, go, and/or /usr/local/gogit/bin/go point to more than one installation of Go.

@randall77
Copy link
Contributor

I take that back, I can reproduce. It only reproduces with the downloaded Go pkg, not the source.

@randall77
Copy link
Contributor

@cagedmantis Anything different about the release process for 1.13.6 from 1.13.5?
None of the patches in 1.13.6 look relevant, and building 1.13.6 on my machine from source does not demonstrate the problem.
Different temp directory? Different environment variables? Not sure what might matter...

@cagedmantis
Copy link
Contributor

@randall77 The only difference with the release process between 1.13.6 and 1.13.5 was the enabling of the Hardened Runtime. See #34986 for details.

@cagedmantis cagedmantis added this to the Backlog milestone Jan 15, 2020
@cagedmantis cagedmantis added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jan 15, 2020
@randall77
Copy link
Contributor

randall77 commented Jan 15, 2020

That would make a lot of sense then.

From an article about the hardened runtime:

This involves a series of restrictions being placed on that app, which are intended to protect that app’s runtime integrity from certain types of exploits used by malware. These include code injection, the hijacking of dynamically-linked libraries (DLLs), and tampering with the app’s memory space.

So hard to be sure, but I suspect that part of hardening is squashing any LD_LIBRARY_PATH settings (the second part of that second sentence). So LD_LIBRARY_PATH won't propagate through the go binary to the binary to be run.
Note that we don't generate hardened binaries. It is only the Go tool itself that is hardened. That's why it works if you build and run separately.

I think that although this is unfortunate, it's intentional and there's nothing we can do.

Just wait until Apple starts forcing us to generate hardened binaries...

@cagedmantis
Copy link
Contributor

@randall77 A member of the release team has suggested that we investigate some of the available entitlements (at some point) such as:

Do you think these are entitlements that we should enable?

@randall77
Copy link
Contributor

That first one might possibly fix this issue.
I don't think we would want the second one. We don't want to actually allow unvalidated libraries to be run by the go tool. This entitlement is one that Go-generated binaries would need (assuming they were hardened, which they aren't at the moment) to use LD_LIBRARY_PATH successfully.

I don't think that's really the right way to go though. It's kind of an accident that the go tool passes environment variables unmolested through to temporary executables. It's asking for issues related to the same environment variable meaning different things in the go binary and the user's binary. (e.g. what if you wanted a different CC value for the go tool and the user's binary? Can't do that with CC=gcc go run.)

I think it is reasonable to ask that if you want to pass environment variables to a program written in Go, you have to build and run as separate steps.

You might be able to use the -exec flag to go run to introduce an environment variable at the right time. Something like

go run -exec "env LD_LIBRARY_PATH=/my/library/path" myprogram.go

@edaniels
Copy link
Contributor Author

I think the bigger issue in terms of development is not wanting to have to build test binaries and run them instead of just running go test. It seems like it makes sense to have the go tooling pass through a flag such as this one. It was before so why not make it continue to do so. I’m not sure I see a practical benefit to limiting it for the sake of hardening a build tool.

@randall77
Copy link
Contributor

I'm not worried about hardening the build tool - that's just something that is being forced on us by Apple.

But this issue demonstrates that it just isn't reliable to depend on the go tool faithfully transmitting all the environment variables. Through no fault of our own. What other environment variables is Apple silently squashing? What new ones are they going to start squashing tomorrow?

I suspect setting the first entitlement described above would fix this particular issue. And maybe we should do that, for backwards compatibility if nothing else. But I don't think the underlying problem is solved by doing that. The underlying problem is only solved by build/run separately, or using the -exec flag as I mentioned.

@edaniels
Copy link
Contributor Author

Yep -exec does work. I think in this case, it makes sense to build up a whitelist of environment variables and have LD_LIBRARY_PATH be in it via entitlement.

@bcmills bcmills modified the milestones: Backlog, Unplanned Jan 22, 2020
@bcmills
Copy link
Contributor

bcmills commented Jan 22, 2020

CC @jayconrod @matloob

@cagedmantis
Copy link
Contributor

@bcmills I'm just double checking that this should not be a release blocker for go1.14rc1.

@bcmills
Copy link
Contributor

bcmills commented Jan 30, 2020

Given that this issue appears to only affect dynamic libraries on macOS, has been present since Go 1.13.6, and has two workarounds (splitting the build step from the execution step, or using -exec as described in #36572 (comment)), I don't think this needs to block 1.14 at all.

@cagedmantis
Copy link
Contributor

Thanks for the response @bcmills. I will update #34986 accordingly.

@adamchel
Copy link

The -exec "env LD_LIBRARY_PATH=/my/library/path" workaround works for go run and go test, it does not appear to work for go tool.

I just upgraded to go 1.14.2 from 1.13.5, and it broke my ability to run tests in GoLand. GoLand uses go tool test2json -t to run tests. I've tried giving go tool test2json the -exec flag but it doesn't appear to work.

This is the test setup invoked by GoLand:

/usr/local/go/bin/go test -c -exec "env LD_LIBRARY_PATH=REDACTED" -o /private/var/folders/REDACTED_go github.com/REDACTED #gosetup
/usr/local/go/bin/go tool test2json -t /private/var/folders/REDACTED_go -test.v -test.run "^REDACTED$" -exec "env LD_LIBRARY_PATH=REDACTED" #gosetup
dyld: Library not loaded: @rpath/REDACTED.dylib
  Referenced from: /private/var/folders/REDACTED_go
  Reason: image not found

@randall77 @cagedmantis Is there a workaround you are aware of for passing an LD_LIBRARY_PATH to an invocation of go tool test2json? Or will this require adding -exec as a supported flag to go tool?

@bcmills
Copy link
Contributor

bcmills commented Apr 14, 2020

@adamchel, you can run go tool -n test2json to locate the path to the executable, then invoke that executable yourself with a suitable LD_LIBRARY_PATH.

@TimothyGu
Copy link
Contributor

TimothyGu commented Apr 10, 2021

Instead of finding a way to pass LD_LIBRARY_PATH through, another workaround appears to be hardcoding the rpath of the resultant binary using the -rpath linker option. This also avoids having to build test binaries and run them ourselves. For instance:

CGO_LDFLAGS='-Wl,-rpath,/path/to/library' go test ./pkg

where /path/to/library is the value of LD_LIBRARY_PATH. I haven't tried, but this should also work well with the GoLand IDE.

Edit: Unfortunately, this only works if the external linker is used… i.e., if ./pkg itself uses cgo. If ./pkg itself doesn't use cgo but one of its dependencies does, you may need to add -ldflags=-linkmode=external – or you could use the -exec trick as mentioned before.

@blacktop
Copy link

Is there a way to catch the "lib not found" error in Go? and/or disable a certain cmd or pkg from being loaded if lib is not there?

@jeffwalter-rum
Copy link

Seems like this might be a SIP bug: https://developer.apple.com/forums/thread/737920

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Darwin
Projects
None yet
Development

No branches or pull requests

8 participants