diff options
Diffstat (limited to 'cmds/incidentd/src')
24 files changed, 3639 insertions, 998 deletions
diff --git a/cmds/incidentd/src/Broadcaster.cpp b/cmds/incidentd/src/Broadcaster.cpp new file mode 100644 index 000000000000..39e5393e1f81 --- /dev/null +++ b/cmds/incidentd/src/Broadcaster.cpp @@ -0,0 +1,440 @@ +/* + * 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> + +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; + } + + // Start a thread to write the data to dropbox. + int readFd = -1; + err = file->startFilteringData(&readFd, args); + if (err != NO_ERROR) { + return err; + } + + // 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 + + diff --git a/cmds/incidentd/src/Broadcaster.h b/cmds/incidentd/src/Broadcaster.h new file mode 100644 index 000000000000..933029709fb6 --- /dev/null +++ b/cmds/incidentd/src/Broadcaster.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 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. + */ +#pragma once + +#include "WorkDirectory.h" + +#include <android/os/BnIncidentAuthListener.h> +#include <android/os/IIncidentCompanion.h> +#include <frameworks/base/cmds/incidentd/src/report_file.pb.h> + +namespace android { +namespace os { +namespace incidentd { + +using android::binder::Status; +using android::os::BnIncidentAuthListener; +using android::os::IIncidentCompanion; + +class ReportHandler; + +class Broadcaster : public virtual RefBase { +public: + enum broadcast_status_t { + BROADCASTS_FINISHED = 0, + BROADCASTS_REPEAT = 1, + BROADCASTS_BACKOFF = 2 + }; + + Broadcaster(const sp<WorkDirectory>& workDirectory); + + void setHandler(const sp<ReportHandler>& handler); + + /** + * Reset the beginning timestamp for broadcasts. Call this when + * the system_server restarts. + */ + void reset(); + + /** + * Remove the history record for the broadcasts, including pending authorizations + * if necessary. + */ + void clearBroadcasts(const string& pkg, const string& cls, const string& id); + void clearPackageBroadcasts(const string& pkg); + + /** + * Send whichever broadcasts have been pending. + */ + broadcast_status_t sendBroadcasts(); + +private: + struct ReportId { + ReportId(); + ReportId(const ReportId& that); + ReportId(const string& i, const string& p, const string& c); + ~ReportId(); + + bool operator<(const ReportId& that) const; + + string id; + string pkg; + string cls; + }; + + class ConsentListener : public BnIncidentAuthListener { + public: + ConsentListener(const sp<Broadcaster>& broadcaster, const ReportId& reportId); + virtual ~ConsentListener(); + virtual Status onReportApproved(); + virtual Status onReportDenied(); + private: + sp<Broadcaster> mBroadcaster; + ReportId mId; + }; + + struct ReportStatus { + ReportStatus(); + ReportStatus(const ReportStatus& that); + ~ReportStatus(); + + bool approval_sent; + bool ready_sent; + sp<ConsentListener> listener; + }; + + sp<ReportHandler> mReportHandler; + sp<WorkDirectory> mWorkDirectory; + + // protected by mLock + mutex mLock; + map<ReportId,ReportStatus> mHistory; // what we sent so we don't send it again + int64_t mLastSent; + + void set_last_sent(int64_t timestamp); + int64_t get_last_sent(); + void print_report_statuses() const; + status_t send_approval_broadcasts(const string& id, const string& pkg, const string& cls); + void report_approved(const ReportId& reportId); + void report_denied(const ReportId& reportId); + status_t send_report_ready_broadcasts(const string& id, const string& pkg, const string& cls); + status_t send_to_dropbox(const sp<ReportFile>& file, const IncidentReportArgs& args); + bool was_approval_sent(const string& id, const string& pkg, const string& cls); + void set_approval_sent(const string& id, const string& pkg, const string& cls, + const sp<ConsentListener>& listener); + bool was_ready_sent(const string& id, const string& pkg, const string& cls); + void set_ready_sent(const string& id, const string& pkg, const string& cls); + sp<IIncidentCompanion> get_incident_companion(); +}; + + +} // namespace incidentd +} // namespace os +} // namespace android + diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index 04819ec75a09..b46c9e357fc4 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -31,12 +31,18 @@ namespace os { namespace incidentd { const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB -const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max +const ssize_t MAX_BUFFER_COUNT = 1536; // 24 MB max FdBuffer::FdBuffer() - : mBuffer(BUFFER_SIZE), mStartTime(-1), mFinishTime(-1), mTimedOut(false), mTruncated(false) {} + :mBuffer(new EncodedBuffer(BUFFER_SIZE)), + mStartTime(-1), + mFinishTime(-1), + mTimedOut(false), + mTruncated(false) { +} -FdBuffer::~FdBuffer() {} +FdBuffer::~FdBuffer() { +} status_t FdBuffer::read(int fd, int64_t timeout) { struct pollfd pfds = {.fd = fd, .events = POLLIN}; @@ -45,12 +51,12 @@ status_t FdBuffer::read(int fd, int64_t timeout) { fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); while (true) { - if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { mTruncated = true; VLOG("Truncating data"); break; } - if (mBuffer.writeBuffer() == NULL) { + if (mBuffer->writeBuffer() == NULL) { VLOG("No memory"); return NO_MEMORY; } @@ -76,7 +82,7 @@ status_t FdBuffer::read(int fd, int64_t timeout) { return errno != 0 ? -errno : UNKNOWN_ERROR; } else { ssize_t amt = TEMP_FAILURE_RETRY( - ::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite())); + ::read(fd, mBuffer->writeBuffer(), mBuffer->currentToWrite())); if (amt < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { continue; @@ -88,7 +94,7 @@ status_t FdBuffer::read(int fd, int64_t timeout) { VLOG("Reached EOF of fd=%d", fd); break; } - mBuffer.wp()->move(amt); + mBuffer->wp()->move(amt); } } } @@ -100,28 +106,28 @@ status_t FdBuffer::readFully(int fd) { mStartTime = uptimeMillis(); while (true) { - if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { // Don't let it get too big. mTruncated = true; VLOG("Truncating data"); break; } - if (mBuffer.writeBuffer() == NULL) { + if (mBuffer->writeBuffer() == NULL) { VLOG("No memory"); return NO_MEMORY; } ssize_t amt = - TEMP_FAILURE_RETRY(::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite())); + TEMP_FAILURE_RETRY(::read(fd, mBuffer->writeBuffer(), mBuffer->currentToWrite())); if (amt < 0) { VLOG("Fail to read %d: %s", fd, strerror(errno)); return -errno; } else if (amt == 0) { - VLOG("Done reading %zu bytes", mBuffer.size()); + VLOG("Done reading %zu bytes", mBuffer->size()); // We're done. break; } - mBuffer.wp()->move(amt); + mBuffer->wp()->move(amt); } mFinishTime = uptimeMillis(); @@ -150,12 +156,12 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f // This is the buffer used to store processed data while (true) { - if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { VLOG("Truncating data"); mTruncated = true; break; } - if (mBuffer.writeBuffer() == NULL) { + if (mBuffer->writeBuffer() == NULL) { VLOG("No memory"); return NO_MEMORY; } @@ -248,7 +254,7 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f // read from parsing process ssize_t amt = TEMP_FAILURE_RETRY( - ::read(fromFd.get(), mBuffer.writeBuffer(), mBuffer.currentToWrite())); + ::read(fromFd.get(), mBuffer->writeBuffer(), mBuffer->currentToWrite())); if (amt < 0) { if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { VLOG("Fail to read fromFd %d: %s", fromFd.get(), strerror(errno)); @@ -258,7 +264,7 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f VLOG("Reached EOF of fromFd %d", fromFd.get()); break; } else { - mBuffer.wp()->move(amt); + mBuffer->wp()->move(amt); } } @@ -266,9 +272,25 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f return NO_ERROR; } -size_t FdBuffer::size() const { return mBuffer.size(); } +status_t FdBuffer::write(uint8_t const* buf, size_t size) { + return mBuffer->writeRaw(buf, size); +} + +status_t FdBuffer::write(const sp<ProtoReader>& reader) { + return mBuffer->writeRaw(reader); +} + +status_t FdBuffer::write(const sp<ProtoReader>& reader, size_t size) { + return mBuffer->writeRaw(reader, size); +} -EncodedBuffer::iterator FdBuffer::data() const { return mBuffer.begin(); } +size_t FdBuffer::size() const { + return mBuffer->size(); +} + +sp<EncodedBuffer> FdBuffer::data() const { + return mBuffer; +} } // namespace incidentd } // namespace os diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h index 20deefd02558..a3493604f425 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -64,6 +64,21 @@ public: const bool isSysfs = false); /** + * Write by hand into the buffer. + */ + status_t write(uint8_t const* buf, size_t size); + + /** + * Write all the data from a ProtoReader into our internal buffer. + */ + status_t write(const sp<ProtoReader>& data); + + /** + * Write size bytes of data from a ProtoReader into our internal buffer. + */ + status_t write(const sp<ProtoReader>& data, size_t size); + + /** * Whether we timed out. */ bool timedOut() const { return mTimedOut; } @@ -89,17 +104,12 @@ public: int64_t durationMs() const { return mFinishTime - mStartTime; } /** - * Reader API for data stored in FdBuffer - */ - EncodedBuffer::iterator data() const; - - /** - * Return the internal buffer, don't call unless you are familiar with EncodedBuffer. + * Get the EncodedBuffer inside. */ - EncodedBuffer* getInternalBuffer() { return &mBuffer; } + sp<EncodedBuffer> data() const; private: - EncodedBuffer mBuffer; + sp<EncodedBuffer> mBuffer; int64_t mStartTime; int64_t mFinishTime; bool mTimedOut; diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp index f8fb4a676ba0..4ba31b45e81c 100644 --- a/cmds/incidentd/src/IncidentService.cpp +++ b/cmds/incidentd/src/IncidentService.cpp @@ -19,7 +19,7 @@ #include "IncidentService.h" #include "FdBuffer.h" -#include "PrivacyBuffer.h" +#include "PrivacyFilter.h" #include "Reporter.h" #include "incidentd_util.h" #include "section_list.h" @@ -35,9 +35,12 @@ #include <unistd.h> -enum { WHAT_RUN_REPORT = 1, WHAT_SEND_BACKLOG_TO_DROPBOX = 2 }; +enum { + WHAT_TAKE_REPORT = 1, + WHAT_SEND_BROADCASTS = 2 +}; -#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL) +#define DEFAULT_DELAY_NS (1000000000LL) #define DEFAULT_BYTES_SIZE_LIMIT (20 * 1024 * 1024) // 20MB #define DEFAULT_REFACTORY_PERIOD_MS (24 * 60 * 60 * 1000) // 1 Day @@ -53,6 +56,7 @@ namespace android { namespace os { namespace incidentd { +String16 const APPROVE_INCIDENT_REPORTS("android.permission.APPROVE_INCIDENT_REPORTS"); String16 const DUMP_PERMISSION("android.permission.DUMP"); String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS"); @@ -60,7 +64,14 @@ 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! + // Root and shell are ok. + return Status::ok(); + } + + if (checkCallingPermission(APPROVE_INCIDENT_REPORTS)) { + // Permission controller (this is a singleton permission that is always granted + // exactly for PermissionController) is allowed to access incident reports + // so it can show the user info about what they are approving. return Status::ok(); } @@ -81,8 +92,8 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) { } // checking calling request uid permission. - switch (args.dest()) { - case DEST_LOCAL: + switch (args.getPrivacyPolicy()) { + case PRIVACY_POLICY_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); @@ -91,7 +102,7 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) { "Calling process does not have permission to get local data."); } break; - case DEST_EXPLICIT: + case PRIVACY_POLICY_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.", @@ -105,78 +116,79 @@ static Status checkIncidentPermissions(const IncidentReportArgs& args) { 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; - } +static string build_uri(const string& pkg, const string& cls, const string& id) { + return "build_uri not implemented " + pkg + "/" + cls + "/" + id; } // ================================================================================ -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(const sp<WorkDirectory>& workDirectory, + const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper, + const sp<Throttler>& throttler) + :mLock(), + mWorkDirectory(workDirectory), + mBroadcaster(broadcaster), + mHandlerLooper(handlerLooper), + mBacklogDelay(DEFAULT_DELAY_NS), + mThrottler(throttler), + mBatch(new ReportBatch()) { +} -ReportHandler::~ReportHandler() {} +ReportHandler::~ReportHandler() { +} void ReportHandler::handleMessage(const Message& message) { switch (message.what) { - case WHAT_RUN_REPORT: - run_report(); + case WHAT_TAKE_REPORT: + take_report(); break; - case WHAT_SEND_BACKLOG_TO_DROPBOX: - send_backlog_to_dropbox(); + case WHAT_SEND_BROADCASTS: + send_broadcasts(); 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::schedulePersistedReport(const IncidentReportArgs& args) { + mBatch->addPersistedReport(args); + mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT); + mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT)); +} + +void ReportHandler::scheduleStreamingReport(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, int streamFd) { + mBatch->addStreamingReport(args, listener, streamFd); + mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT); + mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT)); } -void ReportHandler::scheduleSendBacklogToDropbox() { +void ReportHandler::scheduleSendBacklog() { unique_lock<mutex> lock(mLock); - mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS; - schedule_send_backlog_to_dropbox_locked(); + mBacklogDelay = DEFAULT_DELAY_NS; + schedule_send_broadcasts_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::schedule_send_broadcasts_locked() { + mHandlerLooper->removeMessages(this, WHAT_SEND_BROADCASTS); + mHandlerLooper->sendMessageDelayed(mBacklogDelay, this, Message(WHAT_SEND_BROADCASTS)); } -void ReportHandler::run_report() { - sp<Reporter> reporter = new Reporter(); +void ReportHandler::take_report() { + // Cycle the batch + sp<ReportBatch> batch; + { + unique_lock<mutex> lock(mLock); + batch = mBatch; + mBatch = new ReportBatch(); + } - // 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 (batch->empty()) { + // Nothing to do. + return; } + sp<Reporter> reporter = new Reporter(mWorkDirectory, batch); + + // TODO: Do we really want to clear the reports if we throttle? Should we only throttle + // requests going to dropbox? How do we reconcile throttling with testing? if (mThrottler->shouldThrottle()) { ALOGW("RunReport got throttled."); return; @@ -185,46 +197,76 @@ void ReportHandler::run_report() { // 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. + // TODO: Move this inside reporter. size_t reportByteSize = 0; - Reporter::run_report_status_t reportStatus = reporter->runReport(&reportByteSize); + reporter->runReport(&reportByteSize); + mThrottler->addReportSize(reportByteSize); - if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) { + + // Kick off the next steps, one of which is to send any new or otherwise remaining + // approvals, and one of which is to send any new or remaining broadcasts. + { unique_lock<mutex> lock(mLock); - schedule_send_backlog_to_dropbox_locked(); + schedule_send_broadcasts_locked(); } } -void ReportHandler::send_backlog_to_dropbox() { - if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) { +void ReportHandler::send_broadcasts() { + Broadcaster::broadcast_status_t result = mBroadcaster->sendBroadcasts(); + if (result == Broadcaster::BROADCASTS_FINISHED) { + // We're done. + unique_lock<mutex> lock(mLock); + mBacklogDelay = DEFAULT_DELAY_NS; + } else if (result == Broadcaster::BROADCASTS_REPEAT) { + // It worked, but there are more. + unique_lock<mutex> lock(mLock); + mBacklogDelay = DEFAULT_DELAY_NS; + schedule_send_broadcasts_locked(); + } else if (result == Broadcaster::BROADCASTS_BACKOFF) { // 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; + schedule_send_broadcasts_locked(); } } // ================================================================================ -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(const sp<Looper>& handlerLooper) { + mThrottler = new Throttler(DEFAULT_BYTES_SIZE_LIMIT, DEFAULT_REFACTORY_PERIOD_MS); + mWorkDirectory = new WorkDirectory(); + mBroadcaster = new Broadcaster(mWorkDirectory); + mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper, + mThrottler); + mBroadcaster->setHandler(mHandler); } IncidentService::~IncidentService() {} Status IncidentService::reportIncident(const IncidentReportArgs& args) { - ALOGI("reportIncident"); + // TODO: Validate that the privacy policy is one of the real ones. + // If it isn't, clamp it to the next more restrictive real one. + // TODO: This function should reject the LOCAL privacy policy. + // Those have to stream. + + // TODO: Check that the broadcast recevier has the proper permissions + // TODO: Maybe we should consider relaxing the permissions if it's going to + // dropbox, but definitely not if it's going to the broadcaster. Status status = checkIncidentPermissions(args); if (!status.isOk()) { return status; } - mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1)); + // If they didn't specify a component, use dropbox. + IncidentReportArgs argsCopy(args); + if (argsCopy.receiverPkg().length() == 0 && argsCopy.receiverCls().length() == 0) { + argsCopy.setReceiverPkg(DROPBOX_SENTINEL.getPackageName()); + argsCopy.setReceiverCls(DROPBOX_SENTINEL.getClassName()); + } + + mHandler->schedulePersistedReport(argsCopy); return Status::ok(); } @@ -232,19 +274,29 @@ Status IncidentService::reportIncident(const IncidentReportArgs& args) { Status IncidentService::reportIncidentToStream(const IncidentReportArgs& args, const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream) { - ALOGI("reportIncidentToStream"); + // TODO: Validate that the privacy policy is one of the real ones. + // If it isn't, clamp it to the next more restrictive real one. - Status status = checkIncidentPermissions(args); + // TODO: Only shell should be able to do a LOCAL privacy policy report. + + // Streaming reports can not also be broadcast. + IncidentReportArgs argsCopy(args); + argsCopy.setReceiverPkg(""); + argsCopy.setReceiverCls(""); + + Status status = checkIncidentPermissions(argsCopy); if (!status.isOk()) { return status; } + + // The ReportRequest takes ownership of the fd, so we need to dup it. int fd = dup(stream.get()); if (fd < 0) { return Status::fromStatusT(-errno); } - mHandler->scheduleRunReport(new ReportRequest(args, listener, fd)); + mHandler->scheduleStreamingReport(argsCopy, listener, fd); return Status::ok(); } @@ -256,7 +308,92 @@ Status IncidentService::systemRunning() { } // When system_server is up and running, schedule the dropbox task to run. - mHandler->scheduleSendBacklogToDropbox(); + mBroadcaster->reset(); + mHandler->scheduleSendBacklog(); + + return Status::ok(); +} + +Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16, + vector<String16>* result) { + status_t err; + const string pkg(String8(pkg16).string()); + const string cls(String8(cls16).string()); + + // List the reports + vector<sp<ReportFile>> all; + err = mWorkDirectory->getReports(&all, 0); + if (err != NO_ERROR) { + return Status::fromStatusT(err); + } + + // Find the ones that match pkg and cls. + for (sp<ReportFile>& file: all) { + err = file->loadEnvelope(); + if (err != NO_ERROR) { + continue; + } + const ReportFileProto& envelope = file->getEnvelope(); + size_t reportCount = envelope.report_size(); + for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) { + const ReportFileProto_Report& report = envelope.report(reportIndex); + if (pkg == report.pkg() && cls == report.cls()) { + result->push_back(String16(build_uri(pkg, cls, file->getId()).c_str())); + break; + } + } + } + + return Status::ok(); +} + +Status IncidentService::getIncidentReport(const String16& pkg16, const String16& cls16, + const String16& id16, IncidentManager::IncidentReport* result) { + status_t err; + + const string pkg(String8(pkg16).string()); + const string cls(String8(cls16).string()); + const string id(String8(id16).string()); + + IncidentReportArgs args; + sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args); + if (file != nullptr) { + int fd; + err = file->startFilteringData(&fd, args); + if (err != 0) { + ALOGW("Error reading data file that we think should exist: %s", + file->getDataFileName().c_str()); + return Status::ok(); + } + + result->setTimestampNs(file->getTimestampNs()); + result->setPrivacyPolicy(file->getEnvelope().privacy_policy()); + result->takeFileDescriptor(fd); + } + + return Status::ok(); +} + +Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16, + const String16& id16) { + const string pkg(String8(pkg16).string()); + const string cls(String8(cls16).string()); + const string id(String8(id16).string()); + + sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr); + if (file != nullptr) { + mWorkDirectory->commit(file, pkg, cls); + } + mBroadcaster->clearBroadcasts(pkg, cls, id); + + return Status::ok(); +} + +Status IncidentService::deleteAllIncidentReports(const String16& pkg16) { + const string pkg(String8(pkg16).string()); + + mWorkDirectory->commitAll(pkg); + mBroadcaster->clearPackageBroadcasts(pkg); return Status::ok(); } @@ -354,7 +491,7 @@ status_t IncidentService::cmd_help(FILE* out) { 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); + fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy); if (p->children == NULL) return; for (int i = 0; p->children[i] != NULL; i++) { // NULL-terminated. printPrivacy(p->children[i], out, indent + " "); @@ -362,6 +499,8 @@ static void printPrivacy(const Privacy* p, FILE* out, String8 indent) { } status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<String8>& args) { + (void)in; + const int argCount = args.size(); if (argCount >= 3) { String8 opt = args[1]; @@ -376,6 +515,7 @@ status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<Str 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) { @@ -383,15 +523,17 @@ status_t IncidentService::cmd_privacy(FILE* in, FILE* out, FILE* err, Vector<Str return error; } fprintf(err, "Read %zu bytes\n", buf.size()); - PrivacyBuffer pBuf(p, buf.data()); + PrivacyFilter 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); + fprintf(err, "Error strip pii fields with spec %d\n", spec.policy); return error; } return pBuf.flush(fileno(out)); + */ + return -1; } } else { return cmd_help(out); @@ -408,7 +550,7 @@ status_t IncidentService::dump(int fd, const Vector<String16>& args) { ALOGD("Dump incident proto"); IncidentReportArgs incidentArgs; - incidentArgs.setDest(DEST_EXPLICIT); + incidentArgs.setPrivacyPolicy(PRIVACY_POLICY_EXPLICIT); int skipped[] = SKIPPED_SECTIONS; for (const Section** section = SECTION_LIST; *section; section++) { const int id = (*section)->id; @@ -421,12 +563,16 @@ status_t IncidentService::dump(int fd, const Vector<String16>& args) { return PERMISSION_DENIED; } + // The ReportRequest takes ownership of the fd, so we need to dup it. int fd1 = dup(fd); if (fd1 < 0) { return -errno; } - mHandler->scheduleRunReport(new ReportRequest(incidentArgs, NULL, fd1)); + // TODO: Remove this. Someone even dumpstate, wanting to get an incident report + // should use the API. That will take making dumpstated call the API, which is a + // good thing. It also means it won't be subject to the timeout. + mHandler->scheduleStreamingReport(incidentArgs, NULL, fd1); return NO_ERROR; } diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h index c63a183b70b9..6481321bbd69 100644 --- a/cmds/incidentd/src/IncidentService.h +++ b/cmds/incidentd/src/IncidentService.h @@ -20,13 +20,16 @@ #include "Reporter.h" +#include "Broadcaster.h" +#include "Throttler.h" +#include "WorkDirectory.h" + #include <android/os/BnIncidentManager.h> #include <utils/Looper.h> -#include <deque> +#include <vector> #include <mutex> -#include "Throttler.h" namespace android { namespace os { @@ -38,60 +41,74 @@ using namespace android::binder; using namespace android::os; // ================================================================================ -class ReportRequestQueue : public virtual RefBase { -public: - ReportRequestQueue(); - virtual ~ReportRequestQueue(); - - void addRequest(const sp<ReportRequest>& request); - sp<ReportRequest> getNextRequest(); - -private: - mutex mLock; - deque<sp<ReportRequest> > mQueue; -}; - -// ================================================================================ class ReportHandler : public MessageHandler { public: - ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue, - const sp<Throttler>& throttler); + ReportHandler(const sp<WorkDirectory>& workDirectory, + const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper, + const sp<Throttler>& throttler); virtual ~ReportHandler(); virtual void handleMessage(const Message& message); /** - * Adds a ReportRequest to the queue. + * Schedule a report for the "main" report, where it will be delivered to + * the uploaders and/or dropbox. */ - void scheduleRunReport(const sp<ReportRequest>& request); + void schedulePersistedReport(const IncidentReportArgs& args); + + /** + * Adds a ReportRequest to the queue for one that has a listener an and fd + */ + void scheduleStreamingReport(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, + int streamFd); /** * Resets mBacklogDelay to the default and schedules sending * the messages to dropbox. */ - void scheduleSendBacklogToDropbox(); + void scheduleSendBacklog(); private: mutex mLock; - nsecs_t mBacklogDelay; + + sp<WorkDirectory> mWorkDirectory; + sp<Broadcaster> mBroadcaster; + sp<Looper> mHandlerLooper; - sp<ReportRequestQueue> mQueue; + nsecs_t mBacklogDelay; sp<Throttler> mThrottler; + sp<ReportBatch> mBatch; + /** * Runs all of the reports that have been queued. */ - void run_report(); + void take_report(); + + /** + * Schedules permission controller approve the reports. + */ + void schedule_send_approvals_locked(); /** - * Schedules a dropbox task mBacklogDelay nanoseconds from now. + * Sends the approvals to the PermissionController */ - void schedule_send_backlog_to_dropbox_locked(); + void send_approvals(); /** - * Sends the backlog to the dropbox service. + * Schedules the broadcasts that reports are complete mBacklogDelay nanoseconds from now. + * The delay is because typically when an incident report is taken, the system is not + * really in a happy state. So we wait a bit before sending the report to let things + * quiet down if they can. The urgency is in taking the report, not sharing the report. + * However, we don */ - void send_backlog_to_dropbox(); + void schedule_send_broadcasts_locked(); + + /** + * Sends the broadcasts to the dropbox service. + */ + void send_broadcasts(); }; // ================================================================================ @@ -108,6 +125,17 @@ public: virtual Status systemRunning(); + virtual Status getIncidentReportList(const String16& pkg, const String16& cls, + vector<String16>* result); + + virtual Status getIncidentReport(const String16& pkg, const String16& cls, + const String16& id, IncidentManager::IncidentReport* result); + + virtual Status deleteIncidentReports(const String16& pkg, const String16& cls, + const String16& id); + + virtual Status deleteAllIncidentReports(const String16& pkg); + // Implement commands for debugging purpose. virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override; @@ -115,7 +143,8 @@ public: virtual status_t dump(int fd, const Vector<String16>& args); private: - sp<ReportRequestQueue> mQueue; + sp<WorkDirectory> mWorkDirectory; + sp<Broadcaster> mBroadcaster; sp<ReportHandler> mHandler; sp<Throttler> mThrottler; diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp index 6e55f906087c..386303b038e7 100644 --- a/cmds/incidentd/src/Privacy.cpp +++ b/cmds/incidentd/src/Privacy.cpp @@ -23,6 +23,8 @@ namespace android { namespace os { namespace incidentd { +using namespace android::os; + uint64_t encode_field_id(const Privacy* p) { return (uint64_t)p->type << 32 | p->field_id; } const Privacy* lookup(const Privacy* p, uint32_t fieldId) { @@ -35,39 +37,48 @@ const Privacy* lookup(const Privacy* p, uint32_t fieldId) { return NULL; } -static bool allowDest(const uint8_t dest, const uint8_t policy) { - switch (policy) { - case android::os::DEST_LOCAL: - return dest == android::os::DEST_LOCAL; - case android::os::DEST_EXPLICIT: - case DEST_UNSET: - return dest == android::os::DEST_LOCAL || dest == android::os::DEST_EXPLICIT || - dest == DEST_UNSET; - case android::os::DEST_AUTOMATIC: +static bool isAllowed(const uint8_t policy, const uint8_t check) { + switch (check) { + case PRIVACY_POLICY_LOCAL: + return policy == PRIVACY_POLICY_LOCAL; + case PRIVACY_POLICY_EXPLICIT: + case PRIVACY_POLICY_UNSET: + return policy == PRIVACY_POLICY_LOCAL + || policy == PRIVACY_POLICY_EXPLICIT + || policy == PRIVACY_POLICY_UNSET; + case PRIVACY_POLICY_AUTOMATIC: return true; default: return false; } } -bool PrivacySpec::operator<(const PrivacySpec& other) const { return dest < other.dest; } +PrivacySpec::PrivacySpec(uint8_t argPolicy) { + // TODO: Why on earth do we have two definitions of policy. Maybe + // it's not too late to clean this up. + switch (argPolicy) { + case android::os::PRIVACY_POLICY_AUTOMATIC: + case android::os::PRIVACY_POLICY_EXPLICIT: + case android::os::PRIVACY_POLICY_LOCAL: + mPolicy = argPolicy; + break; + default: + mPolicy = android::os::PRIVACY_POLICY_AUTOMATIC; + break; + } +} -bool PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) const { - uint8_t policy = privacy != NULL ? privacy->dest : defaultDest; - return allowDest(dest, policy); +bool PrivacySpec::operator<(const PrivacySpec& that) const { + return mPolicy < that.mPolicy; } -bool PrivacySpec::RequireAll() const { return dest == android::os::DEST_LOCAL; } +bool PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) const { + uint8_t check = privacy != NULL ? privacy->policy : defaultDest; + return isAllowed(mPolicy, check); +} -PrivacySpec PrivacySpec::new_spec(int dest) { - switch (dest) { - case android::os::DEST_AUTOMATIC: - case android::os::DEST_EXPLICIT: - case android::os::DEST_LOCAL: - return PrivacySpec(dest); - default: - return PrivacySpec(android::os::DEST_AUTOMATIC); - } +bool PrivacySpec::RequireAll() const { + return mPolicy == android::os::PRIVACY_POLICY_LOCAL; } } // namespace incidentd diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h index a0159d9a8649..fc8caae7fc60 100644 --- a/cmds/incidentd/src/Privacy.h +++ b/cmds/incidentd/src/Privacy.h @@ -18,15 +18,15 @@ #ifndef PRIVACY_H #define PRIVACY_H +#include <android/os/IncidentReportArgs.h> + #include <stdint.h> namespace android { namespace os { namespace incidentd { -// This is the default value of DEST enum, sync with privacy.proto -const uint8_t DEST_UNSET = 255; // DEST_UNSET is not exposed to libincident -const uint8_t DEST_DEFAULT_VALUE = DEST_UNSET; +using namespace android::os; /* * In order to NOT auto-generate large chuck of code by proto compiler in incidentd, @@ -48,8 +48,8 @@ struct Privacy { // This array is NULL-terminated. Privacy** children; - // DESTINATION Enum in frameworks/base/libs/incident/proto/android/privacy.proto. - uint8_t dest; + // DESTINATION Enum in frameworks/base/core/proto/android/privacy.proto. + uint8_t policy; // A list of regexp rules for stripping string fields in proto. const char** patterns; }; @@ -63,27 +63,28 @@ const Privacy* lookup(const Privacy* p, uint32_t fieldId); /** * PrivacySpec defines the request has what level of privacy authorization. * For example, a device without user consent should only be able to upload AUTOMATIC fields. - * DEST_UNSET are treated as DEST_EXPLICIT. + * PRIVACY_POLICY_UNSET are treated as PRIVACY_POLICY_EXPLICIT. */ class PrivacySpec { public: - const uint8_t dest; + explicit PrivacySpec(uint8_t argPolicy); - PrivacySpec() : dest(DEST_DEFAULT_VALUE) {} bool operator<(const PrivacySpec& other) const; // check permission of a policy, if returns true, don't strip the data. bool CheckPremission(const Privacy* privacy, - const uint8_t defaultDest = DEST_DEFAULT_VALUE) const; + const uint8_t defaultPrivacyPolicy = PRIVACY_POLICY_UNSET) const; // if returns true, no data need to be stripped. bool RequireAll() const; - // Constructs spec using static methods below. - static PrivacySpec new_spec(int dest); + uint8_t getPolicy() const; private: - explicit PrivacySpec(uint8_t dest) : dest(dest) {} + // unimplemented constructors + explicit PrivacySpec(); + + uint8_t mPolicy; }; } // namespace incidentd diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp deleted file mode 100644 index 08f535db01fa..000000000000 --- a/cmds/incidentd/src/PrivacyBuffer.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2017 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 "PrivacyBuffer.h" -#include "incidentd_util.h" - -#include <android-base/file.h> -#include <android/util/protobuf.h> -#include <log/log.h> - -namespace android { -namespace os { -namespace incidentd { - -/** - * Write the field to buf based on the wire type, iterator will point to next field. - * If skip is set to true, no data will be written to buf. Return number of bytes written. - */ -void PrivacyBuffer::writeFieldOrSkip(uint32_t fieldTag, bool skip) { - uint8_t wireType = read_wire_type(fieldTag); - size_t bytesToWrite = 0; - uint64_t varint = 0; - - switch (wireType) { - case WIRE_TYPE_VARINT: - varint = mData.readRawVarint(); - if (!skip) { - mProto.writeRawVarint(fieldTag); - mProto.writeRawVarint(varint); - } - return; - case WIRE_TYPE_FIXED64: - if (!skip) mProto.writeRawVarint(fieldTag); - bytesToWrite = 8; - break; - case WIRE_TYPE_LENGTH_DELIMITED: - bytesToWrite = mData.readRawVarint(); - if (!skip) mProto.writeLengthDelimitedHeader(read_field_id(fieldTag), bytesToWrite); - break; - case WIRE_TYPE_FIXED32: - if (!skip) mProto.writeRawVarint(fieldTag); - bytesToWrite = 4; - break; - } - if (skip) { - mData.rp()->move(bytesToWrite); - } else { - for (size_t i = 0; i < bytesToWrite; i++) { - mProto.writeRawByte(mData.next()); - } - } -} - -/** - * Strip next field based on its private policy and request spec, then stores data in buf. - * Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in FdBuffer. - * - * The iterator must point to the head of a protobuf formatted field for successful operation. - * After exit with NO_ERROR, iterator points to the next protobuf field's head. - */ -status_t PrivacyBuffer::stripField(const Privacy* parentPolicy, const PrivacySpec& spec, - int depth /* use as a counter for this recusive method. */) { - if (!mData.hasNext() || parentPolicy == NULL) return BAD_VALUE; - uint32_t fieldTag = mData.readRawVarint(); - uint32_t fieldId = read_field_id(fieldTag); - const Privacy* policy = lookup(parentPolicy, fieldId); - - VLOG("[Depth %2d]Try to strip id %d, wiretype %d", depth, fieldId, read_wire_type(fieldTag)); - if (policy == NULL || policy->children == NULL) { - bool skip = !spec.CheckPremission(policy, parentPolicy->dest); - // iterator will point to head of next field - size_t currentAt = mData.rp()->pos(); - writeFieldOrSkip(fieldTag, skip); - VLOG("[Depth %2d]Field %d %ss %zu bytes", depth, fieldId, skip ? "skip" : "write", - get_varint_size(fieldTag) + mData.rp()->pos() - currentAt); - return NO_ERROR; - } - // current field is message type and its sub-fields have extra privacy policies - uint32_t msgSize = mData.readRawVarint(); - size_t start = mData.rp()->pos(); - uint64_t token = mProto.start(encode_field_id(policy)); - while (mData.rp()->pos() - start != msgSize) { - status_t err = stripField(policy, spec, depth + 1); - if (err != NO_ERROR) { - VLOG("Bad value when stripping id %d, wiretype %d, tag %#x, depth %d, size %d, " - "relative pos %zu, ", fieldId, read_wire_type(fieldTag), fieldTag, depth, - msgSize, mData.rp()->pos() - start); - return err; - } - } - mProto.end(token); - return NO_ERROR; -} - -// ================================================================================ -PrivacyBuffer::PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator data) - : mPolicy(policy), mData(data), mProto(), mSize(0) {} - -PrivacyBuffer::~PrivacyBuffer() {} - -status_t PrivacyBuffer::strip(const PrivacySpec& spec) { - VLOG("Strip with spec %d", spec.dest); - // optimization when no strip happens - if (mPolicy == NULL || mPolicy->children == NULL || spec.RequireAll()) { - if (spec.CheckPremission(mPolicy)) mSize = mData.size(); - return NO_ERROR; - } - while (mData.hasNext()) { - status_t err = stripField(mPolicy, spec, 0); - if (err != NO_ERROR) return err; // Error logged in stripField. - } - if (mData.bytesRead() != mData.size()) { - VLOG("Buffer corrupted: expect %zu bytes, read %zu bytes", - mData.size(), mData.bytesRead()); - return BAD_VALUE; - } - mSize = mProto.size(); - mData.rp()->rewind(); // rewind the read pointer back to beginning after the strip. - return NO_ERROR; -} - -void PrivacyBuffer::clear() { - mSize = 0; - mProto.clear(); -} - -size_t PrivacyBuffer::size() const { return mSize; } - -status_t PrivacyBuffer::flush(int fd) { - status_t err = NO_ERROR; - EncodedBuffer::iterator iter = size() == mData.size() ? mData : mProto.data(); - while (iter.readBuffer() != NULL) { - err = WriteFully(fd, iter.readBuffer(), iter.currentToRead()) ? NO_ERROR : -errno; - iter.rp()->move(iter.currentToRead()); - if (err != NO_ERROR) return err; - } - return NO_ERROR; -} - -} // namespace incidentd -} // namespace os -} // namespace android diff --git a/cmds/incidentd/src/PrivacyBuffer.h b/cmds/incidentd/src/PrivacyBuffer.h deleted file mode 100644 index eac3862b2cab..000000000000 --- a/cmds/incidentd/src/PrivacyBuffer.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2017 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. - */ -#pragma once - -#ifndef PRIVACY_BUFFER_H -#define PRIVACY_BUFFER_H - -#include "Privacy.h" - -#include <android/util/EncodedBuffer.h> -#include <android/util/ProtoOutputStream.h> -#include <stdint.h> -#include <utils/Errors.h> - -namespace android { -namespace os { -namespace incidentd { - -using namespace android::util; - -/** - * PrivacyBuffer holds the original protobuf data and strips PII-sensitive fields - * based on the request and holds stripped data in its own buffer for output. - */ -class PrivacyBuffer { -public: - PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator data); - ~PrivacyBuffer(); - - /** - * Strip based on the request and hold data in its own buffer. Return NO_ERROR if strip - * succeeds. - */ - status_t strip(const PrivacySpec& spec); - - /** - * Clear encoded buffer so it can be reused by another request. - */ - void clear(); - - /** - * Return the size of the stripped data. - */ - size_t size() const; - - /** - * Flush buffer to the given fd. NO_ERROR is returned if the flush succeeds. - */ - status_t flush(int fd); - -private: - const Privacy* mPolicy; - EncodedBuffer::iterator mData; - - ProtoOutputStream mProto; - size_t mSize; - - status_t stripField(const Privacy* parentPolicy, const PrivacySpec& spec, int depth); - void writeFieldOrSkip(uint32_t fieldTag, bool skip); -}; - -} // namespace incidentd -} // namespace os -} // namespace android - -#endif // PRIVACY_BUFFER_H
\ No newline at end of file diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp new file mode 100644 index 000000000000..7126322575d5 --- /dev/null +++ b/cmds/incidentd/src/PrivacyFilter.cpp @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2017 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 "incidentd_util.h" +#include "PrivacyFilter.h" +#include "proto_util.h" + +#include <android-base/file.h> +#include <android/util/protobuf.h> +#include <android/util/ProtoFileReader.h> +#include <log/log.h> + +namespace android { +namespace os { +namespace incidentd { + +// ================================================================================ +/** + * Write the field to buf based on the wire type, iterator will point to next field. + * If skip is set to true, no data will be written to buf. Return number of bytes written. + */ +void write_field_or_skip(ProtoOutputStream* out, const sp<ProtoReader>& in, + uint32_t fieldTag, bool skip) { + uint8_t wireType = read_wire_type(fieldTag); + size_t bytesToWrite = 0; + uint64_t varint = 0; + + switch (wireType) { + case WIRE_TYPE_VARINT: + varint = in->readRawVarint(); + if (!skip) { + out->writeRawVarint(fieldTag); + out->writeRawVarint(varint); + } + return; + case WIRE_TYPE_FIXED64: + if (!skip) { + out->writeRawVarint(fieldTag); + } + bytesToWrite = 8; + break; + case WIRE_TYPE_LENGTH_DELIMITED: + bytesToWrite = in->readRawVarint(); + if (!skip) { + out->writeLengthDelimitedHeader(read_field_id(fieldTag), bytesToWrite); + } + break; + case WIRE_TYPE_FIXED32: + if (!skip) { + out->writeRawVarint(fieldTag); + } + bytesToWrite = 4; + break; + } + if (skip) { + in->move(bytesToWrite); + } else { + for (size_t i = 0; i < bytesToWrite; i++) { + out->writeRawByte(in->next()); + } + } +} + +/** + * Strip next field based on its private policy and request spec, then stores data in buf. + * Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in + * FdBuffer. + * + * The iterator must point to the head of a protobuf formatted field for successful operation. + * After exit with NO_ERROR, iterator points to the next protobuf field's head. + * + * depth is the depth of recursion, for debugging. + */ +status_t strip_field(ProtoOutputStream* out, const sp<ProtoReader>& in, + const Privacy* parentPolicy, const PrivacySpec& spec, int depth) { + if (!in->hasNext() || parentPolicy == NULL) { + return BAD_VALUE; + } + uint32_t fieldTag = in->readRawVarint(); + uint32_t fieldId = read_field_id(fieldTag); + const Privacy* policy = lookup(parentPolicy, fieldId); + + if (policy == NULL || policy->children == NULL) { + bool skip = !spec.CheckPremission(policy, parentPolicy->policy); + // iterator will point to head of next field + size_t currentAt = in->bytesRead(); + write_field_or_skip(out, in, fieldTag, skip); + return NO_ERROR; + } + // current field is message type and its sub-fields have extra privacy policies + uint32_t msgSize = in->readRawVarint(); + size_t start = in->bytesRead(); + uint64_t token = out->start(encode_field_id(policy)); + while (in->bytesRead() - start != msgSize) { + status_t err = strip_field(out, in, policy, spec, depth + 1); + if (err != NO_ERROR) { + ALOGW("Bad value when stripping id %d, wiretype %d, tag %#x, depth %d, size %d, " + "relative pos %zu, ", fieldId, read_wire_type(fieldTag), fieldTag, depth, + msgSize, in->bytesRead() - start); + return err; + } + } + out->end(token); + return NO_ERROR; +} + +// ================================================================================ +class FieldStripper { +public: + FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data, + uint8_t bufferLevel); + + /** + * Take the data that we have, and filter it down so that no fields + * are more sensitive than the given privacy policy. + */ + status_t strip(uint8_t privacyPolicy); + + /** + * At the current filter level, how many bytes of data there is. + */ + ssize_t dataSize() const { return mSize; } + + /** + * Write the data from the current filter level to the file descriptor. + */ + status_t writeData(int fd); + +private: + /** + * The global set of field --> required privacy level mapping. + */ + const Privacy* mRestrictions; + + /** + * The current buffer. + */ + sp<ProtoReader> mData; + + /** + * The current size of the buffer inside mData. + */ + ssize_t mSize; + + /** + * The current privacy policy that the data is filtered to, as an optimization + * so we don't always re-filter data that has already been filtered. + */ + uint8_t mCurrentLevel; + +}; + +FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data, + uint8_t bufferLevel) + :mRestrictions(restrictions), + mData(data), + mSize(data->size()), + mCurrentLevel(bufferLevel) { + if (mSize < 0) { + ALOGW("FieldStripper constructed with a ProtoReader that doesn't support size." + " Data will be missing."); + } +} + +status_t FieldStripper::strip(const uint8_t privacyPolicy) { + // If the current strip level is less (fewer fields retained) than what's already in the + // buffer, then we can skip it. + if (mCurrentLevel < privacyPolicy) { + PrivacySpec spec(privacyPolicy); + ProtoOutputStream proto; + + // Optimization when no strip happens. + if (mRestrictions == NULL || mRestrictions->children == NULL || spec.RequireAll()) { + if (spec.CheckPremission(mRestrictions)) { + mSize = mData->size(); + } + return NO_ERROR; + } + + while (mData->hasNext()) { + status_t err = strip_field(&proto, mData, mRestrictions, spec, 0); + if (err != NO_ERROR) { + return err; // Error logged in strip_field. + } + } + + if (mData->bytesRead() != mData->size()) { + ALOGW("Buffer corrupted: expect %zu bytes, read %zu bytes", mData->size(), + mData->bytesRead()); + return BAD_VALUE; + } + + mData = proto.data(); + mSize = proto.size(); + mCurrentLevel = privacyPolicy; + } + return NO_ERROR; +} + +status_t FieldStripper::writeData(int fd) { + status_t err = NO_ERROR; + sp<ProtoReader> reader = mData; + while (reader->readBuffer() != NULL) { + err = WriteFully(fd, reader->readBuffer(), reader->currentToRead()) ? NO_ERROR : -errno; + reader->move(reader->currentToRead()); + if (err != NO_ERROR) return err; + } + return NO_ERROR; +} + + +// ================================================================================ +FilterFd::FilterFd(uint8_t privacyPolicy, int fd) + :mPrivacyPolicy(privacyPolicy), + mFd(fd) { +} + +FilterFd::~FilterFd() { +} + +// ================================================================================ +PrivacyFilter::PrivacyFilter(int sectionId, const Privacy* restrictions) + :mSectionId(sectionId), + mRestrictions(restrictions), + mOutputs() { +} + +PrivacyFilter::~PrivacyFilter() { +} + +void PrivacyFilter::addFd(const sp<FilterFd>& output) { + mOutputs.push_back(output); +} + +status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, + size_t* maxSize) { + status_t err; + + if (maxSize != NULL) { + *maxSize = 0; + } + + // Order the writes by privacy filter, with increasing levels of filtration,k + // so we can do the filter once, and then write many times. + sort(mOutputs.begin(), mOutputs.end(), + [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { + return a->getPrivacyPolicy() < b->getPrivacyPolicy(); + }); + + uint8_t privacyPolicy = PRIVACY_POLICY_LOCAL; // a.k.a. no filtering + FieldStripper fieldStripper(mRestrictions, buffer.data()->read(), bufferLevel); + for (const sp<FilterFd>& output: mOutputs) { + // Do another level of filtering if necessary + if (privacyPolicy != output->getPrivacyPolicy()) { + privacyPolicy = output->getPrivacyPolicy(); + err = fieldStripper.strip(privacyPolicy); + if (err != NO_ERROR) { + // We can't successfully strip this data. We will skip + // the rest of this section. + return err; + } + } + + // Write the resultant buffer to the fd, along with the header. + ssize_t dataSize = fieldStripper.dataSize(); + if (dataSize > 0) { + err = write_section_header(output->getFd(), mSectionId, dataSize); + if (err != NO_ERROR) { + output->onWriteError(err); + continue; + } + + err = fieldStripper.writeData(output->getFd()); + if (err != NO_ERROR) { + output->onWriteError(err); + continue; + } + } + + if (maxSize != NULL) { + if (dataSize > *maxSize) { + *maxSize = dataSize; + } + } + } + + return NO_ERROR; +} + +// ================================================================================ +class ReadbackFilterFd : public FilterFd { +public: + ReadbackFilterFd(uint8_t privacyPolicy, int fd); + + virtual void onWriteError(status_t err); + status_t getError() { return mError; } + +private: + status_t mError; +}; + +ReadbackFilterFd::ReadbackFilterFd(uint8_t privacyPolicy, int fd) + :FilterFd(privacyPolicy, fd), + mError(NO_ERROR) { +} + +void ReadbackFilterFd::onWriteError(status_t err) { + mError = err; +} + +// ================================================================================ +status_t filter_and_write_report(int to, int from, uint8_t bufferLevel, + const IncidentReportArgs& args) { + status_t err; + sp<ProtoFileReader> reader = new ProtoFileReader(from); + + while (reader->hasNext()) { + uint64_t fieldTag = reader->readRawVarint(); + uint32_t fieldId = read_field_id(fieldTag); + uint8_t wireType = read_wire_type(fieldTag); + if (wireType == WIRE_TYPE_LENGTH_DELIMITED && args.containsSection(fieldId)) { + // We need this field, but we need to strip it to the level provided in args. + PrivacyFilter filter(fieldId, get_privacy_of_section(fieldId)); + filter.addFd(new ReadbackFilterFd(args.getPrivacyPolicy(), to)); + + // Read this section from the reader into an FdBuffer + size_t sectionSize = reader->readRawVarint(); + FdBuffer sectionData; + err = sectionData.write(reader, sectionSize); + if (err != NO_ERROR) { + ALOGW("filter_and_write_report FdBuffer.write failed (this shouldn't happen): %s", + strerror(-err)); + return err; + } + + // Do the filter and write. + err = filter.writeData(sectionData, bufferLevel, nullptr); + if (err != NO_ERROR) { + ALOGW("filter_and_write_report filter.writeData had an error: %s", strerror(-err)); + return err; + } + } else { + // We don't need this field. Incident does not have any direct children + // other than sections. So just skip them. + write_field_or_skip(NULL, reader, fieldTag, true); + } + } + + err = reader->getError(); + if (err != NO_ERROR) { + ALOGW("filter_and_write_report reader had an error: %s", strerror(-err)); + return err; + } + + return NO_ERROR; +} + +} // namespace incidentd +} // namespace os +} // namespace android diff --git a/cmds/incidentd/src/PrivacyFilter.h b/cmds/incidentd/src/PrivacyFilter.h new file mode 100644 index 000000000000..76b28498a0ac --- /dev/null +++ b/cmds/incidentd/src/PrivacyFilter.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 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. + */ +#pragma once + +#ifndef PRIVACY_BUFFER_H +#define PRIVACY_BUFFER_H + +#include "Privacy.h" + +#include "FdBuffer.h" + +#include <android/os/IncidentReportArgs.h> +#include <android/util/ProtoOutputStream.h> +#include <stdint.h> +#include <utils/Errors.h> + +namespace android { +namespace os { +namespace incidentd { + +using namespace android::util; + +/** + * Class to wrap a file descriptor, so callers of PrivacyFilter + * can associate additional data with each fd for their own + * purposes. + */ +class FilterFd : public RefBase { +public: + FilterFd(uint8_t privacyPolicy, int fd); + virtual ~FilterFd(); + + uint8_t getPrivacyPolicy() const { return mPrivacyPolicy; } + int getFd() { return mFd;} + + virtual void onWriteError(status_t err) = 0; + +private: + uint8_t mPrivacyPolicy; + int mFd; +}; + +/** + * PrivacyFilter holds the original protobuf data and strips PII-sensitive fields + * for several requests, streaming them to a set of corresponding file descriptors. + */ +class PrivacyFilter { +public: + /** + * Constructor, with the field --> privacy restrictions mapping. + */ + PrivacyFilter(int sectionId, const Privacy* restrictions); + + ~PrivacyFilter(); + + /** + * Add a target file descriptor, and the privacy policy to which + * it should be filtered. + */ + void addFd(const sp<FilterFd>& output); + + /** + * Write the data, filtered according to the privacy specs, to each of the + * file descriptors. Any non-NO_ERROR return codes are fatal to the whole + * report. Individual write errors to streams are reported via the callbacks + * on the FilterFds. + * + * If maxSize is not NULL, it will be set to the maximum size buffer that + * was written (i.e. after filtering). + * + * The buffer is assumed to have already been filtered to bufferLevel. + */ + status_t writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize); + +private: + int mSectionId; + const Privacy* mRestrictions; + vector<sp<FilterFd>> mOutputs; +}; + +status_t filter_and_write_report(int to, int from, uint8_t bufferLevel, + const IncidentReportArgs& args); + +} // namespace incidentd +} // namespace os +} // namespace android + +#endif // PRIVACY_BUFFER_H diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 8f62da202606..e773e74bbf15 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -18,12 +18,17 @@ #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 <android-base/properties.h> +#include <android-base/file.h> #include <android/os/DropBoxManager.h> +#include <android/util/protobuf.h> +#include <android/util/ProtoOutputStream.h> #include <private/android_filesystem_config.h> #include <utils/SystemClock.h> @@ -33,308 +38,664 @@ #include <sys/stat.h> #include <sys/types.h> #include <string> - -/** - * The directory where the incident reports are stored. - */ -static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/"; +#include <time.h> namespace android { namespace os { namespace incidentd { -// ================================================================================ -ReportRequest::ReportRequest(const IncidentReportArgs& a, - const sp<IIncidentReportStatusListener>& l, int f) - : args(a), listener(l), fd(f), err(NO_ERROR) {} +using namespace android::util; -ReportRequest::~ReportRequest() { - if (fd >= 0) { - // clean up the opened file descriptor - close(fd); +/** + * 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; } } -bool ReportRequest::ok() { return fd >= 0 && err == NO_ERROR; } +void poo_make_metadata(IncidentMetadata* result, const IncidentMetadata& full, + int64_t reportId, int32_t privacyPolicy, const IncidentReportArgs& 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 (args.containsSection(sectionStats.id())) { + *result->add_sections() = sectionStats; + } + } +} + +// ARGS must have a containsSection(int) method +template <typename ARGS> 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 (args->containsSection(sectionStats.id())) { + *result->add_sections() = sectionStats; + } + } +} // ================================================================================ -ReportRequestSet::ReportRequestSet() - : mRequests(), mSections(), mMainFd(-1), mMainDest(-1), mMetadata(), mSectionStats() {} +class StreamingFilterFd : public FilterFd { +public: + StreamingFilterFd(uint8_t privacyPolicy, int fd, const sp<ReportRequest>& request); + + virtual void onWriteError(status_t err); -ReportRequestSet::~ReportRequestSet() {} +private: + sp<ReportRequest> mRequest; +}; -// TODO: dedup on exact same args and fd, report the status back to listener! -void ReportRequestSet::add(const sp<ReportRequest>& request) { - mRequests.push_back(request); - mSections.merge(request->args); - mMetadata.set_request_size(mMetadata.request_size() + 1); +StreamingFilterFd::StreamingFilterFd(uint8_t privacyPolicy, int fd, + const sp<ReportRequest>& request) + :FilterFd(privacyPolicy, fd), + mRequest(request) { } -void ReportRequestSet::setMainFd(int fd) { - mMainFd = fd; - mMetadata.set_use_dropbox(fd > 0); +void StreamingFilterFd::onWriteError(status_t err) { + mRequest->setStatus(err); } -void ReportRequestSet::setMainDest(int dest) { - mMainDest = dest; - PrivacySpec spec = PrivacySpec::new_spec(dest); - switch (spec.dest) { - case android::os::DEST_AUTOMATIC: - mMetadata.set_dest(IncidentMetadata_Destination_AUTOMATIC); - break; - case android::os::DEST_EXPLICIT: - mMetadata.set_dest(IncidentMetadata_Destination_EXPLICIT); - break; - case android::os::DEST_LOCAL: - mMetadata.set_dest(IncidentMetadata_Destination_LOCAL); - break; + +// ================================================================================ +class PersistedFilterFd : public FilterFd { +public: + PersistedFilterFd(uint8_t privacyPolicy, int fd, const sp<ReportFile>& reportFile); + + virtual void onWriteError(status_t err); + +private: + sp<ReportFile> mReportFile; +}; + +PersistedFilterFd::PersistedFilterFd(uint8_t privacyPolicy, int fd, + const sp<ReportFile>& reportFile) + :FilterFd(privacyPolicy, fd), + mReportFile(reportFile) { +} + +void PersistedFilterFd::onWriteError(status_t err) { + mReportFile->setWriteError(err); +} + + +// ================================================================================ +ReportRequest::ReportRequest(const IncidentReportArgs& a, + const sp<IIncidentReportStatusListener>& 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 ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); } +bool ReportRequest::ok() { + return mFd >= 0 && mStatus == NO_ERROR; +} -IncidentMetadata::SectionStats* ReportRequestSet::sectionStats(int id) { - if (mSectionStats.find(id) == mSectionStats.end()) { - IncidentMetadata::SectionStats stats; - stats.set_id(id); - mSectionStats[id] = stats; +void ReportRequest::closeFd() { + if (mIsStreaming && mFd >= 0) { + close(mFd); + mFd = -1; } - return &mSectionStats[id]; } // ================================================================================ -Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; }; +ReportBatch::ReportBatch() {} + +ReportBatch::~ReportBatch() {} + +void ReportBatch::addPersistedReport(const IncidentReportArgs& args) { + ComponentName component(args.receiverPkg(), args.receiverCls()); + map<ComponentName, sp<ReportRequest>>::iterator found = mPersistedRequests.find(component); + if (found == mPersistedRequests.end()) { + // not found + mPersistedRequests[component] = new ReportRequest(args, nullptr, -1); + } else { + // found + sp<ReportRequest> request = found->second; + request->args.merge(args); + } +} -Reporter::Reporter(const char* directory) : batch() { - char buf[100]; +void ReportBatch::addStreamingReport(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, int streamFd) { + mStreamingRequests.push_back(new ReportRequest(args, listener, streamFd)); +} - mMaxSize = 30 * 1024 * 1024; // incident reports can take up to 30MB on disk - mMaxCount = 100; +bool ReportBatch::empty() const { + return mPersistedRequests.size() == 0 && mStreamingRequests.size() == 0; +} - // string ends up with '/' is a directory - String8 dir = String8(directory); - if (directory[dir.size() - 1] != '/') dir += "/"; - mIncidentDirectory = dir.string(); +sp<ReportRequest> ReportBatch::getPersistedRequest(const ComponentName& component) { + map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.find(component); + if (it != mPersistedRequests.find(component)) { + return it->second; + } else { + return nullptr; + } +} - // There can't be two at the same time because it's on one thread. - mStartTime = time(NULL); - strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime)); - mFilename = mIncidentDirectory + buf; +void ReportBatch::forEachPersistedRequest(const function<void (const sp<ReportRequest>&)>& func) { + for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + func(it->second); + } } -Reporter::~Reporter() {} +void ReportBatch::forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func) { + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + func(*request); + } +} -Reporter::run_report_status_t Reporter::runReport(size_t* reportByteSize) { - status_t err = NO_ERROR; - bool needMainFd = false; - int mainFd = -1; - int mainDest = -1; - int sectionCount = 0; - HeaderSection headers; - MetadataSection metadataSection; - std::string buildType = android::base::GetProperty("ro.build.type", ""); - const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng"; - - // See if we need the main file - for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { - if ((*it)->fd < 0 && mainFd < 0) { - needMainFd = true; - mainDest = (*it)->args.dest(); - break; +void ReportBatch::forEachListener( + const function<void (const sp<IIncidentReportStatusListener>&)>& func) { + for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + sp<IIncidentReportStatusListener> listener = it->second->getListener(); + if (listener != nullptr) { + func(listener); } } - if (needMainFd) { - // Create the directory - if (!isTest) err = create_directory(mIncidentDirectory); - if (err != NO_ERROR) { - goto DONE; + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + sp<IIncidentReportStatusListener> listener = (*request)->getListener(); + if (listener != nullptr) { + func(listener); } + } +} - // If there are too many files in the directory (for whatever reason), - // delete the oldest ones until it's under the limit. Doing this first - // does mean that we can go over, so the max size is not a hard limit. - if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount); - - // Open the file. - err = create_file(&mainFd); - if (err != NO_ERROR) { - goto DONE; +void ReportBatch::forEachListener(int sectionId, + const function<void (const sp<IIncidentReportStatusListener>&)>& func) { + for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + if (it->second->containsSection(sectionId)) { + sp<IIncidentReportStatusListener> listener = it->second->getListener(); + if (listener != nullptr) { + func(listener); + } } - - // Add to the set - batch.setMainFd(mainFd); - batch.setMainDest(mainDest); } - - // Tell everyone that we're starting. - for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { - if ((*it)->listener != NULL) { - (*it)->listener->onReportStarted(); + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + if ((*request)->containsSection(sectionId)) { + sp<IIncidentReportStatusListener> listener = (*request)->getListener(); + if (listener != nullptr) { + func(listener); + } } } +} - // Write the incident headers - headers.Execute(&batch); +void ReportBatch::getCombinedPersistedArgs(IncidentReportArgs* result) { + for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + result->merge(it->second->args); + } +} - // 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 id = (*section)->id; - if ((*section)->userdebugAndEngOnly && !isUserdebugOrEng) { - VLOG("Skipping incident report section %d '%s' because it's limited to userdebug/eng", - id, (*section)->name.string()); - continue; +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<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + if (it->second->containsSection(sectionId)) { + return true; } - if (this->batch.containsSection(id)) { - VLOG("Taking incident report section %d '%s'", id, (*section)->name.string()); - for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { - if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { - (*it)->listener->onReportSectionStatus( - id, IIncidentReportStatusListener::STATUS_STARTING); - } - } + } + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + if ((*request)->containsSection(sectionId)) { + return true; + } + } + return false; +} - // Execute - go get the data and write it into the file descriptors. - IncidentMetadata::SectionStats* stats = batch.sectionStats(id); - int64_t startTime = uptimeMillis(); - err = (*section)->Execute(&batch); - int64_t endTime = uptimeMillis(); - stats->set_exec_duration_ms(endTime - startTime); - if (err != NO_ERROR) { - ALOGW("Incident section %s (%d) failed: %s. Stopping report.", - (*section)->name.string(), id, strerror(-err)); - // Execute() has already recorded this status. Only update if there's new failure. - stats->set_success(false); - goto DONE; - } - (*reportByteSize) += stats->report_size_bytes(); +void ReportBatch::clearPersistedRequests() { + mPersistedRequests.clear(); +} - // Notify listener of starting - for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { - if ((*it)->listener != NULL && (*it)->args.containsSection(id)) { - (*it)->listener->onReportSectionStatus( - id, IIncidentReportStatusListener::STATUS_FINISHED); - } - } - VLOG("Finish incident report section %d '%s'", id, (*section)->name.string()); - sectionCount++; +void ReportBatch::getFailedRequests(vector<sp<ReportRequest>>* requests) { + for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + if (it->second->getStatus() != NO_ERROR) { + requests->push_back(it->second); } } - -DONE: - ALOGD("Incident reporting took %d sections.", sectionCount); - // Reports the metdadata when taking the incident report. - if (!isTest) metadataSection.Execute(&batch); - - // Close the file. - if (mainFd >= 0) { - close(mainFd); - } - - // Tell everyone that we're done. - for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { - if ((*it)->listener != NULL) { - if (err == NO_ERROR) { - (*it)->listener->onReportFinished(); - } else { - (*it)->listener->onReportFailed(); - } + for (vector<sp<ReportRequest>>::iterator request = mStreamingRequests.begin(); + request != mStreamingRequests.end(); request++) { + if ((*request)->getStatus() != NO_ERROR) { + requests->push_back(*request); } } +} - // Put the report into dropbox. - if (needMainFd && err == NO_ERROR) { - sp<DropBoxManager> dropbox = new DropBoxManager(); - Status status = dropbox->addFile(String16("incident"), mFilename, 0); - ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); - if (!status.isOk()) { - return REPORT_NEEDS_DROPBOX; +void ReportBatch::removeRequest(const sp<ReportRequest>& request) { + for (map<ComponentName, sp<ReportRequest>>::iterator it = mPersistedRequests.begin(); + it != mPersistedRequests.end(); it++) { + if (it->second == request) { + mPersistedRequests.erase(it); + return; } + } + for (vector<sp<ReportRequest>>::iterator it = mStreamingRequests.begin(); + it != mStreamingRequests.end(); it++) { + if (*it == request) { + mStreamingRequests.erase(it); + return; + } + } +} + +// ================================================================================ +ReportWriter::ReportWriter(const sp<ReportBatch>& batch) + :mBatch(batch), + mPersistedFile(), + mMaxPersistedPrivacyPolicy(PRIVACY_POLICY_UNSET) { +} + +ReportWriter::~ReportWriter() { +} - // If the status was ok, delete the file. If not, leave it around until the next - // boot or the next checkin. If the directory gets too big older files will - // be rotated out. - if (!isTest) unlink(mFilename.c_str()); +void ReportWriter::setPersistedFile(sp<ReportFile> 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); } - return REPORT_FINISHED; + 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); } -/** - * Create our output file and set the access permissions to -rw-rw---- - */ -status_t Reporter::create_file(int* fd) { - const char* filename = mFilename.c_str(); +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); +} - *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660); - if (*fd < 0) { - ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno)); - return -errno; +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; } - // Override umask. Not super critical. If it fails go on with life. - chmod(filename, 0660); + sprintf(line, prefixFormat, levelText, section->id, err, strerror(-err)); + + vsprintf(line + prefixLen, format, args); - if (chown(filename, AID_INCIDENTD, AID_INCIDENTD)) { - ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno)); - status_t err = -errno; - unlink(mFilename.c_str()); - return err; + __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)); } - return NO_ERROR; + // Add the fds for the streamed requests + mBatch->forEachStreamingRequest([&filter, this](const sp<ReportRequest>& request) { + if (request->ok() && request->args.containsSection(mCurrentSectionId)) { + filter.addFd(new StreamingFilterFd(request->args.getPrivacyPolicy(), + request->getFd(), request)); + } + }); + + return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize); } -Reporter::run_report_status_t Reporter::upload_backlog() { - DIR* dir; - struct dirent* entry; - struct stat st; - status_t err; - ALOGD("Start uploading backlogs in %s", INCIDENT_DIRECTORY); - if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) { - ALOGE("directory doesn't exist: %s", strerror(-err)); - return REPORT_FINISHED; +// ================================================================================ +Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& 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<ReportRequest>& 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<ReportRequest>& request) { + sp<IIncidentReportStatusListener> listener = request->getListener(); + if (listener != nullptr) { + listener->onReportFailed(); + } + }); + mBatch->clearPersistedRequests(); + } } - if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) { - ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY); - return REPORT_NEEDS_DROPBOX; + // 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; } - sp<DropBoxManager> dropbox = new DropBoxManager(); + // 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<ReportRequest>& request) { + const vector<vector<uint8_t>>& headers = request->args.headers(); + for (vector<vector<uint8_t>>::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); + } + }); + + // 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; - // Enumerate, count and add up size - int count = 0; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') { + // If nobody wants this section, skip it. + if (!mBatch->containsSection(sectionId)) { continue; } - String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name; - if (stat(filename.string(), &st) != 0) { - ALOGE("Unable to stat file %s", filename.string()); - 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; } - if (!S_ISREG(st.st_mode)) { - continue; + + // 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); } + } - Status status = dropbox->addFile(String16("incident"), filename.string(), 0); - ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string()); - if (!status.isOk()) { - return REPORT_NEEDS_DROPBOX; + // Write the metadata to the streaming ones + mBatch->forEachStreamingRequest([reportId, &metadata](const sp<ReportRequest>& 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<ReportRequest>& 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)); +} - // If the status was ok, delete the file. If not, leave it around until the next - // boot or the next checkin. If the directory gets too big older files will - // be rotated out. - unlink(filename.string()); - count++; +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<ReportRequest>& request) { + sp<IIncidentReportStatusListener> listener = request->getListener(); + if (listener != nullptr) { + listener->onReportFailed(); + } + mBatch->removeRequest(request); + }); + mWriter.setPersistedFile(nullptr); + mPersistedFile->closeDataFile(); + mWorkDirectory->remove(mPersistedFile); + mPersistedFile = nullptr; + } } - ALOGD("Successfully uploaded %d files to Dropbox.", count); - closedir(dir); - return REPORT_FINISHED; + // Handle failures in the streaming files + vector<sp<ReportRequest>> failed; + mBatch->getFailedRequests(&failed); + for (sp<ReportRequest>& request: failed) { + ALOGW("Error writing to a request stream (%s). Closing it and canceling.", + strerror(-request->getStatus())); + sp<IIncidentReportStatusListener> listener = request->getListener(); + if (listener != nullptr) { + listener->onReportFailed(); + } + request->closeFd(); // Will only close the streaming ones. + mBatch->removeRequest(request); + } } } // namespace incidentd diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h index 2a3abd7b4d97..e7a474fde40c 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -15,103 +15,247 @@ */ #pragma once -#ifndef REPORTER_H -#define REPORTER_H +#include "FdBuffer.h" +#include "Throttler.h" +#include "WorkDirectory.h" +#include "frameworks/base/core/proto/android/os/metadata.pb.h" +#include <android/content/ComponentName.h> #include <android/os/IIncidentReportStatusListener.h> #include <android/os/IncidentReportArgs.h> +#include <android/util/protobuf.h> #include <map> #include <string> #include <vector> #include <time.h> - -#include "Throttler.h" -#include "frameworks/base/libs/incident/proto/android/os/metadata.pb.h" +#include <stdarg.h> namespace android { namespace os { namespace incidentd { +using namespace std; +using namespace android::content; +using namespace android::os; + +class Section; + // ================================================================================ -struct ReportRequest : public virtual RefBase { +class ReportRequest : public virtual RefBase { +public: IncidentReportArgs args; - sp<IIncidentReportStatusListener> listener; - int fd; - status_t err; ReportRequest(const IncidentReportArgs& args, const sp<IIncidentReportStatusListener>& listener, int fd); virtual ~ReportRequest(); + bool isStreaming() { return mIsStreaming; } + + void setStatus(status_t err) { mStatus = err; } + status_t getStatus() const { return mStatus; } + bool ok(); // returns true if the request is ok for write. + + bool containsSection(int sectionId) const { return args.containsSection(sectionId); } + + sp<IIncidentReportStatusListener> getListener() { return mListener; } + + int getFd() { return mFd; } + + int setPersistedFd(int fd); + + void closeFd(); + +private: + sp<IIncidentReportStatusListener> mListener; + int mFd; + bool mIsStreaming; + status_t mStatus; }; // ================================================================================ -class ReportRequestSet { +class ReportBatch : public virtual RefBase { public: - ReportRequestSet(); - ~ReportRequestSet(); + ReportBatch(); + virtual ~ReportBatch(); + + // TODO: Should there be some kind of listener associated with the + // component? Could be good for getting status updates e.g. in the ui, + // as it progresses. But that's out of scope for now. + + /** + * Schedule a report for the "main" report, where it will be delivered to + * the uploaders and/or dropbox. + */ + void addPersistedReport(const IncidentReportArgs& args); + + /** + * Adds a ReportRequest to the queue for one that has a listener an and fd + */ + void addStreamingReport(const IncidentReportArgs& args, + const sp<IIncidentReportStatusListener>& listener, int streamFd); - void add(const sp<ReportRequest>& request); - void setMainFd(int fd); - void setMainDest(int dest); + /** + * Returns whether both queues are empty. + */ + bool empty() const; - typedef vector<sp<ReportRequest>>::iterator iterator; + /** + * Returns whether there are any persisted records. + */ + bool hasPersistedReports() const { return mPersistedRequests.size() > 0; } - iterator begin() { return mRequests.begin(); } - iterator end() { return mRequests.end(); } + /** + * Return the persisted request for the given component, or nullptr. + */ + sp<ReportRequest> getPersistedRequest(const ComponentName& component); - int mainFd() { return mMainFd; } - int mainDest() { return mMainDest; } - IncidentMetadata& metadata() { return mMetadata; } - map<int, IncidentMetadata::SectionStats>& allSectionStats() { return mSectionStats; } + /** + * Call func(request) for each Request. + */ + void forEachPersistedRequest(const function<void (const sp<ReportRequest>&)>& func); + /** + * Call func(request) for each Request. + */ + void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func); + + /** + * Call func(request) for each file descriptor that has + */ + void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func); + + /** + * Call func(listener) for every listener in this batch. + */ + void forEachListener(const function<void (const sp<IIncidentReportStatusListener>&)>& func); + + /** + * Call func(listener) for every listener in this batch that requests + * sectionId. + */ + void forEachListener(int sectionId, + const function<void (const sp<IIncidentReportStatusListener>&)>& func); + /** + * Get an IncidentReportArgs that represents the combined args for the + * persisted requests. + */ + void getCombinedPersistedArgs(IncidentReportArgs* results); + + /** + * Return whether any of the requests contain the section. + */ bool containsSection(int id); - IncidentMetadata::SectionStats* sectionStats(int id); -private: - vector<sp<ReportRequest>> mRequests; - IncidentReportArgs mSections; - int mMainFd; - int mMainDest; + /** + * Remove all of the broadcast (persisted) requests. + */ + void clearPersistedRequests(); + + /** + * Get the requests that have encountered errors. + */ + void getFailedRequests(vector<sp<ReportRequest>>* requests); - IncidentMetadata mMetadata; - map<int, IncidentMetadata::SectionStats> mSectionStats; + /** + * Remove the request from whichever list it's in. + */ + void removeRequest(const sp<ReportRequest>& request); + + +private: + map<ComponentName, sp<ReportRequest>> mPersistedRequests; + vector<sp<ReportRequest>> mStreamingRequests; }; // ================================================================================ -class Reporter : public virtual RefBase { +class ReportWriter { public: - enum run_report_status_t { REPORT_FINISHED = 0, REPORT_NEEDS_DROPBOX = 1 }; + ReportWriter(const sp<ReportBatch>& batch); + ~ReportWriter(); - ReportRequestSet batch; + void setPersistedFile(sp<ReportFile> file); + void setMaxPersistedPrivacyPolicy(uint8_t privacyPolicy); - Reporter(); // PROD must use this constructor. - explicit Reporter(const char* directory); // For testing purpose only. - virtual ~Reporter(); + void startSection(int sectionId); + void endSection(IncidentMetadata::SectionStats* sectionStats); - // Run the report as described in the batch and args parameters. - run_report_status_t runReport(size_t* reportByteSize); + void setSectionStats(const FdBuffer& buffer); - static run_report_status_t upload_backlog(); + void warning(const Section* section, status_t err, const char* format, ...); + void error(const Section* section, status_t err, const char* format, ...); + + status_t writeSection(const FdBuffer& buffer); private: - String8 mIncidentDirectory; + // Data about all requests + sp<ReportBatch> mBatch; + + /** + * The file on disk where we will store the persisted file. + */ + sp<ReportFile> mPersistedFile; + + /** + * The least restricted privacy policy of all of the perstited + * requests. We pre-filter to that to save disk space. + */ + uint8_t mMaxPersistedPrivacyPolicy; + + /** + * The current section that is being written. + */ + int mCurrentSectionId; + + /** + * The time that that the current section was started. + */ + int64_t mSectionStartTimeMs; + + /** + * The last section that setSectionStats was called for, so if someone misses + * it we can log that. + */ + int mSectionStatsCalledForSectionId; - string mFilename; - off_t mMaxSize; - size_t mMaxCount; - time_t mStartTime; + /* + * Fields for IncidentMetadata.SectionStats. Set by setSectionStats. Accessed by + * getSectionStats. + */ + int32_t mDumpSizeBytes; + int64_t mDumpDurationMs; + bool mSectionTimedOut; + bool mSectionTruncated; + bool mSectionBufferSuccess; + bool mHadError; + string mSectionErrors; + size_t mMaxSectionDataFilteredSize; - status_t create_file(int* fd); + void vflog(const Section* section, status_t err, int level, const char* levelText, + const char* format, va_list args); +}; + +// ================================================================================ +class Reporter : public virtual RefBase { +public: + Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch); + + virtual ~Reporter(); + + // Run the report as described in the batch and args parameters. + void runReport(size_t* reportByteSize); - bool isTest = true; // default to true for testing +private: + sp<WorkDirectory> mWorkDirectory; + ReportWriter mWriter; + sp<ReportBatch> mBatch; + sp<ReportFile> mPersistedFile; + + void cancel_and_remove_failed_requests(); }; } // namespace incidentd } // namespace os } // namespace android - -#endif // REPORTER_H diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 32ec1ba90cda..935a7c43fe90 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -25,8 +25,10 @@ #include <set> #include <android-base/file.h> +#include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android/util/protobuf.h> +#include <android/util/ProtoOutputStream.h> #include <binder/IServiceManager.h> #include <debuggerd/client.h> #include <dumputils/dump_utils.h> @@ -37,7 +39,6 @@ #include "FdBuffer.h" #include "Privacy.h" -#include "PrivacyBuffer.h" #include "frameworks/base/core/proto/android/os/backtrace.proto.h" #include "frameworks/base/core/proto/android/os/data.proto.h" #include "frameworks/base/core/proto/android/util/log.proto.h" @@ -51,7 +52,6 @@ using namespace android::base; using namespace android::util; // special section ids -const int FIELD_ID_INCIDENT_HEADER = 1; const int FIELD_ID_INCIDENT_METADATA = 2; // incident section parameters @@ -64,206 +64,18 @@ static pid_t fork_execute_incident_helper(const int id, Fpipe* p2cPipe, Fpipe* c } // ================================================================================ -static status_t write_section_header(int fd, int sectionId, size_t size) { - uint8_t buf[20]; - uint8_t* p = write_length_delimited_tag_header(buf, sectionId, size); - return WriteFully(fd, buf, p - buf) ? NO_ERROR : -errno; -} - -static void write_section_stats(IncidentMetadata::SectionStats* stats, const FdBuffer& buffer) { - stats->set_dump_size_bytes(buffer.data().size()); - stats->set_dump_duration_ms(buffer.durationMs()); - stats->set_timed_out(buffer.timedOut()); - stats->set_is_truncated(buffer.truncated()); - stats->set_success(!buffer.timedOut() && !buffer.truncated()); -} - -// Reads data from FdBuffer and writes it to the requests file descriptor. -static status_t write_report_requests(const int id, const FdBuffer& buffer, - ReportRequestSet* requests) { - status_t err = -EBADF; - EncodedBuffer::iterator data = buffer.data(); - PrivacyBuffer privacyBuffer(get_privacy_of_section(id), data); - IncidentMetadata::SectionStats* stats = requests->sectionStats(id); - int nPassed = 0; - - // The streaming ones, group requests by spec in order to save unnecessary strip operations - map<PrivacySpec, vector<sp<ReportRequest>>> requestsBySpec; - for (auto it = requests->begin(); it != requests->end(); it++) { - sp<ReportRequest> request = *it; - if (!request->ok() || !request->args.containsSection(id)) { - continue; // skip invalid request - } - PrivacySpec spec = PrivacySpec::new_spec(request->args.dest()); - requestsBySpec[spec].push_back(request); - } - - for (auto mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) { - PrivacySpec spec = mit->first; - err = privacyBuffer.strip(spec); - if (err != NO_ERROR) { - // Privacy Buffer is corrupted, probably due to an ill-formatted proto. This is a - // non-fatal error. The whole report is still valid. So just log the failure. - ALOGW("Failed to strip section %d with spec %d: %s", - id, spec.dest, strerror(-err)); - stats->set_success(false); - stats->set_error_msg("Failed to strip section: privacy buffer corrupted, probably " - "due to an ill-formatted proto"); - nPassed++; - continue; - } - - if (privacyBuffer.size() == 0) continue; - - for (auto it = mit->second.begin(); it != mit->second.end(); it++) { - sp<ReportRequest> request = *it; - err = write_section_header(request->fd, id, privacyBuffer.size()); - if (err != NO_ERROR) { - request->err = err; - continue; - } - err = privacyBuffer.flush(request->fd); - if (err != NO_ERROR) { - request->err = err; - continue; - } - nPassed++; - VLOG("Section %d flushed %zu bytes to fd %d with spec %d", id, privacyBuffer.size(), - request->fd, spec.dest); - } - privacyBuffer.clear(); - } - - // The dropbox file - if (requests->mainFd() >= 0) { - PrivacySpec spec = PrivacySpec::new_spec(requests->mainDest()); - err = privacyBuffer.strip(spec); - if (err != NO_ERROR) { - ALOGW("Failed to strip section %d with spec %d for dropbox: %s", - id, spec.dest, strerror(-err)); - stats->set_success(false); - stats->set_error_msg("Failed to strip section: privacy buffer corrupted, probably " - "due to an ill-formatted proto"); - nPassed++; - goto DONE; - } - if (privacyBuffer.size() == 0) goto DONE; - - err = write_section_header(requests->mainFd(), id, privacyBuffer.size()); - if (err != NO_ERROR) { - requests->setMainFd(-1); - goto DONE; - } - err = privacyBuffer.flush(requests->mainFd()); - if (err != NO_ERROR) { - requests->setMainFd(-1); - goto DONE; - } - nPassed++; - VLOG("Section %d flushed %zu bytes to dropbox %d with spec %d", id, privacyBuffer.size(), - requests->mainFd(), spec.dest); - // Reports bytes of the section uploaded via dropbox after filtering. - requests->sectionStats(id)->set_report_size_bytes(privacyBuffer.size()); - } - -DONE: - // only returns error if there is no fd to write to. - return nPassed > 0 ? NO_ERROR : err; -} - -// ================================================================================ -Section::Section(int i, int64_t timeoutMs, bool userdebugAndEngOnly) +Section::Section(int i, int64_t timeoutMs) : id(i), - timeoutMs(timeoutMs), - userdebugAndEngOnly(userdebugAndEngOnly) {} - -Section::~Section() {} - -// ================================================================================ -HeaderSection::HeaderSection() : Section(FIELD_ID_INCIDENT_HEADER, 0) {} - -HeaderSection::~HeaderSection() {} - -status_t HeaderSection::Execute(ReportRequestSet* requests) const { - for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) { - const sp<ReportRequest> request = *it; - const vector<vector<uint8_t>>& headers = request->args.headers(); - - for (vector<vector<uint8_t>>::const_iterator buf = headers.begin(); buf != headers.end(); - buf++) { - if (buf->empty()) continue; - - // So the idea is only requests with negative fd are written to dropbox file. - int fd = request->fd >= 0 ? request->fd : requests->mainFd(); - write_section_header(fd, id, buf->size()); - WriteFully(fd, (uint8_t const*)buf->data(), buf->size()); - // If there was an error now, there will be an error later and we will remove - // it from the list then. - } - } - return NO_ERROR; + timeoutMs(timeoutMs) { } -// ================================================================================ -MetadataSection::MetadataSection() : Section(FIELD_ID_INCIDENT_METADATA, 0) {} -MetadataSection::~MetadataSection() {} - -status_t MetadataSection::Execute(ReportRequestSet* requests) const { - ProtoOutputStream proto; - IncidentMetadata metadata = requests->metadata(); - proto.write(FIELD_TYPE_ENUM | IncidentMetadata::kDestFieldNumber, metadata.dest()); - proto.write(FIELD_TYPE_INT32 | IncidentMetadata::kRequestSizeFieldNumber, - metadata.request_size()); - proto.write(FIELD_TYPE_BOOL | IncidentMetadata::kUseDropboxFieldNumber, metadata.use_dropbox()); - for (auto iter = requests->allSectionStats().begin(); iter != requests->allSectionStats().end(); - iter++) { - IncidentMetadata::SectionStats stats = iter->second; - uint64_t token = proto.start(FIELD_TYPE_MESSAGE | IncidentMetadata::kSectionsFieldNumber); - proto.write(FIELD_TYPE_INT32 | IncidentMetadata::SectionStats::kIdFieldNumber, stats.id()); - proto.write(FIELD_TYPE_BOOL | IncidentMetadata::SectionStats::kSuccessFieldNumber, - stats.success()); - proto.write(FIELD_TYPE_INT32 | IncidentMetadata::SectionStats::kReportSizeBytesFieldNumber, - stats.report_size_bytes()); - proto.write(FIELD_TYPE_INT64 | IncidentMetadata::SectionStats::kExecDurationMsFieldNumber, - stats.exec_duration_ms()); - proto.write(FIELD_TYPE_INT32 | IncidentMetadata::SectionStats::kDumpSizeBytesFieldNumber, - stats.dump_size_bytes()); - proto.write(FIELD_TYPE_INT64 | IncidentMetadata::SectionStats::kDumpDurationMsFieldNumber, - stats.dump_duration_ms()); - proto.write(FIELD_TYPE_BOOL | IncidentMetadata::SectionStats::kTimedOutFieldNumber, - stats.timed_out()); - proto.write(FIELD_TYPE_BOOL | IncidentMetadata::SectionStats::kIsTruncatedFieldNumber, - stats.is_truncated()); - proto.write(FIELD_TYPE_STRING | IncidentMetadata::SectionStats::kErrorMsgFieldNumber, - stats.error_msg()); - proto.end(token); - } +Section::~Section() {} - for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) { - const sp<ReportRequest> request = *it; - if (request->fd < 0 || request->err != NO_ERROR) { - continue; - } - write_section_header(request->fd, id, proto.size()); - if (!proto.flush(request->fd)) { - ALOGW("Failed to write metadata to fd %d", request->fd); - // we don't fail if we can't write to a single request's fd. - } - } - if (requests->mainFd() >= 0) { - write_section_header(requests->mainFd(), id, proto.size()); - if (!proto.flush(requests->mainFd())) { - ALOGW("Failed to write metadata to dropbox fd %d", requests->mainFd()); - return -1; - } - } - return NO_ERROR; -} // ================================================================================ static inline bool isSysfs(const char* filename) { return strncmp(filename, "/sys/", 5) == 0; } FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs) - : Section(id, timeoutMs, false), mFilename(filename) { + : Section(id, timeoutMs), mFilename(filename) { name = "file "; name += filename; mIsSysfs = isSysfs(filename); @@ -271,7 +83,7 @@ FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs) FileSection::~FileSection() {} -status_t FileSection::Execute(ReportRequestSet* requests) const { +status_t FileSection::Execute(ReportWriter* writer) const { // read from mFilename first, make sure the file is available // add O_CLOEXEC to make sure it is closed when exec incident helper unique_fd fd(open(mFilename, O_RDONLY | O_CLOEXEC)); @@ -301,7 +113,7 @@ status_t FileSection::Execute(ReportRequestSet* requests) const { status_t readStatus = buffer.readProcessedDataInStream(fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs, mIsSysfs); - write_section_stats(requests->sectionStats(this->id), buffer); + writer->setSectionStats(buffer); if (readStatus != NO_ERROR || buffer.timedOut()) { ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s", this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); @@ -315,7 +127,7 @@ status_t FileSection::Execute(ReportRequestSet* requests) const { return ihStatus; } - return write_report_requests(this->id, buffer, requests); + return writer->writeSection(buffer); } // ================================================================================ GZipSection::GZipSection(int id, const char* filename, ...) : Section(id) { @@ -332,7 +144,7 @@ GZipSection::GZipSection(int id, const char* filename, ...) : Section(id) { GZipSection::~GZipSection() { free(mFilenames); } -status_t GZipSection::Execute(ReportRequestSet* requests) const { +status_t GZipSection::Execute(ReportWriter* writer) const { // Reads the files in order, use the first available one. int index = 0; unique_fd fd; @@ -366,7 +178,7 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const { // construct Fdbuffer to output GZippedfileProto, the reason to do this instead of using // ProtoOutputStream is to avoid allocation of another buffer inside ProtoOutputStream. - EncodedBuffer* internalBuffer = buffer.getInternalBuffer(); + sp<EncodedBuffer> internalBuffer = buffer.data(); internalBuffer->writeHeader((uint32_t)GZippedFileProto::FILENAME, WIRE_TYPE_LENGTH_DELIMITED); size_t fileLen = strlen(mFilenames[index]); internalBuffer->writeRawVarint32(fileLen); @@ -383,7 +195,7 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const { status_t readStatus = buffer.readProcessedDataInStream( fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs, isSysfs(mFilenames[index])); - write_section_stats(requests->sectionStats(this->id), buffer); + writer->setSectionStats(buffer); if (readStatus != NO_ERROR || buffer.timedOut()) { ALOGW("[%s] failed to read data from gzip: %s, timedout: %s", this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); @@ -402,7 +214,7 @@ status_t GZipSection::Execute(ReportRequestSet* requests) const { internalBuffer->writeRawVarint32(dataSize); internalBuffer->copy(dataBeginAt, dataSize); - return write_report_requests(this->id, buffer, requests); + return writer->writeSection(buffer); } // ================================================================================ @@ -425,8 +237,8 @@ WorkerThreadData::WorkerThreadData(const WorkerThreadSection* sec) WorkerThreadData::~WorkerThreadData() {} // ================================================================================ -WorkerThreadSection::WorkerThreadSection(int id, const int64_t timeoutMs, bool userdebugAndEngOnly) - : Section(id, timeoutMs, userdebugAndEngOnly) {} +WorkerThreadSection::WorkerThreadSection(int id, const int64_t timeoutMs) + : Section(id, timeoutMs) {} WorkerThreadSection::~WorkerThreadSection() {} @@ -458,13 +270,12 @@ static void* worker_thread_func(void* cookie) { return NULL; } -status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const { +status_t WorkerThreadSection::Execute(ReportWriter* writer) const { status_t err = NO_ERROR; pthread_t thread; pthread_attr_t attr; bool workerDone = false; FdBuffer buffer; - IncidentMetadata::SectionStats* stats = requests->sectionStats(this->id); // Data shared between this thread and the worker thread. sp<WorkerThreadData> data = new WorkerThreadData(this); @@ -474,10 +285,6 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const { return -errno; } - // The worker thread needs a reference and we can't let the count go to zero - // if that thread is slow to start. - data->incStrong(this); - // Create the thread err = pthread_attr_init(&attr); if (err != 0) { @@ -489,12 +296,17 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const { pthread_attr_destroy(&attr); return -err; } + + // The worker thread needs a reference and we can't let the count go to zero + // if that thread is slow to start. + data->incStrong(this); + err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get()); + pthread_attr_destroy(&attr); if (err != 0) { - pthread_attr_destroy(&attr); + data->decStrong(this); return -err; } - pthread_attr_destroy(&attr); // Loop reading until either the timeout or the worker side is done (i.e. eof). err = buffer.read(data->pipe.readFd().get(), this->timeoutMs); @@ -517,18 +329,17 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const { workerDone = data->workerDone; } - write_section_stats(stats, buffer); + writer->setSectionStats(buffer); if (err != NO_ERROR) { char errMsg[128]; snprintf(errMsg, 128, "[%s] failed with error '%s'", this->name.string(), strerror(-err)); - stats->set_success(false); - stats->set_error_msg(errMsg); + writer->error(this, err, "WorkerThreadSection failed."); return NO_ERROR; } if (buffer.truncated()) { ALOGW("[%s] too large, truncating", this->name.string()); - // Do not write a truncated section. It won't pass through the PrivacyBuffer. + // Do not write a truncated section. It won't pass through the PrivacyFilter. return NO_ERROR; } if (!workerDone || buffer.timedOut()) { @@ -537,7 +348,7 @@ status_t WorkerThreadSection::Execute(ReportRequestSet* requests) const { } // Write the data that was collected - return write_report_requests(this->id, buffer, requests); + return writer->writeSection(buffer); } // ================================================================================ @@ -568,7 +379,7 @@ CommandSection::CommandSection(int id, const char* command, ...) : Section(id) { CommandSection::~CommandSection() { free(mCommand); } -status_t CommandSection::Execute(ReportRequestSet* requests) const { +status_t CommandSection::Execute(ReportWriter* writer) const { FdBuffer buffer; Fpipe cmdPipe; Fpipe ihPipe; @@ -591,7 +402,7 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const { cmdPipe.writeFd().reset(); status_t readStatus = buffer.read(ihPipe.readFd().get(), this->timeoutMs); - write_section_stats(requests->sectionStats(this->id), buffer); + writer->setSectionStats(buffer); if (readStatus != NO_ERROR || buffer.timedOut()) { ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s", this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false"); @@ -605,18 +416,18 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const { status_t cmdStatus = wait_child(cmdPid); status_t ihStatus = wait_child(ihPid); if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) { - ALOGW("[%s] abnormal child processes, return status: command: %s, incident " - "helper: %s", + ALOGW("[%s] abnormal child processes, return status: command: %s, incident helper: %s", this->name.string(), strerror(-cmdStatus), strerror(-ihStatus)); - return cmdStatus != NO_ERROR ? cmdStatus : ihStatus; + // Not a fatal error. + return NO_ERROR; } - return write_report_requests(this->id, buffer, requests); + return writer->writeSection(buffer); } // ================================================================================ -DumpsysSection::DumpsysSection(int id, bool userdebugAndEngOnly, const char* service, ...) - : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS, userdebugAndEngOnly), mService(service) { +DumpsysSection::DumpsysSection(int id, const char* service, ...) + : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), mService(service) { name = "dumpsys "; name += service; @@ -842,7 +653,8 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid); std::string exe; if (!android::base::Readlink(link_name, &exe)) { - ALOGE("Can't read '%s': %s\n", link_name.c_str(), strerror(errno)); + ALOGE("Section %s: Can't read '%s': %s\n", name.string(), + link_name.c_str(), strerror(errno)); continue; } @@ -913,10 +725,10 @@ status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { } auto dump = std::make_unique<char[]>(buffer.size()); - auto iterator = buffer.data(); + sp<ProtoReader> reader = buffer.data()->read(); int i = 0; - while (iterator.hasNext()) { - dump[i] = iterator.next(); + while (reader->hasNext()) { + dump[i] = reader->next(); i++; } uint64_t token = proto.start(android::os::BackTraceProto::TRACES); diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index 86d956ff75d8..cfe7e1648ad8 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -40,35 +40,12 @@ class Section { public: const int id; const int64_t timeoutMs; // each section must have a timeout - const bool userdebugAndEngOnly; String8 name; - Section(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS, bool userdebugAndEngOnly = false); + Section(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS); virtual ~Section(); - virtual status_t Execute(ReportRequestSet* requests) const = 0; -}; - -/** - * Section that generates incident headers. - */ -class HeaderSection : public Section { -public: - HeaderSection(); - virtual ~HeaderSection(); - - virtual status_t Execute(ReportRequestSet* requests) const; -}; - -/** - * Section that generates incident metadata. - */ -class MetadataSection : public Section { -public: - MetadataSection(); - virtual ~MetadataSection(); - - virtual status_t Execute(ReportRequestSet* requests) const; + virtual status_t Execute(ReportWriter* writer) const = 0; }; /** @@ -80,7 +57,7 @@ public: int64_t timeoutMs = 5000 /* 5 seconds */); virtual ~FileSection(); - virtual status_t Execute(ReportRequestSet* requests) const; + virtual status_t Execute(ReportWriter* writer) const; private: const char* mFilename; @@ -95,7 +72,7 @@ public: GZipSection(int id, const char* filename, ...); virtual ~GZipSection(); - virtual status_t Execute(ReportRequestSet* requests) const; + virtual status_t Execute(ReportWriter* writer) const; private: // It looks up the content from multiple files and stops when the first one is available. @@ -107,11 +84,10 @@ private: */ class WorkerThreadSection : public Section { public: - WorkerThreadSection(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS, - bool userdebugAndEngOnly = false); + WorkerThreadSection(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS); virtual ~WorkerThreadSection(); - virtual status_t Execute(ReportRequestSet* requests) const; + virtual status_t Execute(ReportWriter* writer) const; virtual status_t BlockingCall(int pipeWriteFd) const = 0; }; @@ -127,7 +103,7 @@ public: virtual ~CommandSection(); - virtual status_t Execute(ReportRequestSet* requests) const; + virtual status_t Execute(ReportWriter* writer) const; private: const char** mCommand; @@ -138,7 +114,7 @@ private: */ class DumpsysSection : public WorkerThreadSection { public: - DumpsysSection(int id, bool userdebugAndEngOnly, const char* service, ...); + DumpsysSection(int id, const char* service, ...); virtual ~DumpsysSection(); virtual status_t BlockingCall(int pipeWriteFd) const; @@ -149,6 +125,21 @@ private: }; /** + * Section that calls dumpsys on a system service. + */ +class SystemPropertyDumpsysSection : public WorkerThreadSection { +public: + SystemPropertyDumpsysSection(int id, const char* service, ...); + virtual ~SystemPropertyDumpsysSection(); + + virtual status_t BlockingCall(int pipeWriteFd) const; + +private: + String16 mService; + Vector<String16> mArgs; +}; + +/** * Section that reads from logd. */ class LogSection : public WorkerThreadSection { diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp new file mode 100644 index 000000000000..aa376ddee082 --- /dev/null +++ b/cmds/incidentd/src/WorkDirectory.cpp @@ -0,0 +1,844 @@ +/* + * Copyright (C) 2019 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 "WorkDirectory.h" + +#include "PrivacyFilter.h" + +#include <google/protobuf/io/zero_copy_stream_impl.h> +#include <private/android_filesystem_config.h> + +#include <iomanip> +#include <map> +#include <sstream> +#include <thread> +#include <vector> + +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <inttypes.h> + +namespace android { +namespace os { +namespace incidentd { + +using std::thread; +using google::protobuf::MessageLite; +using google::protobuf::RepeatedPtrField; +using google::protobuf::io::FileInputStream; +using google::protobuf::io::FileOutputStream; + +/** + * Turn off to skip removing files for debugging. + */ +static const bool DO_UNLINK = true; + +/** + * File extension for envelope files. + */ +static const string EXTENSION_ENVELOPE(".envelope"); + +/** + * File extension for data files. + */ +static const string EXTENSION_DATA(".data"); + +/** + * Send these reports to dropbox. + */ +const ComponentName DROPBOX_SENTINEL("android", "DROPBOX"); + +/** + * Read a protobuf from disk into the message. + */ +static status_t read_proto(MessageLite* msg, const string& filename) { + int fd = open(filename.c_str(), O_RDONLY | O_CLOEXEC); + if (fd < 0) { + return -errno; + } + + FileInputStream stream(fd); + stream.SetCloseOnDelete(fd); + + if (!msg->ParseFromZeroCopyStream(&stream)) { + return BAD_VALUE; + } + + return stream.GetErrno(); +} + +/** + * Write a protobuf to disk. + */ +static status_t write_proto(const MessageLite& msg, const string& filename) { + int fd = open(filename.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660); + if (fd < 0) { + return -errno; + } + + FileOutputStream stream(fd); + stream.SetCloseOnDelete(fd); + + if (!msg.SerializeToZeroCopyStream(&stream)) { + ALOGW("write_proto: error writing to %s", filename.c_str()); + return BAD_VALUE; + } + + return stream.GetErrno(); +} + +static string strip_extension(const string& filename) { + return filename.substr(0, filename.find('.')); +} + +static bool ends_with(const string& str, const string& ending) { + if (str.length() >= ending.length()) { + return str.compare(str.length()-ending.length(), ending.length(), ending) == 0; + } else { + return false; + } +} + +// Returns true if it was a valid timestamp. +static bool parse_timestamp_ns(const string& id, int64_t* result) { + char* endptr; + *result = strtoll(id.c_str(), &endptr, 10); + return id.length() != 0 && *endptr == '\0'; +} + +static bool has_section(const ReportFileProto_Report& report, int section) { + const size_t sectionCount = report.section_size(); + for (int i = 0; i < sectionCount; i++) { + if (report.section(i) == section) { + return true; + } + } + return false; +} + +status_t create_directory(const char* directory) { + struct stat st; + status_t err = NO_ERROR; + char* dir = strdup(directory); + + // Skip first slash + char* d = dir + 1; + + // Create directories, assigning them to the system user + bool last = false; + while (!last) { + d = strchr(d, '/'); + if (d != NULL) { + *d = '\0'; + } else { + last = true; + } + if (stat(dir, &st) == 0) { + if (!S_ISDIR(st.st_mode)) { + err = ALREADY_EXISTS; + goto done; + } + } else { + ALOGE("No such directory %s, something wrong.", dir); + err = -1; + goto done; + } + if (!last) { + *d++ = '/'; + } + } + + // Ensure that the final directory is owned by the system with 0770. If it isn't + // we won't write into it. + if (stat(directory, &st) != 0) { + ALOGE("No incident reports today. Can't stat: %s", directory); + err = -errno; + goto done; + } + if ((st.st_mode & 0777) != 0770) { + ALOGE("No incident reports today. Mode is %0o on report directory %s", st.st_mode, + directory); + err = BAD_VALUE; + goto done; + } + if (st.st_uid != AID_INCIDENTD || st.st_gid != AID_INCIDENTD) { + ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s", + st.st_uid, st.st_gid, directory); + err = BAD_VALUE; + goto done; + } + +done: + free(dir); + return err; +} + +void log_envelope(const ReportFileProto& envelope) { + ALOGD("Envelope: {"); + for (int i=0; i<envelope.report_size(); i++) { + ALOGD(" report {"); + ALOGD(" pkg=%s", envelope.report(i).pkg().c_str()); + ALOGD(" cls=%s", envelope.report(i).cls().c_str()); + ALOGD(" share_approved=%d", envelope.report(i).share_approved()); + ALOGD(" privacy_policy=%d", envelope.report(i).privacy_policy()); + ALOGD(" all_sections=%d", envelope.report(i).all_sections()); + for (int j=0; j<envelope.report(i).section_size(); j++) { + ALOGD(" section[%d]=%d", j, envelope.report(i).section(j)); + } + ALOGD(" }"); + } + ALOGD(" data_file=%s", envelope.data_file().c_str()); + ALOGD(" privacy_policy=%d", envelope.privacy_policy()); + ALOGD(" data_file_size=%lld", envelope.data_file_size()); + ALOGD(" completed=%d", envelope.completed()); + ALOGD("}"); +} + +// ================================================================================ +struct WorkDirectoryEntry { + WorkDirectoryEntry(); + explicit WorkDirectoryEntry(const WorkDirectoryEntry& that); + ~WorkDirectoryEntry(); + + string envelope; + string data; + int64_t timestampNs; + off_t size; +}; + +WorkDirectoryEntry::WorkDirectoryEntry() + :envelope(), + data(), + size(0) { +} + +WorkDirectoryEntry::WorkDirectoryEntry(const WorkDirectoryEntry& that) + :envelope(that.envelope), + data(that.data), + size(that.size) { +} + +WorkDirectoryEntry::~WorkDirectoryEntry() { +} + +// ================================================================================ +ReportFile::ReportFile(const sp<WorkDirectory>& workDirectory, int64_t timestampNs, + const string& envelopeFileName, const string& dataFileName) + :mWorkDirectory(workDirectory), + mTimestampNs(timestampNs), + mEnvelopeFileName(envelopeFileName), + mDataFileName(dataFileName), + mEnvelope(), + mDataFd(-1), + mError(NO_ERROR) { + // might get overwritten when we read but that's ok + mEnvelope.set_data_file(mDataFileName); +} + +ReportFile::~ReportFile() { + if (mDataFd >= 0) { + close(mDataFd); + } +} + +int64_t ReportFile::getTimestampNs() const { + return mTimestampNs; +} + +void ReportFile::addReport(const IncidentReportArgs& args) { + // There is only one report per component. Merge into an existing one if necessary. + ReportFileProto_Report* report; + const int reportCount = mEnvelope.report_size(); + int i = 0; + for (; i < reportCount; i++) { + report = mEnvelope.mutable_report(i); + if (report->pkg() == args.receiverPkg() && report->cls() == args.receiverCls()) { + if (args.getPrivacyPolicy() < report->privacy_policy()) { + // Lower privacy policy (less restrictive) wins. + report->set_privacy_policy(args.getPrivacyPolicy()); + } + report->set_all_sections(report->all_sections() | args.all()); + for (int section: args.sections()) { + if (!has_section(*report, section)) { + report->add_section(section); + } + } + break; + } + } + if (i >= reportCount) { + report = mEnvelope.add_report(); + report->set_pkg(args.receiverPkg()); + report->set_cls(args.receiverCls()); + report->set_privacy_policy(args.getPrivacyPolicy()); + report->set_all_sections(args.all()); + for (int section: args.sections()) { + report->add_section(section); + } + } + + for (const vector<uint8_t>& header: args.headers()) { + report->add_header(header.data(), header.size()); + } +} + +void ReportFile::removeReport(const string& pkg, const string& cls) { + RepeatedPtrField<ReportFileProto_Report>* reports = mEnvelope.mutable_report(); + const int reportCount = reports->size(); + for (int i = 0; i < reportCount; i++) { + const ReportFileProto_Report& r = reports->Get(i); + if (r.pkg() == pkg && r.cls() == cls) { + reports->DeleteSubrange(i, 1); + return; + } + } +} + +void ReportFile::removeReports(const string& pkg) { + RepeatedPtrField<ReportFileProto_Report>* reports = mEnvelope.mutable_report(); + const int reportCount = reports->size(); + for (int i = reportCount-1; i >= 0; i--) { + const ReportFileProto_Report& r = reports->Get(i); + if (r.pkg() == pkg) { + reports->DeleteSubrange(i, 1); + } + } +} + +void ReportFile::setMetadata(const IncidentMetadata& metadata) { + *mEnvelope.mutable_metadata() = metadata; +} + +void ReportFile::markCompleted() { + mEnvelope.set_completed(true); +} + +status_t ReportFile::markApproved(const string& pkg, const string& cls) { + size_t const reportCount = mEnvelope.report_size(); + for (int reportIndex = 0; reportIndex < reportCount; reportIndex++) { + ReportFileProto_Report* report = mEnvelope.mutable_report(reportIndex); + if (report->pkg() == pkg && report->cls() == cls) { + report->set_share_approved(true); + return NO_ERROR; + } + } + return NAME_NOT_FOUND; +} + +void ReportFile::setMaxPersistedPrivacyPolicy(int persistedPrivacyPolicy) { + mEnvelope.set_privacy_policy(persistedPrivacyPolicy); +} + +status_t ReportFile::saveEnvelope() { + return save_envelope_impl(true); +} + +status_t ReportFile::trySaveEnvelope() { + return save_envelope_impl(false); +} + +status_t ReportFile::loadEnvelope() { + return load_envelope_impl(true); +} + +status_t ReportFile::tryLoadEnvelope() { + return load_envelope_impl(false); +} + +const ReportFileProto& ReportFile::getEnvelope() { + return mEnvelope; +} + +status_t ReportFile::startWritingDataFile() { + if (mDataFd >= 0) { + ALOGW("ReportFile::startWritingDataFile called with the file already open: %s", + mDataFileName.c_str()); + return ALREADY_EXISTS; + } + mDataFd = open(mDataFileName.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660); + if (mDataFd < 0) { + return -errno; + } + return NO_ERROR; +} + +void ReportFile::closeDataFile() { + if (mDataFd >= 0) { + mEnvelope.set_data_file_size(lseek(mDataFd, 0, SEEK_END)); + close(mDataFd); + mDataFd = -1; + } +} + +status_t ReportFile::startFilteringData(int* fd, const IncidentReportArgs& args) { + // Open data file. + int dataFd = open(mDataFileName.c_str(), O_RDONLY | O_CLOEXEC); + if (dataFd < 0) { + return -errno; + } + + // Check that the size on disk is what we thought we wrote. + struct stat st; + if (fstat(dataFd, &st) != 0) { + return -errno; + } + if (st.st_size != mEnvelope.data_file_size()) { + ALOGW("File size mismatch. Envelope says %" PRIi64 " bytes but data file is %" PRIi64 + " bytes: %s", (int64_t)mEnvelope.data_file_size(), st.st_size, + mDataFileName.c_str()); + ALOGW("Removing incident report"); + mWorkDirectory->remove(this); + return BAD_VALUE; + } + + // Create pipe + int fds[2]; + if (pipe(fds) != 0) { + ALOGW("Error opening pipe to filter incident report: %s", getDataFileName().c_str()); + return -errno; + } + + *fd = fds[0]; + int writeFd = fds[1]; + + // Spawn off a thread to do the filtering and writing + thread th([this, dataFd, writeFd, args]() { + ALOGD("worker thread started dataFd=%d writeFd=%d", dataFd, writeFd); + status_t err; + + err = filter_and_write_report(writeFd, dataFd, mEnvelope.privacy_policy(), args); + close(writeFd); + + if (err != NO_ERROR) { + ALOGW("Error writing incident report '%s' to dropbox: %s", getDataFileName().c_str(), + strerror(-err)); + // If there's an error here, there will also be an error returned from + // addFile, so we'll use that error to reschedule the send_to_dropbox. + // If the file is corrupted, we will put some logs in logcat, but won't + // actually return an error. + return; + } + }); + + // Better would be to join this thread after write is back, but there is no + // timeout parameter for that, which means we can't clean up if system server + // is stuck. Better is to leak the thread, which will eventually clean itself + // up after system server eventually dies, which it probably will. + th.detach(); + + // If the thread fails to start, we should return an error, but the thread + // class doesn't give us a good way to determine that. Just pretend everything + // is ok. + return NO_ERROR; +} + +string ReportFile::getDataFileName() const { + return mDataFileName; +} + +string ReportFile::getEnvelopeFileName() const { + return mEnvelopeFileName; +} + +int ReportFile::getDataFileFd() { + return mDataFd; +} + +void ReportFile::setWriteError(status_t err) { + mError = err; +} + +status_t ReportFile::getWriteError() { + return mError; +} + +string ReportFile::getId() { + return to_string(mTimestampNs); +} + +status_t ReportFile::save_envelope_impl(bool cleanup) { + status_t err; + err = write_proto(mEnvelope, mEnvelopeFileName); + if (err != NO_ERROR) { + // If there was an error writing the envelope, then delete the whole thing. + if (cleanup) { + mWorkDirectory->remove(this); + } + return err; + } + return NO_ERROR; +} + +status_t ReportFile::load_envelope_impl(bool cleanup) { + status_t err; + err = read_proto(&mEnvelope, mEnvelopeFileName); + if (err != NO_ERROR) { + // If there was an error reading the envelope, then delete the whole thing. + if (cleanup) { + mWorkDirectory->remove(this); + } + return err; + } + return NO_ERROR; +} + + + +// ================================================================================ +// + +WorkDirectory::WorkDirectory() + :mDirectory("/data/misc/incidents"), + mMaxFileCount(100), + mMaxDiskUsageBytes(30 * 1024 * 1024) { // Incident reports can take up to 30MB on disk. + // TODO: Should be a flag. + create_directory(mDirectory.c_str()); +} + +WorkDirectory::WorkDirectory(const string& dir, int maxFileCount, long maxDiskUsageBytes) + :mDirectory(dir), + mMaxFileCount(maxFileCount), + mMaxDiskUsageBytes(maxDiskUsageBytes) { + create_directory(mDirectory.c_str()); +} + +sp<ReportFile> WorkDirectory::createReportFile() { + unique_lock<mutex> lock(mLock); + status_t err; + + clean_directory_locked(); + + int64_t timestampNs = make_timestamp_ns_locked(); + string envelopeFileName = make_filename(timestampNs, EXTENSION_ENVELOPE); + string dataFileName = make_filename(timestampNs, EXTENSION_DATA); + + sp<ReportFile> result = new ReportFile(this, timestampNs, envelopeFileName, dataFileName); + + err = result->trySaveEnvelope(); + if (err != NO_ERROR) { + ALOGW("Can't save envelope file %s: %s", strerror(-errno), envelopeFileName.c_str()); + return nullptr; + } + + return result; +} + +status_t WorkDirectory::getReports(vector<sp<ReportFile>>* result, int64_t after) { + unique_lock<mutex> lock(mLock); + + const bool DBG = true; + + if (DBG) { + ALOGD("WorkDirectory::getReports"); + } + + map<string,WorkDirectoryEntry> files; + get_directory_contents_locked(&files, after); + for (map<string,WorkDirectoryEntry>::iterator it = files.begin(); + it != files.end(); it++) { + sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs, + it->second.envelope, it->second.data); + if (DBG) { + ALOGD(" %s", reportFile->getId().c_str()); + } + result->push_back(reportFile); + } + return NO_ERROR; +} + +sp<ReportFile> WorkDirectory::getReport(const string& pkg, const string& cls, const string& id, + IncidentReportArgs* args) { + unique_lock<mutex> lock(mLock); + + status_t err; + int64_t timestampNs; + if (!parse_timestamp_ns(id, ×tampNs)) { + return nullptr; + } + + // Make the ReportFile object, and then see if it's valid and for pkg and cls. + sp<ReportFile> result = new ReportFile(this, timestampNs, + make_filename(timestampNs, EXTENSION_ENVELOPE), + make_filename(timestampNs, EXTENSION_DATA)); + + err = result->tryLoadEnvelope(); + if (err != NO_ERROR) { + ALOGW("Can't open envelope file for report %s/%s %s", pkg.c_str(), cls.c_str(), id.c_str()); + return nullptr; + } + + const ReportFileProto& envelope = result->getEnvelope(); + const size_t reportCount = envelope.report_size(); + for (int i = 0; i < reportCount; i++) { + const ReportFileProto_Report& report = envelope.report(i); + if (report.pkg() == pkg && report.cls() == cls) { + if (args != nullptr) { + get_args_from_report(args, report); + } + return result; + } + + } + + return nullptr; +} + +bool WorkDirectory::hasMore(int64_t after) { + unique_lock<mutex> lock(mLock); + + map<string,WorkDirectoryEntry> files; + get_directory_contents_locked(&files, after); + return files.size() > 0; +} + +void WorkDirectory::commit(const sp<ReportFile>& report, const string& pkg, const string& cls) { + status_t err; + ALOGI("Committing report %s for %s/%s", report->getId().c_str(), pkg.c_str(), cls.c_str()); + + unique_lock<mutex> lock(mLock); + + // Load the envelope here inside the lock. + err = report->loadEnvelope(); + + report->removeReport(pkg, cls); + + delete_files_for_report_if_necessary(report); +} + +void WorkDirectory::commitAll(const string& pkg) { + status_t err; + ALOGI("All reports for %s", pkg.c_str()); + + unique_lock<mutex> lock(mLock); + + map<string,WorkDirectoryEntry> files; + get_directory_contents_locked(&files, 0); + + for (map<string,WorkDirectoryEntry>::iterator it = files.begin(); + it != files.end(); it++) { + sp<ReportFile> reportFile = new ReportFile(this, it->second.timestampNs, + it->second.envelope, it->second.data); + + err = reportFile->loadEnvelope(); + if (err != NO_ERROR) { + continue; + } + + reportFile->removeReports(pkg); + + delete_files_for_report_if_necessary(reportFile); + } +} + +void WorkDirectory::remove(const sp<ReportFile>& report) { + unique_lock<mutex> lock(mLock); + // Set this to false to leave files around for debugging. + if (DO_UNLINK) { + unlink(report->getDataFileName().c_str()); + unlink(report->getEnvelopeFileName().c_str()); + } +} + +int64_t WorkDirectory::make_timestamp_ns_locked() { + // Guarantee that we don't have duplicate timestamps. + // This is a little bit lame, but since reports are created on the + // same thread and are kinda slow we'll seldomly actually hit the + // condition. The bigger risk is the clock getting reset and causing + // a collision. In that case, we'll just make incident reporting a + // little bit slower. Nobody will notice if we just loop until we + // have a unique file name. + int64_t timestampNs = 0; + do { + struct timespec spec; + if (timestampNs > 0) { + spec.tv_sec = 0; + spec.tv_nsec = 1; + nanosleep(&spec, nullptr); + } + clock_gettime(CLOCK_REALTIME, &spec); + timestampNs = (spec.tv_sec) * 1000 + spec.tv_nsec; + } while (file_exists_locked(timestampNs)); + return timestampNs; +} + +/** + * It is required to hold the lock here so in case someone else adds it + * our result is still correct for the caller. + */ +bool WorkDirectory::file_exists_locked(int64_t timestampNs) { + const string filename = make_filename(timestampNs, EXTENSION_ENVELOPE); + struct stat st; + return stat(filename.c_str(), &st) == 0; +} + +string WorkDirectory::make_filename(int64_t timestampNs, const string& extension) { + // Zero pad the timestamp so it can also be alpha sorted. + stringstream result; + result << mDirectory << '/' << setfill('0') << setw(20) << timestampNs << extension; + return result.str(); +} + +off_t WorkDirectory::get_directory_contents_locked(map<string,WorkDirectoryEntry>* files, + int64_t after) { + DIR* dir; + struct dirent* entry; + + if ((dir = opendir(mDirectory.c_str())) == NULL) { + ALOGE("Couldn't open incident directory: %s", mDirectory.c_str()); + return -1; + } + + string dirbase(mDirectory); + if (mDirectory[dirbase.size() - 1] != '/') dirbase += "/"; + + off_t totalSize = 0; + + // Enumerate, count and add up size + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + string entryname = entry->d_name; // local to this dir + string filename = dirbase + entryname; // fully qualified + + bool isEnvelope = ends_with(entryname, EXTENSION_ENVELOPE); + bool isData = ends_with(entryname, EXTENSION_DATA); + + // If the file isn't one of our files, just ignore it. Otherwise, + // sum up the sizes. + if (isEnvelope || isData) { + string timestamp = strip_extension(entryname); + + int64_t timestampNs; + if (!parse_timestamp_ns(timestamp, ×tampNs)) { + continue; + } + + if (after == 0 || timestampNs > after) { + struct stat st; + if (stat(filename.c_str(), &st) != 0) { + ALOGE("Unable to stat file %s", filename.c_str()); + continue; + } + if (!S_ISREG(st.st_mode)) { + continue; + } + + WorkDirectoryEntry& entry = (*files)[timestamp]; + if (isEnvelope) { + entry.envelope = filename; + } else if (isData) { + entry.data = filename; + } + entry.timestampNs = timestampNs; + entry.size += st.st_size; + totalSize += st.st_size; + } + } + } + + closedir(dir); + + // Now check if there are any data files that don't have envelope files. + // If there are, then just go ahead and delete them now. Don't wait for + // a cleaning. + + if (DO_UNLINK) { + map<string,WorkDirectoryEntry>::iterator it = files->begin(); + while (it != files->end()) { + if (it->second.envelope.length() == 0) { + unlink(it->second.data.c_str()); + it = files->erase(it); + } else { + it++; + } + } + } + + return totalSize; +} + +void WorkDirectory::clean_directory_locked() { + DIR* dir; + struct dirent* entry; + struct stat st; + + // Map of filename without extension to the entries about it. Conveniently, + // this also keeps the list sorted by filename, which is a timestamp. + map<string,WorkDirectoryEntry> files; + off_t totalSize = get_directory_contents_locked(&files, 0); + if (totalSize < 0) { + return; + } + int totalCount = files.size(); + + // Count or size is less than max, then we're done. + if (totalSize < mMaxDiskUsageBytes && totalCount < mMaxFileCount) { + return; + } + + // Remove files until we're under our limits. + if (DO_UNLINK) { + for (map<string, WorkDirectoryEntry>::const_iterator it = files.begin(); + it != files.end() && (totalSize >= mMaxDiskUsageBytes + || totalCount >= mMaxFileCount); + it++) { + unlink(it->second.envelope.c_str()); + unlink(it->second.data.c_str()); + totalSize -= it->second.size; + totalCount--; + } + } +} + +void WorkDirectory::delete_files_for_report_if_necessary(const sp<ReportFile>& report) { + if (report->getEnvelope().report_size() == 0) { + ALOGI("Report %s is finished. Deleting from storage.", report->getId().c_str()); + if (DO_UNLINK) { + unlink(report->getDataFileName().c_str()); + unlink(report->getEnvelopeFileName().c_str()); + } + } +} + +// ================================================================================ +void get_args_from_report(IncidentReportArgs* out, const ReportFileProto_Report& report) { + out->setPrivacyPolicy(report.privacy_policy()); + out->setAll(report.all_sections()); + out->setReceiverPkg(report.pkg()); + out->setReceiverCls(report.cls()); + + const int sectionCount = report.section_size(); + for (int i = 0; i < sectionCount; i++) { + out->addSection(report.section(i)); + } + + const int headerCount = report.header_size(); + for (int i = 0; i < headerCount; i++) { + const string& header = report.header(i); + vector<uint8_t> vec(header.begin(), header.end()); + out->addHeader(vec); + } +} + + +} // namespace incidentd +} // namespace os +} // namespace android + diff --git a/cmds/incidentd/src/WorkDirectory.h b/cmds/incidentd/src/WorkDirectory.h new file mode 100644 index 000000000000..e344371c3682 --- /dev/null +++ b/cmds/incidentd/src/WorkDirectory.h @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2019 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. + */ + +#pragma once + +#include <android/content/ComponentName.h> +#include <android/os/IncidentReportArgs.h> +#include <frameworks/base/core/proto/android/os/metadata.pb.h> +#include <frameworks/base/cmds/incidentd/src/report_file.pb.h> + +#include <utils/RefBase.h> + +#include <mutex> +#include <string> + +namespace android { +namespace os { +namespace incidentd { + +using android::content::ComponentName; +using android::os::IncidentReportArgs; +using namespace std; + +extern const ComponentName DROPBOX_SENTINEL; + +class WorkDirectory; +struct WorkDirectoryEntry; + +void get_args_from_report(IncidentReportArgs* out, const ReportFileProto_Report& report); + +/** + * A ReportFile object is backed by two files. + * - A metadata file, which contains a + */ +class ReportFile : public virtual RefBase { +public: + ReportFile(const sp<WorkDirectory>& workDirectory, int64_t timestampNs, + const string& envelopeFileName, const string& dataFileName); + + virtual ~ReportFile(); + + /** + * Get the timestamp from when this file was added. + */ + int64_t getTimestampNs() const; + + /** + * Add an additional report to this ReportFile. + */ + void addReport(const IncidentReportArgs& args); + + /** + * Remove the reports for pkg/cls from this file. + */ + void removeReport(const string& pkg, const string& cls); + + /** + * Remove all reports for pkg from this file. + */ + void removeReports(const string& pkg); + + /** + * Set the metadata for this incident report. + */ + void setMetadata(const IncidentMetadata& metadata); + + /* + * Mark this incident report as finished and ready for broadcast. + */ + void markCompleted(); + + /* + * Mark this incident report as finished and ready for broadcast. + */ + status_t markApproved(const string& pkg, const string& cls); + + /** + * Set the privacy policy that is being used to pre-filter the data + * going to disk. + */ + void setMaxPersistedPrivacyPolicy(int persistedPrivacyPolicy); + + /** + * Save the metadata (envelope) information about the incident + * report. Must be called after addReport, setMetadata markCompleted + * markApproved to save those changes to disk. + */ + status_t saveEnvelope(); + + /** + * Like saveEnvelope() but will not clean up if there is an error. + */ + status_t trySaveEnvelope(); + + /** + * Read the envelope information from disk. If there was an error, the envelope and + * data file will be removed. If the proto can't be loaded, the whole file is deleted. + */ + status_t loadEnvelope(); + + /** + * Like loadEnvelope() but will not clean up if there is an error. + */ + status_t tryLoadEnvelope(); + + /** + * Get the envelope information. + */ + const ReportFileProto& getEnvelope(); + + /** + * Open the file that will contain the contents of the incident report. Call + * close() or closeDataFile() on the result of getDataFileFd() when you're done. + * This is not done automatically in the desctructor. If there is an error, returns + * it and you will not get an fd. + */ + status_t startWritingDataFile(); + + /** + * Close the data file. + */ + void closeDataFile(); + + /** + * Spawn a thread to start writing and filtering data to a pipe, the read end of which + * will be returned in fd. This thread will be detached and run until somebody finishes + * reading from the fd or closes it. If there is an error, returns it and you will not + * get an fd. + * + * Use the privacy and section configuraiton from the args parameter. + */ + status_t startFilteringData(int* fd, const IncidentReportArgs& args); + + /** + * Get the name of the data file on disk. + */ + string getDataFileName() const; + + /** + * Get the name of the envelope file on disk. + */ + string getEnvelopeFileName() const; + + /** + * Return the file descriptor for the data file, or -1 if it is not + * currently open. + */ + int getDataFileFd(); + + /** + * Record that there was an error writing to the data file. + */ + void setWriteError(status_t err); + + /** + * Get whether there was previously an error writing to the data file. + */ + status_t getWriteError(); + + /** + * Get the unique identifier for this file. + */ + string getId(); + +private: + sp<WorkDirectory> mWorkDirectory; + int64_t mTimestampNs; + string mEnvelopeFileName; + string mDataFileName; + ReportFileProto mEnvelope; + int mDataFd; + status_t mError; + + status_t save_envelope_impl(bool cleanup); + status_t load_envelope_impl(bool cleanup); +}; + +/** + * For directory cleanup to work, WorkDirectory must be kept + * alive for the duration of all of the ReportFiles. In the real + * incidentd, WorkDirectory is a singleton. In tests, it may + * have a shorter duration. + */ +class WorkDirectory : public virtual RefBase { +public: + /** + * Save files to the default location. + */ + WorkDirectory(); + + /** + * Save files to a specific location (primarily for testing). + */ + WorkDirectory(const string& dir, int maxFileCount, long maxDiskUsageBytes); + + /** + * Return a new report file. Creating this object won't fail, but + * subsequent actions on the file could, if the disk is full, permissions + * aren't set correctly, etc. + */ + sp<ReportFile> createReportFile(); + + /** + * Get the reports that are saved on-disk, with the time after (>) than the + * given timestamp. Pass 0 to start at the beginning. These files + * will be sorted by timestamp. The envelope will not have been loaded. + */ + status_t getReports(vector<sp<ReportFile>>* files, int64_t after); + + /** + * Get the report with the given package, class and id. Returns nullptr if + * that can't be found. The envelope will have been loaded. Returns the + * original IncidentReportArgs in *args if args != nullptr. + */ + sp<ReportFile> getReport(const string& pkg, const string& cls, const string& id, + IncidentReportArgs* args); + + /** + * Returns whether there are more reports after the given timestamp. + */ + bool hasMore(int64_t after); + + /** + * Confirm that a particular broadcast receiver has received the data. When all + * broadcast receivers for a particular report file have finished, the envelope + * and data files will be deleted. + */ + void commit(const sp<ReportFile>& report, const string& pkg, const string& cls); + + /** + * Commit all reports the given package. + */ + void commitAll(const string& pkg); + + /** + * Remove the envelope and data file from disk, regardless of whether there are + * more pending readers or broadcasts, for example in response to an error. + */ + void remove(const sp<ReportFile>& report); + +private: + string mDirectory; + int mMaxFileCount; + long mMaxDiskUsageBytes; + + // Held while creating or removing envelope files, which are the file that keeps + // the directory consistent. + mutex mLock; + + int64_t make_timestamp_ns_locked(); + bool file_exists_locked(int64_t timestampNs); + off_t get_directory_contents_locked(map<string,WorkDirectoryEntry>* files, int64_t after); + void clean_directory_locked(); + void delete_files_for_report_if_necessary(const sp<ReportFile>& report); + + string make_filename(int64_t timestampNs, const string& extension); +}; + + +} // namespace incidentd +} // namespace os +} // namespace android + diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp index af685d8adeb8..dfaf89392f90 100644 --- a/cmds/incidentd/src/incidentd_util.cpp +++ b/cmds/incidentd/src/incidentd_util.cpp @@ -68,7 +68,6 @@ pid_t fork_execute_cmd(char* const argv[], Fpipe* input, Fpipe* output) { // fork used in multithreaded environment, avoid adding unnecessary code in child process pid_t pid = fork(); if (pid == 0) { - VLOG("[In child]cmd %s", argv[0]); if (input != NULL && (TEMP_FAILURE_RETRY(dup2(input->readFd().get(), STDIN_FILENO)) < 0 || !input->close())) { ALOGW("Failed to dup2 stdin."); @@ -158,4 +157,4 @@ status_t wait_child(pid_t pid) { } // namespace incidentd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h index 3dac2c4c3759..cc30768fa704 100644 --- a/cmds/incidentd/src/incidentd_util.h +++ b/cmds/incidentd/src/incidentd_util.h @@ -78,6 +78,8 @@ uint64_t Nanotime(); status_t kill_child(pid_t pid); status_t wait_child(pid_t pid); +status_t start_detached_thread(const function<void ()>& func); + } // namespace incidentd } // namespace os } // namespace android diff --git a/cmds/incidentd/src/proto_util.cpp b/cmds/incidentd/src/proto_util.cpp new file mode 100644 index 000000000000..be2f24f97d02 --- /dev/null +++ b/cmds/incidentd/src/proto_util.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 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 "proto_util.h" + +#include <google/protobuf/io/zero_copy_stream_impl.h> + +#include <android/util/protobuf.h> +#include <android/util/ProtoOutputStream.h> +#include <android-base/file.h> + +namespace android { +namespace os { +namespace incidentd { + +using namespace android::base; +using namespace android::util; +using google::protobuf::io::FileOutputStream; + +// special section ids +const int FIELD_ID_INCIDENT_HEADER = 1; + +status_t write_header_section(int fd, const vector<uint8_t>& buf) { + status_t err; + const size_t bufSize = buf.size(); + + if (buf.empty()) { + return NO_ERROR; + } + + err = write_section_header(fd, FIELD_ID_INCIDENT_HEADER, bufSize); + if (err != NO_ERROR) { + return err; + } + + err = WriteFully(fd, (uint8_t const*)buf.data(), bufSize); + if (err != NO_ERROR) { + return err; + } + + return NO_ERROR; +} + +status_t write_section_header(int fd, int sectionId, size_t size) { + uint8_t buf[20]; + uint8_t* p = write_length_delimited_tag_header(buf, sectionId, size); + return WriteFully(fd, buf, p - buf) ? NO_ERROR : -errno; +} + +status_t write_section(int fd, int sectionId, const MessageLite& message) { + status_t err; + + err = write_section_header(fd, sectionId, message.ByteSize()); + if (err != NO_ERROR) { + return err; + } + + FileOutputStream stream(fd); + if (!message.SerializeToZeroCopyStream(&stream)) { + return stream.GetErrno(); + } else { + return NO_ERROR; + } +} + + + +} // namespace incidentd +} // namespace os +} // namespace android + diff --git a/cmds/incidentd/src/proto_util.h b/cmds/incidentd/src/proto_util.h new file mode 100644 index 000000000000..b9df6cbf296d --- /dev/null +++ b/cmds/incidentd/src/proto_util.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 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. + */ + +#pragma once + +#include <utils/Errors.h> + +#include <google/protobuf/message_lite.h> + +#include <vector> + +namespace android { +namespace os { +namespace incidentd { + +using std::vector; +using google::protobuf::MessageLite; + +/** + * Write the IncidentHeaderProto section + */ +status_t write_header_section(int fd, const vector<uint8_t>& buf); + +/** + * Write the prologue for a section in the incident report + * (This is the proto length-prefixed field format). + */ +status_t write_section_header(int fd, int sectionId, size_t size); + +/** + * Write the given protobuf object as a section. + */ +status_t write_section(int fd, int sectionId, const MessageLite& message); + +} // namespace incidentd +} // namespace os +} // namespace android + + diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp index e2883ba04508..7d20a74437ce 100644 --- a/cmds/incidentd/src/report_directory.cpp +++ b/cmds/incidentd/src/report_directory.cpp @@ -33,63 +33,6 @@ namespace android { namespace os { namespace incidentd { -status_t create_directory(const char* directory) { - struct stat st; - status_t err = NO_ERROR; - char* dir = strdup(directory); - - // Skip first slash - char* d = dir + 1; - - // Create directories, assigning them to the system user - bool last = false; - while (!last) { - d = strchr(d, '/'); - if (d != NULL) { - *d = '\0'; - } else { - last = true; - } - if (stat(dir, &st) == 0) { - if (!S_ISDIR(st.st_mode)) { - err = ALREADY_EXISTS; - goto done; - } - } else { - ALOGE("No such directory %s, something wrong.", dir); - err = -1; - goto done; - } - if (!last) { - *d++ = '/'; - } - } - - // Ensure that the final directory is owned by the system with 0770. If it isn't - // we won't write into it. - if (stat(directory, &st) != 0) { - ALOGE("No incident reports today. Can't stat: %s", directory); - err = -errno; - goto done; - } - if ((st.st_mode & 0777) != 0770) { - ALOGE("No incident reports today. Mode is %0o on report directory %s", st.st_mode, - directory); - err = BAD_VALUE; - goto done; - } - if (st.st_uid != AID_INCIDENTD || st.st_gid != AID_INCIDENTD) { - ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s", - st.st_uid, st.st_gid, directory); - err = BAD_VALUE; - goto done; - } - -done: - free(dir); - return err; -} - static bool stat_mtime_cmp(const std::pair<String8, struct stat>& a, const std::pair<String8, struct stat>& b) { return a.second.st_mtime < b.second.st_mtime; @@ -153,4 +96,4 @@ void clean_directory(const char* directory, off_t maxSize, size_t maxCount) { } // namespace incidentd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/incidentd/src/report_file.proto b/cmds/incidentd/src/report_file.proto new file mode 100644 index 000000000000..7563da2c2148 --- /dev/null +++ b/cmds/incidentd/src/report_file.proto @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 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. + */ + +syntax = "proto2"; + +package android.os.incidentd; + +import "frameworks/base/core/proto/android/os/metadata.proto"; + +message ReportFileProto { + /** + * Metadata about each of the calls to reportIncident that + * initiated the incident report. + */ + message Report { + /** + * Package name for broadcast receiver to be told when + * the report is complete. + */ + optional string pkg = 1; + + /** + * Class name for broadcast receiver to be told when + * the report is complete. + */ + optional string cls = 2; + + /** + * Privacy policy at which this report should be shared. + */ + optional uint32 privacy_policy = 4; + + /** + * Whether all available sections should be returned. + */ + optional bool all_sections = 5; + + /** + * If all_sections is not true, then this is the + * list of sections that were requested. + */ + repeated int32 section = 6; + + /** + * Flattened IncidentHeaderProto that was passed with this + * request. + */ + repeated bytes header = 7; + + /** + * Whether the user has approved this report to be shared with + * the given client. + */ + optional bool share_approved = 8; + } + + /** + * Metadata section recorded while the incident report + * was taken. + */ + optional android.os.IncidentMetadata metadata = 1; + + /** + * Report data structures for the incident reports. + */ + repeated Report report = 2; + + /** + * The file name, relative to the work directory where + * the data file is stored. The content of the data file + * is an android.os.IncidentProto, without the metadata + * or header sections. + */ + optional string data_file = 3; + + /** + * The privacy policy to which the file is already filtered. + */ + optional uint32 privacy_policy = 4; + + /** + * How big the data file is expected to be. If the size + * recorded here and the size on disk mismatch, then we + * know there was an error. + */ + optional int64 data_file_size = 5; + + /** + * Whether this report has been finished, and is now + * ready for broadcast / dropbox / etc. + */ + optional bool completed = 6; +} + |