Shutdown host sockets on internal shutdown

This is required to make the shutdown visible to peers outside the
sandbox.

The readClosed / writeClosed fields were dropped, as they were
preventing a shutdown socket from reading the remainder of queued bytes.
The host syscalls will return the appropriate errors for shutdown.

The control message tests have been split out of socket_unix.cc to make
the (few) remaining tests accessible to testing inherited host UDS,
which don't support sending control messages.

Updates #273

PiperOrigin-RevId: 251763060
This commit is contained in:
Michael Pratt
2019-06-05 18:40:37 -07:00
committed by Shentubot
parent a12848ffeb
commit 57772db2e7
10 changed files with 1650 additions and 1621 deletions
+28 -34
View File
@@ -15,6 +15,7 @@
package host
import (
"fmt"
"sync"
"syscall"
@@ -51,20 +52,6 @@ type ConnectedEndpoint struct {
// ref keeps track of references to a connectedEndpoint.
ref refs.AtomicRefCount
// mu protects fd, readClosed and writeClosed.
mu sync.RWMutex `state:"nosave"`
// file is an *fd.FD containing the FD backing this endpoint. It must be
// set to nil if it has been closed.
file *fd.FD `state:"nosave"`
// readClosed is true if the FD has read shutdown or if it has been closed.
readClosed bool
// writeClosed is true if the FD has write shutdown or if it has been
// closed.
writeClosed bool
// If srfd >= 0, it is the host FD that file was imported from.
srfd int `state:"wait"`
@@ -78,6 +65,13 @@ type ConnectedEndpoint struct {
// prevent lots of small messages from filling the real send buffer
// size on the host.
sndbuf int `state:"nosave"`
// mu protects the fields below.
mu sync.RWMutex `state:"nosave"`
// file is an *fd.FD containing the FD backing this endpoint. It must be
// set to nil if it has been closed.
file *fd.FD `state:"nosave"`
}
// init performs initialization required for creating new ConnectedEndpoints and
@@ -208,9 +202,6 @@ func newSocket(ctx context.Context, orgfd int, saveable bool) (*fs.File, error)
func (c *ConnectedEndpoint) Send(data [][]byte, controlMessages transport.ControlMessages, from tcpip.FullAddress) (uintptr, bool, *syserr.Error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.writeClosed {
return 0, false, syserr.ErrClosedForSend
}
if !controlMessages.Empty() {
return 0, false, syserr.ErrInvalidEndpointState
@@ -244,8 +235,13 @@ func (c *ConnectedEndpoint) SendNotify() {}
// CloseSend implements transport.ConnectedEndpoint.CloseSend.
func (c *ConnectedEndpoint) CloseSend() {
c.mu.Lock()
c.writeClosed = true
c.mu.Unlock()
defer c.mu.Unlock()
if err := syscall.Shutdown(c.file.FD(), syscall.SHUT_WR); err != nil {
// A well-formed UDS shutdown can't fail. See
// net/unix/af_unix.c:unix_shutdown.
panic(fmt.Sprintf("failed write shutdown on host socket %+v: %v", c, err))
}
}
// CloseNotify implements transport.ConnectedEndpoint.CloseNotify.
@@ -255,9 +251,7 @@ func (c *ConnectedEndpoint) CloseNotify() {}
func (c *ConnectedEndpoint) Writable() bool {
c.mu.RLock()
defer c.mu.RUnlock()
if c.writeClosed {
return true
}
return fdnotifier.NonBlockingPoll(int32(c.file.FD()), waiter.EventOut)&waiter.EventOut != 0
}
@@ -285,9 +279,6 @@ func (c *ConnectedEndpoint) EventUpdate() {
func (c *ConnectedEndpoint) Recv(data [][]byte, creds bool, numRights uintptr, peek bool) (uintptr, uintptr, transport.ControlMessages, bool, tcpip.FullAddress, bool, *syserr.Error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.readClosed {
return 0, 0, transport.ControlMessages{}, false, tcpip.FullAddress{}, false, syserr.ErrClosedForReceive
}
var cm unet.ControlMessage
if numRights > 0 {
@@ -344,31 +335,34 @@ func (c *ConnectedEndpoint) RecvNotify() {}
// CloseRecv implements transport.Receiver.CloseRecv.
func (c *ConnectedEndpoint) CloseRecv() {
c.mu.Lock()
c.readClosed = true
c.mu.Unlock()
defer c.mu.Unlock()
if err := syscall.Shutdown(c.file.FD(), syscall.SHUT_RD); err != nil {
// A well-formed UDS shutdown can't fail. See
// net/unix/af_unix.c:unix_shutdown.
panic(fmt.Sprintf("failed read shutdown on host socket %+v: %v", c, err))
}
}
// Readable implements transport.Receiver.Readable.
func (c *ConnectedEndpoint) Readable() bool {
c.mu.RLock()
defer c.mu.RUnlock()
if c.readClosed {
return true
}
return fdnotifier.NonBlockingPoll(int32(c.file.FD()), waiter.EventIn)&waiter.EventIn != 0
}
// SendQueuedSize implements transport.Receiver.SendQueuedSize.
func (c *ConnectedEndpoint) SendQueuedSize() int64 {
// SendQueuedSize isn't supported for host sockets because we don't allow the
// sentry to call ioctl(2).
// TODO(gvisor.dev/issue/273): SendQueuedSize isn't supported for host
// sockets because we don't allow the sentry to call ioctl(2).
return -1
}
// RecvQueuedSize implements transport.Receiver.RecvQueuedSize.
func (c *ConnectedEndpoint) RecvQueuedSize() int64 {
// RecvQueuedSize isn't supported for host sockets because we don't allow the
// sentry to call ioctl(2).
// TODO(gvisor.dev/issue/273): RecvQueuedSize isn't supported for host
// sockets because we don't allow the sentry to call ioctl(2).
return -1
}
-156
View File
@@ -198,20 +198,6 @@ func TestListen(t *testing.T) {
}
}
func TestSend(t *testing.T) {
e := ConnectedEndpoint{writeClosed: true}
if _, _, err := e.Send(nil, transport.ControlMessages{}, tcpip.FullAddress{}); err != syserr.ErrClosedForSend {
t.Errorf("Got %#v.Send() = %v, want = %v", e, err, syserr.ErrClosedForSend)
}
}
func TestRecv(t *testing.T) {
e := ConnectedEndpoint{readClosed: true}
if _, _, _, _, _, _, err := e.Recv(nil, false, 0, false); err != syserr.ErrClosedForReceive {
t.Errorf("Got %#v.Recv() = %v, want = %v", e, err, syserr.ErrClosedForReceive)
}
}
func TestPasscred(t *testing.T) {
e := ConnectedEndpoint{}
if got, want := e.Passcred(), false; got != want {
@@ -244,20 +230,6 @@ func TestQueuedSize(t *testing.T) {
}
}
func TestReadable(t *testing.T) {
e := ConnectedEndpoint{readClosed: true}
if got, want := e.Readable(), true; got != want {
t.Errorf("Got %#v.Readable() = %t, want = %t", e, got, want)
}
}
func TestWritable(t *testing.T) {
e := ConnectedEndpoint{writeClosed: true}
if got, want := e.Writable(), true; got != want {
t.Errorf("Got %#v.Writable() = %t, want = %t", e, got, want)
}
}
func TestRelease(t *testing.T) {
f, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
@@ -272,131 +244,3 @@ func TestRelease(t *testing.T) {
t.Errorf("got = %#v, want = %#v", c, want)
}
}
func TestClose(t *testing.T) {
type testCase struct {
name string
cep *ConnectedEndpoint
addFD bool
f func()
want *ConnectedEndpoint
}
var tests []testCase
// nil is the value used by ConnectedEndpoint to indicate a closed file.
// Non-nil files are used to check if the file gets closed.
f, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c := &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f)}
tests = append(tests, testCase{
name: "First CloseRecv",
cep: c,
addFD: false,
f: c.CloseRecv,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true},
})
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true}
tests = append(tests, testCase{
name: "Second CloseRecv",
cep: c,
addFD: false,
f: c.CloseRecv,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true},
})
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f)}
tests = append(tests, testCase{
name: "First CloseSend",
cep: c,
addFD: false,
f: c.CloseSend,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, writeClosed: true},
})
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), writeClosed: true}
tests = append(tests, testCase{
name: "Second CloseSend",
cep: c,
addFD: false,
f: c.CloseSend,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, writeClosed: true},
})
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), writeClosed: true}
tests = append(tests, testCase{
name: "CloseSend then CloseRecv",
cep: c,
addFD: true,
f: c.CloseRecv,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
})
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true}
tests = append(tests, testCase{
name: "CloseRecv then CloseSend",
cep: c,
addFD: true,
f: c.CloseSend,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
})
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true, writeClosed: true}
tests = append(tests, testCase{
name: "Full close then CloseRecv",
cep: c,
addFD: false,
f: c.CloseRecv,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
})
f, err = syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("Creating socket:", err)
}
c = &ConnectedEndpoint{queue: &waiter.Queue{}, file: fd.New(f), readClosed: true, writeClosed: true}
tests = append(tests, testCase{
name: "Full close then CloseSend",
cep: c,
addFD: false,
f: c.CloseSend,
want: &ConnectedEndpoint{queue: c.queue, file: c.file, readClosed: true, writeClosed: true},
})
for _, test := range tests {
if test.addFD {
fdnotifier.AddFD(int32(test.cep.file.FD()), nil)
}
if test.f(); !reflect.DeepEqual(test.cep, test.want) {
t.Errorf("%s: got = %#v, want = %#v", test.name, test.cep, test.want)
}
}
}
+4
View File
@@ -246,6 +246,10 @@ var allowedSyscalls = seccomp.SyscallRules{
},
syscall.SYS_SETITIMER: {},
syscall.SYS_SHUTDOWN: []seccomp.Rule{
// Used by fs/host to shutdown host sockets.
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RD)},
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_WR)},
// Used by unet to shutdown connections.
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RDWR)},
},
syscall.SYS_SIGALTSTACK: {},
+23
View File
@@ -2096,6 +2096,7 @@ cc_binary(
deps = [
":socket_generic_test_cases",
":socket_test_util",
":socket_unix_cmsg_test_cases",
":socket_unix_test_cases",
":unix_domain_socket_test_util",
"//test/util:test_main",
@@ -2369,6 +2370,7 @@ cc_binary(
deps = [
":socket_generic_test_cases",
":socket_test_util",
":socket_unix_cmsg_test_cases",
":socket_unix_test_cases",
":unix_domain_socket_test_util",
"//test/util:test_main",
@@ -2490,6 +2492,26 @@ cc_library(
alwayslink = 1,
)
cc_library(
name = "socket_unix_cmsg_test_cases",
testonly = 1,
srcs = [
"socket_unix_cmsg.cc",
],
hdrs = [
"socket_unix_cmsg.h",
],
deps = [
":socket_test_util",
":unix_domain_socket_test_util",
"//test/util:test_util",
"//test/util:thread_util",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest",
],
alwayslink = 1,
)
cc_library(
name = "socket_stream_blocking_test_cases",
testonly = 1,
@@ -2733,6 +2755,7 @@ cc_binary(
linkstatic = 1,
deps = [
":socket_test_util",
":socket_unix_cmsg_test_cases",
":socket_unix_test_cases",
":unix_domain_socket_test_util",
"//test/util:test_main",
+5
View File
@@ -17,6 +17,7 @@
#include "test/syscalls/linux/socket_generic.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/syscalls/linux/socket_unix.h"
#include "test/syscalls/linux/socket_unix_cmsg.h"
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
#include "test/util/test_util.h"
@@ -38,5 +39,9 @@ INSTANTIATE_TEST_SUITE_P(
AbstractUnixSockets, UnixSocketPairTest,
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
INSTANTIATE_TEST_SUITE_P(
AbstractUnixSockets, UnixSocketPairCmsgTest,
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
} // namespace testing
} // namespace gvisor
+5
View File
@@ -17,6 +17,7 @@
#include "test/syscalls/linux/socket_generic.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/syscalls/linux/socket_unix.h"
#include "test/syscalls/linux/socket_unix_cmsg.h"
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
#include "test/util/test_util.h"
@@ -38,5 +39,9 @@ INSTANTIATE_TEST_SUITE_P(
FilesystemUnixSockets, UnixSocketPairTest,
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
INSTANTIATE_TEST_SUITE_P(
FilesystemUnixSockets, UnixSocketPairCmsgTest,
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
} // namespace testing
} // namespace gvisor
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
#include "test/syscalls/linux/socket_test_util.h"
namespace gvisor {
namespace testing {
// Test fixture for tests that apply to pairs of connected unix sockets about
// control messages.
using UnixSocketPairCmsgTest = SocketPairTest;
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_UNIX_CMSG_H_
+5
View File
@@ -16,6 +16,7 @@
#include "test/syscalls/linux/socket_test_util.h"
#include "test/syscalls/linux/socket_unix.h"
#include "test/syscalls/linux/socket_unix_cmsg.h"
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
#include "test/util/test_util.h"
@@ -33,5 +34,9 @@ INSTANTIATE_TEST_SUITE_P(
AllUnixDomainSockets, UnixSocketPairTest,
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
INSTANTIATE_TEST_SUITE_P(
AllUnixDomainSockets, UnixSocketPairCmsgTest,
::testing::ValuesIn(IncludeReversals(GetSocketPairs())));
} // namespace testing
} // namespace gvisor