Fuzz java parcel surfaces

Adding a fuzzer for java backend of the parcel.
Test: m java_binder_parcel_fuzzer && ./jazzer_helper.sh --fuzz_target
      java_binder_parcel_fuzzer --target_class parcelfuzzer.ParcelFuzzer
Bug: 232439254

Change-Id: I6ebebb810d707d23a6e4ad9dab1fb51a80f96f69
This commit is contained in:
Pawan Wagh
2022-11-29 12:05:24 +00:00
parent e5a82fc9a2
commit 85c87b13cd
10 changed files with 657 additions and 6 deletions

View File

@@ -432,6 +432,7 @@ filegroup {
"android/os/IInterface.java",
"android/os/Binder.java",
"android/os/IBinder.java",
"android/os/Parcelable.java",
],
}

View File

@@ -34,12 +34,12 @@ public class FuzzBinder {
fuzzServiceInternal(binder, data);
}
// This API creates random parcel object
public static void createRandomParcel(Parcel parcel, byte[] data) {
getRandomParcel(parcel, data);
// This API fills parcel object
public static void fillRandomParcel(Parcel parcel, byte[] data) {
fillParcelInternal(parcel, data);
}
private static native void fuzzServiceInternal(IBinder binder, byte[] data);
private static native void getRandomParcel(Parcel parcel, byte[] data);
private static native void fillParcelInternal(Parcel parcel, byte[] data);
private static native int registerNatives();
}

View File

@@ -38,7 +38,7 @@ JNIEXPORT jint JNICALL Java_randomparcel_FuzzBinder_registerNatives(JNIEnv* env)
return registerFrameworkNatives(env);
}
JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData));
uint8_t data[len];
env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data));

View File

@@ -24,5 +24,5 @@ extern "C" {
// Function from AndroidRuntime
jint registerFrameworkNatives(JNIEnv* env);
JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
}

View File

