blob: f56eacf677c4e59f11ab36581abbe47772aaeb21 [file] [log] [blame]
// Copyright 2019 The Crashpad Authors. All rights reserved.
//
// 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/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "third_party/lss/lss.h"
namespace crashpad {
// 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;
}
constexpr size_t UnixCredentialSocket::kMaxSendRecvMsgFDs = 4;
// 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