| /* |
| * Copyright (C) 2022 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 <errno.h> |
| #include <getopt.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/uio.h> |
| #include <unistd.h> |
| |
| #include <condition_variable> |
| #include <cstddef> |
| #include <mutex> |
| #include <queue> |
| |
| #include <android-base/expected.h> |
| #include <android-base/logging.h> |
| #include <android/frameworks/stats/BnStats.h> |
| #include <android/frameworks/stats/IStats.h> |
| #include <android/trusty/stats/nw/setter/IStatsSetter.h> |
| #include <binder/RpcServer.h> |
| #include <binder/RpcSession.h> |
| #include <binder/RpcTransportRaw.h> |
| #include <binder/RpcTransportTipcAndroid.h> |
| #include <binder/RpcTrusty.h> |
| #include <trusty/tipc.h> |
| |
| /** DOC: |
| * ./build-root/build-qemu-generic-arm64-test-debug/run \ |
| * --android $ANDROID_PROJECT_ROOT \ |
| * --headless --shell-command \ |
| * "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test" |
| * |
| * adb -s emulator-5554 shell \ |
| * /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test |
| */ |
| using ::android::base::unique_fd; |
| using ::android::binder::Status; |
| using ::android::frameworks::stats::BnStats; |
| using ::android::frameworks::stats::IStats; |
| using ::android::frameworks::stats::VendorAtom; |
| using ::android::frameworks::stats::VendorAtomValue; |
| using ::android::trusty::stats::nw::setter::IStatsSetter; |
| |
| constexpr const char kTrustyDefaultDeviceName[] = "/dev/trusty-ipc-dev0"; |
| constexpr const char kTrustyStatsSetterTest[] = |
| "com.android.frameworks.stats.trusty.test.relayer.istats_setter"; |
| constexpr const char kTrustyStatsSetterMetrics[] = |
| "com.android.frameworks.stats.trusty.metrics.istats_setter"; |
| constexpr const char kTrustyStatsPortTest[] = "com.android.trusty.stats.test"; |
| constexpr const char kTrustyCrashPortTest[] = "com.android.trusty.crashtest"; |
| constexpr const char kTrustyCrasherUuid[] = "7ee4dddc-177a-420a-96ea-5d413d88228e:crasher"; |
| |
| enum TrustyAtoms : int32_t { |
| TrustyAppCrashed = 100072, |
| TrustyError = 100145, |
| TrustyStorageError = 100146 |
| }; |
| |
| enum TestMsgHeader : int32_t { |
| TEST_PASSED = 0, |
| TEST_FAILED = 1, |
| TEST_MESSAGE = 2, |
| }; |
| |
| namespace android { |
| namespace trusty { |
| namespace stats { |
| |
| class Stats : public BnStats { |
| public: |
| Stats() : BnStats() {} |
| |
| Status reportVendorAtom(const VendorAtom& vendorAtom) override { |
| const char* atomIdStr = vendorAtomStr(vendorAtom.atomId); |
| ALOGD("Vendor atom reported of type: %s\n", atomIdStr); |
| std::lock_guard lock(mLock); |
| mQueueVendorAtom.push(vendorAtom); |
| mCondVar.notify_one(); |
| return Status::ok(); |
| } |
| |
| status_t getVendorAtom(VendorAtom* pVendorAtom, int64_t waitForMs) { |
| std::unique_lock lock(mLock); |
| while (mQueueVendorAtom.empty()) { |
| auto rc = mCondVar.wait_for(lock, std::chrono::milliseconds(waitForMs)); |
| if (rc == std::cv_status::timeout) { |
| return TIMED_OUT; |
| } |
| } |
| *pVendorAtom = mQueueVendorAtom.front(); |
| mQueueVendorAtom.pop(); |
| return NO_ERROR; |
| } |
| |
| private: |
| const char* vendorAtomStr(int32_t atomId) { |
| switch (atomId) { |
| case TrustyAtoms::TrustyAppCrashed: |
| return "TrustyAtoms::TrustyAppCrashed"; |
| case TrustyAtoms::TrustyError: |
| return "TrustyAtoms::TrustyError"; |
| case TrustyAtoms::TrustyStorageError: |
| return "TrustyAtoms::TrustyStorageError"; |
| default: |
| return "unknown TrustyAtoms type"; |
| } |
| } |
| std::mutex mLock; |
| std::condition_variable mCondVar; |
| std::queue<VendorAtom> mQueueVendorAtom; |
| }; |
| |
| class TrustyStatsTestBase : public ::testing::Test { |
| protected: |
| TrustyStatsTestBase(std::string&& portNameStatsSetter, std::string&& portNamePortTest) |
| : mPortTestFd(-1), |
| mPortNameStatsSetter(std::move(portNameStatsSetter)), |
| mPortNamePortTest(std::move(portNamePortTest)) {} |
| |
| void SetUp() override { |
| // Commenting out the server portion because we do not have any direct |
| // incoming call Calls from TA are currently being handled on the mSession's |
| // extra thread. android::sp<::android::RpcServer> server = |
| // ::android::RpcServer::make(::android::RpcTransportCtxFactoryRaw::make()); |
| |
| mStats = android::sp<Stats>::make(); |
| // Increasing number of incoming threads on mSession to be able to receive |
| // callbacks |
| auto session_initializer = [](sp<RpcSession>& session) { |
| session->setMaxIncomingThreads(1); |
| }; |
| |
| ASSERT_FALSE(mSession); |
| mSession = RpcTrustyConnectWithSessionInitializer( |
| kTrustyDefaultDeviceName, mPortNameStatsSetter.c_str(), session_initializer); |
| ASSERT_TRUE(mSession); |
| |
| auto root = mSession->getRootObject(); |
| ASSERT_TRUE(root); |
| auto statsSetter = IStatsSetter::asInterface(root); |
| ASSERT_TRUE(statsSetter); |
| statsSetter->setInterface(mStats); |
| } |
| void TearDown() override { |
| // close connection to unitest app |
| if (mPortTestFd != -1) { |
| tipc_close(mPortTestFd); |
| } |
| mPortTestFd = -1; |
| |
| if (mSession) { |
| // shutdownAndWait here races with sending out the DecStrong |
| // messages after reportVendorAtom returns, so we delay it a little |
| // bit to give the messages time to go out over the transport |
| usleep(50000); |
| ASSERT_TRUE(mSession->shutdownAndWait(true)); |
| } |
| mSession.clear(); |
| mStats.clear(); |
| } |
| void StartPortTest() { |
| // connect to unitest app |
| mPortTestFd = tipc_connect(kTrustyDefaultDeviceName, mPortNamePortTest.c_str()); |
| if (mPortTestFd < 0) { |
| ALOGE("Failed to connect to '%s' app: %s\n", kTrustyStatsPortTest, |
| strerror(-mPortTestFd)); |
| } |
| ASSERT_GT(mPortTestFd, 0); |
| } |
| void WaitPortTestDone() { |
| // wait for test to complete |
| char rxBuf[1024]; |
| const char prolog[] = "Trusty PORT_TEST:"; |
| strncpy(rxBuf, prolog, sizeof(prolog) - 1); |
| char* pRxBuf = rxBuf + sizeof(prolog) - 1; |
| size_t remainingBufSize = sizeof(rxBuf) - sizeof(prolog) - 1; |
| |
| ASSERT_NE(mPortTestFd, -1); |
| for (;;) { |
| int rc = read(mPortTestFd, pRxBuf, remainingBufSize); |
| ASSERT_GT(rc, 0); |
| ASSERT_LT(rc, (int)remainingBufSize); |
| if (pRxBuf[0] == TEST_PASSED) { |
| break; |
| } else if (pRxBuf[0] == TEST_FAILED) { |
| break; |
| } else if (pRxBuf[0] == TEST_MESSAGE) { |
| pRxBuf[0] = ' '; |
| write(STDOUT_FILENO, rxBuf, rc + sizeof(prolog) - 1); |
| } else { |
| ALOGE("Bad message header: %d\n", rxBuf[0]); |
| break; |
| } |
| } |
| ASSERT_EQ(pRxBuf[0], TEST_PASSED); |
| } |
| |
| android::sp<Stats> mStats; |
| |
| private: |
| android::sp<RpcSession> mSession; |
| int mPortTestFd; |
| std::string mPortNameStatsSetter; |
| std::string mPortNamePortTest; |
| }; |
| |
| class TrustyStatsTest : public TrustyStatsTestBase { |
| protected: |
| TrustyStatsTest() : TrustyStatsTestBase(kTrustyStatsSetterTest, kTrustyStatsPortTest) {} |
| }; |
| |
| class TrustyMetricsCrashTest : public TrustyStatsTestBase { |
| protected: |
| TrustyMetricsCrashTest() |
| : TrustyStatsTestBase(kTrustyStatsSetterMetrics, kTrustyCrashPortTest) {} |
| }; |
| |
| TEST_F(TrustyStatsTest, CheckAtoms) { |
| int atomAppCrashedCnt = 0; |
| int atomStorageErrorCnt = 0; |
| int atomTrustyErrorCnt = 0; |
| uint64_t blockForMs = 500; |
| StartPortTest(); |
| WaitPortTestDone(); |
| for (;;) { |
| VendorAtom vendorAtom; |
| auto status = mStats->getVendorAtom(&vendorAtom, blockForMs); |
| ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT)); |
| if (status == TIMED_OUT) { |
| // No more atoms |
| break; |
| } |
| |
| ASSERT_THAT(vendorAtom.atomId, |
| ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed), |
| ::testing::Eq(TrustyAtoms::TrustyError), |
| ::testing::Eq(TrustyAtoms::TrustyStorageError))); |
| ASSERT_EQ(String8(vendorAtom.reverseDomainName), "google.android.trusty"); |
| switch (vendorAtom.atomId) { |
| case TrustyAtoms::TrustyAppCrashed: |
| ++atomAppCrashedCnt; |
| ASSERT_EQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()), |
| "5247d19b-cf09-4272-a450-3ef20dbefc14"); |
| break; |
| case TrustyAtoms::TrustyStorageError: |
| ++atomStorageErrorCnt; |
| ASSERT_EQ(vendorAtom.values[0].get<VendorAtomValue::intValue>(), 5); |
| ASSERT_EQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()), |
| "5247d19b-cf09-4272-a450-3ef20dbefc14"); |
| ASSERT_EQ(String8(vendorAtom.values[2].get<VendorAtomValue::stringValue>()), |
| "5247d19b-cf09-4272-a450-3ef20dbefc14"); |
| ASSERT_EQ(vendorAtom.values[3].get<VendorAtomValue::intValue>(), 1); |
| ASSERT_EQ(vendorAtom.values[4].get<VendorAtomValue::intValue>(), 3); |
| ASSERT_EQ(vendorAtom.values[5].get<VendorAtomValue::longValue>(), |
| 0x4BCDEFABBAFEDCBALL); |
| ASSERT_EQ(vendorAtom.values[6].get<VendorAtomValue::intValue>(), 4); |
| ASSERT_EQ(vendorAtom.values[7].get<VendorAtomValue::longValue>(), 1023); |
| break; |
| case TrustyAtoms::TrustyError: |
| ++atomTrustyErrorCnt; |
| break; |
| default: |
| FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId; |
| break; |
| } |
| }; |
| ASSERT_EQ(atomAppCrashedCnt, 1); |
| ASSERT_EQ(atomStorageErrorCnt, 1); |
| ASSERT_EQ(atomTrustyErrorCnt, 0); |
| } |
| |
| TEST_F(TrustyMetricsCrashTest, CheckTrustyCrashAtoms) { |
| const std::vector<uint32_t> kExpectedCrashReasonsArm64{ |
| 0x00000001U, // exit_failure (twice) |
| 0x00000001U, |
| 0x92000004U, // read_null_ptr |
| 0xf200002aU, // brk_instruction |
| 0x92000004U, // read_bad_ptr |
| 0x92000044U, // crash_write_bad_ptr |
| 0x9200004fU, // crash_write_ro_ptr |
| 0x8200000fU, // crash_exec_rodata |
| 0x8200000fU, // crash_exec_data |
| }; |
| const std::vector<uint32_t> kExpectedCrashReasonsArm32{ |
| 0x00000001U, // exit_failure (twice) |
| 0x00000001U, |
| 0x20000007U, // read_null_ptr |
| 0x20000007U, // read_bad_ptr |
| 0x20000807U, // crash_write_bad_ptr |
| 0x2000080fU, // crash_write_ro_ptr |
| 0x3000000fU, // crash_exec_rodata |
| 0x3000000fU, // crash_exec_data |
| }; |
| |
| int expectedAtomCnt = 7; |
| int atomAppCrashedCnt = 0; |
| int atomStorageErrorCnt = 0; |
| int atomTrustyErrorCnt = 0; |
| std::vector<uint32_t> atomCrashReasons; |
| uint64_t blockForMs = 500; |
| StartPortTest(); |
| WaitPortTestDone(); |
| for (;;) { |
| VendorAtom vendorAtom; |
| auto status = mStats->getVendorAtom(&vendorAtom, blockForMs); |
| ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT)); |
| if (status == TIMED_OUT) { |
| // No more atoms |
| break; |
| } |
| |
| ASSERT_THAT(vendorAtom.atomId, |
| ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed), |
| ::testing::Eq(TrustyAtoms::TrustyError), |
| ::testing::Eq(TrustyAtoms::TrustyStorageError))); |
| ASSERT_EQ(String8(vendorAtom.reverseDomainName), "google.android.trusty"); |
| |
| switch (vendorAtom.atomId) { |
| case TrustyAtoms::TrustyAppCrashed: |
| ++atomAppCrashedCnt; |
| ASSERT_EQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()), |
| kTrustyCrasherUuid); |
| atomCrashReasons.push_back(vendorAtom.values[1].get<VendorAtomValue::intValue>()); |
| break; |
| case TrustyAtoms::TrustyStorageError: |
| ++atomStorageErrorCnt; |
| break; |
| case TrustyAtoms::TrustyError: |
| ++atomTrustyErrorCnt; |
| ASSERT_EQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()), ""); |
| break; |
| default: |
| FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId; |
| } |
| } |
| ASSERT_GE(atomAppCrashedCnt, expectedAtomCnt - 1); |
| ASSERT_EQ(atomStorageErrorCnt, 0); |
| // There is one dropped event left over from Trusty boot, |
| // it may show up here |
| ASSERT_LE(atomTrustyErrorCnt, 1); |
| ASSERT_THAT(atomCrashReasons, |
| ::testing::AnyOf(kExpectedCrashReasonsArm64, kExpectedCrashReasonsArm32)); |
| }; |
| |
| } // namespace stats |
| } // namespace trusty |
| } // namespace android |