/* * 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 #include #include #include #include #include #include #include #include #include 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. Dumpstate allows 10s max for each service to dump. // Skip logs (1100 - 1108) and traces (1200 - 1202) because they are already in the bug report. // Skip 3018 because it takes too long. #define SKIPPED_SECTIONS { 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, /* Logs */ \ 1200, 1201, 1202, /* Native, hal, java traces */ \ 3018 /* "meminfo -a --proto" */ } 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, const sp& broadcaster, const sp& handlerLooper, const sp& 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& 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 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 batch; { unique_lock lock(mLock); batch = mThrottler->filterBatch(mBatch); } if (batch->empty()) { // Nothing to do. return; } sp 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 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 lock(mLock); mBacklogDelay = DEFAULT_DELAY_NS; } else if (result == Broadcaster::BROADCASTS_REPEAT) { // It worked, but there are more. unique_lock lock(mLock); mBacklogDelay = DEFAULT_DELAY_NS; schedule_send_broadcasts_locked(); } else if (result == Broadcaster::BROADCASTS_BACKOFF) { // There was a failure. Exponential backoff. unique_lock 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& 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& 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::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* result) { status_t err; const string pkg(String8(pkg16).string()); const string cls(String8(cls16).string()); // List the reports vector> all; err = mWorkDirectory->getReports(&all, 0); if (err != NO_ERROR) { return Status::fromStatusT(err); } // Find the ones that match pkg and cls. for (sp& 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 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 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 args; for (int i = 0; i < argc && data.dataAvail() > 0; i++) { args.add(String8(data.readString16())); } sp shellCallback = IShellCallback::asInterface(data.readStrongBinder()); sp 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& 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 \n"); fprintf(out, "usage: adb shell cmd incident privacy parse < proto.txt\n"); fprintf(out, " Prints/parses for the section id.\n\n"); fprintf(out, "usage: adb shell cmd incident section \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& 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; } status_t IncidentService::dump(int fd, const Vector& args) { if (std::find(args.begin(), args.end(), String16("--proto")) == args.end()) { ALOGD("Skip dumping incident. Only proto format is supported."); dprintf(fd, "Incident dump only supports proto version.\n"); return NO_ERROR; } ALOGD("Dump incident proto"); IncidentReportArgs incidentArgs; incidentArgs.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT); int skipped[] = SKIPPED_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); } } if (!checkIncidentPermissions(incidentArgs).isOk()) { return PERMISSION_DENIED; } // The ReportRequest takes ownership of the fd, so we need to dup it. int fd1 = dup(fd); if (fd1 < 0) { return -errno; } // TODO: Remove this. Someone even dumpstate, wanting to get an incident report // should use the API. That will take making dumpstated call the API, which is a // good thing. It also means it won't be subject to the timeout. mHandler->scheduleStreamingReport(incidentArgs, NULL, fd1); return NO_ERROR; } } // namespace incidentd } // namespace os } // namespace android