Remove weak references from unix sockets.

The abstract socket namespace no longer holds any references on sockets.
Instead, TryIncRef() is used when a socket is being retrieved in
BoundEndpoint(). Abstract sockets are now responsible for removing themselves
from the namespace they are in, when they are destroyed.

Updates #1486.

PiperOrigin-RevId: 327064173
This commit is contained in:
Dean Deng
2020-08-17 11:42:20 -07:00
committed by gVisor bot
parent 58154194b3
commit 3bd066d503
7 changed files with 91 additions and 39 deletions
+2 -4
View File
@@ -19,10 +19,8 @@ go_template(
)
go_library(
name = "refs",
srcs = [
"refs.go",
],
name = "refs_vfs2",
srcs = ["refs.go"],
visibility = ["//pkg/sentry:internal"],
deps = ["//pkg/context"],
)
+2 -2
View File
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package refs defines an interface for a reference-counted object.
package refs
// Package refs_vfs2 defines an interface for a reference-counted object.
package refs_vfs2
import (
"gvisor.dev/gvisor/pkg/context"
+1
View File
@@ -163,6 +163,7 @@ go_library(
"//pkg/log",
"//pkg/metric",
"//pkg/refs",
"//pkg/refs_vfs2",
"//pkg/safemem",
"//pkg/secio",
"//pkg/sentry/arch",
+50 -27
View File
@@ -15,29 +15,21 @@
package kernel
import (
"fmt"
"syscall"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/refs_vfs2"
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
"gvisor.dev/gvisor/pkg/sync"
)
// +stateify savable
type abstractEndpoint struct {
ep transport.BoundEndpoint
wr *refs.WeakRef
name string
ns *AbstractSocketNamespace
}
// WeakRefGone implements refs.WeakRefUser.WeakRefGone.
func (e *abstractEndpoint) WeakRefGone(context.Context) {
e.ns.mu.Lock()
if e.ns.endpoints[e.name].ep == e.ep {
delete(e.ns.endpoints, e.name)
}
e.ns.mu.Unlock()
ep transport.BoundEndpoint
socket refs_vfs2.RefCounter
name string
ns *AbstractSocketNamespace
}
// AbstractSocketNamespace is used to implement the Linux abstract socket functionality.
@@ -46,7 +38,11 @@ func (e *abstractEndpoint) WeakRefGone(context.Context) {
type AbstractSocketNamespace struct {
mu sync.Mutex `state:"nosave"`
// Keeps mapping from name to endpoint.
// Keeps a mapping from name to endpoint. AbstractSocketNamespace does not hold
// any references on any sockets that it contains; when retrieving a socket,
// TryIncRef() must be called in case the socket is concurrently being
// destroyed. It is the responsibility of the socket to remove itself from the
// abstract socket namespace when it is destroyed.
endpoints map[string]abstractEndpoint
}
@@ -58,15 +54,15 @@ func NewAbstractSocketNamespace() *AbstractSocketNamespace {
}
// A boundEndpoint wraps a transport.BoundEndpoint to maintain a reference on
// its backing object.
// its backing socket.
type boundEndpoint struct {
transport.BoundEndpoint
rc refs.RefCounter
socket refs_vfs2.RefCounter
}
// Release implements transport.BoundEndpoint.Release.
func (e *boundEndpoint) Release(ctx context.Context) {
e.rc.DecRef(ctx)
e.socket.DecRef(ctx)
e.BoundEndpoint.Release(ctx)
}
@@ -81,32 +77,59 @@ func (a *AbstractSocketNamespace) BoundEndpoint(name string) transport.BoundEndp
return nil
}
rc := ep.wr.Get()
if rc == nil {
delete(a.endpoints, name)
if !ep.socket.TryIncRef() {
// The socket has reached zero references and is being destroyed.
return nil
}
return &boundEndpoint{ep.ep, rc}
return &boundEndpoint{ep.ep, ep.socket}
}
// Bind binds the given socket.
//
// When the last reference managed by rc is dropped, ep may be removed from the
// When the last reference managed by socket is dropped, ep may be removed from the
// namespace.
func (a *AbstractSocketNamespace) Bind(ctx context.Context, name string, ep transport.BoundEndpoint, rc refs.RefCounter) error {
func (a *AbstractSocketNamespace) Bind(ctx context.Context, name string, ep transport.BoundEndpoint, socket refs_vfs2.RefCounter) error {
a.mu.Lock()
defer a.mu.Unlock()
// Check if there is already a socket (which has not yet been destroyed) bound at name.
if ep, ok := a.endpoints[name]; ok {
if rc := ep.wr.Get(); rc != nil {
rc.DecRef(ctx)
if ep.socket.TryIncRef() {
ep.socket.DecRef(ctx)
return syscall.EADDRINUSE
}
}
ae := abstractEndpoint{ep: ep, name: name, ns: a}
ae.wr = refs.NewWeakRef(rc, &ae)
ae.socket = socket
a.endpoints[name] = ae
return nil
}
// Remove removes the specified socket at name from the abstract socket
// namespace, if it has not yet been replaced.
func (a *AbstractSocketNamespace) Remove(name string, socket refs_vfs2.RefCounter) {
a.mu.Lock()
defer a.mu.Unlock()
ep, ok := a.endpoints[name]
if !ok {
// We never delete a map entry apart from a socket's destructor (although the
// map entry may be overwritten). Therefore, a socket should exist, even if it
// may not be the one we expect.
panic(fmt.Sprintf("expected socket to exist at '%s' in abstract socket namespace", name))
}
// A Bind() operation may race with callers of Remove(), e.g. in the
// following case:
// socket1 reaches zero references and begins destruction
// a.Bind("foo", ep, socket2) replaces socket1 with socket2
// socket1's destructor calls a.Remove("foo", socket1)
//
// Therefore, we need to check that the socket at name is what we expect
// before modifying the map.
if ep.socket == socket {
delete(a.endpoints, name)
}
}
+14
View File
@@ -1,12 +1,25 @@
load("//tools:defs.bzl", "go_library")
load("//tools/go_generics:defs.bzl", "go_template_instance")
package(licenses = ["notice"])
go_template_instance(
name = "socket_refs",
out = "socket_refs.go",
package = "unix",
prefix = "socketOpsCommon",
template = "//pkg/refs_vfs2:refs_template",
types = {
"T": "socketOpsCommon",
},
)
go_library(
name = "unix",
srcs = [
"device.go",
"io.go",
"socket_refs.go",
"unix.go",
"unix_vfs2.go",
],
@@ -15,6 +28,7 @@ go_library(
"//pkg/abi/linux",
"//pkg/context",
"//pkg/fspath",
"//pkg/log",
"//pkg/refs",
"//pkg/safemem",
"//pkg/sentry/arch",
+17 -5
View File
@@ -24,7 +24,6 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
@@ -80,7 +79,7 @@ func NewWithDirent(ctx context.Context, d *fs.Dirent, ep transport.Endpoint, sty
stype: stype,
},
}
s.EnableLeakCheck("unix.SocketOperations")
s.EnableLeakCheck()
return fs.NewFile(ctx, d, flags, &s)
}
@@ -89,17 +88,26 @@ func NewWithDirent(ctx context.Context, d *fs.Dirent, ep transport.Endpoint, sty
//
// +stateify savable
type socketOpsCommon struct {
refs.AtomicRefCount
socketOpsCommonRefs
socket.SendReceiveTimeout
ep transport.Endpoint
stype linux.SockType
// abstractName and abstractNamespace indicate the name and namespace of the
// socket if it is bound to an abstract socket namespace. Once the socket is
// bound, they cannot be modified.
abstractName string
abstractNamespace *kernel.AbstractSocketNamespace
}
// DecRef implements RefCounter.DecRef.
func (s *socketOpsCommon) DecRef(ctx context.Context) {
s.DecRefWithDestructor(ctx, func(context.Context) {
s.socketOpsCommonRefs.DecRef(func() {
s.ep.Close(ctx)
if s.abstractNamespace != nil {
s.abstractNamespace.Remove(s.abstractName, s)
}
})
}
@@ -284,10 +292,14 @@ func (s *SocketOperations) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error {
if t.IsNetworkNamespaced() {
return syserr.ErrInvalidEndpointState
}
if err := t.AbstractSockets().Bind(t, p[1:], bep, s); err != nil {
asn := t.AbstractSockets()
name := p[1:]
if err := asn.Bind(t, name, bep, s); err != nil {
// syserr.ErrPortInUse corresponds to EADDRINUSE.
return syserr.ErrPortInUse
}
s.abstractName = name
s.abstractNamespace = asn
} else {
// The parent and name.
var d *fs.Dirent
+5 -1
View File
@@ -183,10 +183,14 @@ func (s *SocketVFS2) Bind(t *kernel.Task, sockaddr []byte) *syserr.Error {
if t.IsNetworkNamespaced() {
return syserr.ErrInvalidEndpointState
}
if err := t.AbstractSockets().Bind(t, p[1:], bep, s); err != nil {
asn := t.AbstractSockets()
name := p[1:]
if err := asn.Bind(t, name, bep, s); err != nil {
// syserr.ErrPortInUse corresponds to EADDRINUSE.
return syserr.ErrPortInUse
}
s.abstractName = name
s.abstractNamespace = asn
} else {
path := fspath.Parse(p)
root := t.FSContext().RootDirectoryVFS2()