+ Remove the spawned thread inside the ReportFile for filter_and_write_report
because it leads to accessing freed memory
Instead, let the caller of ReportFile::startFileteringData create the thread.
ReportFile class shouldn't care about whether it's writing to a pipe for IPC
or regular file.
+ Add uri building in incidentd
+ Add metadata and headers to incident reports
Test: existing passed tests in incidentd_test still pass.
Manually tested with statsd
Change-Id: I5fef900d31f5d181275814f1e1c8c98443f201a7
449 lines
14 KiB
C++
449 lines
14 KiB
C++
/*
|
|
* 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
|
|
|
|
|