194 lines
5.7 KiB
C++
194 lines
5.7 KiB
C++
// Copyright 2019 The Crashpad 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.
|
|
|
|
#include "util/linux/socket.h"
|
|
|
|
#include <unistd.h>
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/logging.h"
|
|
#include "base/posix/eintr_wrapper.h"
|
|
#include "third_party/lss/lss.h"
|
|
|
|
namespace crashpad {
|
|
|
|
constexpr size_t UnixCredentialSocket::kMaxSendRecvMsgFDs;
|
|
|
|
// static
|
|
bool UnixCredentialSocket::CreateCredentialSocketpair(ScopedFileHandle* sock1,
|
|
ScopedFileHandle* sock2) {
|
|
int socks[2];
|
|
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socks) != 0) {
|
|
PLOG(ERROR) << "socketpair";
|
|
return false;
|
|
}
|
|
ScopedFileHandle local_sock1(socks[0]);
|
|
ScopedFileHandle local_sock2(socks[1]);
|
|
|
|
int optval = 1;
|
|
socklen_t optlen = sizeof(optval);
|
|
if (setsockopt(local_sock1.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) !=
|
|
0 ||
|
|
setsockopt(local_sock2.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) !=
|
|
0) {
|
|
PLOG(ERROR) << "setsockopt";
|
|
return false;
|
|
}
|
|
|
|
sock1->swap(local_sock1);
|
|
sock2->swap(local_sock2);
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
int UnixCredentialSocket::SendMsg(int fd,
|
|
const void* buf,
|
|
size_t buf_size,
|
|
const int* fds,
|
|
size_t fd_count) {
|
|
// This function is intended to be used after a crash. fds is an integer
|
|
// array instead of a vector to avoid forcing callers to provide a vector,
|
|
// which they would have to create prior to the crash.
|
|
if (fds && fd_count > kMaxSendRecvMsgFDs) {
|
|
DLOG(ERROR) << "too many fds " << fd_count;
|
|
return EINVAL;
|
|
}
|
|
|
|
iovec iov;
|
|
iov.iov_base = const_cast<void*>(buf);
|
|
iov.iov_len = buf_size;
|
|
|
|
msghdr msg = {};
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
char cmsg_buf[CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)];
|
|
if (fds) {
|
|
msg.msg_control = cmsg_buf;
|
|
msg.msg_controllen = CMSG_SPACE(sizeof(int) * fd_count);
|
|
|
|
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
DCHECK(cmsg);
|
|
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fd_count);
|
|
memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * fd_count);
|
|
}
|
|
|
|
// TODO(jperaza): Use sys_sendmsg when lss has macros for maniuplating control
|
|
// messages. https://crbug.com/crashpad/265
|
|
if (HANDLE_EINTR(sendmsg(fd, &msg, MSG_NOSIGNAL)) < 0) {
|
|
DPLOG(ERROR) << "sendmsg";
|
|
return errno;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// static
|
|
bool UnixCredentialSocket::RecvMsg(int fd,
|
|
void* buf,
|
|
size_t buf_size,
|
|
ucred* creds,
|
|
std::vector<ScopedFileHandle>* fds) {
|
|
iovec iov;
|
|
iov.iov_base = buf;
|
|
iov.iov_len = buf_size;
|
|
|
|
msghdr msg = {};
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
char cmsg_buf[CMSG_SPACE(sizeof(ucred)) +
|
|
CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)];
|
|
msg.msg_control = cmsg_buf;
|
|
msg.msg_controllen = sizeof(cmsg_buf);
|
|
|
|
int res = HANDLE_EINTR(recvmsg(fd, &msg, 0));
|
|
if (res < 0) {
|
|
PLOG(ERROR) << "recvmsg";
|
|
return false;
|
|
}
|
|
|
|
ucred* local_creds = nullptr;
|
|
std::vector<ScopedFileHandle> local_fds;
|
|
bool unhandled_cmsgs = false;
|
|
|
|
for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg;
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
|
|
int* fdp = reinterpret_cast<int*>(CMSG_DATA(cmsg));
|
|
size_t fd_count = (reinterpret_cast<char*>(cmsg) + cmsg->cmsg_len -
|
|
reinterpret_cast<char*>(fdp)) /
|
|
sizeof(int);
|
|
DCHECK_LE(fd_count, kMaxSendRecvMsgFDs);
|
|
for (size_t index = 0; index < fd_count; ++index) {
|
|
if (fds) {
|
|
local_fds.emplace_back(fdp[index]);
|
|
} else if (IGNORE_EINTR(close(fdp[index])) != 0) {
|
|
PLOG(ERROR) << "close";
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) {
|
|
DCHECK(!local_creds);
|
|
local_creds = reinterpret_cast<ucred*>(CMSG_DATA(cmsg));
|
|
continue;
|
|
}
|
|
|
|
LOG(ERROR) << "unhandled cmsg " << cmsg->cmsg_level << ", "
|
|
<< cmsg->cmsg_type;
|
|
unhandled_cmsgs = true;
|
|
}
|
|
|
|
if (unhandled_cmsgs) {
|
|
return false;
|
|
}
|
|
|
|
if (msg.msg_name != nullptr || msg.msg_namelen != 0) {
|
|
LOG(ERROR) << "unexpected msg name";
|
|
return false;
|
|
}
|
|
|
|
if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) {
|
|
LOG(ERROR) << "truncated msg";
|
|
return false;
|
|
}
|
|
|
|
// Credentials are missing from the message either when the recv socket wasn't
|
|
// configured with SO_PASSCRED or when all sending sockets have been closed.
|
|
// In the latter case, res == 0. This case is also indistinguishable from an
|
|
// empty message sent to a recv socket which hasn't set SO_PASSCRED.
|
|
if (!local_creds) {
|
|
LOG_IF(ERROR, res != 0) << "missing credentials";
|
|
return false;
|
|
}
|
|
|
|
if (static_cast<size_t>(res) != buf_size) {
|
|
LOG(ERROR) << "incorrect payload size " << res;
|
|
return false;
|
|
}
|
|
|
|
*creds = *local_creds;
|
|
if (fds) {
|
|
fds->swap(local_fds);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace crashpad
|