blob: 35a740263a8eb0c3eb7c8bf11e8438ce3a7ea306 [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 *
Charles Hea9437bd2016-11-24 14:05:00 +000053 * Allows writing one block at a time. Namely, each time {@link IPersistentDataBlockService#write}
54 * is called, it will overwrite the data that was previously written on the block.
Andres Morales68d4acd2014-07-01 19:40:41 -070055 *
56 * Clients can query the size of the currently written block via
Charles Hea9437bd2016-11-24 14:05:00 +000057 * {@link IPersistentDataBlockService#getDataBlockSize}
Andres Morales68d4acd2014-07-01 19:40:41 -070058 *
Charles Hea9437bd2016-11-24 14:05:00 +000059 * Clients can read any number of bytes from the currently written block up to its total size by
60 * invoking {@link IPersistentDataBlockService#read}
Andres Morales68d4acd2014-07-01 19:40:41 -070061 */
62public class PersistentDataBlockService extends SystemService {
63 private static final String TAG = PersistentDataBlockService.class.getSimpleName();
64
65 private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
66 private static final int HEADER_SIZE = 8;
Andres Morales963295e2014-07-10 15:40:24 -070067 // Magic number to mark block device as adhering to the format consumed by this service
Andres Morales28301302014-11-12 07:56:46 -080068 private static final int PARTITION_TYPE_MARKER = 0x19901873;
Andres Morales963295e2014-07-10 15:40:24 -070069 // Limit to 100k as blocks larger than this might cause strain on Binder.
Andres Morales963295e2014-07-10 15:40:24 -070070 private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
Andres Morales28301302014-11-12 07:56:46 -080071 public static final int DIGEST_SIZE_BYTES = 32;
Andres Morales68d4acd2014-07-01 19:40:41 -070072
73 private final Context mContext;
74 private final String mDataBlockFile;
Andres Morales963295e2014-07-10 15:40:24 -070075 private final Object mLock = new Object();
Andres Morales6429f312014-08-04 16:35:15 -070076
Andres Moralesa31c23d2014-10-30 15:31:31 -070077 private int mAllowedUid = -1;
Andres Morales963295e2014-07-10 15:40:24 -070078 private long mBlockDeviceSize;
Charles Hea9437bd2016-11-24 14:05:00 +000079 private boolean mIsWritable = true;
Andres Morales68d4acd2014-07-01 19:40:41 -070080
81 public PersistentDataBlockService(Context context) {
82 super(context);
83 mContext = context;
84 mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
Andres Morales963295e2014-07-10 15:40:24 -070085 mBlockDeviceSize = -1; // Load lazily
Andres Moralesa31c23d2014-10-30 15:31:31 -070086 mAllowedUid = getAllowedUid(UserHandle.USER_OWNER);
Andres Morales6429f312014-08-04 16:35:15 -070087 }
88
Andres Moralesa31c23d2014-10-30 15:31:31 -070089 private int getAllowedUid(int userHandle) {
Andres Morales6429f312014-08-04 16:35:15 -070090 String allowedPackage = mContext.getResources()
Andres Morales68d4acd2014-07-01 19:40:41 -070091 .getString(R.string.config_persistentDataPackageName);
92 PackageManager pm = mContext.getPackageManager();
93 int allowedUid = -1;
94 try {
Andres Morales6429f312014-08-04 16:35:15 -070095 allowedUid = pm.getPackageUid(allowedPackage, userHandle);
Andres Morales68d4acd2014-07-01 19:40:41 -070096 } catch (PackageManager.NameNotFoundException e) {
97 // not expected
Andres Morales963295e2014-07-10 15:40:24 -070098 Slog.e(TAG, "not able to find package " + allowedPackage, e);
Andres Morales68d4acd2014-07-01 19:40:41 -070099 }
Andres Moralesa31c23d2014-10-30 15:31:31 -0700100 return allowedUid;
Andres Morales68d4acd2014-07-01 19:40:41 -0700101 }
102
103 @Override
104 public void onStart() {
Andres Morales28301302014-11-12 07:56:46 -0800105 enforceChecksumValidity();
Andres Morales1ce7d172015-01-07 14:24:57 -0800106 formatIfOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700107 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
108 }
109
Andres Morales1ce7d172015-01-07 14:24:57 -0800110 private void formatIfOemUnlockEnabled() {
111 if (doGetOemUnlockEnabled()) {
112 synchronized (mLock) {
Andres Moralesc8f952c2015-03-19 08:34:55 -0700113 formatPartitionLocked(true);
Andres Morales1ce7d172015-01-07 14:24:57 -0800114 }
115 }
116 }
117
Andres Morales68d4acd2014-07-01 19:40:41 -0700118 private void enforceOemUnlockPermission() {
119 mContext.enforceCallingOrSelfPermission(
120 Manifest.permission.OEM_UNLOCK_STATE,
121 "Can't access OEM unlock state");
122 }
123
124 private void enforceUid(int callingUid) {
Andres Moralesa31c23d2014-10-30 15:31:31 -0700125 if (callingUid != mAllowedUid) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700126 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
127 }
128 }
129
Andres Moralesa31c23d2014-10-30 15:31:31 -0700130 private void enforceIsOwner() {
131 if (!Binder.getCallingUserHandle().isOwner()) {
132 throw new SecurityException("Only the Owner is allowed to change OEM unlock state");
133 }
134 }
135
Andres Morales963295e2014-07-10 15:40:24 -0700136 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales28301302014-11-12 07:56:46 -0800137 // skip over checksum
138 inputStream.skipBytes(DIGEST_SIZE_BYTES);
139
Andres Morales68d4acd2014-07-01 19:40:41 -0700140 int totalDataSize;
141 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700142 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700143 totalDataSize = inputStream.readInt();
144 } else {
145 totalDataSize = 0;
146 }
147 return totalDataSize;
148 }
149
Andres Morales963295e2014-07-10 15:40:24 -0700150 private long getBlockDeviceSize() {
151 synchronized (mLock) {
152 if (mBlockDeviceSize == -1) {
153 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700154 }
155 }
156
157 return mBlockDeviceSize;
158 }
159
Andres Morales28301302014-11-12 07:56:46 -0800160 private boolean enforceChecksumValidity() {
161 byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
162
163 synchronized (mLock) {
164 byte[] digest = computeDigestLocked(storedDigest);
165 if (digest == null || !Arrays.equals(storedDigest, digest)) {
166 Slog.i(TAG, "Formatting FRP partition...");
Andres Moralesc8f952c2015-03-19 08:34:55 -0700167 formatPartitionLocked(false);
Andres Morales28301302014-11-12 07:56:46 -0800168 return false;
169 }
170 }
171
172 return true;
173 }
174
175 private boolean computeAndWriteDigestLocked() {
176 byte[] digest = computeDigestLocked(null);
177 if (digest != null) {
178 DataOutputStream outputStream;
179 try {
180 outputStream = new DataOutputStream(
181 new FileOutputStream(new File(mDataBlockFile)));
182 } catch (FileNotFoundException e) {
183 Slog.e(TAG, "partition not available?", e);
184 return false;
185 }
186
187 try {
188 outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
189 outputStream.flush();
190 } catch (IOException e) {
191 Slog.e(TAG, "failed to write block checksum", e);
192 return false;
193 } finally {
194 IoUtils.closeQuietly(outputStream);
195 }
196 return true;
197 } else {
198 return false;
199 }
200 }
201
202 private byte[] computeDigestLocked(byte[] storedDigest) {
203 DataInputStream inputStream;
204 try {
205 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
206 } catch (FileNotFoundException e) {
207 Slog.e(TAG, "partition not available?", e);
208 return null;
209 }
210
211 MessageDigest md;
212 try {
213 md = MessageDigest.getInstance("SHA-256");
214 } catch (NoSuchAlgorithmException e) {
215 // won't ever happen -- every implementation is required to support SHA-256
216 Slog.e(TAG, "SHA-256 not supported?", e);
217 IoUtils.closeQuietly(inputStream);
218 return null;
219 }
220
221 try {
222 if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
223 inputStream.read(storedDigest);
224 } else {
225 inputStream.skipBytes(DIGEST_SIZE_BYTES);
226 }
227
228 int read;
229 byte[] data = new byte[1024];
230 md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
231 while ((read = inputStream.read(data)) != -1) {
232 md.update(data, 0, read);
233 }
234 } catch (IOException e) {
235 Slog.e(TAG, "failed to read partition", e);
236 return null;
237 } finally {
238 IoUtils.closeQuietly(inputStream);
239 }
240
241 return md.digest();
242 }
243
Andres Moralesc8f952c2015-03-19 08:34:55 -0700244 private void formatPartitionLocked(boolean setOemUnlockEnabled) {
Andres Morales28301302014-11-12 07:56:46 -0800245 DataOutputStream outputStream;
246 try {
247 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
248 } catch (FileNotFoundException e) {
249 Slog.e(TAG, "partition not available?", e);
250 return;
251 }
252
253 byte[] data = new byte[DIGEST_SIZE_BYTES];
254 try {
255 outputStream.write(data, 0, DIGEST_SIZE_BYTES);
256 outputStream.writeInt(PARTITION_TYPE_MARKER);
257 outputStream.writeInt(0); // data size
258 outputStream.flush();
259 } catch (IOException e) {
260 Slog.e(TAG, "failed to format block", e);
261 return;
262 } finally {
263 IoUtils.closeQuietly(outputStream);
264 }
265
Andres Moralesc8f952c2015-03-19 08:34:55 -0700266 doSetOemUnlockEnabledLocked(setOemUnlockEnabled);
Andres Morales28301302014-11-12 07:56:46 -0800267 computeAndWriteDigestLocked();
268 }
269
270 private void doSetOemUnlockEnabledLocked(boolean enabled) {
271 FileOutputStream outputStream;
272 try {
273 outputStream = new FileOutputStream(new File(mDataBlockFile));
274 } catch (FileNotFoundException e) {
275 Slog.e(TAG, "partition not available", e);
276 return;
277 }
278
279 try {
280 FileChannel channel = outputStream.getChannel();
281
282 channel.position(getBlockDeviceSize() - 1);
283
284 ByteBuffer data = ByteBuffer.allocate(1);
285 data.put(enabled ? (byte) 1 : (byte) 0);
286 data.flip();
287 channel.write(data);
288 outputStream.flush();
289 } catch (IOException e) {
290 Slog.e(TAG, "unable to access persistent partition", e);
291 return;
292 } finally {
293 IoUtils.closeQuietly(outputStream);
294 }
295 }
296
Andres Morales1ce7d172015-01-07 14:24:57 -0800297 private boolean doGetOemUnlockEnabled() {
298 DataInputStream inputStream;
299 try {
300 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
301 } catch (FileNotFoundException e) {
302 Slog.e(TAG, "partition not available");
303 return false;
304 }
305
306 try {
307 synchronized (mLock) {
308 inputStream.skip(getBlockDeviceSize() - 1);
309 return inputStream.readByte() != 0;
310 }
311 } catch (IOException e) {
312 Slog.e(TAG, "unable to access persistent partition", e);
313 return false;
314 } finally {
315 IoUtils.closeQuietly(inputStream);
316 }
317 }
318
Andres Morales963295e2014-07-10 15:40:24 -0700319 private native long nativeGetBlockDeviceSize(String path);
320 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700321
322 private final IBinder mService = new IPersistentDataBlockService.Stub() {
323 @Override
324 public int write(byte[] data) throws RemoteException {
325 enforceUid(Binder.getCallingUid());
326
327 // Need to ensure we don't write over the last byte
Andres Morales963295e2014-07-10 15:40:24 -0700328 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
329 if (data.length > maxBlockSize) {
330 // partition is ~500k so shouldn't be a problem to downcast
331 return (int) -maxBlockSize;
Andres Morales68d4acd2014-07-01 19:40:41 -0700332 }
333
334 DataOutputStream outputStream;
335 try {
336 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
337 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700338 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700339 return -1;
340 }
341
342 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700343 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700344 headerAndData.putInt(data.length);
345 headerAndData.put(data);
346
Andres Morales28301302014-11-12 07:56:46 -0800347 synchronized (mLock) {
Charles Hea9437bd2016-11-24 14:05:00 +0000348 if (!mIsWritable) {
349 IoUtils.closeQuietly(outputStream);
350 return -1;
351 }
352
Andres Morales68d4acd2014-07-01 19:40:41 -0700353 try {
Andres Morales28301302014-11-12 07:56:46 -0800354 byte[] checksum = new byte[DIGEST_SIZE_BYTES];
355 outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
356 outputStream.write(headerAndData.array());
357 outputStream.flush();
Andres Morales68d4acd2014-07-01 19:40:41 -0700358 } catch (IOException e) {
Andres Morales28301302014-11-12 07:56:46 -0800359 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;
Andres Morales68d4acd2014-07-01 19:40:41 -0700369 }
370 }
371 }
372
373 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700374 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700375 enforceUid(Binder.getCallingUid());
Andres Morales28301302014-11-12 07:56:46 -0800376 if (!enforceChecksumValidity()) {
377 return new byte[0];
378 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700379
380 DataInputStream inputStream;
381 try {
382 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
383 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700384 Slog.e(TAG, "partition not available?", e);
385 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700386 }
387
388 try {
Andres Morales963295e2014-07-10 15:40:24 -0700389 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 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700406 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700407 Slog.e(TAG, "failed to read data", e);
408 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700409 } finally {
410 try {
411 inputStream.close();
412 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700413 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");
Charles Hea9437bd2016-11-24 14:05:00 +0000427 } else {
428 mIsWritable = false;
429 Slog.i(TAG, "persistent partition now wiped and unwritable");
Andres Morales68d4acd2014-07-01 19:40:41 -0700430 }
431 }
432 }
433
434 @Override
435 public void setOemUnlockEnabled(boolean enabled) {
Guang Zhu514c5802014-09-12 15:14:00 -0700436 // do not allow monkey to flip the flag
437 if (ActivityManager.isUserAMonkey()) {
438 return;
439 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700440 enforceOemUnlockPermission();
Andres Moralesa31c23d2014-10-30 15:31:31 -0700441 enforceIsOwner();
Andres Morales68d4acd2014-07-01 19:40:41 -0700442
Andres Morales28301302014-11-12 07:56:46 -0800443 synchronized (mLock) {
444 doSetOemUnlockEnabledLocked(enabled);
445 computeAndWriteDigestLocked();
Andres Morales68d4acd2014-07-01 19:40:41 -0700446 }
447 }
448
449 @Override
450 public boolean getOemUnlockEnabled() {
451 enforceOemUnlockPermission();
Andres Morales1ce7d172015-01-07 14:24:57 -0800452 return doGetOemUnlockEnabled();
Andres Morales68d4acd2014-07-01 19:40:41 -0700453 }
454
455 @Override
456 public int getDataBlockSize() {
Andres Morales6b0c7ac2014-11-24 14:14:54 -0800457 if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
458 != PackageManager.PERMISSION_GRANTED) {
459 enforceUid(Binder.getCallingUid());
460 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700461
462 DataInputStream inputStream;
463 try {
464 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
465 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700466 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700467 return 0;
468 }
469
470 try {
Andres Morales963295e2014-07-10 15:40:24 -0700471 synchronized (mLock) {
472 return getTotalDataSizeLocked(inputStream);
473 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700474 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700475 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700476 return 0;
477 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700478 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700479 }
480 }
Andres Morales963295e2014-07-10 15:40:24 -0700481
482 @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}