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

Commit c451ffb6 authored by Tianjie's avatar Tianjie
Browse files

Check slot in reboot function

As part of the multi-client ror support, we want to compare the
intended boot slot from the client with the actual slot to boot
into.

Note boot control 1.2 is required to query the actual boot slot;
and we haven't installed V1.2 on Pixel3 and before. So the check is
skipped today if an old boot control HAL is installed on device.

Bug: 173808057
Test: atest CtsAppSecurityHostTestCases:ResumeOnRebootHostTest;
atest FrameworksServicesTests:RecoverySystemServiceTest

Change-Id: I13f9f5c75514424b70f96556b14a13783e9b6a7d
parent 4d20410b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -112,6 +112,9 @@ java_library_static {
        "time_zone_distro",
        "time_zone_distro_installer",
        "android.hardware.authsecret-V1.0-java",
        "android.hardware.boot-V1.0-java",
        "android.hardware.boot-V1.1-java",
        "android.hardware.boot-V1.2-java",
        "android.hardware.broadcastradio-V2.0-java",
        "android.hardware.health-V1.0-java",
        "android.hardware.health-V2.0-java",
+76 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.hardware.boot.V1_0.IBootControl;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Binder;
@@ -73,6 +74,8 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
    static final String INIT_SERVICE_SETUP_BCB = "init.svc.setup-bcb";
    @VisibleForTesting
    static final String INIT_SERVICE_CLEAR_BCB = "init.svc.clear-bcb";
    @VisibleForTesting
    static final String AB_UPDATE = "ro.build.ab_update";

    private static final Object sRequestLock = new Object();

@@ -177,6 +180,25 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
            return socket;
        }

        /**
         * Throws remote exception if there's an error getting the boot control HAL.
         * Returns null if the boot control HAL's version is older than V1_2.
         */
        public android.hardware.boot.V1_2.IBootControl getBootControl() throws RemoteException {
            IBootControl bootControlV10 = IBootControl.getService(true);
            if (bootControlV10 == null) {
                throw new RemoteException("Failed to get boot control HAL V1_0.");
            }

            android.hardware.boot.V1_2.IBootControl bootControlV12 =
                    android.hardware.boot.V1_2.IBootControl.castFrom(bootControlV10);
            if (bootControlV12 == null) {
                Slog.w(TAG, "Device doesn't implement boot control HAL V1_2.");
                return null;
            }
            return bootControlV12;
        }

        public void threadSleep(long millis) throws InterruptedException {
            Thread.sleep(millis);
        }
