Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import android.Manifest;
     20 import android.app.ActivityManager;
     21 import android.content.Context;
     22 import android.content.pm.PackageManager;
     23 import android.os.Binder;
     24 import android.os.IBinder;
     25 import android.os.RemoteException;
     26 import android.os.SystemProperties;
     27 import android.os.UserHandle;
     28 import android.service.persistentdata.IPersistentDataBlockService;
     29 import android.util.Slog;
     30 
     31 import com.android.internal.R;
     32 
     33 import libcore.io.IoUtils;
     34 
     35 import java.io.DataInputStream;
     36 import java.io.DataOutputStream;
     37 import java.io.File;
     38 import java.io.FileInputStream;
     39 import java.io.FileNotFoundException;
     40 import java.io.FileOutputStream;
     41 import java.io.IOException;
     42 import java.nio.ByteBuffer;
     43 import java.nio.channels.FileChannel;
     44 import java.security.MessageDigest;
     45 import java.security.NoSuchAlgorithmException;
     46 import java.util.Arrays;
     47 
     48 /**
     49  * Service for reading and writing blocks to a persistent partition.
     50  * This data will live across factory resets not initiated via the Settings UI.
     51  * When a device is factory reset through Settings this data is wiped.
     52  *
     53  * Allows writing one block at a time. Namely, each time
     54  * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
     55  * is called, it will overwite the data that was previously written on the block.
     56  *
     57  * Clients can query the size of the currently written block via
     58  * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
     59  *
     60  * Clients can any number of bytes from the currently written block up to its total size by invoking
     61  * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
     62  */
     63 public class PersistentDataBlockService extends SystemService {
     64     private static final String TAG = PersistentDataBlockService.class.getSimpleName();
     65 
     66     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
     67     private static final int HEADER_SIZE = 8;
     68     // Magic number to mark block device as adhering to the format consumed by this service
     69     private static final int PARTITION_TYPE_MARKER = 0x19901873;
     70     // Limit to 100k as blocks larger than this might cause strain on Binder.
     71     private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
     72     public static final int DIGEST_SIZE_BYTES = 32;
     73     private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
     74 
     75     private final Context mContext;
     76     private final String mDataBlockFile;
     77     private final Object mLock = new Object();
     78 
     79     private int mAllowedUid = -1;
     80     private long mBlockDeviceSize;
     81 
     82     public PersistentDataBlockService(Context context) {
     83         super(context);
     84         mContext = context;
     85         mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
     86         mBlockDeviceSize = -1; // Load lazily
     87         mAllowedUid = getAllowedUid(UserHandle.USER_OWNER);
     88     }
     89 
     90     private int getAllowedUid(int userHandle) {
     91         String allowedPackage = mContext.getResources()
     92                 .getString(R.string.config_persistentDataPackageName);
     93         PackageManager pm = mContext.getPackageManager();
     94         int allowedUid = -1;
     95         try {
     96             allowedUid = pm.getPackageUid(allowedPackage, userHandle);
     97         } catch (PackageManager.NameNotFoundException e) {
     98             // not expected
     99             Slog.e(TAG, "not able to find package " + allowedPackage, e);
    100         }
    101         return allowedUid;
    102     }
    103 
    104     @Override
    105     public void onStart() {
    106         enforceChecksumValidity();
    107         formatIfOemUnlockEnabled();
    108         publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
    109     }
    110 
    111     private void formatIfOemUnlockEnabled() {
    112         boolean enabled = doGetOemUnlockEnabled();
    113         if (enabled) {
    114             synchronized (mLock) {
    115                 formatPartitionLocked(true);
    116             }
    117         }
    118 
    119         SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
    120     }
    121 
    122     private void enforceOemUnlockPermission() {
    123         mContext.enforceCallingOrSelfPermission(
    124                 Manifest.permission.OEM_UNLOCK_STATE,
    125                 "Can't access OEM unlock state");
    126     }
    127 
    128     private void enforceUid(int callingUid) {
    129         if (callingUid != mAllowedUid) {
    130             throw new SecurityException("uid " + callingUid + " not allowed to access PST");
    131         }
    132     }
    133 
    134     private void enforceIsOwner() {
    135         if (!Binder.getCallingUserHandle().isOwner()) {
    136             throw new SecurityException("Only the Owner is allowed to change OEM unlock state");
    137         }
    138     }
    139     private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
    140         // skip over checksum
    141         inputStream.skipBytes(DIGEST_SIZE_BYTES);
    142 
    143         int totalDataSize;
    144         int blockId = inputStream.readInt();
    145         if (blockId == PARTITION_TYPE_MARKER) {
    146             totalDataSize = inputStream.readInt();
    147         } else {
    148             totalDataSize = 0;
    149         }
    150         return totalDataSize;
    151     }
    152 
    153     private long getBlockDeviceSize() {
    154         synchronized (mLock) {
    155             if (mBlockDeviceSize == -1) {
    156                 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
    157             }
    158         }
    159 
    160         return mBlockDeviceSize;
    161     }
    162 
    163     private boolean enforceChecksumValidity() {
    164         byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
    165 
    166         synchronized (mLock) {
    167             byte[] digest = computeDigestLocked(storedDigest);
    168             if (digest == null || !Arrays.equals(storedDigest, digest)) {
    169                 Slog.i(TAG, "Formatting FRP partition...");
    170                 formatPartitionLocked(false);
    171                 return false;
    172             }
    173         }
    174 
    175         return true;
    176     }
    177 
    178     private boolean computeAndWriteDigestLocked() {
    179         byte[] digest = computeDigestLocked(null);
    180         if (digest != null) {
    181             DataOutputStream outputStream;
    182             try {
    183                 outputStream = new DataOutputStream(
    184                         new FileOutputStream(new File(mDataBlockFile)));
    185             } catch (FileNotFoundException e) {
    186                 Slog.e(TAG, "partition not available?", e);
    187                 return false;
    188             }
    189 
    190             try {
    191                 outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
    192                 outputStream.flush();
    193             } catch (IOException e) {
    194                 Slog.e(TAG, "failed to write block checksum", e);
    195                 return false;
    196             } finally {
    197                 IoUtils.closeQuietly(outputStream);
    198             }
    199             return true;
    200         } else {
    201             return false;
    202         }
    203     }
    204 
    205     private byte[] computeDigestLocked(byte[] storedDigest) {
    206         DataInputStream inputStream;
    207         try {
    208             inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
    209         } catch (FileNotFoundException e) {
    210             Slog.e(TAG, "partition not available?", e);
    211             return null;
    212         }
    213 
    214         MessageDigest md;
    215         try {
    216             md = MessageDigest.getInstance("SHA-256");
    217         } catch (NoSuchAlgorithmException e) {
    218             // won't ever happen -- every implementation is required to support SHA-256
    219             Slog.e(TAG, "SHA-256 not supported?", e);
    220             IoUtils.closeQuietly(inputStream);
    221             return null;
    222         }
    223 
    224         try {
    225             if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
    226                 inputStream.read(storedDigest);
    227             } else {
    228                 inputStream.skipBytes(DIGEST_SIZE_BYTES);
    229             }
    230 
    231             int read;
    232             byte[] data = new byte[1024];
    233             md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
    234             while ((read = inputStream.read(data)) != -1) {
    235                 md.update(data, 0, read);
    236             }
    237         } catch (IOException e) {
    238             Slog.e(TAG, "failed to read partition", e);
    239             return null;
    240         } finally {
    241             IoUtils.closeQuietly(inputStream);
    242         }
    243 
    244         return md.digest();
    245     }
    246 
    247     private void formatPartitionLocked(boolean setOemUnlockEnabled) {
    248         DataOutputStream outputStream;
    249         try {
    250             outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
    251         } catch (FileNotFoundException e) {
    252             Slog.e(TAG, "partition not available?", e);
    253             return;
    254         }
    255 
    256         byte[] data = new byte[DIGEST_SIZE_BYTES];
    257         try {
    258             outputStream.write(data, 0, DIGEST_SIZE_BYTES);
    259             outputStream.writeInt(PARTITION_TYPE_MARKER);
    260             outputStream.writeInt(0); // data size
    261             outputStream.flush();
    262         } catch (IOException e) {
    263             Slog.e(TAG, "failed to format block", e);
    264             return;
    265         } finally {
    266             IoUtils.closeQuietly(outputStream);
    267         }
    268 
    269         doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
    270         computeAndWriteDigestLocked();
    271     }
    272 
    273     private void doSetOemUnlockEnabledLocked(boolean enabled) {
    274         FileOutputStream outputStream;
    275         try {
    276             outputStream = new FileOutputStream(new File(mDataBlockFile));
    277         } catch (FileNotFoundException e) {
    278             Slog.e(TAG, "partition not available", e);
    279             return;
    280         }
    281 
    282         try {
    283             FileChannel channel = outputStream.getChannel();
    284 
    285             channel.position(getBlockDeviceSize() - 1);
    286 
    287             ByteBuffer data = ByteBuffer.allocate(1);
    288             data.put(enabled ? (byte) 1 : (byte) 0);
    289             data.flip();
    290             channel.write(data);
    291             outputStream.flush();
    292         } catch (IOException e) {
    293             Slog.e(TAG, "unable to access persistent partition", e);
    294             return;
    295         } finally {
    296             SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
    297             IoUtils.closeQuietly(outputStream);
    298         }
    299     }
    300 
    301     private boolean doGetOemUnlockEnabled() {
    302         DataInputStream inputStream;
    303         try {
    304             inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
    305         } catch (FileNotFoundException e) {
    306             Slog.e(TAG, "partition not available");
    307             return false;
    308         }
    309 
    310         try {
    311             synchronized (mLock) {
    312                 inputStream.skip(getBlockDeviceSize() - 1);
    313                 return inputStream.readByte() != 0;
    314             }
    315         } catch (IOException e) {
    316             Slog.e(TAG, "unable to access persistent partition", e);
    317             return false;
    318         } finally {
    319             IoUtils.closeQuietly(inputStream);
    320         }
    321     }
    322 
    323     private native long nativeGetBlockDeviceSize(String path);
    324     private native int nativeWipe(String path);
    325 
    326     private final IBinder mService = new IPersistentDataBlockService.Stub() {
    327         @Override
    328         public int write(byte[] data) throws RemoteException {
    329             enforceUid(Binder.getCallingUid());
    330 
    331             // Need to ensure we don't write over the last byte
    332             long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
    333             if (data.length > maxBlockSize) {
    334                 // partition is ~500k so shouldn't be a problem to downcast
    335                 return (int) -maxBlockSize;
    336             }
    337 
    338             DataOutputStream outputStream;
    339             try {
    340                 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
    341             } catch (FileNotFoundException e) {
    342                 Slog.e(TAG, "partition not available?", e);
    343                 return -1;
    344             }
    345 
    346             ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
    347             headerAndData.putInt(PARTITION_TYPE_MARKER);
    348             headerAndData.putInt(data.length);
    349             headerAndData.put(data);
    350 
    351             synchronized (mLock) {
    352                 try {
    353                     byte[] checksum = new byte[DIGEST_SIZE_BYTES];
    354                     outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
    355                     outputStream.write(headerAndData.array());
    356                     outputStream.flush();
    357                 } catch (IOException e) {
    358                     Slog.e(TAG, "failed writing to the persistent data block", e);
    359                     return -1;
    360                 } finally {
    361                     IoUtils.closeQuietly(outputStream);
    362                 }
    363 
    364                 if (computeAndWriteDigestLocked()) {
    365                     return data.length;
    366                 } else {
    367                     return -1;
    368                 }
    369             }
    370         }
    371 
    372         @Override
    373         public byte[] read() {
    374             enforceUid(Binder.getCallingUid());
    375             if (!enforceChecksumValidity()) {
    376                 return new byte[0];
    377             }
    378 
    379             DataInputStream inputStream;
    380             try {
    381                 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
    382             } catch (FileNotFoundException e) {
    383                 Slog.e(TAG, "partition not available?", e);
    384                 return null;
    385             }
    386 
    387             try {
    388                 synchronized (mLock) {
    389                     int totalDataSize = getTotalDataSizeLocked(inputStream);
    390 
    391                     if (totalDataSize == 0) {
    392                         return new byte[0];
    393                     }
    394 
    395                     byte[] data = new byte[totalDataSize];
    396                     int read = inputStream.read(data, 0, totalDataSize);
    397                     if (read < totalDataSize) {
    398                         // something went wrong, not returning potentially corrupt data
    399                         Slog.e(TAG, "failed to read entire data block. bytes read: " +
    400                                 read + "/" + totalDataSize);
    401                         return null;
    402                     }
    403                     return data;
    404                 }
    405             } catch (IOException e) {
    406                 Slog.e(TAG, "failed to read data", e);
    407                 return null;
    408             } finally {
    409                 try {
    410                     inputStream.close();
    411                 } catch (IOException e) {
    412                     Slog.e(TAG, "failed to close OutputStream");
    413                 }
    414             }
    415         }
    416 
    417         @Override
    418         public void wipe() {
    419             enforceOemUnlockPermission();
    420 
    421             synchronized (mLock) {
    422                 int ret = nativeWipe(mDataBlockFile);
    423 
    424                 if (ret < 0) {
    425                     Slog.e(TAG, "failed to wipe persistent partition");
    426                 }
    427             }
    428         }
    429 
    430         @Override
    431         public void setOemUnlockEnabled(boolean enabled) {
    432             // do not allow monkey to flip the flag
    433             if (ActivityManager.isUserAMonkey()) {
    434                 return;
    435             }
    436             enforceOemUnlockPermission();
    437             enforceIsOwner();
    438 
    439             synchronized (mLock) {
    440                 doSetOemUnlockEnabledLocked(enabled);
    441                 computeAndWriteDigestLocked();
    442             }
    443         }
    444 
    445         @Override
    446         public boolean getOemUnlockEnabled() {
    447             enforceOemUnlockPermission();
    448             return doGetOemUnlockEnabled();
    449         }
    450 
    451         @Override
    452         public int getDataBlockSize() {
    453             enforcePersistentDataBlockAccess();
    454 
    455             DataInputStream inputStream;
    456             try {
    457                 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
    458             } catch (FileNotFoundException e) {
    459                 Slog.e(TAG, "partition not available");
    460                 return 0;
    461             }
    462 
    463             try {
    464                 synchronized (mLock) {
    465                     return getTotalDataSizeLocked(inputStream);
    466                 }
    467             } catch (IOException e) {
    468                 Slog.e(TAG, "error reading data block size");
    469                 return 0;
    470             } finally {
    471                 IoUtils.closeQuietly(inputStream);
    472             }
    473         }
    474 
    475         private void enforcePersistentDataBlockAccess() {
    476             if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
    477                     != PackageManager.PERMISSION_GRANTED) {
    478                 enforceUid(Binder.getCallingUid());
    479             }
    480         }
    481 
    482         @Override
    483         public long getMaximumDataBlockSize() {
    484             long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
    485             return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
    486         }
    487     };
    488 }
    489