1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.suid; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 import com.android.cts.verifier.TestResult; 22 import com.android.cts.verifier.os.FileUtils; 23 import com.android.cts.verifier.os.FileUtils.FileStatus; 24 25 import android.app.Activity; 26 import android.app.AlertDialog; 27 import android.app.ProgressDialog; 28 import android.content.DialogInterface; 29 import android.content.DialogInterface.OnCancelListener; 30 import android.content.DialogInterface.OnClickListener; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.util.Log; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.widget.ArrayAdapter; 37 import android.widget.ListView; 38 import android.widget.TextView; 39 40 import java.io.File; 41 import java.io.FileFilter; 42 import java.util.Arrays; 43 import java.util.Collections; 44 import java.util.HashSet; 45 import java.util.Set; 46 47 /** {@link Activity} that tries to find suid files. */ 48 public class SuidFilesActivity extends PassFailButtons.ListActivity { 49 50 private static final String TAG = SuidFilesActivity.class.getSimpleName(); 51 52 /** These programs are expected suid binaries. */ 53 private static final Set<String> WHITELIST = new HashSet<String>(Arrays.asList( 54 "run-as" 55 )); 56 57 private ProgressDialog mProgressDialog; 58 59 private SuidFilesAdapter mAdapter; 60 61 private SuidFilesTask mFindSuidFilesTask; 62 63 @Override 64 protected void onCreate(Bundle savedInstanceState) { 65 super.onCreate(savedInstanceState); 66 setContentView(R.layout.pass_fail_list); 67 setPassFailButtonClickListeners(); 68 getPassButton().setEnabled(false); 69 70 mAdapter = new SuidFilesAdapter(); 71 setListAdapter(mAdapter); 72 73 new AlertDialog.Builder(this) 74 .setIcon(android.R.drawable.ic_dialog_info) 75 .setTitle(R.string.suid_files) 76 .setMessage(R.string.suid_files_info) 77 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 78 @Override 79 public void onClick(DialogInterface dialog, int which) { 80 startScan(); 81 } 82 }) 83 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 84 @Override 85 public void onClick(DialogInterface dialog, int which) { 86 finish(); 87 } 88 }) 89 .setOnCancelListener(new OnCancelListener() { 90 @Override 91 public void onCancel(DialogInterface dialog) { 92 finish(); 93 } 94 }) 95 .show(); 96 } 97 98 private void startScan() { 99 mProgressDialog = new ProgressDialog(this); 100 mProgressDialog.setTitle(getString(R.string.scanning_directory)); 101 mProgressDialog.setOnCancelListener(new OnCancelListener() { 102 @Override 103 public void onCancel(DialogInterface dialog) { 104 // If the scanning dialog is cancelled, then stop the task and finish the activity 105 // to prevent the user from just seeing a blank listview. 106 if (mFindSuidFilesTask != null) { 107 mFindSuidFilesTask.cancel(true); 108 } 109 finish(); 110 } 111 }); 112 113 // Start searching for suid files using a background thread. 114 mFindSuidFilesTask = new SuidFilesTask(); 115 mFindSuidFilesTask.execute(new File("/")); 116 } 117 118 @Override 119 protected void onListItemClick(ListView listView, View view, int position, long id) { 120 super.onListItemClick(listView, view, position, id); 121 File file = mAdapter.getItem(position); 122 String message = getMessage(file); 123 new AlertDialog.Builder(this) 124 .setTitle(file.getName()) 125 .setMessage(message) 126 .show(); 127 } 128 129 private String getMessage(File file) { 130 FileStatus status = new FileStatus(); 131 if (FileUtils.getFileStatus(file.getAbsolutePath(), status, true)) { 132 return getString(R.string.file_status, 133 FileUtils.getUserName(status.getUid()), 134 FileUtils.getGroupName(status.getGid()), 135 FileUtils.getFormattedPermissions(status.getMode()), 136 file.getAbsolutePath()); 137 } else { 138 return getString(R.string.no_file_status); 139 } 140 } 141 142 @Override 143 protected void onDestroy() { 144 Log.e("Suid", "onDestroy"); 145 super.onDestroy(); 146 if (mFindSuidFilesTask != null) { 147 mFindSuidFilesTask.cancel(true); 148 } 149 } 150 151 /** {@link ListView} items display the basenames of the suid files. */ 152 class SuidFilesAdapter extends ArrayAdapter<File> { 153 154 SuidFilesAdapter() { 155 super(SuidFilesActivity.this, android.R.layout.simple_list_item_1); 156 } 157 158 @Override 159 public View getView(int position, View convertView, ViewGroup parent) { 160 TextView view = (TextView) super.getView(position, convertView, parent); 161 File file = getItem(position); 162 view.setText(file.getName()); 163 view.setBackgroundResource(WHITELIST.contains(file.getName()) 164 ? R.drawable.test_pass_gradient 165 : R.drawable.test_fail_gradient); 166 return view; 167 } 168 } 169 170 /** {@link AsyncTask} that searches the file system for suid files. */ 171 class SuidFilesTask extends AsyncTask<File, File, Set<File>> { 172 173 @Override 174 protected void onPreExecute() { 175 super.onPreExecute(); 176 mProgressDialog.show(); 177 } 178 179 @Override 180 protected Set<File> doInBackground(File... paths) { 181 Set<File> suidFiles = new HashSet<File>(); 182 DirectoryFileFilter dirFilter = new DirectoryFileFilter(); 183 SuidFileFilter suidFilter = new SuidFileFilter(); 184 for (File path : paths) { 185 findSuidFiles(path, suidFiles, dirFilter, suidFilter); 186 } 187 return suidFiles; 188 } 189 190 private void findSuidFiles(File dir, Set<File> foundSuidFiles, 191 DirectoryFileFilter dirFilter, SuidFileFilter suidFilter) { 192 193 // Recursively traverse sub directories... 194 File[] subDirs = dir.listFiles(dirFilter); 195 if (subDirs != null && subDirs.length > 0) { 196 for (File subDir : subDirs) { 197 findSuidFiles(subDir, foundSuidFiles, dirFilter, suidFilter); 198 } 199 } 200 201 // / ...then inspect files in directory to find offending binaries. 202 publishProgress(dir); 203 File[] suidFiles = dir.listFiles(suidFilter); 204 if (suidFiles != null && suidFiles.length > 0) { 205 Collections.addAll(foundSuidFiles, suidFiles); 206 } 207 } 208 209 /** {@link FileFilter} that returns only directories that are not symbolic links. */ 210 private class DirectoryFileFilter implements FileFilter { 211 212 private final FileStatus status = new FileStatus(); 213 214 @Override 215 public boolean accept(File pathname) { 216 // Don't follow symlinks to avoid infinite looping. 217 if (FileUtils.getFileStatus(pathname.getPath(), status, true)) { 218 return status.isDirectory() && !status.isSymbolicLink(); 219 } else { 220 Log.w(TAG, "Could not stat " + pathname); 221 return false; 222 } 223 } 224 } 225 226 /** {@link FileFilter} that returns files that have setuid root or setgid root. */ 227 private class SuidFileFilter implements FileFilter { 228 229 private final FileStatus status = new FileStatus(); 230 231 @Override 232 public boolean accept(File pathname) { 233 if (FileUtils.getFileStatus(pathname.getPath(), status, true)) { 234 // only files with setUid which can be executable by CTS are reported. 235 return !status.isDirectory() 236 && !status.isSymbolicLink() 237 && status.isSetUid() 238 && status.isExecutableByCTS(); 239 } else { 240 Log.w(TAG, "Could not stat " + pathname); 241 return false; 242 } 243 } 244 } 245 246 @Override 247 protected void onPostExecute(Set<File> results) { 248 super.onPostExecute(results); 249 mProgressDialog.dismiss(); 250 251 // Task could be cancelled and results could be null but don't bother doing anything. 252 if (results != null) { 253 boolean passed = true; 254 for (File result : results) { 255 if (!WHITELIST.contains(result.getName())) { 256 passed = false; 257 } 258 mAdapter.add(result); 259 } 260 261 // Alert the user that nothing was found rather than showing an empty list view. 262 if (passed) { 263 getPassButton().setEnabled(true); 264 new AlertDialog.Builder(SuidFilesActivity.this) 265 .setTitle(R.string.congratulations) 266 .setMessage(R.string.no_suid_files) 267 .setPositiveButton(android.R.string.ok, new OnClickListener() { 268 @Override 269 public void onClick(DialogInterface dialog, int which) { 270 dialog.dismiss(); 271 } 272 }) 273 .show(); 274 } 275 } 276 } 277 278 @Override 279 protected void onProgressUpdate(File... values) { 280 super.onProgressUpdate(values); 281 282 // Show the current directory being scanned... 283 mProgressDialog.setMessage(values[0].getAbsolutePath()); 284 } 285 } 286 } 287