Read Log Permission Test Fixes

Bug 2964078

Fix a typo in the logcat command used in the test. There was an
extra colon in one of the arguments.

Add a test to check that log files under "/dev/log" are not world
readable. Also check they are owned by user root and group log.

Create a JNI library just for the permissions package. The JNI code
cannot be added to the CtsTestStubs JNI library, because the tests
cannot use CtsTestStubs since that acquires the permissions we
are trying to negative test. I also tried compiling just the single
Java source in CtsTestStubs (initially where I tried to put
FileUtils) and reusing the CtsTestStubs JNI library but the VM did
not like it. Thus, I take the easiest route of making a separate
JNI library just for the permisions package.

Change-Id: I6aafe9cfcdee1250f5d693cf83abb826faf63344
diff --git a/tests/tests/permission/Android.mk b/tests/tests/permission/Android.mk
index 46e7fb7..c1f7f4d 100644
--- a/tests/tests/permission/Android.mk
+++ b/tests/tests/permission/Android.mk
@@ -21,6 +21,8 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
+LOCAL_JNI_SHARED_LIBRARIES := libctspermission_jni
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsPermissionTestCases
@@ -30,3 +32,4 @@
 
 include $(BUILD_PACKAGE)
 
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/permission/jni/Android.mk b/tests/tests/permission/jni/Android.mk
new file mode 100644
index 0000000..0891a72
--- /dev/null
+++ b/tests/tests/permission/jni/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2010 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 := libctspermission_jni
+
+# Don't include this package in any configuration by default.
+LOCAL_MODULE_TAGS := optional
+
+# This isn't part of the system, so don't prelink it.
+LOCAL_PRELINK_MODULE := false
+
+LOCAL_SRC_FILES := \
+		CtsPermissionsJniOnLoad.cpp \
+		android_permission_cts_FileUtils.cpp
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE) 
+
+LOCAL_SHARED_LIBRARIES := libnativehelper liblog
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/permission/jni/CtsPermissionsJniOnLoad.cpp b/tests/tests/permission/jni/CtsPermissionsJniOnLoad.cpp
new file mode 100644
index 0000000..fab33bd
--- /dev/null
+++ b/tests/tests/permission/jni/CtsPermissionsJniOnLoad.cpp
@@ -0,0 +1,34 @@
+/* 
+ * Copyright (C) 2010 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.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int register_android_permission_cts_FileUtils(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+    JNIEnv *env = NULL;
+
+    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+        return JNI_ERR;
+    }
+
+    if (register_android_permission_cts_FileUtils(env)) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
new file mode 100644
index 0000000..1666d50
--- /dev/null
+++ b/tests/tests/permission/jni/android_permission_cts_FileUtils.cpp
@@ -0,0 +1,120 @@
+/* 
+ * Copyright (C) 2010 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.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+#include <pwd.h>
+
+static jclass gFileStatusClass;
+static jfieldID gFileStatusDevFieldID;
+static jfieldID gFileStatusInoFieldID;
+static jfieldID gFileStatusModeFieldID;
+static jfieldID gFileStatusNlinkFieldID;
+static jfieldID gFileStatusUidFieldID;
+static jfieldID gFileStatusGidFieldID;
+static jfieldID gFileStatusSizeFieldID;
+static jfieldID gFileStatusBlksizeFieldID;
+static jfieldID gFileStatusBlocksFieldID;
+static jfieldID gFileStatusAtimeFieldID;
+static jfieldID gFileStatusMtimeFieldID;
+static jfieldID gFileStatusCtimeFieldID;
+
+/*
+ * Native methods used by
+ * cts/tests/tests/permission/src/android/permission/cts/FileUtils.java
+ *
+ * Copied from hidden API: frameworks/base/core/jni/android_os_FileUtils.cpp
+ */
+
+jboolean android_permission_cts_FileUtils_getFileStatus(JNIEnv* env, jobject thiz,
+        jstring path, jobject fileStatus, jboolean statLinks)
+{
+    const char* pathStr = env->GetStringUTFChars(path, NULL);
+    jboolean ret = false;
+    struct stat s;
+
+    int res = statLinks == true ? lstat(pathStr, &s) : stat(pathStr, &s);
+
+    if (res == 0) {
+        ret = true;
+        if (fileStatus != NULL) {
+            env->SetIntField(fileStatus, gFileStatusDevFieldID, s.st_dev);
+            env->SetIntField(fileStatus, gFileStatusInoFieldID, s.st_ino);
+            env->SetIntField(fileStatus, gFileStatusModeFieldID, s.st_mode);
+            env->SetIntField(fileStatus, gFileStatusNlinkFieldID, s.st_nlink);
+            env->SetIntField(fileStatus, gFileStatusUidFieldID, s.st_uid);
+            env->SetIntField(fileStatus, gFileStatusGidFieldID, s.st_gid);
+            env->SetLongField(fileStatus, gFileStatusSizeFieldID, s.st_size);
+            env->SetIntField(fileStatus, gFileStatusBlksizeFieldID, s.st_blksize);
+            env->SetLongField(fileStatus, gFileStatusBlocksFieldID, s.st_blocks);
+            env->SetLongField(fileStatus, gFileStatusAtimeFieldID, s.st_atime);
+            env->SetLongField(fileStatus, gFileStatusMtimeFieldID, s.st_mtime);
+            env->SetLongField(fileStatus, gFileStatusCtimeFieldID, s.st_ctime);
+        }
+    }
+
+    env->ReleaseStringUTFChars(path, pathStr);
+
+    return ret;
+}
+
+jstring android_permission_cts_FileUtils_getUserName(JNIEnv* env, jobject thiz,
+        jint uid)
+{
+    struct passwd *pwd = getpwuid(uid);
+    return env->NewStringUTF(pwd->pw_name);
+}
+
+jstring android_permission_cts_FileUtils_getGroupName(JNIEnv* env, jobject thiz,
+        jint gid)
+{
+    struct group *grp = getgrgid(gid);
+    return env->NewStringUTF(grp->gr_name);
+}
+
+static JNINativeMethod gMethods[] = {
+    {  "getFileStatus", "(Ljava/lang/String;Landroid/permission/cts/FileUtils$FileStatus;Z)Z",
+            (void *) android_permission_cts_FileUtils_getFileStatus  },
+    {  "getUserName", "(I)Ljava/lang/String;",
+            (void *) android_permission_cts_FileUtils_getUserName  },
+    {  "getGroupName", "(I)Ljava/lang/String;",
+            (void *) android_permission_cts_FileUtils_getGroupName  },
+};
+
+int register_android_permission_cts_FileUtils(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/permission/cts/FileUtils");
+
+    gFileStatusClass = env->FindClass("android/permission/cts/FileUtils$FileStatus");
+    gFileStatusDevFieldID = env->GetFieldID(gFileStatusClass, "dev", "I");
+    gFileStatusInoFieldID = env->GetFieldID(gFileStatusClass, "ino", "I");
+    gFileStatusModeFieldID = env->GetFieldID(gFileStatusClass, "mode", "I");
+    gFileStatusNlinkFieldID = env->GetFieldID(gFileStatusClass, "nlink", "I");
+    gFileStatusUidFieldID = env->GetFieldID(gFileStatusClass, "uid", "I");
+    gFileStatusGidFieldID = env->GetFieldID(gFileStatusClass, "gid", "I");
+    gFileStatusSizeFieldID = env->GetFieldID(gFileStatusClass, "size", "J");
+    gFileStatusBlksizeFieldID = env->GetFieldID(gFileStatusClass, "blksize", "I");
+    gFileStatusBlocksFieldID = env->GetFieldID(gFileStatusClass, "blocks", "J");
+    gFileStatusAtimeFieldID = env->GetFieldID(gFileStatusClass, "atime", "J");
+    gFileStatusMtimeFieldID = env->GetFieldID(gFileStatusClass, "mtime", "J");
+    gFileStatusCtimeFieldID = env->GetFieldID(gFileStatusClass, "ctime", "J");
+
+    return env->RegisterNatives(clazz, gMethods, 
+            sizeof(gMethods) / sizeof(JNINativeMethod)); 
+}
diff --git a/tests/tests/permission/src/android/permission/cts/FileUtils.java b/tests/tests/permission/src/android/permission/cts/FileUtils.java
new file mode 100644
index 0000000..5a869de
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/FileUtils.java
@@ -0,0 +1,83 @@
+package android.permission.cts;
+
+/*
+ * Copyright (C) 2010 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.
+ */
+
+/** Bits and pieces copied from hidden API of android.os.FileUtils. */
+public class FileUtils {
+
+    public static final int S_IFSOCK = 0140000;
+    public static final int S_IFLNK = 0120000;
+    public static final int S_IFREG = 0100000;
+    public static final int S_IFBLK = 0060000;
+    public static final int S_IFDIR = 0040000;
+    public static final int S_IFCHR = 0020000;
+    public static final int S_IFIFO = 0010000;
+
+    public static final int S_ISUID = 0004000;
+    public static final int S_ISGID = 0002000;
+    public static final int S_ISVTX = 0001000;
+
+    public static final int S_IRUSR = 00400;
+    public static final int S_IWUSR = 00200;
+    public static final int S_IXUSR = 00100;
+
+    public static final int S_IRGRP = 00040;
+    public static final int S_IWGRP = 00020;
+    public static final int S_IXGRP = 00010;
+
+    public static final int S_IROTH = 00004;
+    public static final int S_IWOTH = 00002;
+    public static final int S_IXOTH = 00001;
+
+    static {
+        System.loadLibrary("ctspermission_jni");
+    }
+
+    public static class FileStatus {
+
+        public int dev;
+        public int ino;
+        public int mode;
+        public int nlink;
+        public int uid;
+        public int gid;
+        public int rdev;
+        public long size;
+        public int blksize;
+        public long blocks;
+        public long atime;
+        public long mtime;
+        public long ctime;
+
+        public boolean hasModeFlag(int flag) {
+            return (mode & flag) == flag;
+        }
+    }
+
+    /**
+     * @param path of the file to stat
+     * @param status object to set the fields on
+     * @param statLinks or don't stat links (lstat vs stat)
+     * @return whether or not we were able to stat the file
+     */
+    public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
+
+    public native static String getUserName(int uid);
+
+    public native static String getGroupName(int gid);
+
+}
diff --git a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
index db57927..256a15c 100644
--- a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
@@ -16,14 +16,16 @@
 
 package android.permission.cts;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
