Add a checksum to FRP partition

It will be hard to mandate the contents
of the FRP partition out of factory. Further, for upgrading
units, it would require that OEMs format the partition and then store
a bit saying that they've done so. This adds another attack vector.
Now defeating FRP means either compromising the FRP partition
OR wherever the OEMs decide to store that bit.

This patch adds a checksum to the FRP partition. If the checksum
is not valid, the partition is wiped - disabling OEM unlock.

This ensures that no matter what data comes on the partition, we will 
always disable OEM unlock by default. It also allows OEMs to not have to
worry about initializing the partition, as it happens automatically. 

Bug: 18322021
Change-Id: Ib30782baa771591c30ea95054d3b83f36fc08cc2
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index de90aa2..17edb53 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -41,6 +41,9 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
 
 /**
  * Service for reading and writing blocks to a persistent partition.
@@ -63,22 +66,16 @@
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
     private static final int HEADER_SIZE = 8;
     // Magic number to mark block device as adhering to the format consumed by this service
-    private static final int PARTITION_TYPE_MARKER = 0x1990;
+    private static final int PARTITION_TYPE_MARKER = 0x19901873;
     // Limit to 100k as blocks larger than this might cause strain on Binder.
-    // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
     private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
+    public static final int DIGEST_SIZE_BYTES = 32;
 
     private final Context mContext;
     private final String mDataBlockFile;
     private final Object mLock = new Object();
 
     private int mAllowedUid = -1;
-    /*
-     * Separate lock for OEM unlock related operations as they can happen in parallel with regular
-     * block operations.
-     */
-    private final Object mOemLock = new Object();
-
     private long mBlockDeviceSize;
 
     public PersistentDataBlockService(Context context) {
@@ -89,7 +86,6 @@
         mAllowedUid = getAllowedUid(UserHandle.USER_OWNER);
     }
 
-
     private int getAllowedUid(int userHandle) {
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
@@ -106,6 +102,7 @@
 
     @Override
     public void onStart() {
+        enforceChecksumValidity();
         publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
     }
 
@@ -128,6 +125,9 @@
     }
 
     private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
+        // skip over checksum
+        inputStream.skipBytes(DIGEST_SIZE_BYTES);
+
         int totalDataSize;
         int blockId = inputStream.readInt();
         if (blockId == PARTITION_TYPE_MARKER) {
@@ -148,6 +148,143 @@
         return mBlockDeviceSize;
     }
 
