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

Fixes potential cgo.Handle panic #13479

Merged
merged 18 commits into from
Jun 16, 2022
Merged

Conversation

ibeckermayer
Copy link
Contributor

The rdpclient.Client in Go has a cgo.Handle field, which is a special type cgo provides to deal with on of the idiosyncrasies of working between Go and C:

Handle provides a way to pass values that contain Go pointers (pointers to memory allocated by Go) between Go and C without breaking the cgo pointer passing rules. A Handle is an integer value that can represent any Go value. A Handle can be passed through C and back to Go, and Go code can use the Handle to retrieve the original Go value.

The underlying object of a cgo.Handle can be retrieved by calling handle.Value(), like we do in handle_bitmap:

//export handle_bitmap
func handle_bitmap(handle C.uintptr_t, cb *C.CGOBitmap) C.CGOErrCode {
return cgo.Handle(handle).Value().(*Client).handleBitmap(cb)
}

and its memory must be explicitly released after use by calling handle.Delete(). Previously, we were calling handle.Delete() in Client.Close()

// Close shuts down the client and closes any existing connections.
// It is safe to call multiple times, from multiple goroutines.
// Calls other than the first one are no-ops.
func (c *Client) Close() {
c.closeOnce.Do(func() {
c.handle.Delete()
if err := C.close_rdp(c.rustClient); err != C.ErrCodeSuccess {
c.cfg.Log.Warningf("failed to close the RDP client")
}
})
}

which was deferred in both the video output streaming goroutine (which calls handle_bitmap repeatedly)

// Video output streaming worker goroutine.
c.wg.Add(1)
go func() {
defer c.wg.Done()
defer c.Close()
defer c.cfg.Log.Info("RDP output streaming finished")

and in the user input streaming goroutine:

// User input streaming worker goroutine.
c.wg.Add(1)
go func() {
defer c.wg.Done()
defer c.Close()
defer c.cfg.Log.Info("TDP input streaming finished")
.

When a user closes a tab like in #13407, the input streaming goroutine returns

msg, err := c.cfg.Conn.InputMessage()
if errors.Is(err, io.EOF) {
return

meaning the defer c.Close() gets called and calls c.handle.Delete(). Therefore, its possible that the video output streaming goroutine continues running for long enough to call handle_bitmap again, which attempts to call c.handle.Value() on an invalid handle (the one that's been Delete()ed), which causes the panic. The precise mechanics of how this happens, and why it happens more often when smartcard negotiation is taking place but not in other circumstances, is not entirely clear to me (though I have the beginnings of a hypothesis). However with these changes, such a scenario is now impossible, and so the panic should never occur.

Closes #13407

Copy link
Collaborator

@zmb3 zmb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you've definitely tracked down the source of the issue, but I'd like to clean up the solution a bit.

lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
Comment on lines 118 to 121
// rdpc.Wait() typically takes care of calling rdpc.Cleanup().
// However, if something fails between the call to New() and
// rdpc.Wait(), the caller MUST call rdpc.Cleanup() to ensure
// all memory is released.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a confusing API and I would like to avoid it.

How is someone supposed to know if "something fails between New() and Wait()" anyway?

Is there some way we could perhaps:

  • unexport cleanup()
  • make it a required convention to always call Wait() after new, to ensure cleanup happens
  • make Wait() just perform cleanup and return immediately if something failed with New

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree its a confusing API. What I was trying to express was what we have going on here:

where we create a New() and then use it for things that might fail before calling Wait().

I think I found a way to refactor this so that the API makes more sense though, see f61a89f

lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
Isaiah Becker-Mayer added 4 commits June 14, 2022 12:52
Copy link
Collaborator

@zmb3 zmb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks much better. A few nits but otherwise LGTM.

lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
lib/srv/desktop/rdp/rdpclient/client.go Outdated Show resolved Hide resolved
// UpdateClientActivity before starting monitor to
// be doubly sure that the client isn't disconnected
// due to an idle timeout before its had the chance to
// call StartAndWait()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// call StartAndWait()
// call Run()

@ibeckermayer ibeckermayer enabled auto-merge (squash) June 15, 2022 18:04
@ibeckermayer ibeckermayer merged commit c3163b8 into master Jun 16, 2022
@github-actions
Copy link

@ibeckermayer See the table below for backport results.

Branch Result
branch/v10 Create PR
branch/v9 Failed

@zmb3
Copy link
Collaborator

zmb3 commented Jun 16, 2022

@ibeckermayer don't forget to open backports :-)

@ibeckermayer
Copy link
Contributor Author

v9 backport fixed: #13590

ibeckermayer pushed a commit that referenced this pull request Jun 21, 2022
@zmb3 zmb3 deleted the isaiah/fix-gohandle-mem-bug branch May 7, 2024 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

desktop access crash
3 participants