@@ -0,0 +1,40 @@
package {
default_applicable_licenses: ["frameworks_base_license"],
}
java_fuzz {
name: "java_binder_parcel_fuzzer",
srcs: [
"ParcelFuzzer.java",
"ReadUtils.java",
"FuzzUtils.java",
"FuzzOperation.java",
"ReadOperation.java",
":framework-core-sources-for-fuzzers",
],
static_libs: [
"jazzer",
"random_parcel_lib",
"binderReadParcelIface-java",
],
jni_libs: [
"librandom_parcel_jni",
"libc++",
"libandroid_runtime",
],
libs: [
"framework",
"unsupportedappusage",
"ext",
"framework-res",
],
native_bridge_supported: true,
fuzz_config: {
cc: [
"smoreland@google.com",
"waghpawan@google.com",
],
// Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
hotlists: ["4637097"],
},
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2022 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 parcelfuzzer;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
public interface FuzzOperation {
void doFuzz(FuzzedDataProvider data);
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2022 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 parcelfuzzer;
import android.os.Parcel;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import randomparcel.FuzzBinder;
public class FuzzUtils {
public static FuzzOperation[] FUZZ_OPERATIONS =
new FuzzOperation[] {
new FuzzOperation() {
@java.lang.Override
public void doFuzz(FuzzedDataProvider provider) {
// Fuzz Append
int start = provider.consumeInt();
int len = provider.consumeInt();
Parcel p1 = null;
Parcel p2 = null;
try {
p1 = Parcel.obtain();
p2 = Parcel.obtain();
byte[] data =
provider.consumeBytes(
provider.consumeInt(0, provider.remainingBytes()));
FuzzBinder.fillRandomParcel(p1, data);
FuzzBinder.fillRandomParcel(p2, provider.consumeRemainingAsBytes());
p1.appendFrom(p2, start, len);
} catch (Exception e) {
// Rethrow exception as runtime exceptions are catched
// at highest level.
throw e;
} finally {
p1.recycle();
p2.recycle();
}
}
},
new FuzzOperation() {
@java.lang.Override
public void doFuzz(FuzzedDataProvider provider) {
// Fuzz Read
// Use maximum bytes to generate read instructions and remaining for parcel
// creation
int maxParcelBytes = provider.remainingBytes() / 3;
byte[] data = provider.consumeBytes(maxParcelBytes);
Parcel randomParcel = null;
try {
randomParcel = Parcel.obtain();
FuzzBinder.fillRandomParcel(randomParcel, data);
while (provider.remainingBytes() > 0) {
provider.pickValue(ReadUtils.READ_OPERATIONS)
.readParcel(randomParcel, provider);
}
} catch (Exception e) {
// Rethrow exception as runtime exceptions are catched
// at highest level.
throw e;
} finally {
randomParcel.recycle();
}
}
},
};
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2022 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 parcelfuzzer;
import android.util.Log;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import randomparcel.FuzzBinder;
public class ParcelFuzzer {
static {
// Initialize JNI dependencies
FuzzBinder.init();
}
public static void fuzzerTestOneInput(FuzzedDataProvider provider) {
// Default behavior for Java APIs is to throw RuntimeException.
// We need to fuzz to detect other problems which are not handled explicitly.
// TODO(b/150808347): Change known API exceptions to subclass of
// RuntimeExceptions and catch those only.
try {
provider.pickValue(FuzzUtils.FUZZ_OPERATIONS).doFuzz(provider);
} catch (RuntimeException e) {
Log.e("ParcelFuzzer", "Exception occurred while fuzzing ", e);
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2022 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 parcelfuzzer;
import android.os.Parcel;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
public interface ReadOperation {
void readParcel(Parcel parcel, FuzzedDataProvider provider);
}

View File

@@ -0,0 +1,435 @@
/*
* Copyright (C) 2022 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 parcelfuzzer;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.HashMap;
import parcelables.EmptyParcelable;
import parcelables.GenericDataParcelable;
import parcelables.SingleDataParcelable;
public class ReadUtils {
public static int MAX_LEN = 1000000;
public static int MIN_LEN = 0;
private static class SomeParcelable implements Parcelable {
private final int mValue;
private SomeParcelable(Parcel in) {
this.mValue = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mValue);
}
public static Parcelable.Creator<SomeParcelable> CREATOR =
new Parcelable.Creator<SomeParcelable>() {
@Override
public SomeParcelable createFromParcel(Parcel source) {
return new SomeParcelable(source);
}
@Override
public SomeParcelable[] newArray(int size) {
return new SomeParcelable[size];
}
};
}
private static class TestClassLoader extends ClassLoader {
TestClassLoader() {
super();
}
}
private static class TestInterface implements IInterface {
public Binder binder;
private static final String DESCRIPTOR = "TestInterface";
TestInterface() {
binder = new Binder();
binder.attachInterface(this, DESCRIPTOR);
}
public IBinder asBinder() {
return binder;
}
public static TestInterface asInterface(IBinder binder) {
if (binder != null) {
IInterface iface = binder.queryLocalInterface(DESCRIPTOR);
if (iface != null && iface instanceof TestInterface) {
return (TestInterface) iface;
}
}
return null;
}
}
public static ReadOperation[] READ_OPERATIONS =
new ReadOperation[] {
(parcel, provider) -> {
parcel.setDataPosition(provider.consumeInt());
},
(parcel, provider) -> {
parcel.setDataCapacity(provider.consumeInt());
},
(parcel, provider) -> {
parcel.setDataSize(provider.consumeInt());
},
(parcel, provider) -> {
parcel.dataSize();
},
(parcel, provider) -> {
parcel.dataPosition();
},
(parcel, provider) -> {
parcel.dataCapacity();
},
// read basic types
(parcel, provider) -> {
parcel.readByte();
},
(parcel, provider) -> {
parcel.readBoolean();
},
(parcel, provider) -> {
parcel.readInt();
},
(parcel, provider) -> {
parcel.readLong();
},
(parcel, provider) -> {
parcel.readFloat();
},
(parcel, provider) -> {
parcel.readDouble();
},
(parcel, provider) -> {
parcel.readString();
},
(parcel, provider) -> {
parcel.readString8();
},
(parcel, provider) -> {
parcel.readString16();
},
(parcel, provider) -> {
parcel.readBlob();
},
(parcel, provider) -> {
parcel.readStrongBinder();
},
// read arrays of random length
(parcel, provider) -> {
byte[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new byte[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new byte[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readByteArray(array);
},
(parcel, provider) -> {
char[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new char[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new char[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readCharArray(array);
},
(parcel, provider) -> {
int[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new int[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new int[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readIntArray(array);
},
(parcel, provider) -> {
double[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new double[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new double[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readDoubleArray(array);
},
(parcel, provider) -> {
float[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new float[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new float[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readFloatArray(array);
},
(parcel, provider) -> {
boolean[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new boolean[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new boolean[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readBooleanArray(array);
},
(parcel, provider) -> {
long[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new long[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new long[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readLongArray(array);
},
(parcel, provider) -> {
IBinder[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new IBinder[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new IBinder[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readBinderArray(array);
},
(parcel, provider) -> {
ArrayList<IBinder> arrayList = new ArrayList<IBinder>();
parcel.readBinderList(arrayList);
},
// unmarshall from random parcel data and random bytes
(parcel, provider) -> {
byte[] data = parcel.marshall();
Parcel p = Parcel.obtain();
p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
p.recycle();
},
(parcel, provider) -> {
byte[] data = provider.consumeRemainingAsBytes();
Parcel p = Parcel.obtain();
p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
p.recycle();
},
(parcel, provider) -> {
parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
},
// read AIDL generated parcelables
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelable(loader, SingleDataParcelable.class);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelableArray(loader, SingleDataParcelable.class);
},
(parcel, provider) -> {
SingleDataParcelable[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new SingleDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new SingleDataParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readTypedArray(array, SingleDataParcelable.CREATOR);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelable(loader, EmptyParcelable.class);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelableArray(loader, EmptyParcelable.class);
},
(parcel, provider) -> {
EmptyParcelable[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new EmptyParcelable[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new EmptyParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readTypedArray(array, EmptyParcelable.CREATOR);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelable(loader, GenericDataParcelable.class);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelableArray(loader, GenericDataParcelable.class);
},
(parcel, provider) -> {
GenericDataParcelable[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new GenericDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
int len = provider.consumeInt(MIN_LEN, MAX_LEN);
array = new GenericDataParcelable[len];
}
parcel.readTypedArray(array, GenericDataParcelable.CREATOR);
},
// read parcelables
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelable(loader, SomeParcelable.class);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelableArray(loader, SomeParcelable.class);
},
(parcel, provider) -> {
SomeParcelable[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new SomeParcelable[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new SomeParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readTypedArray(array, SomeParcelable.CREATOR);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelableArray(loader);
},
(parcel, provider) -> {
parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readParcelableArray(loader);
},
// read lists
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readArrayList(loader);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readArrayList(loader, Object.class);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readArrayList(loader, SomeParcelable.class);
},
// read sparse arrays
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readSparseArray(loader);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readSparseArray(loader, Object.class);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readSparseArray(loader, SomeParcelable.class);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readSerializable(loader, Object.class);
},
// read interface
(parcel, provider) -> {
TestInterface[] array;
if (provider.consumeBoolean()) {
int pos = parcel.dataPosition();
array = new TestInterface[Math.min(MAX_LEN, parcel.readInt())];
parcel.setDataPosition(pos);
} else {
array = new TestInterface[provider.consumeInt(MIN_LEN, MAX_LEN)];
}
parcel.readInterfaceArray(array, TestInterface::asInterface);
},
(parcel, provider) -> {
int w = provider.consumeInt(MIN_LEN, MAX_LEN);
int h = provider.consumeInt(MIN_LEN, MAX_LEN);
TestInterface[][] array = new TestInterface[w][h];
parcel.readFixedArray(array, TestInterface::asInterface);
},
(parcel, provider) -> {
ArrayList<TestInterface> array = new ArrayList<TestInterface>();
parcel.readInterfaceList(array, TestInterface::asInterface);
},
// read bundle
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readBundle(loader);
},
(parcel, provider) -> {
parcel.readBundle();
},
// read HashMap
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readHashMap(loader);
},
(parcel, provider) -> {
TestClassLoader loader = new TestClassLoader();
parcel.readHashMap(loader, String.class, String.class);
},
(parcel, provider) -> {
HashMap<String, String> hashMap = new HashMap<>();
TestClassLoader loader = new TestClassLoader();
parcel.readMap(hashMap, loader, String.class, String.class);
},
};
}