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

Commit bbc9f321 authored by Tri Vo's avatar Tri Vo Committed by Automerger Merge Worker
Browse files

Merge "trusty: Helper library for metrics" am: e23e85be am: 1eed4fbf am: e8f8a915

Original change: https://android-review.googlesource.com/c/platform/system/core/+/1629434

Change-Id: Ic516d1bc54eaee18751400f40f8c7b00a0b68ac5
parents 27595361 e8f8a915
Loading
Loading
Loading
Loading
+51 −0
Original line number Diff line number Diff line
// Copyright (C) 2021 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.

package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

cc_library {
    name: "libtrusty_metrics",
    vendor: true,
    srcs: [
        "metrics.cpp",
    ],
    export_include_dirs: [
        "include",
    ],
    shared_libs: [
        "libbase",
        "liblog",
        "libtrusty",
    ],
}

cc_test {
    name: "libtrusty_metrics_test",
    vendor: true,
    srcs: [
        "metrics_test.cpp",
    ],
    static_libs: [
        "libtrusty_metrics",
    ],
    shared_libs: [
        "libbase",
        "libbinder",
        "liblog",
        "libtrusty",
    ],
    require_root: true,
}
+57 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 <functional>
#include <memory>
#include <string>

#include <android-base/result.h>
#include <android-base/unique_fd.h>

namespace android {
namespace trusty {
namespace metrics {

using android::base::Result;
using android::base::unique_fd;

class TrustyMetrics {
  public:
    /* Wait for next event with a given timeout. Negative timeout means infinite timeout. */
    Result<void> WaitForEvent(int timeout_ms = -1);
    /* Attempt to handle an event from Metrics TA in a non-blocking manner. */
    Result<void> HandleEvent();
    /* Expose TIPC channel so that client can integrate it into an event loop with other fds. */
    int GetRawFd() { return metrics_fd_; };

  protected:
    TrustyMetrics(std::string tipc_dev) : tipc_dev_(std::move(tipc_dev)), metrics_fd_(-1) {}
    virtual ~TrustyMetrics(){};

    Result<void> Open();
    virtual void HandleCrash(const std::string& app_id) = 0;
    virtual void HandleEventDrop() = 0;

  private:
    std::string tipc_dev_;
    unique_fd metrics_fd_;
};

}  // namespace metrics
}  // namespace trusty
}  // namespace android
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021, 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 <stdint.h>

/**
 * DOC: Metrics
 *
 * Metrics interface provides a way for Android to get Trusty metrics data.
 *
 * Currently, only "push" model is supported. Clients are expected to connect to
 * metrics service, listen for events, e.g. app crash events, and respond to
 * every event with a &struct metrics_req.
 *
 * Communication is driven by metrics service, i.e. requests/responses are all
 * sent from/to metrics service.
 *
 * Note that the type of the event is not known to the client ahead of time.
 *
 * In the future, if we need to have Android "pull" metrics data from Trusty,
 * that can be done by introducing a separate port.
 *
 * This interface is shared between Android and Trusty. There is a copy in each
 * repository. They must be kept in sync.
 */

#define METRICS_PORT "com.android.trusty.metrics"

/**
 * enum metrics_cmd - command identifiers for metrics interface
 * @METRICS_CMD_RESP_BIT:          message is a response
 * @METRICS_CMD_REQ_SHIFT:         number of bits used by @METRICS_CMD_RESP_BIT
 * @METRICS_CMD_REPORT_EVENT_DROP: report gaps in the event stream
 * @METRICS_CMD_REPORT_CRASH:      report an app crash event
 */
enum metrics_cmd {
    METRICS_CMD_RESP_BIT = 1,
    METRICS_CMD_REQ_SHIFT = 1,

    METRICS_CMD_REPORT_EVENT_DROP = (1 << METRICS_CMD_REQ_SHIFT),
    METRICS_CMD_REPORT_CRASH = (2 << METRICS_CMD_REQ_SHIFT),
};

/**
 * enum metrics_error - metrics error codes
 * @METRICS_NO_ERROR:        no error
 * @METRICS_ERR_UNKNOWN_CMD: unknown or not implemented command
 */