+    private boolean enforceChecksumValidity() {
+        byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
+
+        synchronized (mLock) {
+            byte[] digest = computeDigestLocked(storedDigest);
+            if (digest == null || !Arrays.equals(storedDigest, digest)) {
+                Slog.i(TAG, "Formatting FRP partition...");
+                formatPartitionLocked();
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean computeAndWriteDigestLocked() {
+        byte[] digest = computeDigestLocked(null);
+        if (digest != null) {
+            DataOutputStream outputStream;
+            try {
+                outputStream = new DataOutputStream(
+                        new FileOutputStream(new File(mDataBlockFile)));
+            } catch (FileNotFoundException e) {
+                Slog.e(TAG, "partition not available?", e);
+                return false;
+            }
+
+            try {
+                outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
+                outputStream.flush();
+            } catch (IOException e) {
+                Slog.e(TAG, "failed to write block checksum", e);
+                return false;
+            } finally {
+                IoUtils.closeQuietly(outputStream);
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private byte[] computeDigestLocked(byte[] storedDigest) {
+        DataInputStream inputStream;
+        try {
+            inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "partition not available?", e);
+            return null;
+        }
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            // won't ever happen -- every implementation is required to support SHA-256
+            Slog.e(TAG, "SHA-256 not supported?", e);
+            IoUtils.closeQuietly(inputStream);
+            return null;
+        }
+
+        try {
+            if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
+                inputStream.read(storedDigest);
+            } else {
+                inputStream.skipBytes(DIGEST_SIZE_BYTES);
+            }
+
+            int read;
+            byte[] data = new byte[1024];
+            md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
+            while ((read = inputStream.read(data)) != -1) {
+                md.update(data, 0, read);
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "failed to read partition", e);
+            return null;
+        } finally {
+            IoUtils.closeQuietly(inputStream);
+        }
+
+        return md.digest();
+    }
+
+    private void formatPartitionLocked() {
+        DataOutputStream outputStream;
+        try {
+            outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "partition not available?", e);
+            return;
+        }
+
+        byte[] data = new byte[DIGEST_SIZE_BYTES];
+        try {
+            outputStream.write(data, 0, DIGEST_SIZE_BYTES);
+            outputStream.writeInt(PARTITION_TYPE_MARKER);
+            outputStream.writeInt(0); // data size
+            outputStream.flush();
+        } catch (IOException e) {
+            Slog.e(TAG, "failed to format block", e);
+            return;
+        } finally {
+            IoUtils.closeQuietly(outputStream);
+        }
+
+        doSetOemUnlockEnabledLocked(false);
+        computeAndWriteDigestLocked();
+    }
+
+    private void doSetOemUnlockEnabledLocked(boolean enabled) {
+        FileOutputStream outputStream;
+        try {
+            outputStream = new FileOutputStream(new File(mDataBlockFile));
+        } catch (FileNotFoundException e) {
+            Slog.e(TAG, "partition not available", e);
+            return;
+        }
+
+        try {
+            FileChannel channel = outputStream.getChannel();
+
+            channel.position(getBlockDeviceSize() - 1);
+
+            ByteBuffer data = ByteBuffer.allocate(1);
+            data.put(enabled ? (byte) 1 : (byte) 0);
+            data.flip();
+            channel.write(data);
+            outputStream.flush();
+        } catch (IOException e) {
+            Slog.e(TAG, "unable to access persistent partition", e);
+            return;
+        } finally {
+            IoUtils.closeQuietly(outputStream);
+        }
+    }
+
     private native long nativeGetBlockDeviceSize(String path);
     private native int nativeWipe(String path);
 
@@ -176,19 +313,23 @@
             headerAndData.putInt(data.length);
             headerAndData.put(data);
 
-            try {
-                synchronized (mLock) {
-                    outputStream.write(headerAndData.array());
-                    return data.length;
-                }
-            } catch (IOException e) {
-                Slog.e(TAG, "failed writing to the persistent data block", e);
-                return -1;
-            } finally {
+            synchronized (mLock) {
                 try {
-                    outputStream.close();
+                    byte[] checksum = new byte[DIGEST_SIZE_BYTES];
+                    outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
+                    outputStream.write(headerAndData.array());
+                    outputStream.flush();
                 } catch (IOException e) {
-                    Slog.e(TAG, "failed closing output stream", e);
+                    Slog.e(TAG, "failed writing to the persistent data block", e);
+                    return -1;
+                } finally {
+                    IoUtils.closeQuietly(outputStream);
+                }
+
+                if (computeAndWriteDigestLocked()) {
+                    return data.length;
+                } else {
+                    return -1;
                 }
             }
         }
@@ -196,6 +337,9 @@
         @Override
         public byte[] read() {
             enforceUid(Binder.getCallingUid());
+            if (!enforceChecksumValidity()) {
+                return new byte[0];
+            }
 
             DataInputStream inputStream;
             try {
@@ -256,30 +400,10 @@
             }
             enforceOemUnlockPermission();
             enforceIsOwner();
-            FileOutputStream outputStream;
-            try {
-                outputStream = new FileOutputStream(new File(mDataBlockFile));
-            } catch (FileNotFoundException e) {
-                Slog.e(TAG, "parition not available", e);
-                return;
-            }
 
-            try {
-                FileChannel channel = outputStream.getChannel();
-
-                channel.position(getBlockDeviceSize() - 1);
-
-                ByteBuffer data = ByteBuffer.allocate(1);
-                data.put(enabled ? (byte) 1 : (byte) 0);
-                data.flip();
-
-                synchronized (mOemLock) {
-                    channel.write(data);
-                }
-            } catch (IOException e) {
-                Slog.e(TAG, "unable to access persistent partition", e);
-            } finally {
-                IoUtils.closeQuietly(outputStream);
+            synchronized (mLock) {
+                doSetOemUnlockEnabledLocked(enabled);
+                computeAndWriteDigestLocked();
             }
         }
 
@@ -295,8 +419,8 @@
             }
 
             try {
-                inputStream.skip(getBlockDeviceSize() - 1);
-                synchronized (mOemLock) {
+                synchronized (mLock) {
+                    inputStream.skip(getBlockDeviceSize() - 1);
                     return inputStream.readByte() != 0;
                 }
             } catch (IOException e) {
@@ -336,6 +460,5 @@
             long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
             return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
         }
-
     };
 }