Loading services/core/java/com/android/server/pdb/PersistentDataBlockService.java +30 −19 Original line number Diff line number Diff line Loading @@ -143,7 +143,8 @@ public class PersistentDataBlockService extends SystemService { // Magic number to mark block device as adhering to the format consumed by this service private static final int PARTITION_TYPE_MARKER = 0x19901873; /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */ private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; @VisibleForTesting static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; /** Maximum size of the FRP credential handle that can be stored. */ @VisibleForTesting static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4; Loading @@ -158,7 +159,8 @@ public class PersistentDataBlockService extends SystemService { /** * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header. */ private static final int TEST_MODE_RESERVED_SIZE = 10000; @VisibleForTesting static final int TEST_MODE_RESERVED_SIZE = 10000; /** Maximum size of the Test Harness Mode data that can be stored. */ @VisibleForTesting static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4; Loading Loading @@ -393,7 +395,8 @@ public class PersistentDataBlockService extends SystemService { return totalDataSize; } private long getBlockDeviceSize() { @VisibleForTesting long getBlockDeviceSize() { synchronized (mLock) { if (mBlockDeviceSize == -1) { if (mIsFileBacked) { Loading Loading @@ -553,26 +556,33 @@ public class PersistentDataBlockService extends SystemService { channel.write(buf); channel.force(true); // 3. skip the test mode data and leave it unformatted. // 3. Write the default FRP secret (all zeros). if (mFrpEnforced) { Slog.i(TAG, "Writing FRP secret magic"); channel.write(ByteBuffer.wrap(FRP_SECRET_MAGIC)); Slog.i(TAG, "Writing default FRP secret"); channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE)); channel.force(true); mFrpActive = false; } // 4. skip the test mode data and leave it unformatted. // This is for a feature that enables testing. channel.position(channel.position() + TEST_MODE_RESERVED_SIZE); // 4. wipe the FRP_CREDENTIAL explicitly // 5. wipe the FRP_CREDENTIAL explicitly buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); channel.write(buf); channel.force(true); // 5. set unlock = 0 because it's a formatPartitionLocked // 6. set unlock = 0 because it's a formatPartitionLocked buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); buf.put((byte)0); buf.flip(); channel.write(buf); channel.force(true); // 6. Write the default FRP secret (all zeros). if (mFrpEnforced) { writeFrpMagicAndDefaultSecret(); } } catch (IOException e) { Slog.e(TAG, "failed to format block", e); return; Loading Loading @@ -616,7 +626,7 @@ public class PersistentDataBlockService extends SystemService { // version. If so, we deactivate FRP and set the secret to the default value. if (isUpgradingFromPreVRelease()) { Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret"); writeFrpMagicAndDefaultSecret(); writeFrpMagicAndDefaultSecretLocked(); mFrpActive = false; return true; } Loading @@ -630,7 +640,7 @@ public class PersistentDataBlockService extends SystemService { try { return deactivateFrp(Files.readAllBytes(Paths.get(frpSecretFile))); } catch (IOException e) { Slog.w(TAG, "Failed to read FRP secret file: " + frpSecretFile + " " Slog.i(TAG, "Failed to read FRP secret file: " + frpSecretFile + " " + e.getClass().getSimpleName()); return false; } Loading @@ -653,7 +663,8 @@ public class PersistentDataBlockService extends SystemService { } /** * Write the provided secret to the FRP secret file in /data and to the /persist partition. * Write the provided secret to the FRP secret file in /data and to the persistent data block * partition. * * Writing is a three-step process, to ensure that we can recover from a crash at any point. */ Loading Loading @@ -713,7 +724,7 @@ public class PersistentDataBlockService extends SystemService { synchronized (mLock) { if (!hasFrpSecretMagic()) { Slog.i(TAG, "No FRP secret magic, system must have been upgraded."); writeFrpMagicAndDefaultSecret(); writeFrpMagicAndDefaultSecretLocked(); } } Loading @@ -735,11 +746,9 @@ public class PersistentDataBlockService extends SystemService { } } private void writeFrpMagicAndDefaultSecret() { private void writeFrpMagicAndDefaultSecretLocked() { try (FileChannel channel = getBlockOutputChannelIgnoringFrp()) { synchronized (mLock) { // Write secret first in case we crash between the writes, causing the first write // to be synced but the second to be lost. Slog.i(TAG, "Writing default FRP secret"); channel.position(getFrpSecretDataOffset()); channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE)); Loading @@ -755,6 +764,7 @@ public class PersistentDataBlockService extends SystemService { } catch (IOException e) { Slog.e(TAG, "Failed to write FRP magic and default secret", e); } computeAndWriteDigestLocked(); } @VisibleForTesting Loading Loading @@ -879,7 +889,7 @@ public class PersistentDataBlockService extends SystemService { if (printSecret) { try { pw.println("FRP secret in " + frpSecretFile + ": " + HexFormat.of() .formatHex(Files.readAllBytes(Paths.get(mFrpSecretFile)))); .formatHex(Files.readAllBytes(Paths.get(frpSecretFile)))); } catch (IOException e) { Slog.e(TAG, "Failed to read " + frpSecretFile, e); } Loading Loading @@ -1230,6 +1240,7 @@ public class PersistentDataBlockService extends SystemService { @Override public boolean setFactoryResetProtectionSecret(byte[] secret) { enforceConfigureFrpPermission(); enforceUid(Binder.getCallingUid()); if (secret == null || secret.length != FRP_SECRET_SIZE) { throw new IllegalArgumentException( Loading services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java +85 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,17 @@ package com.android.server.pdb; import static com.android.server.pdb.PersistentDataBlockService.DIGEST_SIZE_BYTES; import static com.android.server.pdb.PersistentDataBlockService.FRP_CREDENTIAL_RESERVED_SIZE; import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_MAGIC; import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_SIZE; import static com.android.server.pdb.PersistentDataBlockService.HEADER_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_DATA_BLOCK_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_FRP_CREDENTIAL_HANDLE_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_TEST_MODE_DATA_SIZE; import static com.android.server.pdb.PersistentDataBlockService.TEST_MODE_RESERVED_SIZE; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyInt; Loading @@ -36,6 +41,7 @@ import static org.mockito.Mockito.when; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import android.Manifest; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; Loading Loading @@ -63,6 +69,7 @@ import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @RunWith(JUnitParamsRunner.class) public class PersistentDataBlockServiceTest { Loading Loading @@ -395,6 +402,68 @@ public class PersistentDataBlockServiceTest { assertThat(mInterface.getDataBlockSize()).isEqualTo(0); } @Test @Parameters({"false", "true"}) public void testPartitionFormat(boolean frpEnabled) throws Exception { setUp(frpEnabled); /* * 1. Fill the PDB with a specific value, so we can check regions that weren't touched * by formatting */ FileChannel channel = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); byte[] bufArray = new byte[(int) mPdbService.getBlockDeviceSize()]; Arrays.fill(bufArray, (byte) 0x7f); ByteBuffer buf = ByteBuffer.wrap(bufArray); channel.write(buf); channel.close(); /* * 2. Format it. */ mPdbService.formatPartitionLocked(true); /* * 3. Check it. */ channel = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.READ); // 3a. Skip the digest and header channel.position(channel.position() + DIGEST_SIZE_BYTES + HEADER_SIZE); // 3b. Check the FRP data segment assertContains("FRP data", readData(channel, mPdbService.getMaximumFrpDataSize()).array(), (byte) 0); if (frpEnabled) { // 3c. The FRP secret magic & value assertThat(mPdbService.getFrpSecretMagicOffset()).isEqualTo(channel.position()); assertThat(readData(channel, FRP_SECRET_MAGIC.length).array()).isEqualTo( FRP_SECRET_MAGIC); assertThat(mPdbService.getFrpSecretDataOffset()).isEqualTo(channel.position()); assertContains("FRP secret", readData(channel, FRP_SECRET_SIZE).array(), (byte) 0); } // 3d. The test mode data (unmodified by formatPartitionLocked()). assertThat(mPdbService.getTestHarnessModeDataOffset()).isEqualTo(channel.position()); assertContains("Test data", readData(channel, TEST_MODE_RESERVED_SIZE).array(), (byte) 0x7f); // 3e. The FRP credential segment assertThat(mPdbService.getFrpCredentialDataOffset()).isEqualTo(channel.position()); assertContains("FRP credential", readData(channel, FRP_CREDENTIAL_RESERVED_SIZE).array(), (byte) 0); // 3f. OEM unlock byte. assertThat(mPdbService.getOemUnlockDataOffset()).isEqualTo(channel.position()); assertThat(new byte[]{1}).isEqualTo(readData(channel, 1).array()); // 3g. EOF assertThat(channel.position()).isEqualTo(channel.size()); } @Test @Parameters({"false", "true"}) public void wipePermissionCheck(boolean frpEnabled) throws Exception { Loading Loading @@ -987,4 +1056,20 @@ public class PersistentDataBlockServiceTest { return buffer; } } @NonNull private static ByteBuffer readData(FileChannel channel, int length) throws IOException { ByteBuffer buf = ByteBuffer.allocate(length); assertThat(channel.read(buf)).isEqualTo(length); buf.flip(); assertThat(buf.limit()).isEqualTo(length); return buf; } private static void assertContains(String sectionName, byte[] buf, byte expected) { for (int i = 0; i < buf.length; i++) { assertWithMessage(sectionName + " is incorrect at offset " + i) .that(buf[i]).isEqualTo(expected); } } } Loading
services/core/java/com/android/server/pdb/PersistentDataBlockService.java +30 −19 Original line number Diff line number Diff line Loading @@ -143,7 +143,8 @@ public class PersistentDataBlockService extends SystemService { // Magic number to mark block device as adhering to the format consumed by this service private static final int PARTITION_TYPE_MARKER = 0x19901873; /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */ private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; @VisibleForTesting static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; /** Maximum size of the FRP credential handle that can be stored. */ @VisibleForTesting static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4; Loading @@ -158,7 +159,8 @@ public class PersistentDataBlockService extends SystemService { /** * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header. */ private static final int TEST_MODE_RESERVED_SIZE = 10000; @VisibleForTesting static final int TEST_MODE_RESERVED_SIZE = 10000; /** Maximum size of the Test Harness Mode data that can be stored. */ @VisibleForTesting static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4; Loading Loading @@ -393,7 +395,8 @@ public class PersistentDataBlockService extends SystemService { return totalDataSize; } private long getBlockDeviceSize() { @VisibleForTesting long getBlockDeviceSize() { synchronized (mLock) { if (mBlockDeviceSize == -1) { if (mIsFileBacked) { Loading Loading @@ -553,26 +556,33 @@ public class PersistentDataBlockService extends SystemService { channel.write(buf); channel.force(true); // 3. skip the test mode data and leave it unformatted. // 3. Write the default FRP secret (all zeros). if (mFrpEnforced) { Slog.i(TAG, "Writing FRP secret magic"); channel.write(ByteBuffer.wrap(FRP_SECRET_MAGIC)); Slog.i(TAG, "Writing default FRP secret"); channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE)); channel.force(true); mFrpActive = false; } // 4. skip the test mode data and leave it unformatted. // This is for a feature that enables testing. channel.position(channel.position() + TEST_MODE_RESERVED_SIZE); // 4. wipe the FRP_CREDENTIAL explicitly // 5. wipe the FRP_CREDENTIAL explicitly buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); channel.write(buf); channel.force(true); // 5. set unlock = 0 because it's a formatPartitionLocked // 6. set unlock = 0 because it's a formatPartitionLocked buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); buf.put((byte)0); buf.flip(); channel.write(buf); channel.force(true); // 6. Write the default FRP secret (all zeros). if (mFrpEnforced) { writeFrpMagicAndDefaultSecret(); } } catch (IOException e) { Slog.e(TAG, "failed to format block", e); return; Loading Loading @@ -616,7 +626,7 @@ public class PersistentDataBlockService extends SystemService { // version. If so, we deactivate FRP and set the secret to the default value. if (isUpgradingFromPreVRelease()) { Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret"); writeFrpMagicAndDefaultSecret(); writeFrpMagicAndDefaultSecretLocked(); mFrpActive = false; return true; } Loading @@ -630,7 +640,7 @@ public class PersistentDataBlockService extends SystemService { try { return deactivateFrp(Files.readAllBytes(Paths.get(frpSecretFile))); } catch (IOException e) { Slog.w(TAG, "Failed to read FRP secret file: " + frpSecretFile + " " Slog.i(TAG, "Failed to read FRP secret file: " + frpSecretFile + " " + e.getClass().getSimpleName()); return false; } Loading @@ -653,7 +663,8 @@ public class PersistentDataBlockService extends SystemService { } /** * Write the provided secret to the FRP secret file in /data and to the /persist partition. * Write the provided secret to the FRP secret file in /data and to the persistent data block * partition. * * Writing is a three-step process, to ensure that we can recover from a crash at any point. */ Loading Loading @@ -713,7 +724,7 @@ public class PersistentDataBlockService extends SystemService { synchronized (mLock) { if (!hasFrpSecretMagic()) { Slog.i(TAG, "No FRP secret magic, system must have been upgraded."); writeFrpMagicAndDefaultSecret(); writeFrpMagicAndDefaultSecretLocked(); } } Loading @@ -735,11 +746,9 @@ public class PersistentDataBlockService extends SystemService { } } private void writeFrpMagicAndDefaultSecret() { private void writeFrpMagicAndDefaultSecretLocked() { try (FileChannel channel = getBlockOutputChannelIgnoringFrp()) { synchronized (mLock) { // Write secret first in case we crash between the writes, causing the first write // to be synced but the second to be lost. Slog.i(TAG, "Writing default FRP secret"); channel.position(getFrpSecretDataOffset()); channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE)); Loading @@ -755,6 +764,7 @@ public class PersistentDataBlockService extends SystemService { } catch (IOException e) { Slog.e(TAG, "Failed to write FRP magic and default secret", e); } computeAndWriteDigestLocked(); } @VisibleForTesting Loading Loading @@ -879,7 +889,7 @@ public class PersistentDataBlockService extends SystemService { if (printSecret) { try { pw.println("FRP secret in " + frpSecretFile + ": " + HexFormat.of() .formatHex(Files.readAllBytes(Paths.get(mFrpSecretFile)))); .formatHex(Files.readAllBytes(Paths.get(frpSecretFile)))); } catch (IOException e) { Slog.e(TAG, "Failed to read " + frpSecretFile, e); } Loading Loading @@ -1230,6 +1240,7 @@ public class PersistentDataBlockService extends SystemService { @Override public boolean setFactoryResetProtectionSecret(byte[] secret) { enforceConfigureFrpPermission(); enforceUid(Binder.getCallingUid()); if (secret == null || secret.length != FRP_SECRET_SIZE) { throw new IllegalArgumentException( Loading
services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java +85 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,17 @@ package com.android.server.pdb; import static com.android.server.pdb.PersistentDataBlockService.DIGEST_SIZE_BYTES; import static com.android.server.pdb.PersistentDataBlockService.FRP_CREDENTIAL_RESERVED_SIZE; import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_MAGIC; import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_SIZE; import static com.android.server.pdb.PersistentDataBlockService.HEADER_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_DATA_BLOCK_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_FRP_CREDENTIAL_HANDLE_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_TEST_MODE_DATA_SIZE; import static com.android.server.pdb.PersistentDataBlockService.TEST_MODE_RESERVED_SIZE; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyInt; Loading @@ -36,6 +41,7 @@ import static org.mockito.Mockito.when; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import android.Manifest; import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; Loading Loading @@ -63,6 +69,7 @@ import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @RunWith(JUnitParamsRunner.class) public class PersistentDataBlockServiceTest { Loading Loading @@ -395,6 +402,68 @@ public class PersistentDataBlockServiceTest { assertThat(mInterface.getDataBlockSize()).isEqualTo(0); } @Test @Parameters({"false", "true"}) public void testPartitionFormat(boolean frpEnabled) throws Exception { setUp(frpEnabled); /* * 1. Fill the PDB with a specific value, so we can check regions that weren't touched * by formatting */ FileChannel channel = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); byte[] bufArray = new byte[(int) mPdbService.getBlockDeviceSize()]; Arrays.fill(bufArray, (byte) 0x7f); ByteBuffer buf = ByteBuffer.wrap(bufArray); channel.write(buf); channel.close(); /* * 2. Format it. */ mPdbService.formatPartitionLocked(true); /* * 3. Check it. */ channel = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.READ); // 3a. Skip the digest and header channel.position(channel.position() + DIGEST_SIZE_BYTES + HEADER_SIZE); // 3b. Check the FRP data segment assertContains("FRP data", readData(channel, mPdbService.getMaximumFrpDataSize()).array(), (byte) 0); if (frpEnabled) { // 3c. The FRP secret magic & value assertThat(mPdbService.getFrpSecretMagicOffset()).isEqualTo(channel.position()); assertThat(readData(channel, FRP_SECRET_MAGIC.length).array()).isEqualTo( FRP_SECRET_MAGIC); assertThat(mPdbService.getFrpSecretDataOffset()).isEqualTo(channel.position()); assertContains("FRP secret", readData(channel, FRP_SECRET_SIZE).array(), (byte) 0); } // 3d. The test mode data (unmodified by formatPartitionLocked()). assertThat(mPdbService.getTestHarnessModeDataOffset()).isEqualTo(channel.position()); assertContains("Test data", readData(channel, TEST_MODE_RESERVED_SIZE).array(), (byte) 0x7f); // 3e. The FRP credential segment assertThat(mPdbService.getFrpCredentialDataOffset()).isEqualTo(channel.position()); assertContains("FRP credential", readData(channel, FRP_CREDENTIAL_RESERVED_SIZE).array(), (byte) 0); // 3f. OEM unlock byte. assertThat(mPdbService.getOemUnlockDataOffset()).isEqualTo(channel.position()); assertThat(new byte[]{1}).isEqualTo(readData(channel, 1).array()); // 3g. EOF assertThat(channel.position()).isEqualTo(channel.size()); } @Test @Parameters({"false", "true"}) public void wipePermissionCheck(boolean frpEnabled) throws Exception { Loading Loading @@ -987,4 +1056,20 @@ public class PersistentDataBlockServiceTest { return buffer; } } @NonNull private static ByteBuffer readData(FileChannel channel, int length) throws IOException { ByteBuffer buf = ByteBuffer.allocate(length); assertThat(channel.read(buf)).isEqualTo(length); buf.flip(); assertThat(buf.limit()).isEqualTo(length); return buf; } private static void assertContains(String sectionName, byte[] buf, byte expected) { for (int i = 0; i < buf.length; i++) { assertWithMessage(sectionName + " is incorrect at offset " + i) .that(buf[i]).isEqualTo(expected); } } }