PersistentDataBlockService.java revision 830e32cdccdeeeadc5f07ba006b2b5779f8be65d
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 17package com.android.server; 18 19import android.Manifest; 20import android.app.ActivityManager; 21import android.content.Context; 22import android.content.pm.PackageManager; 23import android.os.Binder; 24import android.os.IBinder; 25import android.os.RemoteException; 26import android.os.SystemProperties; 27import android.os.UserHandle; 28import android.os.UserManager; 29import android.provider.Settings; 30import android.service.persistentdata.IPersistentDataBlockService; 31import android.service.persistentdata.PersistentDataBlockManager; 32import android.util.Slog; 33 34import com.android.internal.R; 35 36import libcore.io.IoUtils; 37 38import java.io.DataInputStream; 39import java.io.DataOutputStream; 40import java.io.File; 41import java.io.FileInputStream; 42import java.io.FileNotFoundException; 43import java.io.FileOutputStream; 44import java.io.IOException; 45import java.nio.ByteBuffer; 46import java.nio.channels.FileChannel; 47import java.security.MessageDigest; 48import java.security.NoSuchAlgorithmException; 49import 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 */ 66public 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 enforceOemUnlockPermission() { 130 mContext.enforceCallingOrSelfPermission( 131 Manifest.permission.OEM_UNLOCK_STATE, 132 "Can't access OEM unlock state"); 133 } 134 135 private void enforceUid(int callingUid) { 136 if (callingUid != mAllowedUid) { 137 throw new SecurityException("uid " + callingUid + " not allowed to access PST"); 138 } 139 } 140 141 private void enforceIsAdmin() { 142 final int userId = UserHandle.getCallingUserId(); 143 final boolean isAdmin = UserManager.get(mContext).isUserAdmin(userId); 144 if (!isAdmin) { 145 throw new SecurityException( 146 "Only the Admin user is allowed to change OEM unlock state"); 147 } 148 } 149 150 private void enforceFactoryResetAllowed() { 151 final boolean isOemUnlockRestricted = UserManager.get(mContext) 152 .hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET); 153 if (isOemUnlockRestricted) { 154 throw new SecurityException("OEM unlock is disallowed by DISALLOW_FACTORY_RESET"); 155 } 156 } 157 158 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException { 159 // skip over checksum 160 inputStream.skipBytes(DIGEST_SIZE_BYTES); 161 162 int totalDataSize; 163 int blockId = inputStream.readInt(); 164 if (blockId == PARTITION_TYPE_MARKER) { 165 totalDataSize = inputStream.readInt(); 166 } else { 167 totalDataSize = 0; 168 } 169 return totalDataSize; 170 } 171 172 private long getBlockDeviceSize() { 173 synchronized (mLock) { 174 if (mBlockDeviceSize == -1) { 175 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile); 176 } 177 } 178 179 return mBlockDeviceSize; 180 } 181 182 private boolean enforceChecksumValidity() { 183 byte[] storedDigest = new byte[DIGEST_SIZE_BYTES]; 184 185 synchronized (mLock) { 186 byte[] digest = computeDigestLocked(storedDigest); 187 if (digest == null || !Arrays.equals(storedDigest, digest)) { 188 Slog.i(TAG, "Formatting FRP partition..."); 189 formatPartitionLocked(false); 190 return false; 191 } 192 } 193 194 return true; 195 } 196 197 private boolean computeAndWriteDigestLocked() { 198 byte[] digest = computeDigestLocked(null); 199 if (digest != null) { 200 DataOutputStream outputStream; 201 try { 202 outputStream = new DataOutputStream( 203 new FileOutputStream(new File(mDataBlockFile))); 204 } catch (FileNotFoundException e) { 205 Slog.e(TAG, "partition not available?", e); 206 return false; 207 } 208 209 try { 210 outputStream.write(digest, 0, DIGEST_SIZE_BYTES); 211 outputStream.flush(); 212 } catch (IOException e) { 213 Slog.e(TAG, "failed to write block checksum", e); 214 return false; 215 } finally { 216 IoUtils.closeQuietly(outputStream); 217 } 218 return true; 219 } else { 220 return false; 221 } 222 } 223 224 private byte[] computeDigestLocked(byte[] storedDigest) { 225 DataInputStream inputStream; 226 try { 227 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); 228 } catch (FileNotFoundException e) { 229 Slog.e(TAG, "partition not available?", e); 230 return null; 231 } 232 233 MessageDigest md; 234 try { 235 md = MessageDigest.getInstance("SHA-256"); 236 } catch (NoSuchAlgorithmException e) { 237 // won't ever happen -- every implementation is required to support SHA-256 238 Slog.e(TAG, "SHA-256 not supported?", e); 239 IoUtils.closeQuietly(inputStream); 240 return null; 241 } 242 243 try { 244 if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) { 245 inputStream.read(storedDigest); 246 } else { 247 inputStream.skipBytes(DIGEST_SIZE_BYTES); 248 } 249 250 int read; 251 byte[] data = new byte[1024]; 252 md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest 253 while ((read = inputStream.read(data)) != -1) { 254 md.update(data, 0, read); 255 } 256 } catch (IOException e) { 257 Slog.e(TAG, "failed to read partition", e); 258 return null; 259 } finally { 260 IoUtils.closeQuietly(inputStream); 261 } 262 263 return md.digest(); 264 } 265 266 private void formatPartitionLocked(boolean setOemUnlockEnabled) { 267 DataOutputStream outputStream; 268 try { 269 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); 270 } catch (FileNotFoundException e) { 271 Slog.e(TAG, "partition not available?", e); 272 return; 273 } 274 275 byte[] data = new byte[DIGEST_SIZE_BYTES]; 276 try { 277 outputStream.write(data, 0, DIGEST_SIZE_BYTES); 278 outputStream.writeInt(PARTITION_TYPE_MARKER); 279 outputStream.writeInt(0); // data size 280 outputStream.flush(); 281 } catch (IOException e) { 282 Slog.e(TAG, "failed to format block", e); 283 return; 284 } finally { 285 IoUtils.closeQuietly(outputStream); 286 } 287 288 doSetOemUnlockEnabledLocked(setOemUnlockEnabled); 289 computeAndWriteDigestLocked(); 290 } 291 292 private void doSetOemUnlockEnabledLocked(boolean enabled) { 293 FileOutputStream outputStream; 294 try { 295 outputStream = new FileOutputStream(new File(mDataBlockFile)); 296 } catch (FileNotFoundException e) { 297 Slog.e(TAG, "partition not available", e); 298 return; 299 } 300 301 try { 302 FileChannel channel = outputStream.getChannel(); 303 304 channel.position(getBlockDeviceSize() - 1); 305 306 ByteBuffer data = ByteBuffer.allocate(1); 307 data.put(enabled ? (byte) 1 : (byte) 0); 308 data.flip(); 309 channel.write(data); 310 outputStream.flush(); 311 } catch (IOException e) { 312 Slog.e(TAG, "unable to access persistent partition", e); 313 return; 314 } finally { 315 SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0"); 316 IoUtils.closeQuietly(outputStream); 317 } 318 } 319 320 private boolean doGetOemUnlockEnabled() { 321 DataInputStream inputStream; 322 try { 323 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); 324 } catch (FileNotFoundException e) { 325 Slog.e(TAG, "partition not available"); 326 return false; 327 } 328 329 try { 330 synchronized (mLock) { 331 inputStream.skip(getBlockDeviceSize() - 1); 332 return inputStream.readByte() != 0; 333 } 334 } catch (IOException e) { 335 Slog.e(TAG, "unable to access persistent partition", e); 336 return false; 337 } finally { 338 IoUtils.closeQuietly(inputStream); 339 } 340 } 341 342 private native long nativeGetBlockDeviceSize(String path); 343 private native int nativeWipe(String path); 344 345 private final IBinder mService = new IPersistentDataBlockService.Stub() { 346 @Override 347 public int write(byte[] data) throws RemoteException { 348 enforceUid(Binder.getCallingUid()); 349 350 // Need to ensure we don't write over the last byte 351 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1; 352 if (data.length > maxBlockSize) { 353 // partition is ~500k so shouldn't be a problem to downcast 354 return (int) -maxBlockSize; 355 } 356 357 DataOutputStream outputStream; 358 try { 359 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile))); 360 } catch (FileNotFoundException e) { 361 Slog.e(TAG, "partition not available?", e); 362 return -1; 363 } 364 365 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE); 366 headerAndData.putInt(PARTITION_TYPE_MARKER); 367 headerAndData.putInt(data.length); 368 headerAndData.put(data); 369 370 synchronized (mLock) { 371 try { 372 byte[] checksum = new byte[DIGEST_SIZE_BYTES]; 373 outputStream.write(checksum, 0, DIGEST_SIZE_BYTES); 374 outputStream.write(headerAndData.array()); 375 outputStream.flush(); 376 } catch (IOException e) { 377 Slog.e(TAG, "failed writing to the persistent data block", e); 378 return -1; 379 } finally { 380 IoUtils.closeQuietly(outputStream); 381 } 382 383 if (computeAndWriteDigestLocked()) { 384 return data.length; 385 } else { 386 return -1; 387 } 388 } 389 } 390 391 @Override 392 public byte[] read() { 393 enforceUid(Binder.getCallingUid()); 394 if (!enforceChecksumValidity()) { 395 return new byte[0]; 396 } 397 398 DataInputStream inputStream; 399 try { 400 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); 401 } catch (FileNotFoundException e) { 402 Slog.e(TAG, "partition not available?", e); 403 return null; 404 } 405 406 try { 407 synchronized (mLock) { 408 int totalDataSize = getTotalDataSizeLocked(inputStream); 409 410 if (totalDataSize == 0) { 411 return new byte[0]; 412 } 413 414 byte[] data = new byte[totalDataSize]; 415 int read = inputStream.read(data, 0, totalDataSize); 416 if (read < totalDataSize) { 417 // something went wrong, not returning potentially corrupt data 418 Slog.e(TAG, "failed to read entire data block. bytes read: " + 419 read + "/" + totalDataSize); 420 return null; 421 } 422 return data; 423 } 424 } catch (IOException e) { 425 Slog.e(TAG, "failed to read data", e); 426 return null; 427 } finally { 428 try { 429 inputStream.close(); 430 } catch (IOException e) { 431 Slog.e(TAG, "failed to close OutputStream"); 432 } 433 } 434 } 435 436 @Override 437 public void wipe() { 438 enforceOemUnlockPermission(); 439 440 synchronized (mLock) { 441 int ret = nativeWipe(mDataBlockFile); 442 443 if (ret < 0) { 444 Slog.e(TAG, "failed to wipe persistent partition"); 445 } 446 } 447 } 448 449 @Override 450 public void setOemUnlockEnabled(boolean enabled) throws SecurityException { 451 // do not allow monkey to flip the flag 452 if (ActivityManager.isUserAMonkey()) { 453 return; 454 } 455 456 enforceOemUnlockPermission(); 457 enforceIsAdmin(); 458 459 // Do not allow oem unlock modification if it has been disallowed. 460 if (Settings.Global.getInt(getContext().getContentResolver(), 461 Settings.Global.OEM_UNLOCK_DISALLOWED, 0) == 1) { 462 throw new SecurityException("OEM unlock has been disallowed."); 463 } 464 if (enabled) { 465 enforceFactoryResetAllowed(); 466 } 467 synchronized (mLock) { 468 doSetOemUnlockEnabledLocked(enabled); 469 computeAndWriteDigestLocked(); 470 } 471 } 472 473 @Override 474 public boolean getOemUnlockEnabled() { 475 enforceOemUnlockPermission(); 476 return doGetOemUnlockEnabled(); 477 } 478 479 @Override 480 public int getFlashLockState() { 481 enforceOemUnlockPermission(); 482 String locked = SystemProperties.get(FLASH_LOCK_PROP); 483 switch (locked) { 484 case FLASH_LOCK_LOCKED: 485 return PersistentDataBlockManager.FLASH_LOCK_LOCKED; 486 case FLASH_LOCK_UNLOCKED: 487 return PersistentDataBlockManager.FLASH_LOCK_UNLOCKED; 488 default: 489 return PersistentDataBlockManager.FLASH_LOCK_UNKNOWN; 490 } 491 } 492 493 @Override 494 public int getDataBlockSize() { 495 enforcePersistentDataBlockAccess(); 496 497 DataInputStream inputStream; 498 try { 499 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile))); 500 } catch (FileNotFoundException e) { 501 Slog.e(TAG, "partition not available"); 502 return 0; 503 } 504 505 try { 506 synchronized (mLock) { 507 return getTotalDataSizeLocked(inputStream); 508 } 509 } catch (IOException e) { 510 Slog.e(TAG, "error reading data block size"); 511 return 0; 512 } finally { 513 IoUtils.closeQuietly(inputStream); 514 } 515 } 516 517 private void enforcePersistentDataBlockAccess() { 518 if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE) 519 != PackageManager.PERMISSION_GRANTED) { 520 enforceUid(Binder.getCallingUid()); 521 } 522 } 523 524 @Override 525 public long getMaximumDataBlockSize() { 526 long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1; 527 return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE; 528 } 529 }; 530} 531