enum metrics_error {
    METRICS_NO_ERROR = 0,
    METRICS_ERR_UNKNOWN_CMD = 1,
};

/**
 * struct metrics_req - common structure for metrics requests
 * @cmd:      command identifier - one of &enum metrics_cmd
 * @reserved: must be 0
 */
struct metrics_req {
    uint32_t cmd;
    uint32_t reserved;
} __attribute__((__packed__));

/**
 * struct metrics_resp - common structure for metrics responses
 * @cmd: command identifier - %METRICS_CMD_RESP_BIT or'ed with a cmd in
 *                            one of &enum metrics_cmd
 * @status: response status, one of &enum metrics_error
 */
struct metrics_resp {
    uint32_t cmd;
    uint32_t status;
} __attribute__((__packed__));

/**
 * struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
 *                                   requests
 * @app_id_len: length of app ID that follows this structure
 */
struct metrics_report_crash_req {
    uint32_t app_id_len;
} __attribute__((__packed__));

#define METRICS_MAX_APP_ID_LEN 256

#define METRICS_MAX_MSG_SIZE                                                \
    (sizeof(struct metrics_req) + sizeof(struct metrics_report_crash_req) + \
     METRICS_MAX_APP_ID_LEN)
+142 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Sourete 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 LOG_TAG "metrics"

#include <android-base/logging.h>
#include <fcntl.h>
#include <poll.h>
#include <trusty/metrics/metrics.h>
#include <trusty/metrics/tipc.h>
#include <trusty/tipc.h>
#include <unistd.h>

namespace android {
namespace trusty {
namespace metrics {

using android::base::ErrnoError;
using android::base::Error;

Result<void> TrustyMetrics::Open() {
    int fd = tipc_connect(tipc_dev_.c_str(), METRICS_PORT);
    if (fd < 0) {
        return ErrnoError() << "failed to connect to Trusty metrics TA";
    }

    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) {
        return ErrnoError() << "failed F_GETFL";
    }

    int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    if (rc < 0) {
        return ErrnoError() << "failed F_SETFL";
    }

    metrics_fd_.reset(fd);
    return {};
}

Result<void> TrustyMetrics::WaitForEvent(int timeout_ms) {
    if (!metrics_fd_.ok()) {
        return Error() << "connection to Metrics TA has not been initialized yet";
    }

    struct pollfd pfd = {
            .fd = metrics_fd_,
            .events = POLLIN,
    };

    int rc = poll(&pfd, 1, timeout_ms);
    if (rc != 1) {
        return ErrnoError() << "failed poll()";
    }

    if (!(pfd.revents & POLLIN)) {
        return ErrnoError() << "channel not ready";
    }

    return {};
}

Result<void> TrustyMetrics::HandleEvent() {
    if (!metrics_fd_.ok()) {
        return Error() << "connection to Metrics TA has not been initialized yet";
    }

    uint8_t msg[METRICS_MAX_MSG_SIZE];

    auto rc = read(metrics_fd_, msg, sizeof(msg));
    if (rc < 0) {
        return ErrnoError() << "failed to read metrics message";
    }
    size_t msg_len = rc;

    if (msg_len < sizeof(metrics_req)) {
        return Error() << "message too small: " << rc;
    }
    auto req = reinterpret_cast<metrics_req*>(msg);
    size_t offset = sizeof(metrics_req);
    uint32_t status = METRICS_NO_ERROR;

    switch (req->cmd) {
        case METRICS_CMD_REPORT_CRASH: {
            if (msg_len < offset + sizeof(metrics_report_crash_req)) {
                return Error() << "message too small: " << rc;
            }
            auto crash_args = reinterpret_cast<metrics_report_crash_req*>(msg + offset);
            offset += sizeof(metrics_report_crash_req);

            if (msg_len < offset + crash_args->app_id_len) {
                return Error() << "message too small: " << rc;
            }
            auto app_id_ptr = reinterpret_cast<char*>(msg + offset);
            std::string app_id(app_id_ptr, crash_args->app_id_len);

            HandleCrash(app_id);
            break;
        }

        case METRICS_CMD_REPORT_EVENT_DROP:
            HandleEventDrop();
            break;

        default:
            status = METRICS_ERR_UNKNOWN_CMD;
            break;
    }

    metrics_resp resp = {
            .cmd = req->cmd | METRICS_CMD_RESP_BIT,
            .status = status,
    };

    rc = write(metrics_fd_, &resp, sizeof(resp));
    if (rc < 0) {
        return ErrnoError() << "failed to request next metrics event";
    }

    if (rc != (int)sizeof(resp)) {
        return Error() << "unexpected number of bytes sent event: " << rc;
    }

    return {};
}

}  // namespace metrics
}  // namespace trusty
}  // namespace android
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 <android-base/unique_fd.h>
#include <binder/IPCThreadState.h>
#include <gtest/gtest.h>
#include <poll.h>
#include <trusty/metrics/metrics.h>
#include <trusty/tipc.h>

