Files
frameworks_base/cmds/incidentd/src/IncidentService.cpp
Mike Ma 15f83a3118 Add an API to dump incident report for dumpstate
Instead of just relying on the regular iteration through the system
services inside dumpstate, add another API to IIncidentManager
dedicated for dumpstate.

- It is only callable by dumpstate() (check the calling uid)
- It has the same behavior as the current call inside dump()

Advantages:
- More explicit function name, right next to takeIncidentReport will
  make it easier to keep them in sync.
- Nobody else can call it, make security easier.
- If dumpstate calls it explicitly, it can skip the 10 second timeout
- The regular dump() call should provide debugging data about
  incidentd itself, for example timestamps for the most recent N
  incident reports taken and the current state of the work directory,
  allowing us to debug incidentd itself.

Bug: 137493082
Test: Manually trigger a bug report, and verify
      /proto/incident_log.proto in the zip file.

Change-Id: I19139c765b53ede63d3beb3ea3ac40ada1aba42d
Merged-In: I19139c765b53ede63d3beb3ea3ac40ada1aba42d
2019-09-23 16:20:53 -07:00

589 lines
21 KiB
C++

/*
* Copyright (C) 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.
*/
#define DEBUG false
#include "Log.h"
#include "IncidentService.h"
#include "FdBuffer.h"
#include "PrivacyFilter.h"
#include "Reporter.h"
#include "incidentd_util.h"
#include "section_list.h"
#include <android/os/IncidentReportArgs.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/IServiceManager.h>
#include <binder/IShellCallback.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
#include <utils/Looper.h>
#include <thread>
#include <unistd.h>
enum {
WHAT_TAKE_REPORT = 1,
WHAT_SEND_BROADCASTS = 2
};
#define DEFAULT_DELAY_NS (1000000000LL)
#define DEFAULT_BYTES_SIZE_LIMIT (96 * 1024 * 1024) // 96MB
#define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day
// Skip these sections (for dumpstate only)
// Skip logs (1100 - 1108) and traces (1200 - 1202) because they are already in the bug report.
#define SKIPPED_DUMPSTATE_SECTIONS { \
1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, /* Logs */ \
1200, 1201, 1202, /* Native, hal, java traces */ }
namespace android {
namespace os {
namespace incidentd {
String16 const APPROVE_INCIDENT_REPORTS("android.permission.APPROVE_INCIDENT_REPORTS");
String16 const DUMP_PERMISSION("android.permission.DUMP");
String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS");
static Status checkIncidentPermissions(const IncidentReportArgs& args) {
uid_t callingUid = IPCThreadState::self()->getCallingUid();
pid_t callingPid = IPCThreadState::self()->getCallingPid();
if (callingUid == AID_ROOT || callingUid == AID_SHELL) {
// Root and shell are ok.
return Status::ok();
}
if (checkCallingPermission(APPROVE_INCIDENT_REPORTS)) {
// Permission controller (this is a singleton permission that is always granted
// exactly for PermissionController) is allowed to access incident reports
// so it can show the user info about what they are approving.
return Status::ok();
}
// checking calling permission.
if (!checkCallingPermission(DUMP_PERMISSION)) {
ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP",
callingPid, callingUid);
return Status::fromExceptionCode(
Status::EX_SECURITY,
"Calling process does not have permission: android.permission.DUMP");
}
if (!checkCallingPermission(USAGE_STATS_PERMISSION)) {
ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS",
callingPid, callingUid);
return Status::fromExceptionCode(
Status::EX_SECURITY,
"Calling process does not have permission: android.permission.USAGE_STATS");
}
// checking calling request uid permission.
switch (args.getPrivacyPolicy()) {
case PRIVACY_POLICY_LOCAL:
if (callingUid != AID_SHELL && callingUid != AID_ROOT) {
ALOGW("Calling pid %d and uid %d does not have permission to get local data.",
callingPid, callingUid);
return Status::fromExceptionCode(
Status::EX_SECURITY,
"Calling process does not have permission to get local data.");
}
break;
case PRIVACY_POLICY_EXPLICIT:
if (callingUid != AID_SHELL && callingUid != AID_ROOT && callingUid != AID_STATSD &&
callingUid != AID_SYSTEM) {
ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.",
callingPid, callingUid);
return Status::fromExceptionCode(
Status::EX_SECURITY,
"Calling process does not have permission to get explicit data.");
}
break;
}
return Status::ok();
}
static string build_uri(const string& pkg, const string& cls, const string& id) {
return "content://android.os.IncidentManager/pending?pkg="
+ pkg + "&receiver=" + cls + "&r=" + id;
}
// ================================================================================
ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
const sp<Throttler>& throttler)
:mLock(),
mWorkDirectory(workDirectory),
mBroadcaster(broadcaster),
mHandlerLooper(handlerLooper),
mBacklogDelay(DEFAULT_DELAY_NS),
mThrottler(throttler),
mBatch(new ReportBatch()) {
}
ReportHandler::~ReportHandler() {
}
void ReportHandler::handleMessage(const Message& message) {
switch (message.what) {
case WHAT_TAKE_REPORT:
take_report();
break;
case WHAT_SEND_BROADCASTS:
send_broadcasts();
break;
}
}
void ReportHandler::schedulePersistedReport(const IncidentReportArgs& args) {
mBatch->addPersistedReport(args);
mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
}
void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener, int streamFd) {
mBatch->addStreamingReport(args, listener, streamFd);
mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);
mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));
}
void ReportHandler::scheduleSendBacklog() {
unique_lock<mutex> lock(mLock);
mBacklogDelay = DEFAULT_DELAY_NS;
schedule_send_broadcasts_locked();
}
void ReportHandler::schedule_send_broadcasts_locked() {
mHandlerLooper->removeMessages(this, WHAT_SEND_BROADCASTS);
mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BROADCASTS));
}
void ReportHandler::take_report() {
// Cycle the batch and throttle.
sp<ReportBatch> batch;
{
unique_lock<mutex> lock(mLock);
batch = mThrottler->filterBatch(mBatch);
}
if (batch->empty()) {
// Nothing to do.
return;
}
sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
// Take the report, which might take a while. More requests might queue
// up while we're doing this, and we'll handle them in their next batch.
// TODO: We should further rate-limit the reports to no more than N per time-period.
// TODO: Move this inside reporter.
size_t reportByteSize = 0;
reporter->runReport(&reportByteSize);
// Tell the throttler how big it was, for the next throttling.
// TODO: This still isn't ideal. The throttler really should just track the
// persisted reqeusts, but changing Reporter::runReport() to track that individually
// will be a big change.
if (batch->hasPersistedReports()) {
mThrottler->addReportSize(reportByteSize);
}
// Kick off the next steps, one of which is to send any new or otherwise remaining
// approvals, and one of which is to send any new or remaining broadcasts.
{
unique_lock<mutex> lock(mLock);
schedule_send_broadcasts_locked();
}
}
void ReportHandler::send_broadcasts() {
Broadcaster::broadcast_status_t result = mBroadcaster->sendBroadcasts();
if (result == Broadcaster::BROADCASTS_FINISHED) {
// We're done.
unique_lock<mutex> lock(mLock);
mBacklogDelay = DEFAULT_DELAY_NS;
} else if (result == Broadcaster::BROADCASTS_REPEAT) {
// It worked, but there are more.
unique_lock<mutex> lock(mLock);
mBacklogDelay = DEFAULT_DELAY_NS;
schedule_send_broadcasts_locked();
} else if (result == Broadcaster::BROADCASTS_BACKOFF) {
// There was a failure. Exponential backoff.
unique_lock<mutex> lock(mLock);
mBacklogDelay *= 2;
ALOGI("Error sending to dropbox. Trying again in %lld minutes",
(mBacklogDelay / (1000000000LL * 60)));
schedule_send_broadcasts_locked();
}
}
// ================================================================================
IncidentService::IncidentService(const sp<Looper>& handlerLooper) {
mThrottler = new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS);
mWorkDirectory = new WorkDirectory();
mBroadcaster = new Broadcaster(mWorkDirectory);
mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
mThrottler);
mBroadcaster->setHandler(mHandler);
}
IncidentService::~IncidentService() {}
Status IncidentService::reportIncident(const IncidentReportArgs& args) {
IncidentReportArgs argsCopy(args);
// Validate that the privacy policy is one of the real ones.
// If it isn't, clamp it to the next more restrictive real one.
argsCopy.setPrivacyPolicy(cleanup_privacy_policy(args.getPrivacyPolicy()));
// TODO: Check that the broadcast recevier has the proper permissions
// TODO: Maybe we should consider relaxing the permissions if it's going to
// dropbox, but definitely not if it's going to the broadcaster.
Status status = checkIncidentPermissions(args);
if (!status.isOk()) {
return status;
}
// If they asked for the LOCAL privacy policy, give them EXPLICT. LOCAL has to
// be streamed. (This only applies to shell/root, because everyone else would have
// been rejected by checkIncidentPermissions()).
if (argsCopy.getPrivacyPolicy() < PRIVACY_POLICY_EXPLICIT) {
ALOGI("Demoting privacy policy to EXPLICT for persisted report.");
argsCopy.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT);
}
// If they didn't specify a component, use dropbox.
if (argsCopy.receiverPkg().length() == 0 && argsCopy.receiverCls().length() == 0) {
argsCopy.setReceiverPkg(DROPBOX_SENTINEL.getPackageName());
argsCopy.setReceiverCls(DROPBOX_SENTINEL.getClassName());
}
mHandler->schedulePersistedReport(argsCopy);
return Status::ok();
}
Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
const sp<IIncidentReportStatusListener>& listener,
const unique_fd& stream) {
IncidentReportArgs argsCopy(args);
// Streaming reports can not also be broadcast.
argsCopy.setReceiverPkg("");
argsCopy.setReceiverCls("");
// Validate that the privacy policy is one of the real ones.
// If it isn't, clamp it to the next more restrictive real one.
argsCopy.setPrivacyPolicy(cleanup_privacy_policy(args.getPrivacyPolicy()));
Status status = checkIncidentPermissions(argsCopy);
if (!status.isOk()) {
return status;
}
// The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
if (fd < 0) {
return Status::fromStatusT(-errno);
}
mHandler->scheduleStreamingReport(argsCopy, listener, fd);
return Status::ok();
}
Status IncidentService::reportIncidentToDumpstate(const unique_fd& stream,
const sp<IIncidentReportStatusListener>& listener) {
uid_t caller = IPCThreadState::self()->getCallingUid();
if (caller != AID_ROOT && caller != AID_SHELL) {
ALOGW("Calling uid %d does not have permission: only ROOT or SHELL allowed", caller);
return Status::fromExceptionCode(Status::EX_SECURITY, "Only ROOT or SHELL allowed");
}
ALOGD("Stream incident report to dumpstate");
IncidentReportArgs incidentArgs;
// Privacy policy for dumpstate incident reports is always EXPLICIT.
incidentArgs.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT);
int skipped[] = SKIPPED_DUMPSTATE_SECTIONS;
for (const Section** section = SECTION_LIST; *section; section++) {
const int id = (*section)->id;
if (std::find(std::begin(skipped), std::end(skipped), id) == std::end(skipped)
&& !section_requires_specific_mention(id)) {
incidentArgs.addSection(id);
}
}
// The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
if (fd < 0) {
return Status::fromStatusT(-errno);
}
mHandler->scheduleStreamingReport(incidentArgs, listener, fd);
return Status::ok();
}
Status IncidentService::systemRunning() {
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
"Only system uid can call systemRunning");
}
// When system_server is up and running, schedule the dropbox task to run.
mBroadcaster->reset();
mHandler->scheduleSendBacklog();
return Status::ok();
}
Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16,
vector<String16>* result) {
status_t err;
const string pkg(String8(pkg16).string());
const string cls(String8(cls16).string());
// List the reports
vector<sp<ReportFile>> all;
err = mWorkDirectory->getReports(&all, 0);
if (err != NO_ERROR) {
return Status::fromStatusT(err);
}
// Find the ones that match pkg and cls.
for (sp<ReportFile>& file: all) {
err = file->loadEnvelope();
if (err != NO_ERROR) {
continue;
}
const ReportFileProto& envelope = file->getEnvelope();
size_t reportCount = envelope.report_size();
for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) {
const ReportFileProto_Report& report = envelope.report(reportIndex);
if (pkg == report.pkg() && cls == report.cls()) {
result->push_back(String16(build_uri(pkg, cls, file->getId()).c_str()));
break;
}
}
}
return Status::ok();
}
Status IncidentService::getIncidentReport(const String16& pkg16, const String16& cls16,
const String16& id16, IncidentManager::IncidentReport* result) {
status_t err;
const string pkg(String8(pkg16).string());
const string cls(String8(cls16).string());
const string id(String8(id16).string());
IncidentReportArgs args;
sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args);
if (file != nullptr) {
// Create pipe
int fds[2];
if (pipe(fds) != 0) {
ALOGW("Error opening pipe to filter incident report: %s",
file->getDataFileName().c_str());
return Status::ok();
}
result->setTimestampNs(file->getTimestampNs());
result->setPrivacyPolicy(file->getEnvelope().privacy_policy());
result->takeFileDescriptor(fds[0]);
int writeFd = fds[1];
// spawn a thread to write the data. Release the writeFd ownership to the thread.
thread th([file, writeFd, args]() { file->startFilteringData(writeFd, args); });
th.detach();
}
return Status::ok();
}
Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16,
const String16& id16) {
const string pkg(String8(pkg16).string());
const string cls(String8(cls16).string());
const string id(String8(id16).string());
sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr);
if (file != nullptr) {
mWorkDirectory->commit(file, pkg, cls);
}
mBroadcaster->clearBroadcasts(pkg, cls, id);
return Status::ok();
}
Status IncidentService::deleteAllIncidentReports(const String16& pkg16) {
const string pkg(String8(pkg16).string());
mWorkDirectory->commitAll(pkg);
mBroadcaster->clearPackageBroadcasts(pkg);
return Status::ok();
}
/**
* Implement our own because the default binder implementation isn't
* properly handling SHELL_COMMAND_TRANSACTION.
*/
status_t IncidentService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
uint32_t flags) {
status_t err;
switch (code) {
case SHELL_COMMAND_TRANSACTION: {
int in = data.readFileDescriptor();
int out = data.readFileDescriptor();
int err = data.readFileDescriptor();
int argc = data.readInt32();
Vector<String8> args;
for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
args.add(String8(data.readString16()));
}
sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder());
sp<IResultReceiver> resultReceiver =
IResultReceiver::asInterface(data.readStrongBinder());
FILE* fin = fdopen(in, "r");
FILE* fout = fdopen(out, "w");
FILE* ferr = fdopen(err, "w");
if (fin == NULL || fout == NULL || ferr == NULL) {
resultReceiver->send(NO_MEMORY);
} else {
err = command(fin, fout, ferr, args);
resultReceiver->send(err);
}
if (fin != NULL) {
fflush(fin);
fclose(fin);
}
if (fout != NULL) {
fflush(fout);
fclose(fout);
}
if (fout != NULL) {
fflush(ferr);
fclose(ferr);
}
return NO_ERROR;
} break;
default: { return BnIncidentManager::onTransact(code, data, reply, flags); }
}
}
status_t IncidentService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
const int argCount = args.size();
if (argCount >= 1) {
if (!args[0].compare(String8("privacy"))) {
return cmd_privacy(in, out, err, args);
}
if (!args[0].compare(String8("throttler"))) {
mThrottler->dump(out);
return NO_ERROR;
}
if (!args[0].compare(String8("section"))) {
int id = atoi(args[1]);
int idx = 0;
while (SECTION_LIST[idx] != NULL) {
const Section* section = SECTION_LIST[idx];
if (section->id == id) {
fprintf(out, "Section[%d] %s\n", id, section->name.string());
break;
}
idx++;
}
return NO_ERROR;
}
}
return cmd_help(out);
}
status_t IncidentService::cmd_help(FILE* out) {
fprintf(out, "usage: adb shell cmd incident privacy print <section_id>\n");
fprintf(out, "usage: adb shell cmd incident privacy parse <section_id> < proto.txt\n");
fprintf(out, " Prints/parses for the section id.\n\n");
fprintf(out, "usage: adb shell cmd incident section <section_id>\n");
fprintf(out, " Prints section id and its name.\n\n");
fprintf(out, "usage: adb shell cmd incident throttler\n");
fprintf(out, " Prints the current throttler state\n");
return NO_ERROR;
}
static void printPrivacy(const Privacy* p, FILE* out, String8 indent) {
if (p == NULL) return;
fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy);
if (p->children == NULL) return;
for (int i = 0; p->children[i] != NULL; i++) { // NULL-terminated.
printPrivacy(p->children[i], out, indent + " ");
}
}
status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
(void)in;
const int argCount = args.size();
if (argCount >= 3) {
String8 opt = args[1];
int sectionId = atoi(args[2].string());
const Privacy* p = get_privacy_of_section(sectionId);
if (p == NULL) {
fprintf(err, "Can't find section id %d\n", sectionId);
return NO_ERROR;
}
fprintf(err, "Get privacy for %d\n", sectionId);
if (opt == "print") {
printPrivacy(p, out, String8(""));
} else if (opt == "parse") {
/*
FdBuffer buf;
status_t error = buf.read(fileno(in), 60000);
if (error != NO_ERROR) {
fprintf(err, "Error reading from stdin\n");
return error;
}
fprintf(err, "Read %zu bytes\n", buf.size());
PrivacyFilter pBuf(p, buf.data());
PrivacySpec spec = PrivacySpec::new_spec(argCount > 3 ? atoi(args[3]) : -1);
error = pBuf.strip(spec);
if (error != NO_ERROR) {
fprintf(err, "Error strip pii fields with spec %d\n", spec.policy);
return error;
}
return pBuf.flush(fileno(out));
*/
return -1;
}
} else {
return cmd_help(out);
}
return NO_ERROR;
}
} // namespace incidentd
} // namespace os
} // namespace android