Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f2f2f20 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Use the consent service to check if user has consented to taking a bugreport"

parents b5fd6ace d2f5f089
Loading
Loading
Loading
Loading
+18 −5
Original line number Diff line number Diff line
@@ -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) {
+3 −0
Original line number Diff line number Diff line
@@ -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.
     */
+149 −19
Original line number Diff line number Diff line
@@ -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);
    }
}
+39 −3
Original line number Diff line number Diff line
@@ -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:
    RunStatus RunInternal();
        ConsentResult result_;
        uint64_t start_time_;
        std::mutex lock_;
    };

  private:
    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);
};