blob: 17edb5372cdd600e94f72d47881e31645eb4e4af [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 Morales68d4acd2014-07-01 19:40:41 -070073
74 private final Context mContext;
75 private final String mDataBlockFile;
Andres Morales963295e2014-07-10 15:40:24 -070076 private final Object mLock = new Object();
Andres Morales6429f312014-08-04 16:35:15 -070077
Andres Moralesa31c23d2014-10-30 15:31:31 -070078 private int mAllowedUid = -1;
Andres Morales963295e2014-07-10 15:40:24 -070079 private long mBlockDeviceSize;
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 Morales68d4acd2014-07-01 19:40:41 -0700106 publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
107 }
108
109 private void enforceOemUnlockPermission() {
110 mContext.enforceCallingOrSelfPermission(
111 Manifest.permission.OEM_UNLOCK_STATE,
112 "Can't access OEM unlock state");
113 }
114
115 private void enforceUid(int callingUid) {
Andres Moralesa31c23d2014-10-30 15:31:31 -0700116 if (callingUid != mAllowedUid) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700117 throw new SecurityException("uid " + callingUid + " not allowed to access PST");
118 }
119 }
120
Andres Moralesa31c23d2014-10-30 15:31:31 -0700121 private void enforceIsOwner() {
122 if (!Binder.getCallingUserHandle().isOwner()) {
123 throw new SecurityException("Only the Owner is allowed to change OEM unlock state");
124 }
125 }
126
Andres Morales963295e2014-07-10 15:40:24 -0700127 private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
Andres Morales28301302014-11-12 07:56:46 -0800128 // skip over checksum
129 inputStream.skipBytes(DIGEST_SIZE_BYTES);
130
Andres Morales68d4acd2014-07-01 19:40:41 -0700131 int totalDataSize;
132 int blockId = inputStream.readInt();
Andres Morales963295e2014-07-10 15:40:24 -0700133 if (blockId == PARTITION_TYPE_MARKER) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700134 totalDataSize = inputStream.readInt();
135 } else {
136 totalDataSize = 0;
137 }
138 return totalDataSize;
139 }
140
Andres Morales963295e2014-07-10 15:40:24 -0700141 private long getBlockDeviceSize() {
142 synchronized (mLock) {
143 if (mBlockDeviceSize == -1) {
144 mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
Andres Morales68d4acd2014-07-01 19:40:41 -0700145 }
146 }
147
148 return mBlockDeviceSize;
149 }
150
Andres Morales28301302014-11-12 07:56:46 -0800151 private boolean enforceChecksumValidity() {
152 byte[] storedDigest = new byte[DIGEST_SIZE_BYTES];
153
154 synchronized (mLock) {
155 byte[] digest = computeDigestLocked(storedDigest);
156 if (digest == null || !Arrays.equals(storedDigest, digest)) {
157 Slog.i(TAG, "Formatting FRP partition...");
158 formatPartitionLocked();
159 return false;
160 }
161 }
162
163 return true;
164 }
165
166 private boolean computeAndWriteDigestLocked() {
167 byte[] digest = computeDigestLocked(null);
168 if (digest != null) {
169 DataOutputStream outputStream;
170 try {
171 outputStream = new DataOutputStream(
172 new FileOutputStream(new File(mDataBlockFile)));
173 } catch (FileNotFoundException e) {
174 Slog.e(TAG, "partition not available?", e);
175 return false;
176 }
177
178 try {
179 outputStream.write(digest, 0, DIGEST_SIZE_BYTES);
180 outputStream.flush();
181 } catch (IOException e) {
182 Slog.e(TAG, "failed to write block checksum", e);
183 return false;
184 } finally {
185 IoUtils.closeQuietly(outputStream);
186 }
187 return true;
188 } else {
189 return false;
190 }
191 }
192
193 private byte[] computeDigestLocked(byte[] storedDigest) {
194 DataInputStream inputStream;
195 try {
196 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
197 } catch (FileNotFoundException e) {
198 Slog.e(TAG, "partition not available?", e);
199 return null;
200 }
201
202 MessageDigest md;
203 try {
204 md = MessageDigest.getInstance("SHA-256");
205 } catch (NoSuchAlgorithmException e) {
206 // won't ever happen -- every implementation is required to support SHA-256
207 Slog.e(TAG, "SHA-256 not supported?", e);
208 IoUtils.closeQuietly(inputStream);
209 return null;
210 }
211
212 try {
213 if (storedDigest != null && storedDigest.length == DIGEST_SIZE_BYTES) {
214 inputStream.read(storedDigest);
215 } else {
216 inputStream.skipBytes(DIGEST_SIZE_BYTES);
217 }
218
219 int read;
220 byte[] data = new byte[1024];
221 md.update(data, 0, DIGEST_SIZE_BYTES); // include 0 checksum in digest
222 while ((read = inputStream.read(data)) != -1) {
223 md.update(data, 0, read);
224 }
225 } catch (IOException e) {
226 Slog.e(TAG, "failed to read partition", e);
227 return null;
228 } finally {
229 IoUtils.closeQuietly(inputStream);
230 }
231
232 return md.digest();
233 }
234
235 private void formatPartitionLocked() {
236 DataOutputStream outputStream;
237 try {
238 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
239 } catch (FileNotFoundException e) {
240 Slog.e(TAG, "partition not available?", e);
241 return;
242 }
243
244 byte[] data = new byte[DIGEST_SIZE_BYTES];
245 try {
246 outputStream.write(data, 0, DIGEST_SIZE_BYTES);
247 outputStream.writeInt(PARTITION_TYPE_MARKER);
248 outputStream.writeInt(0); // data size
249 outputStream.flush();
250 } catch (IOException e) {
251 Slog.e(TAG, "failed to format block", e);
252 return;
253 } finally {
254 IoUtils.closeQuietly(outputStream);
255 }
256
257 doSetOemUnlockEnabledLocked(false);
258 computeAndWriteDigestLocked();
259 }
260
261 private void doSetOemUnlockEnabledLocked(boolean enabled) {
262 FileOutputStream outputStream;
263 try {
264 outputStream = new FileOutputStream(new File(mDataBlockFile));
265 } catch (FileNotFoundException e) {
266 Slog.e(TAG, "partition not available", e);
267 return;
268 }
269
270 try {
271 FileChannel channel = outputStream.getChannel();
272
273 channel.position(getBlockDeviceSize() - 1);
274
275 ByteBuffer data = ByteBuffer.allocate(1);
276 data.put(enabled ? (byte) 1 : (byte) 0);
277 data.flip();
278 channel.write(data);
279 outputStream.flush();
280 } catch (IOException e) {
281 Slog.e(TAG, "unable to access persistent partition", e);
282 return;
283 } finally {
284 IoUtils.closeQuietly(outputStream);
285 }
286 }
287
Andres Morales963295e2014-07-10 15:40:24 -0700288 private native long nativeGetBlockDeviceSize(String path);
289 private native int nativeWipe(String path);
Andres Morales68d4acd2014-07-01 19:40:41 -0700290
291 private final IBinder mService = new IPersistentDataBlockService.Stub() {
292 @Override
293 public int write(byte[] data) throws RemoteException {
294 enforceUid(Binder.getCallingUid());
295
296 // Need to ensure we don't write over the last byte
Andres Morales963295e2014-07-10 15:40:24 -0700297 long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
298 if (data.length > maxBlockSize) {
299 // partition is ~500k so shouldn't be a problem to downcast
300 return (int) -maxBlockSize;
Andres Morales68d4acd2014-07-01 19:40:41 -0700301 }
302
303 DataOutputStream outputStream;
304 try {
305 outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
306 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700307 Slog.e(TAG, "partition not available?", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700308 return -1;
309 }
310
311 ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
Andres Morales963295e2014-07-10 15:40:24 -0700312 headerAndData.putInt(PARTITION_TYPE_MARKER);
Andres Morales68d4acd2014-07-01 19:40:41 -0700313 headerAndData.putInt(data.length);
314 headerAndData.put(data);
315
Andres Morales28301302014-11-12 07:56:46 -0800316 synchronized (mLock) {
Andres Morales68d4acd2014-07-01 19:40:41 -0700317 try {
Andres Morales28301302014-11-12 07:56:46 -0800318 byte[] checksum = new byte[DIGEST_SIZE_BYTES];
319 outputStream.write(checksum, 0, DIGEST_SIZE_BYTES);
320 outputStream.write(headerAndData.array());
321 outputStream.flush();
Andres Morales68d4acd2014-07-01 19:40:41 -0700322 } catch (IOException e) {
Andres Morales28301302014-11-12 07:56:46 -0800323 Slog.e(TAG, "failed writing to the persistent data block", e);
324 return -1;
325 } finally {
326 IoUtils.closeQuietly(outputStream);
327 }
328
329 if (computeAndWriteDigestLocked()) {
330 return data.length;
331 } else {
332 return -1;
Andres Morales68d4acd2014-07-01 19:40:41 -0700333 }
334 }
335 }
336
337 @Override
Andres Morales963295e2014-07-10 15:40:24 -0700338 public byte[] read() {
Andres Morales68d4acd2014-07-01 19:40:41 -0700339 enforceUid(Binder.getCallingUid());
Andres Morales28301302014-11-12 07:56:46 -0800340 if (!enforceChecksumValidity()) {
341 return new byte[0];
342 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700343
344 DataInputStream inputStream;
345 try {
346 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
347 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700348 Slog.e(TAG, "partition not available?", e);
349 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700350 }
351
352 try {
Andres Morales963295e2014-07-10 15:40:24 -0700353 synchronized (mLock) {
354 int totalDataSize = getTotalDataSizeLocked(inputStream);
355
356 if (totalDataSize == 0) {
357 return new byte[0];
358 }
359
360 byte[] data = new byte[totalDataSize];
361 int read = inputStream.read(data, 0, totalDataSize);
362 if (read < totalDataSize) {
363 // something went wrong, not returning potentially corrupt data
364 Slog.e(TAG, "failed to read entire data block. bytes read: " +
365 read + "/" + totalDataSize);
366 return null;
367 }
368 return data;
369 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700370 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700371 Slog.e(TAG, "failed to read data", e);
372 return null;
Andres Morales68d4acd2014-07-01 19:40:41 -0700373 } finally {
374 try {
375 inputStream.close();
376 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700377 Slog.e(TAG, "failed to close OutputStream");
378 }
379 }
380 }
381
382 @Override
383 public void wipe() {
384 enforceOemUnlockPermission();
385
386 synchronized (mLock) {
387 int ret = nativeWipe(mDataBlockFile);
388
389 if (ret < 0) {
390 Slog.e(TAG, "failed to wipe persistent partition");
Andres Morales68d4acd2014-07-01 19:40:41 -0700391 }
392 }
393 }
394
395 @Override
396 public void setOemUnlockEnabled(boolean enabled) {
Guang Zhu514c5802014-09-12 15:14:00 -0700397 // do not allow monkey to flip the flag
398 if (ActivityManager.isUserAMonkey()) {
399 return;
400 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700401 enforceOemUnlockPermission();
Andres Moralesa31c23d2014-10-30 15:31:31 -0700402 enforceIsOwner();
Andres Morales68d4acd2014-07-01 19:40:41 -0700403
Andres Morales28301302014-11-12 07:56:46 -0800404 synchronized (mLock) {
405 doSetOemUnlockEnabledLocked(enabled);
406 computeAndWriteDigestLocked();
Andres Morales68d4acd2014-07-01 19:40:41 -0700407 }
408 }
409
410 @Override
411 public boolean getOemUnlockEnabled() {
412 enforceOemUnlockPermission();
413 DataInputStream inputStream;
414 try {
415 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
416 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700417 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700418 return false;
419 }
420
421 try {
Andres Morales28301302014-11-12 07:56:46 -0800422 synchronized (mLock) {
423 inputStream.skip(getBlockDeviceSize() - 1);
Andres Morales963295e2014-07-10 15:40:24 -0700424 return inputStream.readByte() != 0;
425 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700426 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700427 Slog.e(TAG, "unable to access persistent partition", e);
Andres Morales68d4acd2014-07-01 19:40:41 -0700428 return false;
429 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700430 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700431 }
432 }
433
434 @Override
435 public int getDataBlockSize() {
436 enforceUid(Binder.getCallingUid());
437
438 DataInputStream inputStream;
439 try {
440 inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
441 } catch (FileNotFoundException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700442 Slog.e(TAG, "partition not available");
Andres Morales68d4acd2014-07-01 19:40:41 -0700443 return 0;
444 }
445
446 try {
Andres Morales963295e2014-07-10 15:40:24 -0700447 synchronized (mLock) {
448 return getTotalDataSizeLocked(inputStream);
449 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700450 } catch (IOException e) {
Andres Morales963295e2014-07-10 15:40:24 -0700451 Slog.e(TAG, "error reading data block size");
Andres Morales68d4acd2014-07-01 19:40:41 -0700452 return 0;
453 } finally {
Andres Morales963295e2014-07-10 15:40:24 -0700454 IoUtils.closeQuietly(inputStream);
Andres Morales68d4acd2014-07-01 19:40:41 -0700455 }
456 }
Andres Morales963295e2014-07-10 15:40:24 -0700457
458 @Override
459 public long getMaximumDataBlockSize() {
460 long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
461 return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
462 }
Andres Morales68d4acd2014-07-01 19:40:41 -0700463 };
464}