blob: 63464f2d58c56f0bdb5b3a83e9afc3c2a94a1362 [file] [log] [blame]
/*
* 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