@@ -476,6 +498,56 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
        return needClear ? ROR_REQUESTED_NEED_CLEAR : ROR_REQUESTED_SKIP_CLEAR;
    }

    private boolean isAbDevice() {
        return "true".equalsIgnoreCase(mInjector.systemPropertiesGet(AB_UPDATE));
    }

    private boolean verifySlotForNextBoot(boolean slotSwitch) {
        if (!isAbDevice()) {
            Slog.w(TAG, "Device isn't a/b, skipping slot verification.");
            return true;
        }

        android.hardware.boot.V1_2.IBootControl bootControl;
        try {
            bootControl = mInjector.getBootControl();
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to get the boot control HAL " + e);
            return false;
        }

        // TODO(xunchang) enforce boot control V1_2 HAL on devices using multi client RoR
        if (bootControl == null) {
            Slog.w(TAG, "Cannot get the boot control HAL, skipping slot verification.");
            return true;
        }

        int current_slot;
        int next_active_slot;
        try {
            current_slot = bootControl.getCurrentSlot();
            if (current_slot != 0 && current_slot != 1) {
                throw new IllegalStateException("Current boot slot should be 0 or 1, got "
                        + current_slot);
            }
            next_active_slot = bootControl.getActiveBootSlot();
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed to query the active slots", e);
            return false;
        }

        int expected_active_slot = current_slot;
        if (slotSwitch) {
            expected_active_slot = current_slot == 0 ? 1 : 0;
        }
        if (next_active_slot != expected_active_slot) {
            Slog.w(TAG, "The next active boot slot doesn't match the expected value, "
                    + "expected " + expected_active_slot + ", got " + next_active_slot);
            return false;
        }
        return true;
    }

    private boolean rebootWithLskfImpl(String packageName, String reason, boolean slotSwitch) {
        if (packageName == null) {
            Slog.w(TAG, "Missing packageName when rebooting with lskf.");
@@ -485,7 +557,10 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
            return false;
        }

        // TODO(xunchang) check the slot to boot into, and fail the reboot upon slot mismatch.
        if (!verifySlotForNextBoot(slotSwitch)) {
            return false;
        }

        // TODO(xunchang) write the vbmeta digest along with the escrowKey before reboot.
        if (!mInjector.getLockSettingsService().armRebootEscrow()) {
            Slog.w(TAG, "Failure to escrow key for reboot");
+1 −1
Original line number Diff line number Diff line
@@ -76,7 +76,7 @@ public class RecoverySystemShellCommand extends ShellCommand {
    private int rebootAndApply() throws RemoteException {
        String packageName = getNextArgRequired();
        String rebootReason = getNextArgRequired();
        boolean success = mService.rebootWithLskf(packageName, rebootReason, true);
        boolean success = mService.rebootWithLskf(packageName, rebootReason, false);
        PrintWriter pw = getOutPrintWriter();
        // Keep the old message for cts test.
        pw.printf("%s Reboot and apply status: %s\n", packageName,
+18 −2
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.hardware.boot.V1_2.IBootControl;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IRecoverySystemProgressListener;
@@ -68,12 +69,13 @@ public class RecoverySystemServiceTest {
    private IThermalService mIThermalService;
    private FileWriter mUncryptUpdateFileWriter;
    private LockSettingsInternal mLockSettingsInternal;
    private IBootControl mIBootControl;

    private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package";
    private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package";

    @Before
    public void setup() {
    public void setup() throws Exception {
        mContext = mock(Context.class);
        mSystemProperties = new RecoverySystemServiceTestable.FakeSystemProperties();
        mUncryptSocket = mock(RecoverySystemService.UncryptSocket.class);
@@ -88,8 +90,13 @@ public class RecoverySystemServiceTest {
        PowerManager powerManager = new PowerManager(mock(Context.class), mIPowerManager,
                mIThermalService, new Handler(looper));

        mIBootControl = mock(IBootControl.class);
        when(mIBootControl.getCurrentSlot()).thenReturn(0);
        when(mIBootControl.getActiveBootSlot()).thenReturn(1);

        mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
                powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal);
                powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal,
                mIBootControl);
    }

    @Test
@@ -332,6 +339,15 @@ public class RecoverySystemServiceTest {
        verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
    }


    @Test
    public void rebootWithLskf_slotMismatch_Failure() throws Exception {
        assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
        mRecoverySystemService.onPreparedForReboot(true);
        assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", false),
                is(false));
    }

    @Test
    public void rebootWithLskf_withoutPrepare_Failure() throws Exception {
        assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true),
+15 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.recoverysystem;

import android.content.Context;
import android.hardware.boot.V1_2.IBootControl;
import android.os.PowerManager;

import com.android.internal.widget.LockSettingsInternal;
@@ -30,16 +31,19 @@ public class RecoverySystemServiceTestable extends RecoverySystemService {
        private final FileWriter mUncryptPackageFileWriter;
        private final UncryptSocket mUncryptSocket;
        private final LockSettingsInternal mLockSettingsInternal;
        private final IBootControl mIBootControl;

        MockInjector(Context context, FakeSystemProperties systemProperties,
                PowerManager powerManager, FileWriter uncryptPackageFileWriter,
                UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
                UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
                IBootControl bootControl) {
            super(context);
            mSystemProperties = systemProperties;
            mPowerManager = powerManager;
            mUncryptPackageFileWriter = uncryptPackageFileWriter;
            mUncryptSocket = uncryptSocket;
            mLockSettingsInternal = lockSettingsInternal;
            mIBootControl = bootControl;
        }

        @Override
@@ -85,13 +89,19 @@ public class RecoverySystemServiceTestable extends RecoverySystemService {
        public LockSettingsInternal getLockSettingsService() {
            return mLockSettingsInternal;
        }

        @Override
        public IBootControl getBootControl() {
            return mIBootControl;
        }
    }

    RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties,
            PowerManager powerManager, FileWriter uncryptPackageFileWriter,
            UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
            UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal,
            IBootControl bootControl) {
        super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter,
                uncryptSocket, lockSettingsInternal));
                uncryptSocket, lockSettingsInternal, bootControl));
    }

    public static class FakeSystemProperties {
@@ -102,6 +112,8 @@ public class RecoverySystemServiceTestable extends RecoverySystemService {
                    || RecoverySystemService.INIT_SERVICE_SETUP_BCB.equals(key)
                    || RecoverySystemService.INIT_SERVICE_CLEAR_BCB.equals(key)) {
                return null;
            } else if (RecoverySystemService.AB_UPDATE.equals(key)) {
                return "true";
            } else {
                throw new IllegalArgumentException("unexpected test key: " + key);
            }