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

Commit 28301307 authored by Andres Morales's avatar Andres Morales
Browse files

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
parent 73546e53
Loading
Loading
Loading
Loading
+169 −46
Original line number Diff line number Diff line
@@ -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) {
                try {
                    byte[] checksum = new byte[DIGEST_SIZE_BYTES];
                    outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
                    outputStream.write(headerAndData.array());
                    return data.length;
                }
                    outputStream.flush();
                } catch (IOException e) {
                    Slog.e(TAG, "failed writing to the persistent data block", e);
                    return -1;
                } finally {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    Slog.e(TAG, "failed closing output stream", e);
                    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 {
                synchronized (mLock) {
                    inputStream.skip(getBlockDeviceSize() - 1);
                synchronized (mOemLock) {
                    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;
        }

    };
}