| /* | |
| * 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. | |
| */ | |
| #include "Log.h" | |
| #include "Broadcaster.h" | |
| #include "IncidentService.h" | |
| #include <android/os/DropBoxManager.h> | |
| #include <binder/IServiceManager.h> | |
| #include <thread> | |
| namespace android { | |
| namespace os { | |
| namespace incidentd { | |
| using android::os::IIncidentCompanion; | |
| using binder::Status; | |
| // ============================================================ | |
| Broadcaster::ConsentListener::ConsentListener(const sp<Broadcaster>& broadcaster, | |
| const ReportId& reportId) | |
| :mBroadcaster(broadcaster), | |
| mId(reportId) { | |
| } | |
| Broadcaster::ConsentListener::~ConsentListener() { | |
| } | |
| Status Broadcaster::ConsentListener::onReportApproved() { | |
| mBroadcaster->report_approved(mId); | |
| return Status::ok(); | |
| } | |
| Status Broadcaster::ConsentListener::onReportDenied() { | |
| mBroadcaster->report_denied(mId); | |
| return Status::ok(); | |
| } | |
| // ============================================================ | |
| Broadcaster::ReportId::ReportId() | |
| :id(), | |
| pkg(), | |
| cls() { | |
| } | |
| Broadcaster::ReportId::ReportId(const ReportId& that) | |
| :id(that.id), | |
| pkg(that.pkg), | |
| cls(that.cls) { | |
| } | |
| Broadcaster::ReportId::ReportId(const string& i, const string& p, const string& c) | |
| :id(i), | |
| pkg(p), | |
| cls(c) { | |
| } | |
| Broadcaster::ReportId::~ReportId() { | |
| } | |
| bool Broadcaster::ReportId::operator<(const ReportId& that) const { | |
| if (id < that.id) { | |
| return true; | |
| } | |
| if (id > that.id) { | |
| return false; | |
| } | |
| if (pkg < that.pkg) { | |
| return true; | |
| } | |
| if (pkg > that.pkg) { | |
| return false; | |
| } | |
| if (cls < that.cls) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| // ============================================================ | |
| Broadcaster::ReportStatus::ReportStatus() | |
| :approval_sent(false), | |
| ready_sent(false), | |
| listener(nullptr) { | |
| } | |
| Broadcaster::ReportStatus::ReportStatus(const ReportStatus& that) | |
| :approval_sent(that.approval_sent), | |
| ready_sent(that.ready_sent), | |
| listener(that.listener) { | |
| } | |
| Broadcaster::ReportStatus::~ReportStatus() { | |
| } | |
| // ============================================================ | |
| Broadcaster::Broadcaster(const sp<WorkDirectory>& workDirectory) | |
| :mReportHandler(), | |
| mWorkDirectory(workDirectory) { | |
| } | |
| void Broadcaster::setHandler(const sp<ReportHandler>& handler) { | |
| mReportHandler = handler; | |
| } | |
| void Broadcaster::reset() { | |
| unique_lock<mutex> lock(mLock); | |
| mLastSent = 0; | |
| mHistory.clear(); | |
| // Could cancel the listeners, but this happens when | |
| // the system process crashes, so don't bother. | |
| } | |
| void Broadcaster::clearBroadcasts(const string& pkg, const string& cls, const string& id) { | |
| unique_lock<mutex> lock(mLock); | |
| map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls)); | |
| if (found != mHistory.end()) { | |
| if (found->second.listener != nullptr) { | |
| sp<IIncidentCompanion> ics = get_incident_companion(); | |
| if (ics != nullptr) { | |
| ics->cancelAuthorization(found->second.listener); | |
| } | |
| } | |
| mHistory.erase(found); | |
| } | |
| } | |
| void Broadcaster::clearPackageBroadcasts(const string& pkg) { | |
| unique_lock<mutex> lock(mLock); | |
| map<ReportId,ReportStatus>::iterator it = mHistory.begin(); | |
| while (it != mHistory.end()) { | |
| if (it->first.pkg == pkg) { | |
| if (it->second.listener != nullptr) { | |
| sp<IIncidentCompanion> ics = get_incident_companion(); | |
| if (ics != nullptr) { | |
| ics->cancelAuthorization(it->second.listener); | |
| } | |
| } | |
| it = mHistory.erase(it); | |
| } else { | |
| it++; | |
| } | |
| } | |
| } | |
| Broadcaster::broadcast_status_t Broadcaster::sendBroadcasts() { | |
| int err; | |
| int64_t lastSent = get_last_sent(); | |
| vector<sp<ReportFile>> files; | |
| mWorkDirectory->getReports(&files, 0); //lastSent); | |
| // Don't send multiple broadcasts to the same receiver. | |
| set<ReportId> reportReadyBroadcasts; | |
| for (const sp<ReportFile>& file: files) { | |
| err = file->loadEnvelope(); | |
| if (err != NO_ERROR) { | |
| ALOGW("Error (%s) loading envelope from %s", strerror(-err), | |
| file->getEnvelopeFileName().c_str()); | |
| continue; | |
| } | |
| const ReportFileProto& envelope = file->getEnvelope(); | |
| if (!envelope.completed()) { | |
| ALOGI("Incident report not completed skipping it: %s", | |
| file->getEnvelopeFileName().c_str()); | |
| continue; | |
| } | |
| // When one of the broadcast functions in this loop fails, it's almost | |
| // certainly because the system process is crashing or has crashed. Rather | |
| // than continuing to pound on the system process and potentially make things | |
| // worse, we bail right away, return BROADCASTS_BACKOFF, and we will try | |
| // again later. In the meantime, if the system process did crash, it might | |
| // clear out mHistory, which means we'll be back here again to send the | |
| // backlog. | |
| size_t reportCount = envelope.report_size(); | |
| bool hasApprovalPending = false; | |
| for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) { | |
| const ReportFileProto_Report& report = envelope.report(reportIndex); | |
| status_t err; | |
| if (report.privacy_policy() == PRIVACY_POLICY_AUTOMATIC || report.share_approved()) { | |
| // It's privacy policy is AUTO, or it's been approved, | |
| // so send the actual broadcast. | |
| if (!was_ready_sent(file->getId(), report.pkg(), report.cls())) { | |
| if (report.pkg() == DROPBOX_SENTINEL.getPackageName() | |
| && report.cls() == DROPBOX_SENTINEL.getClassName()) { | |
| IncidentReportArgs args; | |
| get_args_from_report(&args, report); | |
| err = send_to_dropbox(file, args); | |
| if (err != NO_ERROR) { | |
| return BROADCASTS_BACKOFF; | |
| } | |
| } else { | |
| reportReadyBroadcasts.insert(ReportId(file->getId(), report.pkg(), | |
| report.cls())); | |
| } | |
| } | |
| } else { | |
| // It's not approved yet, so send the approval. | |
| if (!was_approval_sent(file->getId(), report.pkg(), report.cls())) { | |
| err = send_approval_broadcasts(file->getId(), report.pkg(), report.cls()); | |
| if (err != NO_ERROR) { | |
| return BROADCASTS_BACKOFF; | |
| } | |
| hasApprovalPending = true; | |
| } | |
| } | |
| } | |
| lastSent = file->getTimestampNs(); | |
| if (!hasApprovalPending) { | |
| set_last_sent(lastSent); | |
| } | |
| } | |
| for (const ReportId& report: reportReadyBroadcasts) { | |
| err = send_report_ready_broadcasts(report.id, report.pkg, report.cls); | |
| if (err != NO_ERROR) { | |
| return BROADCASTS_BACKOFF; | |
| } | |
| } | |
| return mWorkDirectory->hasMore(lastSent) ? BROADCASTS_REPEAT : BROADCASTS_FINISHED; | |
| } | |
| void Broadcaster::set_last_sent(int64_t timestamp) { | |
| unique_lock<mutex> lock(mLock); | |
| mLastSent = timestamp; | |
| } | |
| int64_t Broadcaster::get_last_sent() { | |
| unique_lock<mutex> lock(mLock); | |
| return mLastSent; | |
| } | |
| /* | |
| void Broadcaster::printReportStatuses() const { | |
| ALOGD("mHistory {"); | |
| for (map<ReportId,ReportStatus>::const_iterator it = mHistory.begin(); | |
| it != mHistory.end(); it++) { | |
| ALOGD(" [%s %s] --> [%d %d]", it->first.id.c_str(), it->first.pkg.c_str(), | |
| it->second.approval_sent, it->second.ready_sent); | |
| } | |
| ALOGD("}"); | |
| } | |
| */ | |
| bool Broadcaster::was_approval_sent(const string& id, const string& pkg, const string& cls) { | |
| unique_lock<mutex> lock(mLock); | |
| map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls)); | |
| if (found != mHistory.end()) { | |
| return found->second.approval_sent; | |
| } | |
| return false; | |
| } | |
| void Broadcaster::set_approval_sent(const string& id, const string& pkg, const string& cls, | |
| const sp<ConsentListener>& listener) { | |
| unique_lock<mutex> lock(mLock); | |
| ReportStatus& reportStatus = mHistory[ReportId(id, pkg, cls)]; | |
| reportStatus.approval_sent = true; | |
| reportStatus.listener = listener; | |
| } | |
| bool Broadcaster::was_ready_sent(const string& id, const string& pkg, const string& cls) { | |
| unique_lock<mutex> lock(mLock); | |
| map<ReportId,ReportStatus>::const_iterator found = mHistory.find(ReportId(id, pkg, cls)); | |
| if (found != mHistory.end()) { | |
| return found->second.ready_sent; | |
| } | |
| return false; | |
| } | |
| void Broadcaster::set_ready_sent(const string& id, const string& pkg, const string& cls) { | |
| unique_lock<mutex> lock(mLock); | |
| mHistory[ReportId(id, pkg, cls)].ready_sent = true; | |
| } | |
| status_t Broadcaster::send_approval_broadcasts(const string& id, const string& pkg, | |
| const string& cls) { | |
| sp<IIncidentCompanion> ics = get_incident_companion(); | |
| if (ics == nullptr) { | |
| return NAME_NOT_FOUND; | |
| } | |
| sp<ConsentListener> listener = new ConsentListener(this, ReportId(id, pkg, cls)); | |
| ALOGI("send_approval_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str()); | |
| Status status = ics->authorizeReport(0, String16(pkg.c_str()), | |
| String16(cls.c_str()), String16(id.c_str()), 0, listener); | |
| if (!status.isOk()) { | |
| // authorizeReport is oneway, so any error is a transaction error. | |
| return status.transactionError(); | |
| } | |
| set_approval_sent(id, pkg, cls, listener); | |
| return NO_ERROR; | |
| } | |
| void Broadcaster::report_approved(const ReportId& reportId) { | |
| status_t err; | |
| // Kick off broadcaster to do send the ready broadcasts. | |
| ALOGI("The user approved the report, so kicking off another broadcast pass. %s %s/%s", | |
| reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str()); | |
| sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id, | |
| nullptr); | |
| if (file != nullptr) { | |
| err = file->loadEnvelope(); | |
| if (err != NO_ERROR) { | |
| return; | |
| } | |
| err = file->markApproved(reportId.pkg, reportId.cls); | |
| if (err != NO_ERROR) { | |
| ALOGI("Couldn't find report that was just approved: %s %s/%s", | |
| reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str()); | |
| return; | |
| } | |
| file->saveEnvelope(); | |
| if (err != NO_ERROR) { | |
| return; | |
| } | |
| } | |
| mReportHandler->scheduleSendBacklog(); | |
| } | |
| void Broadcaster::report_denied(const ReportId& reportId) { | |
| // The user didn't approve the report, so remove it from the WorkDirectory. | |
| ALOGI("The user denied the report, so deleting it. %s %s/%s", | |
| reportId.id.c_str(), reportId.pkg.c_str(), reportId.cls.c_str()); | |
| sp<ReportFile> file = mWorkDirectory->getReport(reportId.pkg, reportId.cls, reportId.id, | |
| nullptr); | |
| if (file != nullptr) { | |
| mWorkDirectory->commit(file, reportId.pkg, reportId.cls); | |
| } | |
| } | |
| status_t Broadcaster::send_report_ready_broadcasts(const string& id, const string& pkg, | |
| const string& cls) { | |
| sp<IIncidentCompanion> ics = get_incident_companion(); | |
| if (ics == nullptr) { | |
| return NAME_NOT_FOUND; | |
| } | |
| ALOGI("send_report_ready_broadcasts for %s %s/%s", id.c_str(), pkg.c_str(), cls.c_str()); | |
| Status status = ics->sendReportReadyBroadcast(String16(pkg.c_str()), String16(cls.c_str())); | |
| if (!status.isOk()) { | |
| // sendReportReadyBroadcast is oneway, so any error is a transaction error. | |
| return status.transactionError(); | |
| } | |
| set_ready_sent(id, pkg, cls); | |
| return NO_ERROR; | |
| } | |
| status_t Broadcaster::send_to_dropbox(const sp<ReportFile>& file, | |
| const IncidentReportArgs& args) { | |
| status_t err; | |
| sp<DropBoxManager> dropbox = new DropBoxManager(); | |
| if (dropbox == nullptr) { | |
| ALOGW("Can't reach dropbox now, so we won't be able to write the incident report to there"); | |
| return NO_ERROR; | |
| } | |
| int fds[2]; | |
| if (pipe(fds) != 0) { | |
| ALOGW("Error opening pipe to filter incident report: %s", file->getDataFileName().c_str()); | |
| return NO_ERROR; | |
| } | |
| int readFd = 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(); | |
| // Takes ownership of readFd. | |
| Status status = dropbox->addFile(String16("incident"), readFd, 0); | |
| if (!status.isOk()) { | |
| // TODO: This may or may not leak the readFd, depending on where it failed. | |
| // Not sure how to fix this given the dropbox API. | |
| ALOGW("Error sending incident report to dropbox."); | |
| return -errno; | |
| } | |
| // On successful write, tell the working directory that this file is done. | |
| mWorkDirectory->commit(file, DROPBOX_SENTINEL.getPackageName(), | |
| DROPBOX_SENTINEL.getClassName()); | |
| // Don't need to call set_ready_sent, because we just removed it from the ReportFile, | |
| // so we'll never hear about it again. | |
| return NO_ERROR; | |
| } | |
| sp<IIncidentCompanion> Broadcaster::get_incident_companion() { | |
| sp<IBinder> binder = defaultServiceManager()->getService(String16("incidentcompanion")); | |
| if (binder == nullptr) { | |
| ALOGI("Can not find IIncidentCompanion service to send broadcast. Will try again later."); | |
| return nullptr; | |
| } | |
| sp<IIncidentCompanion> ics = interface_cast<IIncidentCompanion>(binder); | |
| if (ics == nullptr) { | |
| ALOGI("The incidentcompanion service is not an IIncidentCompanion. Will try again later."); | |
| return nullptr; | |
| } | |
| return ics; | |
| } | |
| } // namespace incidentd | |
| } // namespace os | |
| } // namespace android | |