blob: 94316fe826e0efeaced6829ce9587fab8c4524d5 [file] [log] [blame]
Andres Morales963295e2014-07-10 15:40:24 -07001/*
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
Andres Morales68d4acd2014-07-01 19:40:41 -070017package com.android.server;
18
19import android.Manifest;
Guang Zhu514c5802014-09-12 15:14:00 -070020import android.app.ActivityManager;
Andres Morales68d4acd2014-07-01 19:40:41 -070021import android.content.Context;
22import android.content.pm.PackageManager;
23import android.os.Binder;
24import android.os.IBinder;
25import android.os.RemoteException;
26import android.os.SystemProperties;
Andres Morales6429f312014-08-04 16:35:15 -070027import android.os.UserHandle;
Andres Morales68d4acd2014-07-01 19:40:41 -070028import android.service.persistentdata.IPersistentDataBlockService;
Andres Morales963295e2014-07-10 15:40:24 -070029import android.util.Slog;
Guang Zhu514c5802014-09-12 15:14:00 -070030
Andres Morales68d4acd2014-07-01 19:40:41 -070031import com.android.internal.R;
Guang Zhu514c5802014-09-12 15:14:00 -070032
Andres Morales963295e2014-07-10 15:40:24 -070033import libcore.io.IoUtils;
Andres Morales68d4acd2014-07-01 19:40:41 -070034
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;
Andres Morales28301302014-11-12 07:56:46 -080044import java.security.MessageDigest;
45import java.security.NoSuchAlgorithmException;
46import java.util.Arrays;
Andres Morales68d4acd2014-07-01 19:40:41 -070047
48/**
49 * Service for reading and writing blocks to a persistent partition.
Andres Morales963295e2014-07-10 15:40:24 -070050 * 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.
Andres Morales68d4acd2014-07-01 19:40:41 -070052 *
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;
Andres Morales963295e2014-07-10 15:40:24 -070068 // Magic number to mark block device as adhering to the format consumed by this service
Andres Morales28301302014-11-12 07:56:46 -080069 private static final int PARTITION_TYPE_MARKER = 0x19901873;
Andres Morales963295e2014-07-10 15:40:24 -070070 // Limit to 100k as blocks larger than this might cause strain on Binder.
Andres Morales963295e2014-07-10 15:40:24 -070071 private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
Andres Morales28301302014-11-12 07:56:46 -080072 public static final int DIGEST_SIZE_BYTES = 32;
Andres Morales5ca4cc52015-03-19 16:37:54 -070073 private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
Andres Morales68d4acd2014-07-01 19:40:41 -070074
75 private final Context mContext;
76 private final String mDataBlockFile;
Andres Morales963295e2014-07-10 15:40:24 -070077 private final Object mLock = new Object();
Andres Morales6429f312014-08-04 16:35:15 -070078
Andres Moralesa31c23d2014-10-30 15:31:31 -070079 private int mAllowedUid = -1;
Andres Morales963295e2014-07-10 15:40:24 -070080 private long mBlockDeviceSize;
Andres Morales68d4acd2014-07-01 19:40:41 -070081
82 public PersistentDataBlockService(Context context) {
83 super(context);
84 mContext = context;
85 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
Andres Morales963295e2014-07-10 15:40:24 -070086 mBlockDeviceSize = -1; // Load lazily
Andres Moralesa31c23d2014-10-30 15:31:31 -070087 mAllowedUid = getAllowedUid(UserHandle.USER_OWNER);
Andres Morales6429f312014-08-04 16:35:15 -070088 }
89
Andres Moralesa31c23d2014-10-30 15:31:31 -070090 private int getAllowedUid(int userHandle) {
Andres Morales6429f312014-08-04 16:35:15 -070091 String allowedPackage = mContext.getResources()
Andres Morales68d4acd2014-07-01 19:40:41 -070092 .getString(R.string.config_persistentDataPackageName);
93 PackageManager pm = mContext.getPackageManager();
94 int allowedUid = -1;
95 try {
Andres Morales6429f312014-08-04 16:35:15 -070096 allowedUid = pm.getPackageUid(allowedPackage, userHandle);
Andres Morales68d4acd2014-07-01 19:40:41 -070097 } catch (PackageManager.NameNotFoundException e) {
98 // not expected
Andres Morales963295e2014-07-10 15:40:24 -070099 Slog.e(TAG, "not able to find package " + allowedPackage, e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700100 }
Andres Moralesa31c23d2014-10-30 15:31:31 -0700101 return allowedUid;
Andres Morales68d4acd2014-07-01 19:40:41 -0700102 }
103
104 @Override
105 public void onStart() {
Andres Morales28301302014-11-12 07:56:46 -0800106 enforceChecksumValidity();
Andres Morales1ce7d172015-01-07 14:24:57 -0800107 formatIfOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700108 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
109 }
110
Andres Morales1ce7d172015-01-07 14:24:57 -0800111 private void formatIfOemUnlockEnabled() {
Andres Morales5ca4cc52015-03-19 16:37:54 -0700112 boolean enabled = doGetOemUnlockEnabled();
113 if (enabled) {
Andres Morales1ce7d172015-01-07 14:24:57 -0800114 synchronized (mLock) {
Andres Moralesc8f952c2015-03-19 08:34:55 -0700115 formatPartitionLocked(true);
Andres Morales1ce7d172015-01-07 14:24:57 -0800116 }
117 }
Andres Morales5ca4cc52015-03-19 16:37:54 -0700118
119 SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
Andres Morales1ce7d172015-01-07 14:24:57 -0800120 }
121
Andres Morales68d4acd2014-07-01 19:40:41 -0700122 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) {
Andres Moralesa31c23d2014-10-30 15:31:31 -0700129 if (callingUid != mAllowedUid) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700130 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
131 }
132 }
133
Andres Moralesa31c23d2014-10-30 15:31:31 -0700134 private void enforceIsOwner() {
135 if (!Binder.getCallingUserHandle().isOwner()) {
136 throw new SecurityException("Only the Owner is allowed to change OEM unlock state");
137 }
138 }
Andres Morales963295e2014-07-10 15:40:24 -0700139 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales28301302014-11-12 07:56:46 -0800140 // skip over checksum
141 inputStream.skipBytes(DIGEST_SIZE_BYTES);
142
Andres Morales68d4acd2014-07-01 19:40:41 -0700143 int totalDataSize;
144 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700145 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700146 totalDataSize = inputStream.readInt();
147 } else {
148 totalDataSize = 0;
149 }
150 return totalDataSize;
151 }
152
Andres Morales963295e2014-07-10 15:40:24 -0700153 private long getBlockDeviceSize() {
154 synchronized (mLock) {
155 if (mBlockDeviceSize == -1) {
156 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700157 }
158 }
159
160 return mBlockDeviceSize;
161 }
162
Andres Morales28301302014-11-12 07:56:46 -0800163 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...");
Andres Moralesc8f952c2015-03-19 08:34:55 -0700170 formatPartitionLocked(false);
Andres Morales28301302014-11-12 07:56:46 -0800171 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
Andres Moralesc8f952c2015-03-19 08:34:55 -0700247 private void formatPartitionLocked(boolean setOemUnlockEnabled) {
Andres Morales28301302014-11-12 07:56:46 -0800248 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
Andres Moralesc8f952c2015-03-19 08:34:55 -0700269 doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
Andres Morales28301302014-11-12 07:56:46 -0800270 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 {
Andres Morales5ca4cc52015-03-19 16:37:54 -0700296 SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
Andres Morales28301302014-11-12 07:56:46 -0800297 IoUtils.closeQuietly(outputStream);
298 }
299 }
300
Andres Morales1ce7d172015-01-07 14:24:57 -0800301 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
Andres Morales963295e2014-07-10 15:40:24 -0700323 private native long nativeGetBlockDeviceSize(String path);
324 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700325
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
Andres Morales963295e2014-07-10 15:40:24 -0700332 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;
Andres Morales68d4acd2014-07-01 19:40:41 -0700336 }
337
338 DataOutputStream outputStream;
339 try {
340 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
341 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700342 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700343 return -1;
344 }
345
346 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700347 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700348 headerAndData.putInt(data.length);
349 headerAndData.put(data);
350
Andres Morales28301302014-11-12 07:56:46 -0800351 synchronized (mLock) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700352 try {
Andres Morales28301302014-11-12 07:56:46 -0800353 byte[] checksum = new byte[DIGEST_SIZE_BYTES];
354 outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
355 outputStream.write(headerAndData.array());
356 outputStream.flush();
Andres Morales68d4acd2014-07-01 19:40:41 -0700357 } catch (IOException e) {
Andres Morales28301302014-11-12 07:56:46 -0800358 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;
Andres Morales68d4acd2014-07-01 19:40:41 -0700368 }
369 }
370 }
371
372 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700373 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700374 enforceUid(Binder.getCallingUid());
Andres Morales28301302014-11-12 07:56:46 -0800375 if (!enforceChecksumValidity()) {
376 return new byte[0];
377 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700378
379 DataInputStream inputStream;
380 try {
381 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
382 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700383 Slog.e(TAG, "partition not available?", e);
384 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700385 }
386
387 try {
Andres Morales963295e2014-07-10 15:40:24 -0700388 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 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700405 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700406 Slog.e(TAG, "failed to read data", e);
407 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700408 } finally {
409 try {
410 inputStream.close();
411 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700412 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");
Andres Morales68d4acd2014-07-01 19:40:41 -0700426 }
427 }
428 }
429
430 @Override
431 public void setOemUnlockEnabled(boolean enabled) {
Guang Zhu514c5802014-09-12 15:14:00 -0700432 // do not allow monkey to flip the flag
433 if (ActivityManager.isUserAMonkey()) {
434 return;
435 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700436 enforceOemUnlockPermission();
Andres Moralesa31c23d2014-10-30 15:31:31 -0700437 enforceIsOwner();
Andres Morales68d4acd2014-07-01 19:40:41 -0700438
Andres Morales28301302014-11-12 07:56:46 -0800439 synchronized (mLock) {
440 doSetOemUnlockEnabledLocked(enabled);
441 computeAndWriteDigestLocked();
Andres Morales68d4acd2014-07-01 19:40:41 -0700442 }
443 }
444
445 @Override
446 public boolean getOemUnlockEnabled() {
447 enforceOemUnlockPermission();
Andres Morales1ce7d172015-01-07 14:24:57 -0800448 return doGetOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700449 }
450
451 @Override
452 public int getDataBlockSize() {
Craig Lafayette66445a62015-03-27 09:01:43 -0400453 enforcePersistentDataBlockAccess();
Andres Morales68d4acd2014-07-01 19:40:41 -0700454
455 DataInputStream inputStream;
456 try {
457 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
458 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700459 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700460 return 0;
461 }
462
463 try {
Andres Morales963295e2014-07-10 15:40:24 -0700464 synchronized (mLock) {
465 return getTotalDataSizeLocked(inputStream);
466 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700467 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700468 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700469 return 0;
470 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700471 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700472 }
473 }
Andres Morales963295e2014-07-10 15:40:24 -0700474
Craig Lafayette66445a62015-03-27 09:01:43 -0400475 private void enforcePersistentDataBlockAccess() {
476 if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
477 != PackageManager.PERMISSION_GRANTED) {
478 enforceUid(Binder.getCallingUid());
479 }
480 }
481
Andres Morales963295e2014-07-10 15:40:24 -0700482 @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 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700487 };
488}