Home | History | Annotate | Download | only in suid
      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