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