mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
2a709a1b7b
commit
515fee5b6d
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user