blob: a291cc72ea60a24f9d2e51e9f25c1c535cec1499 [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;
Xiaohui Chenf0660782015-09-02 14:25:19 -070028import android.os.UserManager;
Andres Morales68d4acd2014-07-01 19:40:41 -070029import android.service.persistentdata.IPersistentDataBlockService;
Andres Morales963295e2014-07-10 15:40:24 -070030import android.util.Slog;
Guang Zhu514c5802014-09-12 15:14:00 -070031
Andres Morales68d4acd2014-07-01 19:40:41 -070032import com.android.internal.R;
Guang Zhu514c5802014-09-12 15:14:00 -070033
Andres Morales963295e2014-07-10 15:40:24 -070034import libcore.io.IoUtils;
Andres Morales68d4acd2014-07-01 19:40:41 -070035
36import java.io.DataInputStream;
37import java.io.DataOutputStream;
38import java.io.File;
39import java.io.FileInputStream;
40import java.io.FileNotFoundException;
41import java.io.FileOutputStream;
42import java.io.IOException;
43import java.nio.ByteBuffer;
44import java.nio.channels.FileChannel;
Andres Morales28301302014-11-12 07:56:46 -080045import java.security.MessageDigest;
46import java.security.NoSuchAlgorithmException;
47import java.util.Arrays;
Andres Morales68d4acd2014-07-01 19:40:41 -070048
49/**
50 * Service for reading and writing blocks to a persistent partition.
Andres Morales963295e2014-07-10 15:40:24 -070051 * This data will live across factory resets not initiated via the Settings UI.
52 * When a device is factory reset through Settings this data is wiped.
Andres Morales68d4acd2014-07-01 19:40:41 -070053 *
54 * Allows writing one block at a time. Namely, each time
55 * {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
56 * is called, it will overwite the data that was previously written on the block.
57 *
58 * Clients can query the size of the currently written block via
59 * {@link android.service.persistentdata.IPersistentDataBlockService}.getTotalDataSize().
60 *
61 * Clients can any number of bytes from the currently written block up to its total size by invoking
62 * {@link android.service.persistentdata.IPersistentDataBlockService}.read(byte[] data)
63 */
64public class PersistentDataBlockService extends SystemService {
65 private static final String TAG = PersistentDataBlockService.class.getSimpleName();
66
67 private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
68 private static final int HEADER_SIZE = 8;
Andres Morales963295e2014-07-10 15:40:24 -070069 // Magic number to mark block device as adhering to the format consumed by this service
Andres Morales28301302014-11-12 07:56:46 -080070 private static final int PARTITION_TYPE_MARKER = 0x19901873;
Andres Morales963295e2014-07-10 15:40:24 -070071 // Limit to 100k as blocks larger than this might cause strain on Binder.
Andres Morales963295e2014-07-10 15:40:24 -070072 private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
Andres Morales28301302014-11-12 07:56:46 -080073 public static final int DIGEST_SIZE_BYTES = 32;
Andres Morales5ca4cc52015-03-19 16:37:54 -070074 private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
Andres Morales68d4acd2014-07-01 19:40:41 -070075
76 private final Context mContext;
77 private final String mDataBlockFile;
Andres Morales963295e2014-07-10 15:40:24 -070078 private final Object mLock = new Object();
Andres Morales6429f312014-08-04 16:35:15 -070079
Andres Moralesa31c23d2014-10-30 15:31:31 -070080 private int mAllowedUid = -1;
Andres Morales963295e2014-07-10 15:40:24 -070081 private long mBlockDeviceSize;
Andres Morales68d4acd2014-07-01 19:40:41 -070082
83 public PersistentDataBlockService(Context context) {
84 super(context);
85 mContext = context;
86 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
Andres Morales963295e2014-07-10 15:40:24 -070087 mBlockDeviceSize = -1; // Load lazily
Xiaohui Chenf0660782015-09-02 14:25:19 -070088 mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
Andres Morales6429f312014-08-04 16:35:15 -070089 }
90
Andres Moralesa31c23d2014-10-30 15:31:31 -070091 private int getAllowedUid(int userHandle) {
Andres Morales6429f312014-08-04 16:35:15 -070092 String allowedPackage = mContext.getResources()
Andres Morales68d4acd2014-07-01 19:40:41 -070093 .getString(R.string.config_persistentDataPackageName);
94 PackageManager pm = mContext.getPackageManager();
95 int allowedUid = -1;
96 try {
Jeff Sharkeyc5967e92016-01-07 18:50:29 -070097 allowedUid = pm.getPackageUidAsUser(allowedPackage,
98 PackageManager.MATCH_SYSTEM_ONLY, userHandle);
Andres Morales68d4acd2014-07-01 19:40:41 -070099 } catch (PackageManager.NameNotFoundException e) {
100 // not expected
Andres Morales963295e2014-07-10 15:40:24 -0700101 Slog.e(TAG, "not able to find package " + allowedPackage, e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700102 }
Andres Moralesa31c23d2014-10-30 15:31:31 -0700103 return allowedUid;
Andres Morales68d4acd2014-07-01 19:40:41 -0700104 }
105
106 @Override
107 public void onStart() {
Andres Morales28301302014-11-12 07:56:46 -0800108 enforceChecksumValidity();
Andres Morales1ce7d172015-01-07 14:24:57 -0800109 formatIfOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700110 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
111 }
112
Andres Morales1ce7d172015-01-07 14:24:57 -0800113 private void formatIfOemUnlockEnabled() {
Andres Morales5ca4cc52015-03-19 16:37:54 -0700114 boolean enabled = doGetOemUnlockEnabled();
115 if (enabled) {
Andres Morales1ce7d172015-01-07 14:24:57 -0800116 synchronized (mLock) {
Andres Moralesc8f952c2015-03-19 08:34:55 -0700117 formatPartitionLocked(true);
Andres Morales1ce7d172015-01-07 14:24:57 -0800118 }
119 }
Andres Morales5ca4cc52015-03-19 16:37:54 -0700120
121 SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
Andres Morales1ce7d172015-01-07 14:24:57 -0800122 }
123
Andres Morales68d4acd2014-07-01 19:40:41 -0700124 private void enforceOemUnlockPermission() {
125 mContext.enforceCallingOrSelfPermission(
126 Manifest.permission.OEM_UNLOCK_STATE,
127 "Can't access OEM unlock state");
128 }
129
130 private void enforceUid(int callingUid) {
Andres Moralesa31c23d2014-10-30 15:31:31 -0700131 if (callingUid != mAllowedUid) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700132 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
133 }
134 }
135
Xiaohui Chenf0660782015-09-02 14:25:19 -0700136 private void enforceIsAdmin() {
137 final int userId = UserHandle.getCallingUserId();
138 final boolean isAdmin = UserManager.get(mContext).isUserAdmin(userId);
139 if (!isAdmin) {
140 throw new SecurityException(
141 "Only the Admin user is allowed to change OEM unlock state");
Andres Moralesa31c23d2014-10-30 15:31:31 -0700142 }
143 }
Andres Morales963295e2014-07-10 15:40:24 -0700144 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales28301302014-11-12 07:56:46 -0800145 // skip over checksum
146 inputStream.skipBytes(DIGEST_SIZE_BYTES);
147
Andres Morales68d4acd2014-07-01 19:40:41 -0700148 int totalDataSize;
149 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700150 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700151 totalDataSize = inputStream.readInt();
152 } else {
153 totalDataSize = 0;
154 }
155 return totalDataSize;
156 }
157
Andres Morales963295e2014-07-10 15:40:24 -0700158 private long getBlockDeviceSize() {
159 synchronized (mLock) {
160 if (mBlockDeviceSize == -1) {
161 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700162 }
163 }
164
165 return mBlockDeviceSize;
166 }
167
Andres Morales28301302014-11-12 07:56:46 -0800168 private boolean enforceChecksumValidity() {
169 byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
170
171 synchronized (mLock) {
172 byte[] digest = computeDigestLocked(storedDigest);
173 if (digest == null || !Arrays.equals(storedDigest, digest)) {
174 Slog.i(TAG, "Formatting FRP partition...");
Andres Moralesc8f952c2015-03-19 08:34:55 -0700175 formatPartitionLocked(false);
Andres Morales28301302014-11-12 07:56:46 -0800176 return false;
177 }
178 }
179
180 return true;
181 }
182
183 private boolean computeAndWriteDigestLocked() {
184 byte[] digest = computeDigestLocked(null);
185 if (digest != null) {
186 DataOutputStream outputStream;
187 try {
188 outputStream = new DataOutputStream(
189 new FileOutputStream(new File(mDataBlockFile)));
190 } catch (FileNotFoundException e) {
191 Slog.e(TAG, "partition not available?", e);
192 return false;
193 }
194
195 try {
196 outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
197 outputStream.flush();
198 } catch (IOException e) {
199 Slog.e(TAG, "failed to write block checksum", e);
200 return false;
201 } finally {
202 IoUtils.closeQuietly(outputStream);
203 }
204 return true;
205 } else {
206 return false;
207 }
208 }
209
210 private byte[] computeDigestLocked(byte[] storedDigest) {
211 DataInputStream inputStream;
212 try {
213 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
214 } catch (FileNotFoundException e) {
215 Slog.e(TAG, "partition not available?", e);
216 return null;
217 }
218
219 MessageDigest md;
220 try {
221 md = MessageDigest.getInstance("SHA-256");
222 } catch (NoSuchAlgorithmException e) {
223 // won't ever happen -- every implementation is required to support SHA-256
224 Slog.e(TAG, "SHA-256 not supported?", e);
225 IoUtils.closeQuietly(inputStream);
226 return null;
227 }
228
229 try {
230 if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
231 inputStream.read(storedDigest);
232 } else {
233 inputStream.skipBytes(DIGEST_SIZE_BYTES);
234 }
235
236 int read;
237 byte[] data = new byte[1024];
238 md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
239 while ((read = inputStream.read(data)) != -1) {
240 md.update(data, 0, read);
241 }
242 } catch (IOException e) {
243 Slog.e(TAG, "failed to read partition", e);
244 return null;
245 } finally {
246 IoUtils.closeQuietly(inputStream);
247 }
248
249 return md.digest();
250 }
251
Andres Moralesc8f952c2015-03-19 08:34:55 -0700252 private void formatPartitionLocked(boolean setOemUnlockEnabled) {
Andres Morales28301302014-11-12 07:56:46 -0800253 DataOutputStream outputStream;
254 try {
255 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
256 } catch (FileNotFoundException e) {
257 Slog.e(TAG, "partition not available?", e);
258 return;
259 }
260
261 byte[] data = new byte[DIGEST_SIZE_BYTES];
262 try {
263 outputStream.write(data, 0, DIGEST_SIZE_BYTES);
264 outputStream.writeInt(PARTITION_TYPE_MARKER);
265 outputStream.writeInt(0); // data size
266 outputStream.flush();
267 } catch (IOException e) {
268 Slog.e(TAG, "failed to format block", e);
269 return;
270 } finally {
271 IoUtils.closeQuietly(outputStream);
272 }
273
Andres Moralesc8f952c2015-03-19 08:34:55 -0700274 doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
Andres Morales28301302014-11-12 07:56:46 -0800275 computeAndWriteDigestLocked();
276 }
277
278 private void doSetOemUnlockEnabledLocked(boolean enabled) {
279 FileOutputStream outputStream;
280 try {
281 outputStream = new FileOutputStream(new File(mDataBlockFile));
282 } catch (FileNotFoundException e) {
283 Slog.e(TAG, "partition not available", e);
284 return;
285 }
286
287 try {
288 FileChannel channel = outputStream.getChannel();
289
290 channel.position(getBlockDeviceSize() - 1);
291
292 ByteBuffer data = ByteBuffer.allocate(1);
293 data.put(enabled ? (byte) 1 : (byte) 0);
294 data.flip();
295 channel.write(data);
296 outputStream.flush();
297 } catch (IOException e) {
298 Slog.e(TAG, "unable to access persistent partition", e);
299 return;
300 } finally {
Andres Morales5ca4cc52015-03-19 16:37:54 -0700301 SystemProperties.set(OEM_UNLOCK_PROP, enabled ? "1" : "0");
Andres Morales28301302014-11-12 07:56:46 -0800302 IoUtils.closeQuietly(outputStream);
303 }
304 }
305
Andres Morales1ce7d172015-01-07 14:24:57 -0800306 private boolean doGetOemUnlockEnabled() {
307 DataInputStream inputStream;
308 try {
309 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
310 } catch (FileNotFoundException e) {
311 Slog.e(TAG, "partition not available");
312 return false;
313 }
314
315 try {
316 synchronized (mLock) {
317 inputStream.skip(getBlockDeviceSize() - 1);
318 return inputStream.readByte() != 0;
319 }
320 } catch (IOException e) {
321 Slog.e(TAG, "unable to access persistent partition", e);
322 return false;
323 } finally {
324 IoUtils.closeQuietly(inputStream);
325 }
326 }
327
Andres Morales963295e2014-07-10 15:40:24 -0700328 private native long nativeGetBlockDeviceSize(String path);
329 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700330
331 private final IBinder mService = new IPersistentDataBlockService.Stub() {
332 @Override
333 public int write(byte[] data) throws RemoteException {
334 enforceUid(Binder.getCallingUid());
335
336 // Need to ensure we don't write over the last byte
Andres Morales963295e2014-07-10 15:40:24 -0700337 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
338 if (data.length > maxBlockSize) {
339 // partition is ~500k so shouldn't be a problem to downcast
340 return (int) -maxBlockSize;
Andres Morales68d4acd2014-07-01 19:40:41 -0700341 }
342
343 DataOutputStream outputStream;
344 try {
345 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
346 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700347 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700348 return -1;
349 }
350
351 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700352 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700353 headerAndData.putInt(data.length);
354 headerAndData.put(data);
355
Andres Morales28301302014-11-12 07:56:46 -0800356 synchronized (mLock) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700357 try {
Andres Morales28301302014-11-12 07:56:46 -0800358 byte[] checksum = new byte[DIGEST_SIZE_BYTES];
359 outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
360 outputStream.write(headerAndData.array());
361 outputStream.flush();
Andres Morales68d4acd2014-07-01 19:40:41 -0700362 } catch (IOException e) {
Andres Morales28301302014-11-12 07:56:46 -0800363 Slog.e(TAG, "failed writing to the persistent data block", e);
364 return -1;
365 } finally {
366 IoUtils.closeQuietly(outputStream);
367 }
368
369 if (computeAndWriteDigestLocked()) {
370 return data.length;
371 } else {
372 return -1;
Andres Morales68d4acd2014-07-01 19:40:41 -0700373 }
374 }
375 }
376
377 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700378 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700379 enforceUid(Binder.getCallingUid());
Andres Morales28301302014-11-12 07:56:46 -0800380 if (!enforceChecksumValidity()) {
381 return new byte[0];
382 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700383
384 DataInputStream inputStream;
385 try {
386 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
387 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700388 Slog.e(TAG, "partition not available?", e);
389 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700390 }
391
392 try {
Andres Morales963295e2014-07-10 15:40:24 -0700393 synchronized (mLock) {
394 int totalDataSize = getTotalDataSizeLocked(inputStream);
395
396 if (totalDataSize == 0) {
397 return new byte[0];
398 }
399
400 byte[] data = new byte[totalDataSize];
401 int read = inputStream.read(data, 0, totalDataSize);
402 if (read < totalDataSize) {
403 // something went wrong, not returning potentially corrupt data
404 Slog.e(TAG, "failed to read entire data block. bytes read: " +
405 read + "/" + totalDataSize);
406 return null;
407 }
408 return data;
409 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700410 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700411 Slog.e(TAG, "failed to read data", e);
412 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700413 } finally {
414 try {
415 inputStream.close();
416 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700417 Slog.e(TAG, "failed to close OutputStream");
418 }
419 }
420 }
421
422 @Override
423 public void wipe() {
424 enforceOemUnlockPermission();
425
426 synchronized (mLock) {
427 int ret = nativeWipe(mDataBlockFile);
428
429 if (ret < 0) {
430 Slog.e(TAG, "failed to wipe persistent partition");
Andres Morales68d4acd2014-07-01 19:40:41 -0700431 }
432 }
433 }
434
435 @Override
436 public void setOemUnlockEnabled(boolean enabled) {
Guang Zhu514c5802014-09-12 15:14:00 -0700437 // do not allow monkey to flip the flag
438 if (ActivityManager.isUserAMonkey()) {
439 return;
440 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700441 enforceOemUnlockPermission();
Xiaohui Chenf0660782015-09-02 14:25:19 -0700442 enforceIsAdmin();
Andres Morales68d4acd2014-07-01 19:40:41 -0700443
Andres Morales28301302014-11-12 07:56:46 -0800444 synchronized (mLock) {
445 doSetOemUnlockEnabledLocked(enabled);
446 computeAndWriteDigestLocked();
Andres Morales68d4acd2014-07-01 19:40:41 -0700447 }
448 }
449
450 @Override
451 public boolean getOemUnlockEnabled() {
452 enforceOemUnlockPermission();
Andres Morales1ce7d172015-01-07 14:24:57 -0800453 return doGetOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700454 }
455
456 @Override
457 public int getDataBlockSize() {
Craig Lafayette66445a62015-03-27 09:01:43 -0400458 enforcePersistentDataBlockAccess();
Andres Morales68d4acd2014-07-01 19:40:41 -0700459
460 DataInputStream inputStream;
461 try {
462 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
463 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700464 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700465 return 0;
466 }
467
468 try {
Andres Morales963295e2014-07-10 15:40:24 -0700469 synchronized (mLock) {
470 return getTotalDataSizeLocked(inputStream);
471 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700472 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700473 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700474 return 0;
475 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700476 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700477 }
478 }
Andres Morales963295e2014-07-10 15:40:24 -0700479
Craig Lafayette66445a62015-03-27 09:01:43 -0400480 private void enforcePersistentDataBlockAccess() {
481 if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
482 != PackageManager.PERMISSION_GRANTED) {
483 enforceUid(Binder.getCallingUid());
484 }
485 }
486
Andres Morales963295e2014-07-10 15:40:24 -0700487 @Override
488 public long getMaximumDataBlockSize() {
489 long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
490 return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
491 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700492 };
493}