Skip to content

[next, master] HandleList.Track: Creates an int variable with no references in Go world, only C, causes panics because GC frees it. #218

Closed
@dmitshur

Description

@dmitshur

As mentioned in #196 (comment), I've found a reproducible problem where HandleList.Track is called to track a Go pointer, but when HandleList.Get is later called to retrieve it, there will be a panic (unless Go's GC is turned off via GOGC=off environment variable).

I think this issue affects both master and next branches, but this failing test case uses next branch.

In HandleList.Track:

// Track adds the given pointer to the list of pointers to track and
// returns a pointer value which can be passed to C as an opaque
// pointer.
func (v *HandleList) Track(pointer interface{}) unsafe.Pointer {
    v.Lock()

    slot := v.findUnusedSlot()
    v.handles[slot] = pointer
    v.set[slot] = true

    v.Unlock()

    return unsafe.Pointer(&slot)
}

Note that the local variable slot, of type int, created with the value returned by v.findUnusedSlot(), is only used in the return statement by being taken an address of. That pointer goes into the C world only.

When the C world calls back into Go world, it calls HandleList.Get to try to retrieve the Go pointer stored by HandleList:

// Get retrieves the pointer from the given handle
func (v *HandleList) Get(handle unsafe.Pointer) interface{} {
    slot := *(*int)(handle)

    v.RLock()

    if _, ok := v.set[slot]; !ok {
        panic(fmt.Sprintf("invalid pointer handle: %p", handle))
    }

    ptr := v.handles[slot]

    v.RUnlock()

    return ptr
}

The problem is, by now, the Go GC has run and removed the original slot local variable, so trying to dereference the handle via slot := *(*int)(handle) ends up creating an incorrect value of slot.

I've added a bunch of print statements to git2go and here's the stack trace during the fail case. Note when Track(handle) is called, slot value was 1, but when it's reconstructed by dereferencing a pointer in Get(handle), despite all pointers being the same, the result slot is 0, not 1, causing a panic:

$ git status
On branch update-git2go-api
Your branch is up-to-date with 'origin/update-git2go-api'.
nothing to commit, working directory clean
$ go test -run=TestRepository_Clone_ssh sourcegraph.com/sourcegraph/go-vcs/vcs
2015/07/06 19:07:18 0xc20805a100.Track(handle): 0xc20802af18 [slot: 1]
2015/07/06 19:07:18 populateRemoteCallbacks: ptr.payload: 0xc20802af18
2015/07/06 19:07:18 certificateCheckCallback: data: 0xc20802af18
2015/07/06 19:07:18 0xc20805a100.Get(handle): 0xc20802af18 [slot: 0]
--- FAIL: TestRepository_Clone_ssh (0.31s)
    ssh_test.go:74: Cloning from ssh://[email protected]:55020/git817689297 to /var/folders/_w/3kl_g4rn5sv1nyjblk7tf4h00000gn/T/go-vcs-test/ssh-clone177029419
panic: invalid pointer handle: 0xc20802af18 [recovered]
    panic: invalid pointer handle: 0xc20802af18

goroutine 5 [running]:
testing.func·006()
    /usr/local/go/src/testing/testing.go:441 +0x181
github.com/libgit2/git2go.(*HandleList).Get(0xc20805a100, 0xc20802af18, 0x0, 0x0)
    /Users/Dmitri/go/src/github.com/libgit2/git2go/handles.go:97 +0x2f8
github.com/libgit2/git2go.certificateCheckCallback(0x7fff5fbfe460, 0x0, 0x4f128f0, 0xc20802af18, 0x4f12510)
    /Users/Dmitri/go/src/github.com/libgit2/git2go/remote.go:244 +0x101
github.com/libgit2/git2go._Cfunc_git_clone(0xc20809b930, 0x6000480, 0x60004b0, 0x6000500, 0xc200000000)
    /Users/Dmitri/go/src/github.com/libgit2/git2go/:663 +0x43
