[DO NOT MERGE] Added ContentProviderCursorWindowTest
am: b60929d93f

Change-Id: I16d14278c026aab69e9cd21ef4f9e31710b3f69c
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
index 7d7858a..23410ed8 100644
--- a/tests/tests/content/Android.mk
+++ b/tests/tests/content/Android.mk
@@ -21,6 +21,8 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
+LOCAL_JNI_SHARED_LIBRARIES := libnativecursorwindow_jni libnativehelper_compat_libc++
+
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner services.core
@@ -35,3 +37,5 @@
 LOCAL_PACKAGE_NAME := CtsContentTestCases
 
 include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index d54bc02..fc1677f 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -196,6 +196,13 @@
             </intent-filter>
         </activity>
 
+        <provider
+                android:name="android.content.cts.CursorWindowContentProvider"
+                android:authorities="cursorwindow.provider"
+                android:exported="true"
+                android:process=":providerProcess">
+        </provider>
+
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/content/jni/Android.mk b/tests/tests/content/jni/Android.mk
new file mode 100644
index 0000000..4737b35
--- /dev/null
+++ b/tests/tests/content/jni/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libnativecursorwindow_jni
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := NativeCursorWindow.c
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+LOCAL_SHARED_LIBRARIES := libnativehelper_compat_libc++ liblog
+LOCAL_CXX_STL := libc++_static
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/content/jni/NativeCursorWindow.c b/tests/tests/content/jni/NativeCursorWindow.c
new file mode 100644
index 0000000..a2fb92a
--- /dev/null
+++ b/tests/tests/content/jni/NativeCursorWindow.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define LOG_TAG "NativeCursorWindow"
+
+#include <jni.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <linux/ashmem.h>
+#include <utils/Log.h>
+
+struct Header {
+    // Offset of the lowest unused byte in the window.
+    uint32_t freeOffset;
+
+    // Offset of the first row slot chunk.
+    uint32_t firstChunkOffset;
+
+    uint32_t numRows;
+    uint32_t numColumns;
+};
+
+struct RowSlot {
+    uint32_t offset;
+};
+
+#define ROW_SLOT_CHUNK_NUM_ROWS 100
+
+struct RowSlotChunk {
+    struct RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
+    uint32_t nextChunkOffset;
+};
+
+/* Field types. */
+enum {
+    FIELD_TYPE_NULL = 0,
+    FIELD_TYPE_INTEGER = 1,
+    FIELD_TYPE_FLOAT = 2,
+    FIELD_TYPE_STRING = 3,
+    FIELD_TYPE_BLOB = 4,
+};
+
+/* Opaque type that describes a field slot. */
+struct FieldSlot {
+    int32_t type;
+    union {
+        double d;
+        int64_t l;
+        struct {
+            uint32_t offset;
+            uint32_t size;
+        } buffer;
+    } data;
+} __attribute((packed));
+
+JNIEXPORT jint JNICALL
+Java_android_content_cts_CursorWindowContentProvider_makeNativeCursorWindowFd(JNIEnv *env, jclass clazz,
+jint offset, jint size, jboolean isBlob) {
+    int fd = open("/dev/ashmem", O_RDWR);
+    ioctl(fd, ASHMEM_SET_NAME, "Fake CursorWindow");
+
+    ioctl(fd, ASHMEM_SET_SIZE, 1024);
+
+    void *data = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+    struct Header *header = (struct Header *) data;
+    unsigned rowSlotChunkOffset = sizeof(struct Header);
+    struct RowSlotChunk *rowSlotChunk = (struct RowSlotChunk *)(data + rowSlotChunkOffset);
+    unsigned fieldSlotOffset = rowSlotChunkOffset + sizeof(struct RowSlotChunk);
+    struct FieldSlot *fieldSlot = (struct FieldSlot *) (data + fieldSlotOffset);
+
+    header->numRows = 1;
+    header->numColumns = 1;
+    header->firstChunkOffset = rowSlotChunkOffset;
+
+    rowSlotChunk->slots[0].offset = fieldSlotOffset;
+
+    fieldSlot->type = isBlob ? FIELD_TYPE_BLOB : FIELD_TYPE_STRING;
+    fieldSlot->data.buffer.offset = offset;
+    fieldSlot->data.buffer.size = size;
+
+    munmap(data, 1024);
+
+    return fd;
+
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
new file mode 100644
index 0000000..004b193
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContentProviderCursorWindowTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.content.cts;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Test {@link CursorWindowContentProvider} .
+ */
+public class ContentProviderCursorWindowTest extends AndroidTestCase {
+    private static final String TAG = "ContentProviderCursorWindowTest";
+
+    public void testQuery() throws IOException {
+        Cursor cursor = getContext().getContentResolver().query(
+                Uri.parse("content://cursorwindow.provider/hello"),
+                null, null, null, null
+        );
+        try {
+            cursor.moveToFirst();
+
+            int type = cursor.getType(0);
+            if (type != Cursor.FIELD_TYPE_BLOB) {
+                fail("Unexpected type " + type);
+            }
+            byte[] blob = cursor.getBlob(0);
+            Log.i(TAG,  "Blob length " + blob.length);
+            fail("getBlob should fail due to invalid offset used in the field slot");
+        } catch (SQLiteException expected) {
+            Log.i(TAG, "Expected exception: " + expected);
+        } finally {
+            cursor.close();
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java b/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java
new file mode 100644
index 0000000..4266f35
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/CursorWindowContentProvider.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 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.content.cts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.AbstractWindowedCursor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * Content provider that uses a custom {@link CursorWindow} to inject file descriptor
+ * pointing to another ashmem region having window slots with references outside of allowed ranges.
+ *
+ * <p>Used in {@link ContentProviderCursorWindowTest}
+ */
+public class CursorWindowContentProvider extends ContentProvider {
+    private static final String TAG = "CursorWindowContentProvider";
+    static {
+        System.loadLibrary("nativecursorwindow_jni");
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        AbstractWindowedCursor cursor = new AbstractWindowedCursor() {
+            @Override
+            public int getCount() {
+                return 1;
+            }
+
+            @Override
+            public String[] getColumnNames() {
+                return new String[] {"a"};
+            }
+        };
+        cursor.setWindow(new InjectingCursorWindow("TmpWindow"));
+        return cursor;
+    }
+
+    class InjectingCursorWindow extends CursorWindow {
+        InjectingCursorWindow(String name) {
+            super(name);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            Parcel tmp = Parcel.obtain();
+
+            super.writeToParcel(tmp, flags);
+            tmp.setDataPosition(0);
+            // Find location of file descriptor
+            int fdPos = -1;
+            while (tmp.dataAvail() > 0) {
+                fdPos = tmp.dataPosition();
+                int frameworkFdMarker = tmp.readInt();
+                if (frameworkFdMarker == 0x66642a85 /* BINDER_TYPE_FD */) {
+                    break;
+                }
+            }
+            if (fdPos == -1) {
+                tmp.recycle();
+                throw new IllegalStateException("File descriptor not found in the output of "
+                        + "CursorWindow.writeToParcel");
+            }
+            // Write reply with replaced file descriptor
+            ParcelFileDescriptor evilFd = ParcelFileDescriptor
+                    .adoptFd(makeNativeCursorWindowFd(1000, 1000, true));
+            dest.appendFrom(tmp, 0, fdPos);
+            dest.writeFileDescriptor(evilFd.getFileDescriptor());
+            tmp.setDataPosition(dest.dataPosition());
+            dest.appendFrom(tmp, dest.dataPosition(), tmp.dataAvail());
+            tmp.recycle();
+        }
+    }
+
+    private native static int makeNativeCursorWindowFd(int offset, int size, boolean isBlob);
+
+    // Stubs
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        Log.e(TAG, "delete() not implemented");
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        Log.e(TAG, "getType() not implemented");
+        return "";
+    }
+
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        Log.e(TAG, "insert() not implemented");
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        Log.e(TAG, "update() not implemented");
+        return 0;
+    }
+
+}