| /* |
| * 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 (20 * 1024 * 1024) // 20MB |
| #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>& 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::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; |
| } |
| |
| status_t IncidentService::dump(int fd, const Vector<String16>& 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)) { |
| 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 |