diff options
| -rw-r--r-- | cmds/dumpstate/DumpstateService.cpp | 23 | ||||
| -rw-r--r-- | cmds/dumpstate/binder/android/os/IDumpstateListener.aidl | 3 | ||||
| -rw-r--r-- | cmds/dumpstate/dumpstate.cpp | 168 | ||||
| -rw-r--r-- | cmds/dumpstate/dumpstate.h | 42 |
4 files changed, 209 insertions, 27 deletions
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp index eb0d50da28..b13478c3a0 100644 --- a/cmds/dumpstate/DumpstateService.cpp +++ b/cmds/dumpstate/DumpstateService.cpp @@ -31,6 +31,13 @@ namespace os { namespace { +struct DumpstateInfo { + public: + Dumpstate* ds = nullptr; + int32_t calling_uid = -1; + std::string calling_package; +}; + static binder::Status exception(uint32_t code, const std::string& msg) { MYLOGE("%s (%d) ", msg.c_str(), code); return binder::Status::fromExceptionCode(code, String8(msg.c_str())); @@ -42,14 +49,15 @@ static binder::Status error(uint32_t code, const std::string& msg) { } static void* callAndNotify(void* data) { - Dumpstate& ds = *static_cast<Dumpstate*>(data); - ds.Run(); + DumpstateInfo& ds_info = *static_cast<DumpstateInfo*>(data); + ds_info.ds->Run(ds_info.calling_uid, ds_info.calling_package); MYLOGD("Finished Run()\n"); return nullptr; } class DumpstateToken : public BnDumpstateToken {}; -} + +} // namespace DumpstateService::DumpstateService() : ds_(Dumpstate::GetInstance()) { } @@ -98,8 +106,8 @@ binder::Status DumpstateService::setListener(const std::string& name, } // TODO(b/111441001): Hook up to consent service & copy final br only if user approves. -binder::Status DumpstateService::startBugreport(int32_t /* calling_uid */, - const std::string& /* calling_package */, +binder::Status DumpstateService::startBugreport(int32_t calling_uid, + const std::string& calling_package, const android::base::unique_fd& bugreport_fd, const android::base::unique_fd& screenshot_fd, int bugreport_mode, @@ -132,6 +140,11 @@ binder::Status DumpstateService::startBugreport(int32_t /* calling_uid */, ds_.listener_ = listener; } + DumpstateInfo ds_info; + ds_info.ds = &ds_; + ds_info.calling_uid = calling_uid; + ds_info.calling_package = calling_package; + pthread_t thread; status_t err = pthread_create(&thread, nullptr, callAndNotify, &ds_); if (err != 0) { diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl index 8396acd0b4..907a67c907 100644 --- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl +++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl @@ -38,6 +38,9 @@ interface IDumpstateListener { /* User denied consent to share the bugreport with the specified app */ const int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; + /* The request to get user consent timed out */ + const int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; + /** * Called on an error condition with one of the error codes listed above. */ diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp index c0050845a0..dfc5b49c6f 100644 --- a/cmds/dumpstate/dumpstate.cpp +++ b/cmds/dumpstate/dumpstate.cpp @@ -51,6 +51,7 @@ #include <android-base/unique_fd.h> #include <android/hardware/dumpstate/1.0/IDumpstateDevice.h> #include <android/hidl/manager/1.0/IServiceManager.h> +#include <android/os/IIncidentCompanion.h> #include <cutils/native_handle.h> #include <cutils/properties.h> #include <dumpsys.h> @@ -83,15 +84,19 @@ using android::TIMED_OUT; using android::UNKNOWN_ERROR; using android::Vector; using android::base::StringPrintf; +using android::os::IDumpstateListener; using android::os::dumpstate::CommandOptions; using android::os::dumpstate::DumpFileToFd; using android::os::dumpstate::DumpstateSectionReporter; using android::os::dumpstate::GetPidByName; using android::os::dumpstate::PropertiesHelper; +typedef Dumpstate::ConsentCallback::ConsentResult UserConsentResult; + /* read before root is shed */ static char cmdline_buf[16384] = "(unknown)"; static const char *dump_traces_path = nullptr; +static const uint64_t USER_CONSENT_TIMEOUT_MS = 30 * 1000; // TODO: variables and functions below should be part of dumpstate object @@ -165,6 +170,13 @@ static bool CopyFileToFd(const std::string& input_file, int out_fd) { return false; } +static bool UnlinkAndLogOnError(const std::string& file) { + if (unlink(file.c_str()) != -1) { + MYLOGE("Failed to remove file (%s): %s\n", file.c_str(), strerror(errno)); + return false; + } + return true; +} } // namespace } // namespace os @@ -657,6 +669,32 @@ static unsigned long logcat_timeout(const std::vector<std::string>& buffers) { return timeout_ms > MINIMUM_LOGCAT_TIMEOUT_MS ? timeout_ms : MINIMUM_LOGCAT_TIMEOUT_MS; } +Dumpstate::ConsentCallback::ConsentCallback() : result_(UNAVAILABLE), start_time_(Nanotime()) { +} + +android::binder::Status Dumpstate::ConsentCallback::onReportApproved() { + std::lock_guard<std::mutex> lock(lock_); + result_ = APPROVED; + MYLOGD("User approved consent to share bugreport\n"); + return android::binder::Status::ok(); +} + +android::binder::Status Dumpstate::ConsentCallback::onReportDenied() { + std::lock_guard<std::mutex> lock(lock_); + result_ = DENIED; + MYLOGW("User denied consent to share bugreport\n"); + return android::binder::Status::ok(); +} + +UserConsentResult Dumpstate::ConsentCallback::getResult() { + std::lock_guard<std::mutex> lock(lock_); + return result_; +} + +uint64_t Dumpstate::ConsentCallback::getElapsedTimeMs() const { + return Nanotime() - start_time_; +} + void Dumpstate::PrintHeader() const { std::string build, fingerprint, radio, bootloader, network; char date[80]; @@ -1886,14 +1924,6 @@ static void FinalizeFile() { ds.path_ = new_path; } } - // The zip file lives in an internal directory. Copy it over to output. - if (ds.options_->bugreport_fd.get() != -1) { - bool copy_succeeded = - android::os::CopyFileToFd(ds.path_, ds.options_->bugreport_fd.get()); - if (!copy_succeeded && remove(ds.path_.c_str())) { - MYLOGE("remove(%s): %s", ds.path_.c_str(), strerror(errno)); - } - } // else - the file just remains in the internal directory. } } if (do_text_file) { @@ -2186,8 +2216,8 @@ void Dumpstate::SetOptions(std::unique_ptr<DumpOptions> options) { options_ = std::move(options); } -Dumpstate::RunStatus Dumpstate::Run() { - Dumpstate::RunStatus status = RunInternal(); +Dumpstate::RunStatus Dumpstate::Run(int32_t calling_uid, const std::string& calling_package) { + Dumpstate::RunStatus status = RunInternal(calling_uid, calling_package); if (listener_ != nullptr) { switch (status) { case Dumpstate::RunStatus::OK: @@ -2196,10 +2226,16 @@ Dumpstate::RunStatus Dumpstate::Run() { case Dumpstate::RunStatus::HELP: break; case Dumpstate::RunStatus::INVALID_INPUT: - listener_->onError(android::os::IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT); + listener_->onError(IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT); break; case Dumpstate::RunStatus::ERROR: - listener_->onError(android::os::IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR); + listener_->onError(IDumpstateListener::BUGREPORT_ERROR_RUNTIME_ERROR); + break; + case Dumpstate::RunStatus::USER_CONSENT_DENIED: + listener_->onError(IDumpstateListener::BUGREPORT_ERROR_USER_DENIED_CONSENT); + break; + case Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT: + listener_->onError(IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT); break; } } @@ -2227,7 +2263,8 @@ Dumpstate::RunStatus Dumpstate::Run() { * Bugreports are first generated in a local directory and later copied to the caller's fd if * supplied. */ -Dumpstate::RunStatus Dumpstate::RunInternal() { +Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid, + const std::string& calling_package) { LogDumpOptions(*options_); if (!options_->ValidateOptions()) { MYLOGE("Invalid options specified\n"); @@ -2265,6 +2302,12 @@ Dumpstate::RunStatus Dumpstate::RunInternal() { return RunStatus::OK; } + if (options_->bugreport_fd.get() != -1) { + // If the output needs to be copied over to the caller's fd, get user consent. + android::String16 package(calling_package.c_str()); + CheckUserConsent(calling_uid, package); + } + // Redirect output if needed bool is_redirecting = options_->OutputToFile(); @@ -2416,11 +2459,24 @@ Dumpstate::RunStatus Dumpstate::RunInternal() { TEMP_FAILURE_RETRY(dup2(dup_stdout_fd, fileno(stdout))); } - /* rename or zip the (now complete) .tmp file to its final location */ + // Rename, and/or zip the (now complete) .tmp file within the internal directory. if (options_->OutputToFile()) { FinalizeFile(); } + // Share the final file with the caller if the user has consented. + Dumpstate::RunStatus status = Dumpstate::RunStatus::OK; + if (options_->bugreport_fd.get() != -1) { + status = CopyBugreportIfUserConsented(); + if (status != Dumpstate::RunStatus::OK && + status != Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT) { + // Do an early return if there were errors. We make an exception for consent + // timing out because it's possible the user got distracted. In this case the + // bugreport is not shared but made available for manual retrieval. + return status; + } + } + /* vibrate a few but shortly times to let user know it's finished */ if (options_->do_vibrate) { for (int i = 0; i < 3; i++) { @@ -2452,7 +2508,73 @@ Dumpstate::RunStatus Dumpstate::RunInternal() { tombstone_data_.clear(); anr_data_.clear(); - return RunStatus::OK; + return (consent_callback_ != nullptr && + consent_callback_->getResult() == UserConsentResult::UNAVAILABLE) + ? USER_CONSENT_TIMED_OUT + : RunStatus::OK; +} + +void Dumpstate::CheckUserConsent(int32_t calling_uid, const android::String16& calling_package) { + consent_callback_ = new ConsentCallback(); + const String16 incidentcompanion("incidentcompanion"); + sp<android::IBinder> ics(defaultServiceManager()->getService(incidentcompanion)); + if (ics != nullptr) { + MYLOGD("Checking user consent via incidentcompanion service\n"); + android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport( + calling_uid, calling_package, 0x1 /* FLAG_CONFIRMATION_DIALOG */, + consent_callback_.get()); + } else { + MYLOGD("Unable to check user consent; incidentcompanion service unavailable\n"); + } +} + +void Dumpstate::CleanupFiles() { + android::os::UnlinkAndLogOnError(tmp_path_); + android::os::UnlinkAndLogOnError(screenshot_path_); + android::os::UnlinkAndLogOnError(path_); +} + +Dumpstate::RunStatus Dumpstate::HandleUserConsentDenied() { + MYLOGD("User denied consent; deleting files and returning\n"); + CleanupFiles(); + return USER_CONSENT_DENIED; +} + +Dumpstate::RunStatus Dumpstate::CopyBugreportIfUserConsented() { + // If the caller has asked to copy the bugreport over to their directory, we need explicit + // user consent. + UserConsentResult consent_result = consent_callback_->getResult(); + if (consent_result == UserConsentResult::UNAVAILABLE) { + // User has not responded yet. + uint64_t elapsed_ms = consent_callback_->getElapsedTimeMs(); + if (elapsed_ms < USER_CONSENT_TIMEOUT_MS) { + uint delay_seconds = (USER_CONSENT_TIMEOUT_MS - elapsed_ms) / 1000; + MYLOGD("Did not receive user consent yet; going to wait for %d seconds", delay_seconds); + sleep(delay_seconds); + } + consent_result = consent_callback_->getResult(); + } + if (consent_result == UserConsentResult::DENIED) { + // User has explicitly denied sharing with the app. To be safe delete the + // internal bugreport & tmp files. + return HandleUserConsentDenied(); + } + if (consent_result == UserConsentResult::APPROVED) { + bool copy_succeeded = android::os::CopyFileToFd(ds.path_, ds.options_->bugreport_fd.get()); + if (copy_succeeded && remove(ds.path_.c_str())) { + MYLOGE("remove(%s): %s", ds.path_.c_str(), strerror(errno)); + } + return copy_succeeded ? Dumpstate::RunStatus::OK : Dumpstate::RunStatus::ERROR; + } else if (consent_result == UserConsentResult::UNAVAILABLE) { + // consent_result is still UNAVAILABLE. The user has likely not responded yet. + // Since we do not have user consent to share the bugreport it does not get + // copied over to the calling app but remains in the internal directory from + // where the user can manually pull it. + return Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT; + } + // Unknown result; must be a programming error. + MYLOGE("Unknown user consent result:%d\n", consent_result); + return Dumpstate::RunStatus::ERROR; } /* Main entry point for dumpstate binary. */ @@ -2461,7 +2583,14 @@ int run_main(int argc, char* argv[]) { Dumpstate::RunStatus status = options->Initialize(argc, argv); if (status == Dumpstate::RunStatus::OK) { ds.SetOptions(std::move(options)); - status = ds.Run(); + // When directly running dumpstate binary, the output is not expected to be written + // to any external file descriptor. + assert(ds.options_->bugreport_fd.get() == -1); + + // calling_uid and calling_package are for user consent to share the bugreport with + // an app; they are irrelvant here because bugreport is only written to a local + // directory, and not shared. + status = ds.Run(-1 /* calling_uid */, "" /* calling_package */); } switch (status) { @@ -2475,9 +2604,10 @@ int run_main(int argc, char* argv[]) { ShowUsage(); exit(1); case Dumpstate::RunStatus::ERROR: - exit(2); - default: - fprintf(stderr, "Unknown status: %d\n", status); + FALLTHROUGH_INTENDED; + case Dumpstate::RunStatus::USER_CONSENT_DENIED: + FALLTHROUGH_INTENDED; + case Dumpstate::RunStatus::USER_CONSENT_TIMED_OUT: exit(2); } } diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 3dfe4e955f..4766d82909 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -27,6 +27,7 @@ #include <android-base/macros.h> #include <android-base/unique_fd.h> +#include <android/os/BnIncidentAuthListener.h> #include <android/os/IDumpstate.h> #include <android/os/IDumpstateListener.h> #include <utils/StrongPointer.h> @@ -192,7 +193,7 @@ class Dumpstate { friend class DumpstateTest; public: - enum RunStatus { OK, HELP, INVALID_INPUT, ERROR }; + enum RunStatus { OK, HELP, INVALID_INPUT, ERROR, USER_CONSENT_DENIED, USER_CONSENT_TIMED_OUT }; // The mode under which the bugreport should be run. Each mode encapsulates a few options. enum BugreportMode { @@ -319,7 +320,7 @@ class Dumpstate { struct DumpOptions; /* Main entry point for running a complete bugreport. */ - RunStatus Run(); + RunStatus Run(int32_t calling_uid, const std::string& calling_package); /* Sets runtime options. */ void SetOptions(std::unique_ptr<DumpOptions> options); @@ -447,12 +448,47 @@ class Dumpstate { // List of open ANR dump files. std::vector<DumpData> anr_data_; + // A callback to IncidentCompanion service, which checks user consent for sharing the + // bugreport with the calling app. If the user has not responded yet to the dialog it will + // be neither confirmed nor denied. + class ConsentCallback : public android::os::BnIncidentAuthListener { + public: + ConsentCallback(); + android::binder::Status onReportApproved() override; + android::binder::Status onReportDenied() override; + + enum ConsentResult { APPROVED, DENIED, UNAVAILABLE }; + + ConsentResult getResult(); + + // Returns the time since creating this listener + uint64_t getElapsedTimeMs() const; + + private: + ConsentResult result_; + uint64_t start_time_; + std::mutex lock_; + }; + private: - RunStatus RunInternal(); + RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package); + + void CheckUserConsent(int32_t calling_uid, const android::String16& calling_package); + + // Removes the in progress files output files (tmp file, zip/txt file, screenshot), + // but leaves the log file alone. + void CleanupFiles(); + + RunStatus HandleUserConsentDenied(); + + // Copies bugreport artifacts over to the caller's directories provided there is user consent. + RunStatus CopyBugreportIfUserConsented(); // Used by GetInstance() only. explicit Dumpstate(const std::string& version = VERSION_CURRENT); + android::sp<ConsentCallback> consent_callback_; + DISALLOW_COPY_AND_ASSIGN(Dumpstate); }; |