223 lines
8.0 KiB
C++
223 lines
8.0 KiB
C++
/*
|
|
* Copyright 2016, The Android Open Source Project
|
|
*
|
|
* 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 "intercept_manager.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <unordered_map>
|
|
|
|
#include <event2/event.h>
|
|
#include <event2/listener.h>
|
|
|
|
#include <android-base/cmsg.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <cutils/sockets.h>
|
|
|
|
#include "protocol.h"
|
|
#include "util.h"
|
|
|
|
using android::base::ReceiveFileDescriptors;
|
|
using android::base::unique_fd;
|
|
|
|
static void intercept_close_cb(evutil_socket_t sockfd, short event, void* arg) {
|
|
auto intercept = reinterpret_cast<Intercept*>(arg);
|
|
InterceptManager* intercept_manager = intercept->intercept_manager;
|
|
|
|
CHECK_EQ(sockfd, intercept->sockfd.get());
|
|
|
|
// If we can read, either we received unexpected data from the other side, or the other side
|
|
// closed their end of the socket. Either way, kill the intercept.
|
|
|
|
// Ownership of intercept differs based on whether we've registered it with InterceptManager.
|
|
if (!intercept->registered) {
|
|
delete intercept;
|
|
} else {
|
|
auto it = intercept_manager->intercepts.find(intercept->intercept_pid);
|
|
if (it == intercept_manager->intercepts.end()) {
|
|
LOG(FATAL) << "intercept close callback called after intercept was already removed?";
|
|
}
|
|
if (it->second.get() != intercept) {
|
|
LOG(FATAL) << "intercept close callback has different Intercept from InterceptManager?";
|
|
}
|
|
|
|
const char* reason;
|
|
if ((event & EV_TIMEOUT) != 0) {
|
|
reason = "due to timeout";
|
|
} else {
|
|
reason = "due to input";
|
|
}
|
|
|
|
LOG(INFO) << "intercept for pid " << intercept->intercept_pid << " and type "
|
|
<< intercept->dump_type << " terminated: " << reason;
|
|
intercept_manager->intercepts.erase(it);
|
|
}
|
|
}
|
|
|
|
static bool is_intercept_request_valid(const InterceptRequest& request) {
|
|
if (request.pid <= 0 || request.pid > std::numeric_limits<pid_t>::max()) {
|
|
return false;
|
|
}
|
|
|
|
if (request.dump_type < 0 || request.dump_type > kDebuggerdJavaBacktrace) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void intercept_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
|
|
auto intercept = reinterpret_cast<Intercept*>(arg);
|
|
InterceptManager* intercept_manager = intercept->intercept_manager;
|
|
|
|
CHECK_EQ(sockfd, intercept->sockfd.get());
|
|
|
|
if ((ev & EV_TIMEOUT) != 0) {
|
|
LOG(WARNING) << "tombstoned didn't receive InterceptRequest before timeout";
|
|
goto fail;
|
|
} else if ((ev & EV_READ) == 0) {
|
|
LOG(WARNING) << "tombstoned received unexpected event on intercept socket";
|
|
goto fail;
|
|
}
|
|
|
|
{
|
|
unique_fd rcv_fd;
|
|
InterceptRequest intercept_request;
|
|
ssize_t result =
|
|
ReceiveFileDescriptors(sockfd, &intercept_request, sizeof(intercept_request), &rcv_fd);
|
|
|
|
if (result == -1) {
|
|
PLOG(WARNING) << "failed to read from intercept socket";
|
|
goto fail;
|
|
} else if (result != sizeof(intercept_request)) {
|
|
LOG(WARNING) << "intercept socket received short read of length " << result << " (expected "
|
|
<< sizeof(intercept_request) << ")";
|
|
goto fail;
|
|
}
|
|
|
|
// Move the received FD to the upper half, in order to more easily notice FD leaks.
|
|
int moved_fd = fcntl(rcv_fd.get(), F_DUPFD, 512);
|
|
if (moved_fd == -1) {
|
|
LOG(WARNING) << "failed to move received fd (" << rcv_fd.get() << ")";
|
|
goto fail;
|
|
}
|
|
rcv_fd.reset(moved_fd);
|
|
|
|
// We trust the other side, so only do minimal validity checking.
|
|
if (!is_intercept_request_valid(intercept_request)) {
|
|
InterceptResponse response = {};
|
|
response.status = InterceptStatus::kFailed;
|
|
snprintf(response.error_message, sizeof(response.error_message), "invalid intercept request");
|
|
TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response)));
|
|
goto fail;
|
|
}
|
|
|
|
intercept->intercept_pid = intercept_request.pid;
|
|
intercept->dump_type = intercept_request.dump_type;
|
|
|
|
// Check if it's already registered.
|
|
if (intercept_manager->intercepts.count(intercept_request.pid) > 0) {
|
|
InterceptResponse response = {};
|
|
response.status = InterceptStatus::kFailedAlreadyRegistered;
|
|
snprintf(response.error_message, sizeof(response.error_message),
|
|
"pid %" PRId32 " already intercepted, type %d", intercept_request.pid,
|
|
intercept_request.dump_type);
|
|
TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response)));
|
|
LOG(WARNING) << response.error_message;
|
|
goto fail;
|
|
}
|
|
|
|
// Let the other side know that the intercept has been registered, now that we know we can't
|
|
// fail. tombstoned is single threaded, so this isn't racy.
|
|
InterceptResponse response = {};
|
|
response.status = InterceptStatus::kRegistered;
|
|
if (TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response))) == -1) {
|
|
PLOG(WARNING) << "failed to notify interceptor of registration";
|
|
goto fail;
|
|
}
|
|
|
|
intercept->output_fd = std::move(rcv_fd);
|
|
intercept_manager->intercepts[intercept_request.pid] = std::unique_ptr<Intercept>(intercept);
|
|
intercept->registered = true;
|
|
|
|
LOG(INFO) << "registered intercept for pid " << intercept_request.pid << " and type "
|
|
<< intercept_request.dump_type;
|
|
|
|
// Register a different read event on the socket so that we can remove intercepts if the socket
|
|
// closes (e.g. if a user CTRL-C's the process that requested the intercept).
|
|
event_assign(intercept->intercept_event, intercept_manager->base, sockfd, EV_READ | EV_TIMEOUT,
|
|
intercept_close_cb, arg);
|
|
|
|
struct timeval timeout = { .tv_sec = 10, .tv_usec = 0 };
|
|
event_add(intercept->intercept_event, &timeout);
|
|
}
|
|
|
|
return;
|
|
|
|
fail:
|
|
delete intercept;
|
|
}
|
|
|
|
static void intercept_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int,
|
|
void* arg) {
|
|
Intercept* intercept = new Intercept();
|
|
intercept->intercept_manager = static_cast<InterceptManager*>(arg);
|
|
intercept->sockfd.reset(sockfd);
|
|
|
|
struct timeval timeout = { 1, 0 };
|
|
event_base* base = evconnlistener_get_base(listener);
|
|
event* intercept_event =
|
|
event_new(base, sockfd, EV_TIMEOUT | EV_READ, intercept_request_cb, intercept);
|
|
intercept->intercept_event = intercept_event;
|
|
event_add(intercept_event, &timeout);
|
|
}
|
|
|
|
InterceptManager::InterceptManager(event_base* base, int intercept_socket) : base(base) {
|
|
this->listener = evconnlistener_new(base, intercept_accept_cb, this, LEV_OPT_CLOSE_ON_FREE,
|
|
/* backlog */ -1, intercept_socket);
|
|
}
|
|
|
|
bool InterceptManager::GetIntercept(pid_t pid, DebuggerdDumpType dump_type,
|
|
android::base::unique_fd* out_fd) {
|
|
auto it = this->intercepts.find(pid);
|
|
if (it == this->intercepts.end()) {
|
|
return false;
|
|
}
|
|
|
|
if (dump_type == kDebuggerdAnyIntercept) {
|
|
LOG(INFO) << "found registered intercept of type " << it->second->dump_type
|
|
<< " for requested type kDebuggerdAnyIntercept";
|
|
} else if (it->second->dump_type != dump_type) {
|
|
LOG(WARNING) << "found non-matching intercept of type " << it->second->dump_type
|
|
<< " for requested type: " << dump_type;
|
|
return false;
|
|
}
|
|
|
|
auto intercept = std::move(it->second);
|
|
this->intercepts.erase(it);
|
|
|
|
LOG(INFO) << "found intercept fd " << intercept->output_fd.get() << " for pid " << pid
|
|
<< " and type " << intercept->dump_type;
|
|
InterceptResponse response = {};
|
|
response.status = InterceptStatus::kStarted;
|
|
TEMP_FAILURE_RETRY(write(intercept->sockfd, &response, sizeof(response)));
|
|
*out_fd = std::move(intercept->output_fd);
|
|
|
|
return true;
|
|
}
|