/* * 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 "Reporter.h" #include "incidentd_util.h" #include "Privacy.h" #include "PrivacyFilter.h" #include "proto_util.h" #include "report_directory.h" #include "section_list.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace android { namespace os { namespace incidentd { using namespace android::util; /** * The field id of the metadata section from * frameworks/base/core/proto/android/os/incident.proto */ const int FIELD_ID_METADATA = 2; IncidentMetadata_Destination privacy_policy_to_dest(uint8_t privacyPolicy) { switch (privacyPolicy) { case PRIVACY_POLICY_AUTOMATIC: return IncidentMetadata_Destination_AUTOMATIC; case PRIVACY_POLICY_EXPLICIT: return IncidentMetadata_Destination_EXPLICIT; case PRIVACY_POLICY_LOCAL: return IncidentMetadata_Destination_LOCAL; default: // Anything else reverts to automatic return IncidentMetadata_Destination_AUTOMATIC; } } static bool contains_section(const IncidentReportArgs& args, int sectionId) { return args.containsSection(sectionId, section_requires_specific_mention(sectionId)); } static bool contains_section(const sp& args, int sectionId) { return args->containsSection(sectionId); } // ARGS must have a containsSection(int) method template void make_metadata(IncidentMetadata* result, const IncidentMetadata& full, int64_t reportId, int32_t privacyPolicy, ARGS args) { result->set_report_id(reportId); result->set_dest(privacy_policy_to_dest(privacyPolicy)); size_t sectionCount = full.sections_size(); for (int sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { const IncidentMetadata::SectionStats& sectionStats = full.sections(sectionIndex); if (contains_section(args, sectionStats.id())) { *result->add_sections() = sectionStats; } } } // ================================================================================ class StreamingFilterFd : public FilterFd { public: StreamingFilterFd(uint8_t privacyPolicy, int fd, const sp& request); virtual void onWriteError(status_t err); private: sp mRequest; }; StreamingFilterFd::StreamingFilterFd(uint8_t privacyPolicy, int fd, const sp& request) :FilterFd(privacyPolicy, fd), mRequest(request) { } void StreamingFilterFd::onWriteError(status_t err) { mRequest->setStatus(err); } // ================================================================================ class PersistedFilterFd : public FilterFd { public: PersistedFilterFd(uint8_t privacyPolicy, int fd, const sp& reportFile); virtual void onWriteError(status_t err); private: sp mReportFile; }; PersistedFilterFd::PersistedFilterFd(uint8_t privacyPolicy, int fd, const sp& reportFile) :FilterFd(privacyPolicy, fd), mReportFile(reportFile) { } void PersistedFilterFd::onWriteError(status_t err) { mReportFile->setWriteError(err); } // ================================================================================ ReportRequest::ReportRequest(const IncidentReportArgs& a, const sp& listener, int fd) :args(a), mListener(listener), mFd(fd), mIsStreaming(fd >= 0), mStatus(NO_ERROR) { } ReportRequest::~ReportRequest() { if (mIsStreaming && mFd >= 0) { // clean up the opened file descriptor close(mFd); } } bool ReportRequest::ok() { return mFd >= 0 && mStatus == NO_ERROR; } bool ReportRequest::containsSection(int sectionId) const { return args.containsSection(sectionId, section_requires_specific_mention(sectionId)); } void ReportRequest::closeFd() { if (mIsStreaming && mFd >= 0) { close(mFd); mFd = -1; } } // ================================================================================ ReportBatch::ReportBatch() {} ReportBatch::~ReportBatch() {} void ReportBatch::addPersistedReport(const IncidentReportArgs& args) { ComponentName component(args.receiverPkg(), args.receiverCls()); map>::iterator found = mPersistedRequests.find(component); if (found == mPersistedRequests.end()) { // not found mPersistedRequests[component] = new ReportRequest(args, nullptr, -1); } else { // found sp request = found->second; request->args.merge(args); } } void ReportBatch::addStreamingReport(const IncidentReportArgs& args, const sp& listener, int streamFd) { mStreamingRequests.push_back(new ReportRequest(args, listener, streamFd)); } bool ReportBatch::empty() const { return mPersistedRequests.size() == 0 && mStreamingRequests.size() == 0; } sp ReportBatch::getPersistedRequest(const ComponentName& component) { map>::iterator it = mPersistedRequests.find(component); if (it != mPersistedRequests.find(component)) { return it->second; } else { return nullptr; } } void ReportBatch::forEachPersistedRequest(const function&)>& func) { for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { func(it->second); } } void ReportBatch::forEachStreamingRequest(const function&)>& func) { for (vector>::iterator request = mStreamingRequests.begin(); request != mStreamingRequests.end(); request++) { func(*request); } } void ReportBatch::forEachListener( const function&)>& func) { for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { sp listener = it->second->getListener(); if (listener != nullptr) { func(listener); } } for (vector>::iterator request = mStreamingRequests.begin(); request != mStreamingRequests.end(); request++) { sp listener = (*request)->getListener(); if (listener != nullptr) { func(listener); } } } void ReportBatch::forEachListener(int sectionId, const function&)>& func) { for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { if (it->second->containsSection(sectionId)) { sp listener = it->second->getListener(); if (listener != nullptr) { func(listener); } } } for (vector>::iterator request = mStreamingRequests.begin(); request != mStreamingRequests.end(); request++) { if ((*request)->containsSection(sectionId)) { sp listener = (*request)->getListener(); if (listener != nullptr) { func(listener); } } } } void ReportBatch::getCombinedPersistedArgs(IncidentReportArgs* result) { for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { result->merge(it->second->args); } } bool ReportBatch::containsSection(int sectionId) { // We don't cache this, because in case of error, we remove requests // from the batch, and this is easier than recomputing the set. for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { if (it->second->containsSection(sectionId)) { return true; } } for (vector>::iterator request = mStreamingRequests.begin(); request != mStreamingRequests.end(); request++) { if ((*request)->containsSection(sectionId)) { return true; } } return false; } void ReportBatch::clearPersistedRequests() { mPersistedRequests.clear(); } void ReportBatch::transferStreamingRequests(const sp& that) { for (vector>::iterator request = mStreamingRequests.begin(); request != mStreamingRequests.end(); request++) { that->mStreamingRequests.push_back(*request); } mStreamingRequests.clear(); } void ReportBatch::transferPersistedRequests(const sp& that) { for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { that->mPersistedRequests[it->first] = it->second; } mPersistedRequests.clear(); } void ReportBatch::getFailedRequests(vector>* requests) { for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { if (it->second->getStatus() != NO_ERROR) { requests->push_back(it->second); } } for (vector>::iterator request = mStreamingRequests.begin(); request != mStreamingRequests.end(); request++) { if ((*request)->getStatus() != NO_ERROR) { requests->push_back(*request); } } } void ReportBatch::removeRequest(const sp& request) { for (map>::iterator it = mPersistedRequests.begin(); it != mPersistedRequests.end(); it++) { if (it->second == request) { mPersistedRequests.erase(it); return; } } for (vector>::iterator it = mStreamingRequests.begin(); it != mStreamingRequests.end(); it++) { if (*it == request) { mStreamingRequests.erase(it); return; } } } // ================================================================================ ReportWriter::ReportWriter(const sp& batch) :mBatch(batch), mPersistedFile(), mMaxPersistedPrivacyPolicy(PRIVACY_POLICY_UNSET) { } ReportWriter::~ReportWriter() { } void ReportWriter::setPersistedFile(sp file) { mPersistedFile = file; } void ReportWriter::setMaxPersistedPrivacyPolicy(uint8_t privacyPolicy) { mMaxPersistedPrivacyPolicy = privacyPolicy; } void ReportWriter::startSection(int sectionId) { mCurrentSectionId = sectionId; mSectionStartTimeMs = uptimeMillis(); mSectionStatsCalledForSectionId = -1; mDumpSizeBytes = 0; mDumpDurationMs = 0; mSectionTimedOut = false; mSectionTruncated = false; mSectionBufferSuccess = false; mHadError = false; mSectionErrors.clear(); } void ReportWriter::setSectionStats(const FdBuffer& buffer) { mSectionStatsCalledForSectionId = mCurrentSectionId; mDumpSizeBytes = buffer.size(); mDumpDurationMs = buffer.durationMs(); mSectionTimedOut = buffer.timedOut(); mSectionTruncated = buffer.truncated(); mSectionBufferSuccess = !buffer.timedOut() && !buffer.truncated(); } void ReportWriter::endSection(IncidentMetadata::SectionStats* sectionMetadata) { long endTime = uptimeMillis(); if (mSectionStatsCalledForSectionId != mCurrentSectionId) { ALOGW("setSectionStats not called for section %d", mCurrentSectionId); } sectionMetadata->set_id(mCurrentSectionId); sectionMetadata->set_success((!mHadError) && mSectionBufferSuccess); sectionMetadata->set_report_size_bytes(mMaxSectionDataFilteredSize); sectionMetadata->set_exec_duration_ms(endTime - mSectionStartTimeMs); sectionMetadata->set_dump_size_bytes(mDumpSizeBytes); sectionMetadata->set_dump_duration_ms(mDumpDurationMs); sectionMetadata->set_timed_out(mSectionTimedOut); sectionMetadata->set_is_truncated(mSectionTruncated); sectionMetadata->set_error_msg(mSectionErrors); } void ReportWriter::warning(const Section* section, status_t err, const char* format, ...) { va_list args; va_start(args, format); vflog(section, err, ANDROID_LOG_ERROR, "error", format, args); va_end(args); } void ReportWriter::error(const Section* section, status_t err, const char* format, ...) { va_list args; va_start(args, format); vflog(section, err, ANDROID_LOG_WARN, "warning", format, args); va_end(args); } void ReportWriter::vflog(const Section* section, status_t err, int level, const char* levelText, const char* format, va_list args) { const char* prefixFormat = "%s in section %d (%d) '%s': "; int prefixLen = snprintf(NULL, 0, prefixFormat, levelText, section->id, err, strerror(-err)); va_list measureArgs; va_copy(measureArgs, args); int messageLen = vsnprintf(NULL, 0, format, args); va_end(measureArgs); char* line = (char*)malloc(prefixLen + messageLen + 1); if (line == NULL) { // All hope is lost, just give up. return; } sprintf(line, prefixFormat, levelText, section->id, err, strerror(-err)); vsprintf(line + prefixLen, format, args); __android_log_write(level, LOG_TAG, line); if (mSectionErrors.length() == 0) { mSectionErrors = line; } else { mSectionErrors += '\n'; mSectionErrors += line; } free(line); if (level >= ANDROID_LOG_ERROR) { mHadError = true; } } // Reads data from FdBuffer and writes it to the requests file descriptor. status_t ReportWriter::writeSection(const FdBuffer& buffer) { PrivacyFilter filter(mCurrentSectionId, get_privacy_of_section(mCurrentSectionId)); // Add the fd for the persisted requests if (mPersistedFile != nullptr) { filter.addFd(new PersistedFilterFd(mMaxPersistedPrivacyPolicy, mPersistedFile->getDataFileFd(), mPersistedFile)); } // Add the fds for the streamed requests mBatch->forEachStreamingRequest([&filter, this](const sp& request) { if (request->ok() && request->args.containsSection(mCurrentSectionId, section_requires_specific_mention(mCurrentSectionId))) { filter.addFd(new StreamingFilterFd(request->args.getPrivacyPolicy(), request->getFd(), request)); } }); return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize); } // ================================================================================ Reporter::Reporter(const sp& workDirectory, const sp& batch) :mWorkDirectory(workDirectory), mWriter(batch), mBatch(batch) { } Reporter::~Reporter() { } void Reporter::runReport(size_t* reportByteSize) { status_t err = NO_ERROR; IncidentMetadata metadata; int persistedPrivacyPolicy = PRIVACY_POLICY_UNSET; (*reportByteSize) = 0; // Tell everyone that we're starting. ALOGI("Starting incident report"); mBatch->forEachListener([](const auto& listener) { listener->onReportStarted(); }); if (mBatch->hasPersistedReports()) { // Open a work file to contain the contents of all of the persisted reports. // For this block, if we can't initialize the report file for some reason, // then we will remove the persisted ReportRequests from the report, but // continue with the streaming ones. mPersistedFile = mWorkDirectory->createReportFile(); ALOGI("Report will be persisted: envelope: %s data: %s", mPersistedFile->getEnvelopeFileName().c_str(), mPersistedFile->getDataFileName().c_str()); // Record all of the metadata to the persisted file's metadata file. // It will be read from there and reconstructed as the actual reports // are sent out. if (mPersistedFile != nullptr) { mBatch->forEachPersistedRequest([this, &persistedPrivacyPolicy]( const sp& request) { mPersistedFile->addReport(request->args); if (request->args.getPrivacyPolicy() < persistedPrivacyPolicy) { persistedPrivacyPolicy = request->args.getPrivacyPolicy(); } }); mPersistedFile->setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy); err = mPersistedFile->saveEnvelope(); if (err != NO_ERROR) { mWorkDirectory->remove(mPersistedFile); mPersistedFile = nullptr; } mWriter.setMaxPersistedPrivacyPolicy(persistedPrivacyPolicy); } if (mPersistedFile != nullptr) { err = mPersistedFile->startWritingDataFile(); if (err != NO_ERROR) { mWorkDirectory->remove(mPersistedFile); mPersistedFile = nullptr; } } if (mPersistedFile != nullptr) { mWriter.setPersistedFile(mPersistedFile); } else { ALOGW("Error creating the persisted file, so clearing persisted reports."); // If we couldn't open the file (permissions err, etc), then // we still want to proceed with any streaming reports, but // cancel all of the persisted ones. mBatch->forEachPersistedRequest([](const sp& request) { sp listener = request->getListener(); if (listener != nullptr) { listener->onReportFailed(); } }); mBatch->clearPersistedRequests(); } } // If we have a persisted ID, then we allow all the readers to see that. There's // enough in the data to allow for a join, and nothing in here that intrisincally // could ever prevent that, so just give them the ID. If we don't have that then we // make and ID that's extremely likely to be unique, but clock resetting could allow // it to be duplicate. int64_t reportId; if (mPersistedFile != nullptr) { reportId = mPersistedFile->getTimestampNs(); } else { struct timespec spec; clock_gettime(CLOCK_REALTIME, &spec); reportId = (spec.tv_sec) * 1000 + spec.tv_nsec; } // Write the incident report headers - each request gets its own headers. It's different // from the other top-level fields in IncidentReport that are the sections where the rest // is all shared data (although with their own individual privacy filtering). mBatch->forEachStreamingRequest([](const sp& request) { const vector>& headers = request->args.headers(); for (vector>::const_iterator buf = headers.begin(); buf != headers.end(); buf++) { // If there was an error now, there will be an error later and we will remove // it from the list then. write_header_section(request->getFd(), buf->data(), buf->size()); } }); // If writing to any of the headers failed, we don't want to keep processing // sections for it. cancel_and_remove_failed_requests(); // For each of the report fields, see if we need it, and if so, execute the command // and report to those that care that we're doing it. for (const Section** section = SECTION_LIST; *section; section++) { const int sectionId = (*section)->id; // If nobody wants this section, skip it. if (!mBatch->containsSection(sectionId)) { continue; } ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string()); IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections(); // Notify listener of starting mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { listener->onReportSectionStatus( sectionId, IIncidentReportStatusListener::STATUS_STARTING); }); // Go get the data and write it into the file descriptors. mWriter.startSection(sectionId); err = (*section)->Execute(&mWriter); mWriter.endSection(sectionMetadata); // Sections returning errors are fatal. Most errors should not be fatal. if (err != NO_ERROR) { mWriter.error((*section), err, "Section failed. Stopping report."); goto DONE; } // The returned max data size is used for throttling too many incident reports. (*reportByteSize) += sectionMetadata->report_size_bytes(); // For any requests that failed during this section, remove them now. We do this // before calling back about section finished, so listeners do not erroniously get the // impression that the section succeeded. But we do it here instead of inside // writeSection so that the callback is done from a known context and not from the // bowels of a section, where changing the batch could cause odd errors. cancel_and_remove_failed_requests(); // Notify listener of finishing mBatch->forEachListener(sectionId, [sectionId](const auto& listener) { listener->onReportSectionStatus( sectionId, IIncidentReportStatusListener::STATUS_FINISHED); }); ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string()); } DONE: // Finish up the persisted file. if (mPersistedFile != nullptr) { mPersistedFile->closeDataFile(); // Set the stored metadata IncidentReportArgs combinedArgs; mBatch->getCombinedPersistedArgs(&combinedArgs); IncidentMetadata persistedMetadata; make_metadata(&persistedMetadata, metadata, mPersistedFile->getTimestampNs(), persistedPrivacyPolicy, combinedArgs); mPersistedFile->setMetadata(persistedMetadata); mPersistedFile->markCompleted(); err = mPersistedFile->saveEnvelope(); if (err != NO_ERROR) { ALOGW("mPersistedFile->saveEnvelope returned %s. Won't send broadcast", strerror(-err)); // Abandon ship. mWorkDirectory->remove(mPersistedFile); } } // Write the metadata to the streaming ones mBatch->forEachStreamingRequest([reportId, &metadata](const sp& request) { IncidentMetadata streamingMetadata; make_metadata(&streamingMetadata, metadata, reportId, request->args.getPrivacyPolicy(), request); status_t nonFatalErr = write_section(request->getFd(), FIELD_ID_METADATA, streamingMetadata); if (nonFatalErr != NO_ERROR) { ALOGW("Error writing the metadata to streaming incident report. This is the last" " thing so we won't return an error: %s", strerror(nonFatalErr)); } }); // Finish up the streaming ones. mBatch->forEachStreamingRequest([](const sp& request) { request->closeFd(); }); // Tell the listeners that we're done. if (err == NO_ERROR) { mBatch->forEachListener([](const auto& listener) { listener->onReportFinished(); }); } else { mBatch->forEachListener([](const auto& listener) { listener->onReportFailed(); }); } ALOGI("Done taking incident report err=%s", strerror(-err)); } void Reporter::cancel_and_remove_failed_requests() { // Handle a failure in the persisted file if (mPersistedFile != nullptr) { if (mPersistedFile->getWriteError() != NO_ERROR) { ALOGW("Error writing to the persisted file (%s). Closing it and canceling.", strerror(-mPersistedFile->getWriteError())); mBatch->forEachPersistedRequest([this](const sp& request) { sp listener = request->getListener(); if (listener != nullptr) { listener->onReportFailed(); } mBatch->removeRequest(request); }); mWriter.setPersistedFile(nullptr); mPersistedFile->closeDataFile(); mWorkDirectory->remove(mPersistedFile); mPersistedFile = nullptr; } } // Handle failures in the streaming files vector> failed; mBatch->getFailedRequests(&failed); for (sp& request: failed) { ALOGW("Error writing to a request stream (%s). Closing it and canceling.", strerror(-request->getStatus())); sp listener = request->getListener(); if (listener != nullptr) { listener->onReportFailed(); } request->closeFd(); // Will only close the streaming ones. mBatch->removeRequest(request); } } } // namespace incidentd } // namespace os } // namespace android