am 91678546: am accc6844: CtsVerifier Suid File Scanner
Merge commit '916785461c85787f7212e0f0772643a9b2dee1ab' into gingerbread
* commit '916785461c85787f7212e0f0772643a9b2dee1ab':
CtsVerifier Suid File Scanner
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 7bc2249..112b1bf 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -25,6 +25,8 @@
LOCAL_PACKAGE_NAME := CtsVerifier
+LOCAL_JNI_SHARED_LIBRARIES := libctsverifier_jni
+
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 605404a..08d96e8 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="com.android.cts.verifier"
android:versionCode="1"
android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="5" />
<application android:label="@string/app_name">
@@ -31,7 +33,9 @@
<activity android:name=".TestListActivity" android:label="@string/test_list_title" />
- <activity android:name=".suid.SuidBinariesActivity" android:label="@string/suid_binaries">
+ <activity android:name=".suid.SuidFilesActivity"
+ android:label="@string/suid_files"
+ android:configChanges="keyboardHidden|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.cts.intent.category.MANUAL_TEST" />
diff --git a/apps/CtsVerifier/jni/Android.mk b/apps/CtsVerifier/jni/Android.mk
new file mode 100644
index 0000000..98a4678
--- /dev/null
+++ b/apps/CtsVerifier/jni/Android.mk
@@ -0,0 +1,32 @@
+#
+# 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 := libctsverifier_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_PRELINK_MODULE := false
+
+LOCAL_SRC_FILES := \
+ CtsVerifierJniOnLoad.cpp \
+ com_android_cts_verifier_os_FileUtils.cpp
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidBinariesActivity.java b/apps/CtsVerifier/jni/CtsVerifierJniOnLoad.cpp
similarity index 61%
rename from apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidBinariesActivity.java
rename to apps/CtsVerifier/jni/CtsVerifierJniOnLoad.cpp
index 9f973bc..81e5690 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidBinariesActivity.java
+++ b/apps/CtsVerifier/jni/CtsVerifierJniOnLoad.cpp
@@ -1,4 +1,4 @@
-/*
+/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,15 +14,21 @@
* limitations under the License.
*/
-package com.android.cts.verifier.suid;
+#include <jni.h>
+#include <stdio.h>
-import android.app.Activity;
-import android.os.Bundle;
+extern int register_com_android_cts_verifier_os_FileUtils(JNIEnv*);
-public class SuidBinariesActivity extends Activity {
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+ JNIEnv *env = NULL;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+ return JNI_ERR;
}
+
+ if (register_com_android_cts_verifier_os_FileUtils(env)) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_4;
}
diff --git a/apps/CtsVerifier/jni/com_android_cts_verifier_os_FileUtils.cpp b/apps/CtsVerifier/jni/com_android_cts_verifier_os_FileUtils.cpp
new file mode 100644
index 0000000..14e58eb
--- /dev/null
+++ b/apps/CtsVerifier/jni/com_android_cts_verifier_os_FileUtils.cpp
@@ -0,0 +1,114 @@
+/*
+ * 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;
+
+/* Copied from hidden API: frameworks/base/core/jni/android_os_FileUtils.cpp */
+jboolean com_android_cts_verifier_os_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 com_android_cts_verifier_os_FileUtils_getUserName(JNIEnv* env, jobject thiz,
+ jint uid)
+{
+ struct passwd *pwd = getpwuid(uid);
+ return env->NewStringUTF(pwd->pw_name);
+}
+
+jstring com_android_cts_verifier_os_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;Lcom/android/cts/verifier/os/FileUtils$FileStatus;Z)Z",
+ (void *) com_android_cts_verifier_os_FileUtils_getFileStatus },
+ { "getUserName", "(I)Ljava/lang/String;",
+ (void *) com_android_cts_verifier_os_FileUtils_getUserName },
+ { "getGroupName", "(I)Ljava/lang/String;",
+ (void *) com_android_cts_verifier_os_FileUtils_getGroupName },
+};
+
+int register_com_android_cts_verifier_os_FileUtils(JNIEnv* env)
+{
+ jclass clazz = env->FindClass("com/android/cts/verifier/os/FileUtils");
+
+ gFileStatusClass = env->FindClass("com/android/cts/verifier/os/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/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 57c0b81..2f62a06 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -18,16 +18,23 @@
<string name="welcome_text">Welcome to the CTS Verifier!</string>
<string name="continue_button_text">Continue</string>
<string name="test_list_title">Manual Test List</string>
- <string name="suid_binaries">SUID Binaries</string>
- <!-- strings for FeatureSummaryActivity -->
+ <!-- Strings for FeatureSummaryActivity -->
<string name="feature_summary">Hardware/Software Feature Summary</string>
<string name="fs_disallowed">WARNING: device reports a disallowed feature name</string>
<string name="fs_missing_wifi_telephony">WARNING: device reports neither WiFi nor telephony</string>
<string name="fs_no_data">No data.</string>
<string name="empty"></string>
- <!-- strings for AccelerometerTestActivity and MagnetometerTestActivity -->
+ <!-- Strings for AccelerometerTestActivity and MagnetometerTestActivity -->
<string name="snsr_accel_test">Accelerometer Test</string>
<string name="snsr_mag_test">Magnetometer Test</string>
+
+ <!-- Strings for SuidFilesActivity -->
+ <string name="suid_files">SUID Files</string>
+ <string name="starting_scan">Starting scan...</string>
+ <string name="file_status">User: %s\nGroup: %s\nPermissions: %s\nPath: %s</string>
+ <string name="no_file_status">Could not stat file...</string>
+ <string name="congratulations">Congratulations!</string>
+ <string name="no_suid_files">No unauthorized suid files detected!</string>
</resources>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 143f44d..9bf06ee 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -29,6 +29,8 @@
import android.widget.SimpleAdapter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -91,6 +93,14 @@
addItem(data, title, intent);
}
+ Collections.sort(data, new Comparator<Map<String, ?>> () {
+ public int compare(Map<String, ?> item, Map<String, ?> otherItem) {
+ String title = (String) item.get(TITLE);
+ String otherTitle = (String) otherItem.get(TITLE);
+ return title.compareTo(otherTitle);
+ }
+ });
+
return data;
}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java
new file mode 100644
index 0000000..c767e7a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/os/FileUtils.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.os;
+
+/** Bits and pieces copied from hidden API of android.os.FileUtils. */
+public class FileUtils {
+
+ private static final int S_IFSOCK = 0140000;
+ private static final int S_IFLNK = 0120000;
+ private static final int S_IFREG = 0100000;
+ private static final int S_IFBLK = 0060000;
+ private static final int S_IFDIR = 0040000;
+ private static final int S_IFCHR = 0020000;
+ private static final int S_IFIFO = 0010000;
+
+ private static final int S_ISUID = 0004000;
+ private static final int S_ISGID = 0002000;
+ private static final int S_ISVTX = 0001000;
+
+ private static final int S_IRUSR = 00400;
+ private static final int S_IWUSR = 00200;
+ private static final int S_IXUSR = 00100;
+
+ private static final int S_IRGRP = 00040;
+ private static final int S_IWGRP = 00020;
+ private static final int S_IXGRP = 00010;
+
+ private static final int S_IROTH = 00004;
+ private static final int S_IWOTH = 00002;
+ private static final int S_IXOTH = 00001;
+
+ static {
+ System.loadLibrary("ctsverifier_jni");
+ }
+
+ public static class FileStatus {
+
+ private int dev;
+ private int ino;
+ private int mode;
+ private int nlink;
+ private int uid;
+ private int gid;
+ private int rdev;
+ private long size;
+ private int blksize;
+ private long blocks;
+ private long atime;
+ private long mtime;
+ private long ctime;
+
+ public int getUid() {
+ return uid;
+ }
+
+ public int getGid() {
+ return gid;
+ }
+
+ public int getMode() {
+ return mode;
+ }
+
+ public boolean isDirectory() {
+ return hasModeFlag(mode, S_IFDIR);
+ }
+
+ public boolean isSymbolicLink() {
+ return hasModeFlag(mode, S_IFLNK);
+ }
+
+ public boolean isSetUid() {
+ return hasModeFlag(mode, S_ISUID);
+ }
+
+ public boolean isSetGid() {
+ return hasModeFlag(mode, S_ISGID);
+ }
+ }
+
+ /**
+ * @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);
+
+ /** Display the file's mode like "ls -l" does. */
+ public static String getFormattedPermissions(int mode) {
+ StringBuilder permissions = new StringBuilder("-rwxrwxrwx");
+
+ int[] typeMasks = {S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFDIR, S_IFCHR, S_IFIFO};
+ char[] typeSymbols = {'s', 'l', '-', 'b', 'd', 'c', 'p'};
+ for (int i = 0; i < typeMasks.length; i++) {
+ if (hasModeFlag(mode, typeMasks[i])) {
+ permissions.setCharAt(0, typeSymbols[i]);
+ break;
+ }
+ }
+
+ int[] masks = {S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP,
+ S_IROTH, S_IWOTH, S_IXOTH};
+ for (int i = 0; i < masks.length; i++) {
+ if (!hasModeFlag(mode, masks[i])) {
+ permissions.setCharAt(1 + i, '-');
+ }
+ }
+
+
+ if (hasModeFlag(mode, S_ISUID)) {
+ permissions.setCharAt(3, hasModeFlag(mode, S_IXUSR) ? 's' : 'S');
+ }
+
+ if (hasModeFlag(mode, S_ISGID)) {
+ permissions.setCharAt(6, hasModeFlag(mode, S_IXGRP) ? 's' : 'S');
+ }
+
+ if (hasModeFlag(mode, S_ISVTX)) {
+ permissions.setCharAt(9, hasModeFlag(mode, S_IXOTH) ? 't' : 'T');
+ }
+
+ return permissions.toString();
+ }
+
+ private static boolean hasModeFlag(int mode, int flag) {
+ return (mode & flag) == flag;
+ }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
new file mode 100644
index 0000000..c89b8b4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/suid/SuidFilesActivity.java
@@ -0,0 +1,242 @@
+/*
+ * 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.
+ */
+
+package com.android.cts.verifier.suid;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.os.FileUtils;
+import com.android.cts.verifier.os.FileUtils.FileStatus;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/** {@link Activity} that tries to find suid files. */
+public class SuidFilesActivity extends ListActivity {
+
+ private static final String TAG = SuidFilesActivity.class.getSimpleName();
+
+ /** These programs are expected suid binaries. */
+ private static final Set<String> WHITELIST = new HashSet<String>(Arrays.asList(
+ "run-as"
+ ));
+
+ private ProgressDialog mProgressDialog;
+
+ private SuidFilesAdapter mAdapter;
+
+ private SuidFilesTask mFindSuidFilesTask;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mAdapter = new SuidFilesAdapter();
+ setListAdapter(mAdapter);
+
+ mProgressDialog = new ProgressDialog(this);
+ mProgressDialog.setMessage(getString(R.string.starting_scan));
+ mProgressDialog.setOnCancelListener(new OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ // If the scanning dialog is cancelled, then stop the task and finish the activity
+ // to prevent the user from just seeing a blank listview.
+ if (mFindSuidFilesTask != null) {
+ mFindSuidFilesTask.cancel(true);
+ }
+ finish();
+ }
+ });
+
+ // Start searching for suid files using a background thread.
+ mFindSuidFilesTask = new SuidFilesTask();
+ mFindSuidFilesTask.execute(new File("/"));
+ }
+
+ @Override
+ protected void onListItemClick(ListView listView, View view, int position, long id) {
+ super.onListItemClick(listView, view, position, id);
+ File file = mAdapter.getItem(position);
+ String message = getMessage(file);
+ new AlertDialog.Builder(this)
+ .setTitle(file.getName())
+ .setMessage(message)
+ .show();
+ }
+
+ private String getMessage(File file) {
+ FileStatus status = new FileStatus();
+ if (FileUtils.getFileStatus(file.getAbsolutePath(), status, true)) {
+ return getString(R.string.file_status,
+ FileUtils.getUserName(status.getUid()),
+ FileUtils.getGroupName(status.getGid()),
+ FileUtils.getFormattedPermissions(status.getMode()),
+ file.getAbsolutePath());
+ } else {
+ return getString(R.string.no_file_status);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mFindSuidFilesTask != null) {
+ mFindSuidFilesTask.cancel(true);
+ }
+ }
+
+ /** {@link ListView} items display the basenames of the suid files. */
+ class SuidFilesAdapter extends ArrayAdapter<File> {
+
+ SuidFilesAdapter() {
+ super(SuidFilesActivity.this, android.R.layout.simple_list_item_1);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView view = (TextView) super.getView(position, convertView, parent);
+ File file = getItem(position);
+ view.setText(file.getName());
+ return view;
+ }
+ }
+
+ /** {@link AsyncTask} that searches the file system for suid files. */
+ class SuidFilesTask extends AsyncTask<File, File, Set<File>> {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ mProgressDialog.show();
+ }
+
+ @Override
+ protected Set<File> doInBackground(File... paths) {
+ Set<File> suidFiles = new HashSet<File>();
+ DirectoryFileFilter dirFilter = new DirectoryFileFilter();
+ SuidFileFilter suidFilter = new SuidFileFilter();
+ for (File path : paths) {
+ findSuidFiles(path, suidFiles, dirFilter, suidFilter);
+ }
+ return suidFiles;
+ }
+
+ private void findSuidFiles(File dir, Set<File> foundSuidFiles,
+ DirectoryFileFilter dirFilter, SuidFileFilter suidFilter) {
+
+ // Recursively traverse sub directories...
+ File[] subDirs = dir.listFiles(dirFilter);
+ if (subDirs != null && subDirs.length > 0) {
+ for (File subDir : subDirs) {
+ findSuidFiles(subDir, foundSuidFiles, dirFilter, suidFilter);
+ }
+ }
+
+ // / ...then inspect files in directory to find offending binaries.
+ publishProgress(dir);
+ File[] suidFiles = dir.listFiles(suidFilter);
+ if (suidFiles != null && suidFiles.length > 0) {
+ Collections.addAll(foundSuidFiles, suidFiles);
+ }
+ }
+
+ /** {@link FileFilter} that returns only directories that are not symbolic links. */
+ private class DirectoryFileFilter implements FileFilter {
+
+ private final FileStatus status = new FileStatus();
+
+ public boolean accept(File pathname) {
+ // Don't follow symlinks to avoid infinite looping.
+ if (FileUtils.getFileStatus(pathname.getPath(), status, true)) {
+ return status.isDirectory() && !status.isSymbolicLink();
+ } else {
+ Log.w(TAG, "Could not stat " + pathname);
+ return false;
+ }
+ }
+ }
+
+ /** {@link FileFilter} that returns files that have setuid root or setgid root. */
+ private class SuidFileFilter implements FileFilter {
+
+ private final FileStatus status = new FileStatus();
+
+ public boolean accept(File pathname) {
+ if (!WHITELIST.contains(pathname.getName())
+ && FileUtils.getFileStatus(pathname.getPath(), status, true)) {
+ return !status.isDirectory()
+ && !status.isSymbolicLink()
+ && status.isSetUid();
+ } else {
+ Log.w(TAG, "Could not stat " + pathname);
+ return false;
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Set<File> results) {
+ super.onPostExecute(results);
+ mProgressDialog.hide();
+
+ // Task could be cancled and results could be null but don't bother doing anything.
+ if (results != null) {
+ for (File result : results) {
+ mAdapter.add(result);
+ }
+
+ // Alert the user that nothing was found rather than showing an empty list view.
+ if (results.isEmpty()) {
+ new AlertDialog.Builder(SuidFilesActivity.this)
+ .setTitle(R.string.congratulations)
+ .setMessage(R.string.no_suid_files)
+ .setOnCancelListener(new OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ // No reason to hang around if there were no offending files.
+ finish();
+ }
+ })
+ .show();
+ }
+ }
+ }
+
+ @Override
+ protected void onProgressUpdate(File... values) {
+ super.onProgressUpdate(values);
+
+ // Show the current directory being scanned...
+ mProgressDialog.setMessage(values[0].getAbsolutePath());
+ }
+ }
+}