github.com/libgit2/git2go.Clone(0xc20803d080, 0x29, 0xc208075310, 0x4f, 0xc208051f00, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/github.com/libgit2/git2go/clone.go:41 +0x1a3
sourcegraph.com/sourcegraph/go-vcs/vcs/git.Clone(0xc20803d080, 0x29, 0xc208075310, 0x4f, 0x1, 0xc208051e80, 0x0, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/git/clone_update.go:61 +0x265
sourcegraph.com/sourcegraph/go-vcs/vcs_test.func·026(0xc20803d080, 0x29, 0xc208075310, 0x4f, 0x1, 0xc208051e80, 0x0, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/ssh_test.go:52 +0x87
sourcegraph.com/sourcegraph/go-vcs/vcs_test.func·028()
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/ssh_test.go:75 +0x3d7
sourcegraph.com/sourcegraph/go-vcs/vcs_test.TestRepository_Clone_ssh(0xc20808e000)
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/ssh_test.go:101 +0x3e0
testing.tRunner(0xc20808e000, 0x4726700)
    /usr/local/go/src/testing/testing.go:447 +0xbf
created by testing.RunTests
    /usr/local/go/src/testing/testing.go:555 +0xa8b

goroutine 1 [chan receive]:
testing.RunTests(0x45d3aa0, 0x4726460, 0x1e, 0x1e, 0x473fc01)
    /usr/local/go/src/testing/testing.go:576 +0x58e
testing.(*M).Run(0xc20803a0f0, 0x47470c0)
    /usr/local/go/src/testing/testing.go:485 +0x6c
main.main()
    sourcegraph.com/sourcegraph/go-vcs/vcs/_test/_testmain.go:136 +0x1d5

goroutine 17 [syscall, locked to thread]:
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:2232 +0x1

goroutine 6 [chan receive]:
testing.func·007()
    /usr/local/go/src/testing/testing.go:559 +0x5f
created by testing.RunTests
    /usr/local/go/src/testing/testing.go:560 +0xb9b

goroutine 15 [runnable]:
net.(*pollDesc).Wait(0xc2080b9950, 0x72, 0x0, 0x0)
    /usr/local/go/src/net/fd_poll_runtime.go:84 +0x47
net.(*pollDesc).WaitRead(0xc2080b9950, 0x0, 0x0)
    /usr/local/go/src/net/fd_poll_runtime.go:89 +0x43
net.(*netFD).accept(0xc2080b98f0, 0x0, 0x4d28b20, 0xc20802b040)
    /usr/local/go/src/net/fd_unix.go:419 +0x40b
net.(*TCPListener).AcceptTCP(0xc208038038, 0xc208018728, 0x0, 0x0)
    /usr/local/go/src/net/tcpsock_posix.go:234 +0x4e
net.(*TCPListener).Accept(0xc208038038, 0x0, 0x0, 0x0, 0x0)
    /usr/local/go/src/net/tcpsock_posix.go:244 +0x4c
sourcegraph.com/sourcegraph/go-vcs/vcs/ssh.func·004()
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/ssh/server.go:101 +0x43
created by sourcegraph.com/sourcegraph/go-vcs/vcs/ssh.(*Server).Start
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/ssh/server.go:114 +0x32e

goroutine 16 [chan receive]:
golang.org/x/crypto/ssh.(*connection).serverAuthenticate(0xc20805c900, 0xc208088370, 0x11, 0x0, 0x0)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/server.go:274 +0x139
golang.org/x/crypto/ssh.(*connection).serverHandshake(0xc20805c900, 0xc208088370, 0xc208038088, 0x0, 0x0)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/server.go:221 +0xc22
golang.org/x/crypto/ssh.NewServerConn(0x4d2bcf8, 0xc208038088, 0xc208080120, 0x0, 0x0, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/server.go:146 +0x103
sourcegraph.com/sourcegraph/go-vcs/vcs/ssh.(*Server).handleConn(0xc208080100, 0x4d2bcf8, 0xc208038088)
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/ssh/server.go:124 +0x94
created by sourcegraph.com/sourcegraph/go-vcs/vcs/ssh.func·004
    /Users/Dmitri/go/src/sourcegraph.com/sourcegraph/go-vcs/vcs/ssh/server.go:112 +0x1ea

goroutine 18 [IO wait]:
net.(*pollDesc).Wait(0xc2080b8840, 0x72, 0x0, 0x0)
    /usr/local/go/src/net/fd_poll_runtime.go:84 +0x47
net.(*pollDesc).WaitRead(0xc2080b8840, 0x0, 0x0)
    /usr/local/go/src/net/fd_poll_runtime.go:89 +0x43
net.(*netFD).Read(0xc2080b87e0, 0xc20800f000, 0x1000, 0x1000, 0x0, 0x4d28b20, 0xc20802a9c8)
    /usr/local/go/src/net/fd_unix.go:242 +0x40f
net.(*conn).Read(0xc208038088, 0xc20800f000, 0x1000, 0x1000, 0x0, 0x0, 0x0)
    /usr/local/go/src/net/net.go:121 +0xdc
bufio.(*Reader).fill(0xc208084600)
    /usr/local/go/src/bufio/bufio.go:97 +0x1ce
bufio.(*Reader).Read(0xc208084600, 0xc20805c220, 0x5, 0x5, 0xc2080b8150, 0x0, 0x0)
    /usr/local/go/src/bufio/bufio.go:174 +0x26c
io.ReadAtLeast(0x4d2bf08, 0xc208084600, 0xc20805c220, 0x5, 0x5, 0x5, 0x0, 0x0, 0x0)
    /usr/local/go/src/io/io.go:298 +0xf1
io.ReadFull(0x4d2bf08, 0xc208084600, 0xc20805c220, 0x5, 0x5, 0x14, 0x0, 0x0)
    /usr/local/go/src/io/io.go:316 +0x6d
golang.org/x/crypto/ssh.(*streamPacketCipher).readPacket(0xc20805c200, 0xc200000004, 0x4d2bf08, 0xc208084600, 0x0, 0x0, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/cipher.go:142 +0xd4
golang.org/x/crypto/ssh.(*connectionState).readPacket(0xc208001e60, 0xc208084600, 0x0, 0x0, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/transport.go:111 +0xe1
golang.org/x/crypto/ssh.(*transport).readPacket(0xc208001e60, 0x0, 0x0, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/transport.go:107 +0x68
golang.org/x/crypto/ssh.(*handshakeTransport).readOnePacket(0xc2080ba000, 0x0, 0x0, 0x0, 0x0, 0x0)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/handshake.go:153 +0x101
golang.org/x/crypto/ssh.(*handshakeTransport).readLoop(0xc2080ba000)
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/handshake.go:133 +0x28
created by golang.org/x/crypto/ssh.newServerTransport
    /Users/Dmitri/go/src/golang.org/x/crypto/ssh/handshake.go:108 +0xea
FAIL    sourcegraph.com/sourcegraph/go-vcs/vcs  0.377s

Note that the issue disappears if you turn off the GC:

vcs $ GOGC=off go test -run=TestRepository_Clone_ssh sourcegraph.com/sourcegraph/go-vcs/vcs
ok      sourcegraph.com/sourcegraph/go-vcs/vcs  0.666s

To reproduce it (without the added prints, of course), do:

go get -u sourcegraph.com/sourcegraph/go-vcs/vcs

Then check out its update-git2go-api branch. Also install git2go on its next branch. And run the go test command as shown above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions