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