From 2830130770b0d010007d126f64756413f922974b Mon Sep 17 00:00:00 2001 From: Andres Morales Date: Wed, 12 Nov 2014 07:56:46 -0800 Subject: [PATCH] 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 --- .../server/PersistentDataBlockService.java | 215 ++++++++++++++---- 1 file changed, 169 insertions(+), 46 deletions(-) diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index de90aa2d4e255..17edb5372cdd6 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.FileOutputStream; 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { mAllowedUid = getAllowedUid(UserHandle.USER_OWNER); } - private int getAllowedUid(int userHandle) { String allowedPackage = mContext.getResources() .getString(R.string.config_persistentDataPackageName); @@ -106,6 +102,7 @@ public class PersistentDataBlockService extends SystemService { @Override public void onStart() { + enforceChecksumValidity(); publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService); } @@ -128,6 +125,9 @@ public class PersistentDataBlockService extends SystemService { } 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { 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 @@ public class PersistentDataBlockService extends SystemService { @Override public byte[] read() { enforceUid(Binder.getCallingUid()); + if (!enforceChecksumValidity()) { + return new byte[0]; + } DataInputStream inputStream; try { @@ -256,30 +400,10 @@ public class PersistentDataBlockService extends SystemService { } 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 @@ public class PersistentDataBlockService extends SystemService { } try { - inputStream.skip(getBlockDeviceSize() - 1); - synchronized (mOemLock) { + synchronized (mLock) { + inputStream.skip(getBlockDeviceSize() - 1); return inputStream.readByte() != 0; } } catch (IOException e) { @@ -336,6 +460,5 @@ public class PersistentDataBlockService extends SystemService { long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1; return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE; } - }; }