+import android.permission.cts.FileUtils.FileStatus;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
 /**
  * Verify the read system log require specific permissions.
  */
@@ -37,12 +39,12 @@
      * @throws IOException
      */
     @MediumTest
-    public void testSetMicrophoneMute() throws IOException {
+    public void testLogcat() throws IOException {
         Process logcatProc = null;
         BufferedReader reader = null;
         try {
             logcatProc = Runtime.getRuntime().exec(new String[]
-                    {"logcat", "-d", "AndroidRuntime:E :" + LOGTAG + ":V *:S" });
+                    {"logcat", "-d", "AndroidRuntime:E " + LOGTAG + ":V *:S" });
             Log.d(LOGTAG, "no read logs permission test");
 
             reader = new BufferedReader(new InputStreamReader(logcatProc.getInputStream()));
@@ -54,6 +56,7 @@
                 log.append(line);
                 log.append(separator);
             }
+
             // no permission get empty log
             assertEquals(0, log.length());
 
@@ -63,4 +66,23 @@
             }
         }
     }
+
+    public void testLogFilePermissions() {
+        File logDir = new File("/dev/log");
+        File[] logFiles = logDir.listFiles();
+        assertTrue("Where are the log files? Please check that they are not world readable.",
+                logFiles.length > 0);
+
+        FileStatus status = new FileStatus();
+        for (File log : logFiles) {
+            if (FileUtils.getFileStatus(log.getAbsolutePath(), status, false)) {
+                assertEquals("Log file " + log.getAbsolutePath() + " should have user root.",
+                        0, status.uid);
+                assertTrue("Log file " + log.getAbsolutePath() + " should have group log.",
+                        "log".equals(FileUtils.getGroupName(status.gid)));
+                assertFalse("Log file "  + log.getAbsolutePath() + " should not be world readable.",
+                        status.hasModeFlag(FileUtils.S_IROTH));
+            }
+        }
+    }
 }