#define TIPC_DEV "/dev/trusty-ipc-dev0"
#define CRASHER_PORT "com.android.trusty.metrics.test.crasher"

namespace android {
namespace trusty {
namespace metrics {

using android::base::unique_fd;

static void TriggerCrash() {
    size_t num_retries = 3;
    int fd = -1;

    for (size_t i = 0; i < num_retries; i++) {
        /* It's possible to time out waiting for crasher TA to restart. */
        fd = tipc_connect(TIPC_DEV, CRASHER_PORT);
        if (fd >= 0) {
            break;
        }
    }

    unique_fd crasher(fd);
    ASSERT_GE(crasher, 0);

    int msg = 0;
    int rc = write(crasher, &msg, sizeof(msg));
    ASSERT_EQ(rc, sizeof(msg));
}

class TrustyMetricsTest : public TrustyMetrics, public ::testing::Test {
  public:
    TrustyMetricsTest() : TrustyMetrics(TIPC_DEV) {}

    virtual void HandleCrash(const std::string& app_id) override { crashed_app_ = app_id; }

    virtual void HandleEventDrop() override { event_drop_count_++; }

    virtual void SetUp() override {
        auto ret = Open();
        ASSERT_TRUE(ret.ok()) << ret.error();
    }

    void WaitForAndHandleEvent() {
        auto ret = WaitForEvent(30000 /* 30 second timeout */);
        ASSERT_TRUE(ret.ok()) << ret.error();

        ret = HandleEvent();
        ASSERT_TRUE(ret.ok()) << ret.error();
    }

    std::string crashed_app_;
    size_t event_drop_count_;
};

TEST_F(TrustyMetricsTest, Crash) {
    TriggerCrash();
    WaitForAndHandleEvent();

    /* Check that correct TA crashed. */
    ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
}

TEST_F(TrustyMetricsTest, PollSet) {
    int binder_fd;
    int rc = IPCThreadState::self()->setupPolling(&binder_fd);
    ASSERT_EQ(rc, 0);
    ASSERT_GE(binder_fd, 0);

    TriggerCrash();

    struct pollfd pfds[] = {
            {
                    .fd = binder_fd,
                    .events = POLLIN,
            },
            {
                    .fd = GetRawFd(),
                    .events = POLLIN,
            },
    };

    rc = poll(pfds, 2, 30000 /* 30 second timeout */);
    /* We expect one event on the metrics fd. */
    ASSERT_EQ(rc, 1);
    ASSERT_TRUE(pfds[1].revents & POLLIN);

    auto ret = HandleEvent();
    ASSERT_TRUE(ret.ok()) << ret.error();

    /* Check that correct TA crashed. */
    ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
}

TEST_F(TrustyMetricsTest, EventDrop) {
    /* We know the size of the internal event queue is less than this. */
    size_t num_events = 3;

    ASSERT_EQ(event_drop_count_, 0);

    for (auto i = 0; i < num_events; i++) {
        TriggerCrash();
    }

    for (auto i = 0; i < num_events; i++) {
        WaitForAndHandleEvent();
        if (event_drop_count_ > 0) {
            break;
        }
    }

    ASSERT_EQ(event_drop_count_, 1);
}

}  // namespace metrics
}  // namespace trusty
}  // namespace android