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

Commit aec4f406 authored by Tianjie's avatar Tianjie
Browse files

Report the true value of more RoR metrics

Design ErrorCode for errors of loadRebootEscrowData. Also report
the status of vbmeta digest, as we plan to associate k_k with
vbmeta in the future.

Bug: 179105110
Test: atest FrameworksServicesTests:RebootEscrowManagerTests
Change-Id: I07168a7bc4fd20d41df9b87136e1e5158129d7f6
parent b079fe66
Loading
Loading
Loading
Loading
+116 −11
Original line number Diff line number Diff line
@@ -15,14 +15,17 @@
 */

package com.android.server.locksettings;

import static android.os.UserHandle.USER_SYSTEM;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -35,6 +38,8 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.widget.RebootEscrowListener;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
@@ -66,6 +71,21 @@ class RebootEscrowManager {

    static final String REBOOT_ESCROW_KEY_ARMED_TIMESTAMP = "reboot_escrow_key_stored_timestamp";

    /**
     * The verified boot 2.0 vbmeta digest of the current slot, the property value is always
     * available after boot.
     */
    static final String VBMETA_DIGEST_PROP_NAME = "ro.boot.vbmeta.digest";
    /**
     * The system prop contains vbmeta digest of the inactive slot. The build property is set after
     * an OTA update. RebootEscrowManager will store it in disk before the OTA reboot, so the value
     * is available for vbmeta digest verification after the device reboots.
     */
    static final String OTHER_VBMETA_DIGEST_PROP_NAME = "ota.other.vbmeta_digest";
    static final String REBOOT_ESCROW_KEY_VBMETA_DIGEST = "reboot_escrow_key_vbmeta_digest";
    static final String REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST =
            "reboot_escrow_key_other_vbmeta_digest";

    /**
     * Number of boots until we consider the escrow data to be stale for the purposes of metrics.
     * <p>
@@ -86,6 +106,27 @@ class RebootEscrowManager {
    private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
    private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;

    @IntDef(prefix = {"ERROR_"}, value = {
            ERROR_NONE,
            ERROR_UNKNOWN,
            ERROR_NO_PROVIDER,
            ERROR_LOAD_ESCROW_KEY,
            ERROR_RETRY_COUNT_EXHAUSTED,
            ERROR_UNLOCK_ALL_USERS,
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface RebootEscrowErrorCode {
    }

    static final int ERROR_NONE = 0;
    static final int ERROR_UNKNOWN = 1;
    static final int ERROR_NO_PROVIDER = 2;
    static final int ERROR_LOAD_ESCROW_KEY = 3;
    static final int ERROR_RETRY_COUNT_EXHAUSTED = 4;
    static final int ERROR_UNLOCK_ALL_USERS = 5;

    private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;

    /**
     * Logs events for later debugging in bugreports.
     */
@@ -199,6 +240,10 @@ class RebootEscrowManager {
                    0);
        }

        public long getCurrentTimeMillis() {
            return System.currentTimeMillis();
        }

        public int getLoadEscrowDataRetryLimit() {
            return DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA,
                    "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT);
@@ -221,6 +266,11 @@ class RebootEscrowManager {
        public RebootEscrowEventLog getEventLog() {
            return new RebootEscrowEventLog();
        }

        public String getVbmetaDigest(boolean other) {
            return other ? SystemProperties.get(OTHER_VBMETA_DIGEST_PROP_NAME)
                    : SystemProperties.get(VBMETA_DIGEST_PROP_NAME);
        }
    }

    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
@@ -261,6 +311,7 @@ class RebootEscrowManager {
        if (rebootEscrowUsers.isEmpty()) {
            Slog.i(TAG, "No reboot escrow data found for users,"
                    + " skipping loading escrow data");
            clearMetricsStorage();
            return;
        }

@@ -284,6 +335,7 @@ class RebootEscrowManager {
        }

        Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
        mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED;
        onGetRebootEscrowKeyFailed(users, attemptNumber);
    }

@@ -307,6 +359,9 @@ class RebootEscrowManager {
        }

        if (escrowKey == null) {
            if (mLoadEscrowDataErrorCode == ERROR_NONE) {
                mLoadEscrowDataErrorCode = ERROR_LOAD_ESCROW_KEY;
            }
            onGetRebootEscrowKeyFailed(users, attemptNumber + 1);
            return;
        }
@@ -321,9 +376,48 @@ class RebootEscrowManager {
        // Clear the old key in keystore. A new key will be generated by new RoR requests.
        mKeyStoreManager.clearKeyStoreEncryptionKey();

        if (!allUsersUnlocked && mLoadEscrowDataErrorCode == ERROR_NONE) {
            mLoadEscrowDataErrorCode = ERROR_UNLOCK_ALL_USERS;
        }
        onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1);
    }

    private void clearMetricsStorage() {
        mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
        mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM);
        mStorage.removeKey(REBOOT_ESCROW_KEY_VBMETA_DIGEST, USER_SYSTEM);
        mStorage.removeKey(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST, USER_SYSTEM);
    }

    private int getVbmetaDigestStatusOnRestoreComplete() {
        String currentVbmetaDigest = mInjector.getVbmetaDigest(false);
        String vbmetaDigestStored = mStorage.getString(REBOOT_ESCROW_KEY_VBMETA_DIGEST,
                "", USER_SYSTEM);
        String vbmetaDigestOtherStored = mStorage.getString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST,
                "", USER_SYSTEM);

        // The other vbmeta digest is never set, assume no slot switch is attempted.
        if (vbmetaDigestOtherStored.isEmpty()) {
            if (currentVbmetaDigest.equals(vbmetaDigestStored)) {
                return FrameworkStatsLog
                        .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
            }
            return FrameworkStatsLog
                    .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH;
        }

        // The other vbmeta digest is set, we expect to boot into the new slot.
        if (currentVbmetaDigest.equals(vbmetaDigestOtherStored)) {
            return FrameworkStatsLog
                    .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
        } else if (currentVbmetaDigest.equals(vbmetaDigestStored)) {
            return FrameworkStatsLog
                    .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_FALLBACK_SLOT;
        }
        return FrameworkStatsLog
                .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH;
    }

    private void reportMetricOnRestoreComplete(boolean success, int attemptCount) {
        int serviceType = mInjector.serverBasedResumeOnReboot()
                ? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED
@@ -331,26 +425,31 @@ class RebootEscrowManager {

        long armedTimestamp = mStorage.getLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, -1,
                USER_SYSTEM);
        mStorage.removeKey(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, USER_SYSTEM);
        int escrowDurationInSeconds = armedTimestamp != -1
                ? (int) (System.currentTimeMillis() - armedTimestamp) / 1000 : -1;

        // TODO(b/179105110) design error code; and report the true value for other fields.
        int vbmetaDigestStatus = FrameworkStatsLog
                .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MATCH_EXPECTED_SLOT;
        int escrowDurationInSeconds = -1;
        long currentTimeStamp = mInjector.getCurrentTimeMillis();
        if (armedTimestamp != -1 && currentTimeStamp > armedTimestamp) {
            escrowDurationInSeconds = (int) (currentTimeStamp - armedTimestamp) / 1000;
        }

        mInjector.reportMetric(success, 0 /* error code */, serviceType, attemptCount,
        int vbmetaDigestStatus = getVbmetaDigestStatusOnRestoreComplete();
        if (!success && mLoadEscrowDataErrorCode == ERROR_NONE) {
            mLoadEscrowDataErrorCode = ERROR_UNKNOWN;
        }
        // TODO(179105110) report the duration since boot complete.
        mInjector.reportMetric(success, mLoadEscrowDataErrorCode, serviceType, attemptCount,
                escrowDurationInSeconds, vbmetaDigestStatus, -1);

        mLoadEscrowDataErrorCode = ERROR_NONE;
    }

    private void onEscrowRestoreComplete(boolean success, int attemptCount) {
        int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM);
        mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);

        int bootCountDelta = mInjector.getBootCount() - previousBootCount;
        if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) {
            reportMetricOnRestoreComplete(success, attemptCount);
        }
        clearMetricsStorage();
    }

    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
