Added test to verify run-as command has valid attributes.

This test verifies that the /system/bin/run-as command has the following
properties:

1. It is readable and executable by the file owner and group
2. It is owned by the root user and the group is shell
3. The file has extended attributes for setuid and setgid

This test catches the failure condition that showed up in this bug:
https://code.google.com/p/android/issues/detail?id=58373

Under some conditions, /system/bin/run-as would not have the setuid or
setgid extended attributes set. If that happened, the device would
prevent developers from running the gdb debugger against executables on
the device, rendering native development ineffective. Currently it is
confirmed that doing a system image update over the air (OTA) to Android
4.3 build JWR66Y on Nexus devices will cause this CTS test to fail as
the extended attributes are not applied by the update. As of this
writing, the work-around is to flash the system image over the USB cable
rather than using an OTA update.

This test is designed to catch and prevent this issue from surfacing
again in future releases.

To implement this test, two new native methods were exported from the
java android.permission.cts.FileUtils class:

* public native static boolean hasSetUidCapability(String path)
* public native static boolean hasSetGidCapability(String path)

These methods return true if the filename indicated by path has extended
attributes and has the setuid or setgid permission set, respectively.

A new test has been added to android.permission.cts.FileSystemPermissionTest:
testRunAsHasCorrectCapabilities(). This test implements the test
described at the top of this comment.

It is strongly recommended that vendors run the CTS test suite against
updates applied by OTA mechanisms in addition to over-the-wire system
flashes.

Bug: 10183961
Change-Id: I268fb68d8754bf89f7316892ff8dfbc6d7c31b98
diff --git a/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
index c658af6..272bbdc 100644
--- a/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
+++ b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
@@ -16,10 +16,15 @@
 
 #include <jni.h>
 #include <stdio.h>
+#include <cutils/log.h>
+#include <linux/xattr.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/xattr.h>
+#include <sys/capability.h>
 #include <grp.h>
 #include <pwd.h>
+#include <string.h>
 
 static jfieldID gFileStatusDevFieldID;
 static jfieldID gFileStatusInoFieldID;
@@ -87,6 +92,44 @@
     return env->NewStringUTF(grp->gr_name);
 }
 
+static jboolean isPermittedCapBitSet(JNIEnv* env, jstring path, size_t capId)
+{
+    const char* pathStr = env->GetStringUTFChars(path, NULL);
+    jboolean ret = false;
+
+    struct vfs_cap_data capData;
+    memset(&capData, 0, sizeof(capData));
+
+    ssize_t result = getxattr(pathStr, XATTR_NAME_CAPS, &capData,
+                              sizeof(capData));
+    if (result > 0) {
+      ret = (capData.data[CAP_TO_INDEX(capId)].permitted &
+             CAP_TO_MASK(capId)) != 0;
+      ALOGD("isPermittedCapBitSet(): getxattr(\"%s\") call succeeded, "
+            "cap bit %u %s",
+            pathStr, capId, ret ? "set" : "unset");
+    } else {
+      ALOGD("isPermittedCapBitSet(): getxattr(\"%s\") call failed: "
+            "return %d (error: %s (%d))\n",
+            pathStr, result, strerror(errno), errno);
+    }
+
+    env->ReleaseStringUTFChars(path, pathStr);
+    return ret;
+}
+
+jboolean android_permission_cts_FileUtils_hasSetUidCapability(JNIEnv* env,
+        jobject clazz, jstring path)
+{
+    return isPermittedCapBitSet(env, path, CAP_SETUID);
+}
+
+jboolean android_permission_cts_FileUtils_hasSetGidCapability(JNIEnv* env,
+        jobject clazz, jstring path)
+{
+    return isPermittedCapBitSet(env, path, CAP_SETGID);
+}
+
 static JNINativeMethod gMethods[] = {
     {  "getFileStatus", "(Ljava/lang/String;Landroid/permission/cts/FileUtils$FileStatus;Z)Z",
             (void *) android_permission_cts_FileUtils_getFileStatus  },
@@ -94,6 +137,10 @@
             (void *) android_permission_cts_FileUtils_getUserName  },
     {  "getGroupName", "(I)Ljava/lang/String;",
             (void *) android_permission_cts_FileUtils_getGroupName  },
+    {  "hasSetUidCapability", "(Ljava/lang/String;)Z",
+            (void *) android_permission_cts_FileUtils_hasSetUidCapability   },
+    {  "hasSetGidCapability", "(Ljava/lang/String;)Z",
+            (void *) android_permission_cts_FileUtils_hasSetGidCapability   },
 };
 
 int register_android_permission_cts_FileUtils(JNIEnv* env)
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 987b78c..66b59f5 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -739,6 +739,30 @@
                 (status.mode & 0666) == 0666);
     }
 
+    /**
+     * Test that the /system/bin/run-as command has setuid and setgid
+     * attributes set on the file.  If these calls fail, debugger
+     * breakpoints for native code will not work as run-as will not
+     * be able to perform required elevated-privilege functionality.
+     */
+    public void testRunAsHasCorrectCapabilities() throws Exception {
+        // ensure file is user and group read/executable
+        String filename = "/system/bin/run-as";
+        FileUtils.FileStatus status = new FileUtils.FileStatus();
+        assertTrue(FileUtils.getFileStatus(filename, status, false));
+        assertTrue(status.hasModeFlag(FileUtils.S_IRUSR | FileUtils.S_IXUSR));
+        assertTrue(status.hasModeFlag(FileUtils.S_IRGRP | FileUtils.S_IXGRP));
+
+        // ensure file owner/group is set correctly
+        File f = new File(filename);
+        assertFileOwnedBy(f, "root");
+        assertFileOwnedByGroup(f, "shell");
+
+        // ensure file has setuid/setgid enabled
+        assertTrue(FileUtils.hasSetUidCapability(filename));
+        assertTrue(FileUtils.hasSetGidCapability(filename));
+    }
+
     private static Set<File>
     getAllInsecureDevicesInDirAndSubdir(File dir, int type) throws Exception {
         assertTrue(dir.isDirectory());
diff --git a/tests/tests/permission/src/android/permission/cts/FileUtils.java b/tests/tests/permission/src/android/permission/cts/FileUtils.java
index 56e773a..9cd4999 100644
--- a/tests/tests/permission/src/android/permission/cts/FileUtils.java
+++ b/tests/tests/permission/src/android/permission/cts/FileUtils.java
@@ -94,4 +94,7 @@
 
     public native static String getGroupName(int gid);
 
+    public native static boolean hasSetUidCapability(String path);
+
+    public native static boolean hasSetGidCapability(String path);
 }