mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
+2
-4
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -163,6 +163,7 @@ go_library(
|
||||
"//pkg/log",
|
||||
"//pkg/metric",
|
||||
"//pkg/refs",
|
||||
"//pkg/refs_vfs2",
|
||||
"//pkg/safemem",
|
||||
"//pkg/secio",
|
||||
"//pkg/sentry/arch",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user