@@ -358,6 +457,7 @@ class RebootEscrowManager {
        if (rebootEscrowProvider == null) {
            Slog.w(TAG,
                    "Had reboot escrow data for users, but RebootEscrowProvider is unavailable");
            mLoadEscrowDataErrorCode = ERROR_NO_PROVIDER;
            return null;
        }

@@ -463,7 +563,7 @@ class RebootEscrowManager {
            return;
        }

        mStorage.removeKey(REBOOT_ESCROW_ARMED_KEY, USER_SYSTEM);
        clearMetricsStorage();
        rebootEscrowProvider.clearRebootEscrowKey();

        List<UserInfo> users = mUserManager.getUsers();
@@ -505,8 +605,13 @@ class RebootEscrowManager {
        boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk);
        if (armedRebootEscrow) {
            mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
            mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, System.currentTimeMillis(),
            mStorage.setLong(REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, mInjector.getCurrentTimeMillis(),
                    USER_SYSTEM);
            // Store the vbmeta digest of both slots.
            mStorage.setString(REBOOT_ESCROW_KEY_VBMETA_DIGEST, mInjector.getVbmetaDigest(false),
                    USER_SYSTEM);
            mStorage.setString(REBOOT_ESCROW_KEY_OTHER_VBMETA_DIGEST,
                    mInjector.getVbmetaDigest(true), USER_SYSTEM);
            mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS);
        }

