Permit settings to "wipe" the persistent partition
One of the requirements is that when the user does a
factory reset through settings, all data on the
persistent partition should be cleared.
This adds one last API method that allows settings
to wipe the partition.
Bug: 14288780
Change-Id: Ib87ee741d1e5195814516ae1d66eb7c4cf754dcf
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 06e776d..52db223 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.service.persistentdata;
import android.os.ParcelFileDescriptor;
@@ -12,8 +28,10 @@
*/
interface IPersistentDataBlockService {
int write(in byte[] data);
- int read(out byte[] data);
+ byte[] read();
+ void wipe();
int getDataBlockSize();
+ long getMaximumDataBlockSize();
void setOemUnlockEnabled(boolean enabled);
boolean getOemUnlockEnabled();
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index afcf717..42a2e57 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.service.persistentdata;
import android.os.RemoteException;
@@ -7,14 +23,16 @@
* Interface for reading and writing data blocks to a persistent partition.
*
* Allows writing one block at a time. Namely, each time
- * {@link android.service.persistentdata.PersistentDataBlockManager}.write(byte[] data)
+ * {@link PersistentDataBlockManager#write(byte[])}
* is called, it will overwite the data that was previously written on the block.
*
* Clients can query the size of the currently written block via
- * {@link android.service.persistentdata.PersistentDataBlockManager}.getTotalDataSize().
+ * {@link PersistentDataBlockManager#getDataBlockSize()}.
*
- * Clients can any number of bytes from the currently written block up to its total size by invoking
- * {@link android.service.persistentdata.PersistentDataBlockManager}.read(byte[] data).
+ * Clients can query the maximum size for a block via
+ *
+ * Clients can read the currently written block by invoking
+ * {@link PersistentDataBlockManager#read()}.
*
* @hide
*/
@@ -30,41 +48,69 @@
* Writes {@code data} to the persistent partition. Previously written data
* will be overwritten. This data will persist across factory resets.
*
+ * Returns the number of bytes written or -1 on error. If the block is too big
+ * to fit on the partition, returns -MAX_BLOCK_SIZE.
+ *
* @param data the data to write
*/
- public void write(byte[] data) {
+ public int write(byte[] data) {
try {
- sService.write(data);
+ return sService.write(data);
} catch (RemoteException e) {
onError("writing data");
- }
- }
-
- /**
- * Tries to read {@code data.length} bytes into {@code data}. Call {@code getDataBlockSize()}
- * to determine the total size of the block currently residing in the persistent partition.
- *
- * @param data the buffer in which to read the data
- * @return the actual number of bytes read
- */
- public int read(byte[] data) {
- try {
- return sService.read(data);
- } catch (RemoteException e) {
- onError("reading data");
return -1;
}
}
/**
+ * Returns the data block stored on the persistent partition.
+ */
+ public byte[] read() {
+ try {
+ return sService.read();
+ } catch (RemoteException e) {
+ onError("reading data");
+ return null;
+ }
+ }
+
+ /**
* Retrieves the size of the block currently written to the persistent partition.
+ *
+ * Return -1 on error.
*/
public int getDataBlockSize() {
try {
return sService.getDataBlockSize();
} catch (RemoteException e) {
onError("getting data block size");
- return 0;
+ return -1;
+ }
+ }
+
+ /**
+ * Retrieves the maximum size allowed for a data block.
+ *
+ * Returns -1 on error.
+ */
+ public long getMaximumDataBlockSize() {
+ try {
+ return sService.getMaximumDataBlockSize();
+ } catch (RemoteException e) {
+ onError("getting maximum data block size");
+ return -1;
+ }
+ }
+
+ /**
+ * Zeroes the previously written block in its entirety. Calling this method
+ * will erase all data written to the persistent data partition.
+ */
+ public void wipe() {
+ try {
+ sService.wipe();
+ } catch (RemoteException e) {
+ onError("wiping persistent partition");
}
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 1eded6f..f71a18a 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.server;
import android.Manifest;
@@ -8,8 +24,9 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.service.persistentdata.IPersistentDataBlockService;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.R;
+import libcore.io.IoUtils;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -23,7 +40,8 @@
/**
* Service for reading and writing blocks to a persistent partition.
- * This data will live across factory resets.
+ * This data will live across factory resets not initiated via the Settings UI.
+ * When a device is factory reset through Settings this data is wiped.
*
* Allows writing one block at a time. Namely, each time
* {@link android.service.persistentdata.IPersistentDataBlockService}.write(byte[] data)
@@ -40,19 +58,29 @@
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private static final int HEADER_SIZE = 8;
- private static final int BLOCK_ID = 0x1990;
+ // Magic number to mark block device as adhering to the format consumed by this service
+ private static final int PARTITION_TYPE_MARKER = 0x1990;
+ // Limit to 100k as blocks larger than this might cause strain on Binder.
+ // TODO(anmorales): Consider splitting up too-large blocks in PersistentDataBlockManager
+ private static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
private final Context mContext;
private final String mDataBlockFile;
- private long mBlockDeviceSize;
-
private final int mAllowedUid;
+ private final Object mLock = new Object();
+ /*
+ * Separate lock for OEM unlock related operations as they can happen in parallel with regular
+ * block operations.
+ */
+ private final Object mOemLock = new Object();
+
+ private long mBlockDeviceSize;
public PersistentDataBlockService(Context context) {
super(context);
mContext = context;
mDataBlockFile = SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP);
- mBlockDeviceSize = 0; // Load lazily
+ mBlockDeviceSize = -1; // Load lazily
String allowedPackage = context.getResources()
.getString(R.string.config_persistentDataPackageName);
PackageManager pm = mContext.getPackageManager();
@@ -62,7 +90,7 @@
Binder.getCallingUserHandle().getIdentifier());
} catch (PackageManager.NameNotFoundException e) {
// not expected
- Log.e(TAG, "not able to find package " + allowedPackage, e);
+ Slog.e(TAG, "not able to find package " + allowedPackage, e);
}
mAllowedUid = allowedUid;
@@ -85,10 +113,10 @@
}
}
- private int getTotalDataSize(DataInputStream inputStream) throws IOException {
+ private int getTotalDataSizeLocked(DataInputStream inputStream) throws IOException {
int totalDataSize;
int blockId = inputStream.readInt();
- if (blockId == BLOCK_ID) {
+ if (blockId == PARTITION_TYPE_MARKER) {
totalDataSize = inputStream.readInt();
} else {
totalDataSize = 0;
@@ -96,17 +124,18 @@
return totalDataSize;
}
- private long maybeReadBlockDeviceSize() {
- synchronized (this) {
- if (mBlockDeviceSize == 0) {
- mBlockDeviceSize = getBlockDeviceSize(mDataBlockFile);
+ private long getBlockDeviceSize() {
+ synchronized (mLock) {
+ if (mBlockDeviceSize == -1) {
+ mBlockDeviceSize = nativeGetBlockDeviceSize(mDataBlockFile);
}
}
return mBlockDeviceSize;
}
- private native long getBlockDeviceSize(String path);
+ private native long nativeGetBlockDeviceSize(String path);
+ private native int nativeWipe(String path);
private final IBinder mService = new IPersistentDataBlockService.Stub() {
@Override
@@ -114,62 +143,93 @@
enforceUid(Binder.getCallingUid());
// Need to ensure we don't write over the last byte
- if (data.length > maybeReadBlockDeviceSize() - HEADER_SIZE - 1) {
- return -1;
+ long maxBlockSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ if (data.length > maxBlockSize) {
+ // partition is ~500k so shouldn't be a problem to downcast
+ return (int) -maxBlockSize;
}
DataOutputStream outputStream;
try {
outputStream = new DataOutputStream(new FileOutputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available?", e);
+ Slog.e(TAG, "partition not available?", e);
return -1;
}
ByteBuffer headerAndData = ByteBuffer.allocate(data.length + HEADER_SIZE);
- headerAndData.putInt(BLOCK_ID);
+ headerAndData.putInt(PARTITION_TYPE_MARKER);
headerAndData.putInt(data.length);
headerAndData.put(data);
try {
- outputStream.write(headerAndData.array());
- return data.length;
+ synchronized (mLock) {
+ outputStream.write(headerAndData.array());
+ return data.length;
+ }
} catch (IOException e) {
- Log.e(TAG, "failed writing to the persistent data block", e);
+ Slog.e(TAG, "failed writing to the persistent data block", e);
return -1;
} finally {
try {
outputStream.close();
} catch (IOException e) {
- Log.e(TAG, "failed closing output stream", e);
+ Slog.e(TAG, "failed closing output stream", e);
}
}
}
@Override
- public int read(byte[] data) {
+ public byte[] read() {
enforceUid(Binder.getCallingUid());
DataInputStream inputStream;
try {
inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available?", e);
- return -1;
+ Slog.e(TAG, "partition not available?", e);
+ return null;
}
try {
- int totalDataSize = getTotalDataSize(inputStream);
- return inputStream.read(data, 0,
- (data.length > totalDataSize) ? totalDataSize : data.length);
+ synchronized (mLock) {
+ int totalDataSize = getTotalDataSizeLocked(inputStream);
+
+ if (totalDataSize == 0) {
+ return new byte[0];
+ }
+
+ byte[] data = new byte[totalDataSize];
+ int read = inputStream.read(data, 0, totalDataSize);
+ if (read < totalDataSize) {
+ // something went wrong, not returning potentially corrupt data
+ Slog.e(TAG, "failed to read entire data block. bytes read: " +
+ read + "/" + totalDataSize);
+ return null;
+ }
+ return data;
+ }
} catch (IOException e) {
- Log.e(TAG, "failed to read data", e);
- return -1;
+ Slog.e(TAG, "failed to read data", e);
+ return null;
} finally {
try {
inputStream.close();
} catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
+ Slog.e(TAG, "failed to close OutputStream");
+ }
+ }
+ }
+
+ @Override
+ public void wipe() {
+ enforceOemUnlockPermission();
+
+ synchronized (mLock) {
+ int ret = nativeWipe(mDataBlockFile);
+
+ if (ret < 0) {
+ Slog.e(TAG, "failed to wipe persistent partition");
}
}
}
@@ -181,28 +241,26 @@
try {
outputStream = new FileOutputStream(new File(mDataBlockFile));
} catch (FileNotFoundException e) {
- Log.e(TAG, "parition not available", e);
+ Slog.e(TAG, "parition not available", e);
return;
}
try {
FileChannel channel = outputStream.getChannel();
- channel.position(maybeReadBlockDeviceSize() - 1);
+ channel.position(getBlockDeviceSize() - 1);
ByteBuffer data = ByteBuffer.allocate(1);
data.put(enabled ? (byte) 1 : (byte) 0);
data.flip();
- channel.write(data);
- } catch (IOException e) {
- Log.e(TAG, "unable to access persistent partition", e);
- } finally {
- try {
- outputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
+ synchronized (mOemLock) {
+ channel.write(data);
}
+ } catch (IOException e) {
+ Slog.e(TAG, "unable to access persistent partition", e);
+ } finally {
+ IoUtils.closeQuietly(outputStream);
}
}
@@ -213,22 +271,20 @@
try {
inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available");
+ Slog.e(TAG, "partition not available");
return false;
}
try {
- inputStream.skip(maybeReadBlockDeviceSize() - 1);
- return inputStream.readByte() != 0;
+ inputStream.skip(getBlockDeviceSize() - 1);
+ synchronized (mOemLock) {
+ return inputStream.readByte() != 0;
+ }
} catch (IOException e) {
- Log.e(TAG, "unable to access persistent partition", e);
+ Slog.e(TAG, "unable to access persistent partition", e);
return false;
} finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
- }
+ IoUtils.closeQuietly(inputStream);
}
}
@@ -240,22 +296,27 @@
try {
inputStream = new DataInputStream(new FileInputStream(new File(mDataBlockFile)));
} catch (FileNotFoundException e) {
- Log.e(TAG, "partition not available");
+ Slog.e(TAG, "partition not available");
return 0;
}
try {
- return getTotalDataSize(inputStream);
+ synchronized (mLock) {
+ return getTotalDataSizeLocked(inputStream);
+ }
} catch (IOException e) {
- Log.e(TAG, "error reading data block size");
+ Slog.e(TAG, "error reading data block size");
return 0;
} finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "failed to close OutputStream");
- }
+ IoUtils.closeQuietly(inputStream);
}
}
+
+ @Override
+ public long getMaximumDataBlockSize() {
+ long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
+ return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
+ }
+
};
}
diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
index 673a6e2..e842eeb 100644
--- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp
+++ b/services/core/jni/com_android_server_PersistentDataBlockService.cpp
@@ -21,9 +21,13 @@
#include <utils/misc.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
+#include <utils/Log.h>
+
#include <inttypes.h>
#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
namespace android {
@@ -40,7 +44,39 @@
return size;
}
- static jlong com_android_server_PeristentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) {
+ int wipe_block_device(int fd)
+ {
+ uint64_t range[2];
+ int ret;
+ uint64_t len = get_block_device_size(fd);
+
+ range[0] = 0;
+ range[1] = len;
+
+ if (range[1] == 0)
+ return 0;
+
+ ret = ioctl(fd, BLKSECDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno));
+ range[0] = 0;
+ range[1] = len;
+ ret = ioctl(fd, BLKDISCARD, &range);
+ if (ret < 0) {
+ ALOGE("Discard failed: %s\n", strerror(errno));
+ return -1;
+ } else {
+ ALOGE("Wipe via secure discard failed, used non-secure discard instead\n");
+ return 0;
+ }
+
+ }
+
+ return ret;
+ }
+
+ static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath)
+ {
const char *path = env->GetStringUTFChars(jpath, 0);
int fd = open(path, O_RDONLY);
@@ -50,9 +86,20 @@
return get_block_device_size(fd);
}
+ static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) {
+ const char *path = env->GetStringUTFChars(jpath, 0);
+ int fd = open(path, O_WRONLY);
+
+ if (fd < 0)
+ return 0;
+
+ return wipe_block_device(fd);
+ }
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
- {"getBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PeristentDataBlockService_getBlockDeviceSize},
+ {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize},
+ {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe},
};
int register_android_server_PersistentDataBlockService(JNIEnv* env)