| /* |
| * 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 "PrivacyBuffer.h" |
| #include "Reporter.h" |
| #include "incidentd_util.h" |
| #include "section_list.h" |
| |
| #include <binder/IPCThreadState.h> |
| #include <binder/IResultReceiver.h> |
| #include <binder/IServiceManager.h> |
| #include <binder/IShellCallback.h> |
| #include <cutils/log.h> |
| #include <private/android_filesystem_config.h> |
| #include <utils/Looper.h> |
| |
| #include <unistd.h> |
| |
| enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 }; |
| |
| #define DEFAULT_BACKLOG_DELAY_NS (1000000000LL) |
| |
| #define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024) // 20MB |
| #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day |
| |
| namespace android { |
| namespace os { |
| namespace incidentd { |
| |
| 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 doesn't have permission.DUMP if don't do this! |
| 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.dest()) { |
| case DEST_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."); |
| } |
| case DEST_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."); |
| } |
| } |
| return Status::ok(); |
| } |
| |
| // ================================================================================ |
| ReportRequestQueue::ReportRequestQueue() {} |
| |
| ReportRequestQueue::~ReportRequestQueue() {} |
| |
| void ReportRequestQueue::addRequest(const sp<ReportRequest>& request) { |
| unique_lock<mutex> lock(mLock); |
| mQueue.push_back(request); |
| } |
| |
| sp<ReportRequest> ReportRequestQueue::getNextRequest() { |
| unique_lock<mutex> lock(mLock); |
| if (mQueue.empty()) { |
| return NULL; |
| } else { |
| sp<ReportRequest> front(mQueue.front()); |
| mQueue.pop_front(); |
| return front; |
| } |
| } |
| |
| // ================================================================================ |
| ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue, |
| const sp<Throttler>& throttler) |
| : mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS), |
| mHandlerLooper(handlerLooper), |
| mQueue(queue), |
| mThrottler(throttler) {} |
| |
| ReportHandler::~ReportHandler() {} |
| |
| void ReportHandler::handleMessage(const Message& message) { |
| switch (message.what) { |
| case WHAT_RUN_REPORT: |
| run_report(); |
| break; |
| case WHAT_SEND_BACKLOG_TO_DROPBOX: |
| send_backlog_to_dropbox(); |
| break; |
| } |
| } |
| |
| void ReportHandler::scheduleRunReport(const sp<ReportRequest>& request) { |
| mQueue->addRequest(request); |
| mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT); |
| mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT)); |
| } |
| |
| void ReportHandler::scheduleSendBacklogToDropbox() { |
| unique_lock<mutex> lock(mLock); |
| mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; |
| schedule_send_backlog_to_dropbox_locked(); |
| } |
| |
| void ReportHandler::schedule_send_backlog_to_dropbox_locked() { |
| mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX); |
| mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BACKLOG_TO_DROPBOX)); |
| } |
| |
| void ReportHandler::run_report() { |
| sp<Reporter> reporter = new Reporter(); |
| |
| // Merge all of the requests into one that has all of the |
| // requested fields. |
| while (true) { |
| sp<ReportRequest> request = mQueue->getNextRequest(); |
| if (request == NULL) { |
| break; |
| } |
| reporter->batch.add(request); |
| } |
| |
| if (mThrottler->shouldThrottle()) { |
| ALOGW("RunReport got throttled."); |
| return; |
| } |
| |
| // 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. |
| size_t reportByteSize = 0; |
| Reporter::run_report_status_t reportStatus = reporter->runReport(&reportByteSize); |
| mThrottler->addReportSize(reportByteSize); |
| if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) { |
| unique_lock<mutex> lock(mLock); |
| schedule_send_backlog_to_dropbox_locked(); |
| } |
| } |
| |
| void ReportHandler::send_backlog_to_dropbox() { |
| if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) { |
| // 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_backlog_to_dropbox_locked(); |
| } else { |
| mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; |
| } |
| } |
| |
| // ================================================================================ |
| IncidentService::IncidentService(const sp<Looper>& handlerLooper) |
| : mQueue(new ReportRequestQueue()), |
| mThrottler(new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS)) { |
| mHandler = new ReportHandler(handlerLooper, mQueue, mThrottler); |
| } |
| |
| IncidentService::~IncidentService() {} |
| |
| Status IncidentService::reportIncident(const IncidentReportArgs& args) { |
| ALOGI("reportIncident"); |
| |
| Status status = checkIncidentPermissions(args); |
| if (!status.isOk()) { |
| return status; |
| } |
| |
| mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1)); |
| |
| return Status::ok(); |
| } |
| |
| Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args, |
| const sp<IIncidentReportStatusListener>& listener, |
| const unique_fd& stream) { |
| ALOGI("reportIncidentToStream"); |
| |
| Status status = checkIncidentPermissions(args); |
| if (!status.isOk()) { |
| return status; |
| } |
| |
| int fd = dup(stream.get()); |
| if (fd < 0) { |
| return Status::fromStatusT(-errno); |
| } |
| |
| mHandler->scheduleRunReport(new ReportRequest(args, 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. |
| mHandler->scheduleSendBacklogToDropbox(); |
| |
| 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; |
| } |
| 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; |
| } |
| } |
| 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"); |
| fprintf(out, "\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->dest); |
| 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) { |
| 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()); |
| PrivacyBuffer 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.dest); |
| return error; |
| } |
| return pBuf.flush(fileno(out)); |
| } |
| } else { |
| return cmd_help(out); |
| } |
| return NO_ERROR; |
| } |
| |
| } // namespace incidentd |
| } // namespace os |
| } // namespace android |