+26 −3
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.content.pm.UserInfo.FLAG_PRIMARY;
import static android.content.pm.UserInfo.FLAG_PROFILE;
import static android.os.UserHandle.USER_SYSTEM;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -110,6 +111,8 @@ public class RebootEscrowManagerTests {
    public interface MockableRebootEscrowInjected {
        int getBootCount();

        long getCurrentTimeMillis();

        void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
                int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete);
    }
@@ -204,6 +207,16 @@ public class RebootEscrowManagerTests {
            return 1;
        }

        @Override
        public String getVbmetaDigest(boolean other) {
            return other ? "" : "fake digest";
        }

        @Override
        public long getCurrentTimeMillis() {
            return mInjected.getCurrentTimeMillis();
        }

        @Override
        public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
                int escrowDurationInSeconds, int vbmetaDigestStatus,
@@ -430,16 +443,21 @@ public class RebootEscrowManagerTests {
        // pretend reboot happens here

        when(mInjected.getBootCount()).thenReturn(1);
        when(mInjected.getCurrentTimeMillis()).thenReturn(30000L);
        mStorage.setLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP, 10000L,
                USER_SYSTEM);
        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
                eq(0) /* error code */, eq(1) /* HAL based */, eq(1) /* attempt count */,
                anyInt(), anyInt(), anyInt());
                eq(20), eq(0) /* vbmeta status */, anyInt());
        when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());

        mService.loadRebootEscrowDataIfAvailable(null);
        verify(mRebootEscrow).retrieveKey();
        assertTrue(metricsSuccessCaptor.getValue());
        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
        assertEquals(mStorage.getLong(RebootEscrowManager.REBOOT_ESCROW_KEY_ARMED_TIMESTAMP,
                -1, USER_SYSTEM), -1);
    }

    @Test
@@ -468,7 +486,7 @@ public class RebootEscrowManagerTests {
        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
                eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */,
                anyInt(), anyInt(), anyInt());
                anyInt(), eq(0) /* vbmeta status */, anyInt());

        when(mServiceConnection.unwrap(any(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));
@@ -607,9 +625,14 @@ public class RebootEscrowManagerTests {
        when(mInjected.getBootCount()).thenReturn(10);
        when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());

        // Trigger a vbmeta digest mismatch
        mStorage.setString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
                "non sense value", USER_SYSTEM);
        mService.loadRebootEscrowDataIfAvailable(null);
        verify(mInjected).reportMetric(eq(true), eq(0) /* error code */, eq(1) /* HAL based */,
                eq(1) /* attempt count */, anyInt(), anyInt(), anyInt());
                eq(1) /* attempt count */, anyInt(), eq(2) /* vbmeta status */, anyInt());
        assertEquals(mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
                "", USER_SYSTEM), "");
    }

    @Test