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