Add SO_PASSCRED support to netlink sockets

Since we only supporting sending messages from the kernel, the peer is always
the kernel, simplifying handling.

There are currently no known users of SO_PASSCRED that would actually receive
messages from gVisor, but adding full support is barely more work than stubbing
out fake support.

Updates #1117
Fixes #1119

PiperOrigin-RevId: 277981465
This commit is contained in:
Michael Pratt
2019-11-01 12:45:11 -07:00
committed by gVisor bot
parent 2a709a1b7b
commit 515fee5b6d
3 changed files with 183 additions and 4 deletions
+1
View File
@@ -20,6 +20,7 @@ go_library(
"//pkg/sentry/fs",
"//pkg/sentry/fs/fsutil",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/time",
"//pkg/sentry/safemem",
"//pkg/sentry/socket",
+73 -3
View File
@@ -27,6 +27,7 @@ import (
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
"gvisor.dev/gvisor/pkg/sentry/safemem"
"gvisor.dev/gvisor/pkg/sentry/socket"
@@ -61,7 +62,7 @@ var netlinkSocketDevice = device.NewAnonDevice()
// This implementation only supports userspace sending and receiving messages
// to/from the kernel.
//
// Socket implements socket.Socket.
// Socket implements socket.Socket and transport.Credentialer.
//
// +stateify savable
type Socket struct {
@@ -104,9 +105,13 @@ type Socket struct {
// sendBufferSize is the send buffer "size". We don't actually have a
// fixed buffer but only consume this many bytes.
sendBufferSize uint32
// passcred indicates if this socket wants SCM credentials.
passcred bool
}
var _ socket.Socket = (*Socket)(nil)
var _ transport.Credentialer = (*Socket)(nil)
// NewSocket creates a new Socket.
func NewSocket(t *kernel.Task, skType linux.SockType, protocol Protocol) (*Socket, *syserr.Error) {
@@ -172,6 +177,22 @@ func (s *Socket) EventUnregister(e *waiter.Entry) {
s.ep.EventUnregister(e)
}
// Passcred implements transport.Credentialer.Passcred.
func (s *Socket) Passcred() bool {
s.mu.Lock()
passcred := s.passcred
s.mu.Unlock()
return passcred
}
// ConnectedPasscred implements transport.Credentialer.ConnectedPasscred.
func (s *Socket) ConnectedPasscred() bool {
// This socket is connected to the kernel, which doesn't need creds.
//
// This is arbitrary, as ConnectedPasscred on this type has no callers.
return false
}
// Ioctl implements fs.FileOperations.Ioctl.
func (*Socket) Ioctl(context.Context, *fs.File, usermem.IO, arch.SyscallArguments) (uintptr, error) {
// TODO(b/68878065): no ioctls supported.
@@ -309,9 +330,20 @@ func (s *Socket) GetSockOpt(t *kernel.Task, level int, name int, outPtr usermem.
// We don't have limit on receiving size.
return int32(math.MaxInt32), nil
case linux.SO_PASSCRED:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
}
var passcred int32
if s.Passcred() {
passcred = 1
}
return passcred, nil
default:
socket.GetSockOptEmitUnimplementedEvent(t, name)
}
case linux.SOL_NETLINK:
switch name {
case linux.NETLINK_BROADCAST_ERROR,
@@ -348,6 +380,7 @@ func (s *Socket) SetSockOpt(t *kernel.Task, level int, name int, opt []byte) *sy
s.sendBufferSize = size
s.mu.Unlock()
return nil
case linux.SO_RCVBUF:
if len(opt) < sizeOfInt32 {
return syserr.ErrInvalidArgument
@@ -355,6 +388,18 @@ func (s *Socket) SetSockOpt(t *kernel.Task, level int, name int, opt []byte) *sy
// We don't have limit on receiving size. So just accept anything as
// valid for compatibility.
return nil
case linux.SO_PASSCRED:
if len(opt) < sizeOfInt32 {
return syserr.ErrInvalidArgument
}
passcred := usermem.ByteOrder.Uint32(opt)
s.mu.Lock()
s.passcred = passcred != 0
s.mu.Unlock()
return nil
default:
socket.SetSockOptEmitUnimplementedEvent(t, name)
}
@@ -483,6 +528,26 @@ func (s *Socket) Read(ctx context.Context, _ *fs.File, dst usermem.IOSequence, _
})
}
// kernelSCM implements control.SCMCredentials with credentials that represent
// the kernel itself rather than a Task.
//
// +stateify savable
type kernelSCM struct{}
// Equals implements transport.CredentialsControlMessage.Equals.
func (kernelSCM) Equals(oc transport.CredentialsControlMessage) bool {
_, ok := oc.(kernelSCM)
return ok
}
// Credentials implements control.SCMCredentials.Credentials.
func (kernelSCM) Credentials(*kernel.Task) (kernel.ThreadID, auth.UID, auth.GID) {
return 0, auth.RootUID, auth.RootGID
}
// kernelCreds is the concrete version of kernelSCM used in all creds.
var kernelCreds = &kernelSCM{}
// sendResponse sends the response messages in ms back to userspace.
func (s *Socket) sendResponse(ctx context.Context, ms *MessageSet) *syserr.Error {
// Linux combines multiple netlink messages into a single datagram.
@@ -491,10 +556,15 @@ func (s *Socket) sendResponse(ctx context.Context, ms *MessageSet) *syserr.Error
bufs = append(bufs, m.Finalize())
}
// All messages are from the kernel.
cms := transport.ControlMessages{
Credentials: kernelCreds,
}
if len(bufs) > 0 {
// RecvMsg never receives the address, so we don't need to send
// one.
_, notify, err := s.connection.Send(bufs, transport.ControlMessages{}, tcpip.FullAddress{})
_, notify, err := s.connection.Send(bufs, cms, tcpip.FullAddress{})
// If the buffer is full, we simply drop messages, just like
// Linux.
if err != nil && err != syserr.ErrWouldBlock {
@@ -521,7 +591,7 @@ func (s *Socket) sendResponse(ctx context.Context, ms *MessageSet) *syserr.Error
// Add the dump_done_errno payload.
m.Put(int64(0))
_, notify, err := s.connection.Send([][]byte{m.Finalize()}, transport.ControlMessages{}, tcpip.FullAddress{})
_, notify, err := s.connection.Send([][]byte{m.Finalize()}, cms, tcpip.FullAddress{})
if err != nil && err != syserr.ErrWouldBlock {
return err
}
+109 -1
View File
@@ -195,7 +195,8 @@ INSTANTIATE_TEST_SUITE_P(
std::make_tuple(SO_DOMAIN, IsEqual(AF_NETLINK),
absl::StrFormat("AF_NETLINK (%d)", AF_NETLINK)),
std::make_tuple(SO_PROTOCOL, IsEqual(NETLINK_ROUTE),
absl::StrFormat("NETLINK_ROUTE (%d)", NETLINK_ROUTE))));
absl::StrFormat("NETLINK_ROUTE (%d)", NETLINK_ROUTE)),
std::make_tuple(SO_PASSCRED, IsEqual(0), "0")));
// Validates the reponses to RTM_GETLINK + NLM_F_DUMP.
void CheckGetLinkResponse(const struct nlmsghdr* hdr, int seq, int port) {
@@ -692,6 +693,113 @@ TEST(NetlinkRouteTest, RecvmsgTruncPeek) {
} while (type != NLMSG_DONE && type != NLMSG_ERROR);
}
// No SCM_CREDENTIALS are received without SO_PASSCRED set.
TEST(NetlinkRouteTest, NoPasscredNoCreds) {
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOff,
sizeof(kSockOptOff)),
SyscallSucceeds());
struct request {
struct nlmsghdr hdr;
struct rtgenmsg rgm;
};
constexpr uint32_t kSeq = 12345;
struct request req;
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETADDR;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = kSeq;
req.rgm.rtgen_family = AF_UNSPEC;
struct iovec iov = {};
iov.iov_base = &req;
iov.iov_len = sizeof(req);
struct msghdr msg = {};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds());
iov.iov_base = NULL;
iov.iov_len = 0;
char control[CMSG_SPACE(sizeof(struct ucred))] = {};
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
// Note: This test assumes at least one message is returned by the
// RTM_GETADDR request.
ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), SyscallSucceeds());
// No control messages.
EXPECT_EQ(CMSG_FIRSTHDR(&msg), nullptr);
}
// SCM_CREDENTIALS are received with SO_PASSCRED set.
TEST(NetlinkRouteTest, PasscredCreds) {
FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket());
ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceeds());
struct request {
struct nlmsghdr hdr;
struct rtgenmsg rgm;
};
constexpr uint32_t kSeq = 12345;
struct request req;
req.hdr.nlmsg_len = sizeof(req);
req.hdr.nlmsg_type = RTM_GETADDR;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = kSeq;
req.rgm.rtgen_family = AF_UNSPEC;
struct iovec iov = {};
iov.iov_base = &req;
iov.iov_len = sizeof(req);
struct msghdr msg = {};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds());
iov.iov_base = NULL;
iov.iov_len = 0;
char control[CMSG_SPACE(sizeof(struct ucred))] = {};
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
// Note: This test assumes at least one message is returned by the
// RTM_GETADDR request.
ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), SyscallSucceeds());
struct ucred creds;
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
ASSERT_NE(cmsg, nullptr);
ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(creds)));
ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
ASSERT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS);
memcpy(&creds, CMSG_DATA(cmsg), sizeof(creds));
// The peer is the kernel, which is "PID" 0.
EXPECT_EQ(creds.pid, 0);
// The kernel identifies as root. Also allow nobody in case this test is
// running in a userns without root mapped.
EXPECT_THAT(creds.uid, AnyOf(Eq(0), Eq(65534)));
EXPECT_THAT(creds.gid, AnyOf(Eq(0), Eq(65534)));
}
} // namespace
} // namespace testing