Android 靜態(tài)代碼掃描流程及工具說明

1. 靜態(tài)掃描流程

1.1 版本發(fā)布流程

大致分為5個階段萌京,靜態(tài)代碼掃描的工作在第3步進(jìn)行,如圖:

版本發(fā)布流程圖

1.2 典型案例分析

  • [空指針]空指針引用
  • [內(nèi)存泄露]Stream資源關(guān)閉
  • [性能]使用indexOf(字符)
  • [兼容]系統(tǒng)API兼容性隱患
  • [越界]數(shù)組下標(biāo)越界隱患
  • [異常] 使用除法或求余沒有判斷分母長度隱患
  • [SQL]注入風(fēng)險
  • [應(yīng)用安全] AndroidMannifest.xml文件中allowBackup設(shè)置為true時會導(dǎo)致數(shù)據(jù)泄露

更多的錯誤檢查示例請查看各檢查工具的檢查規(guī)則說明文檔。

1.2.1 [空指針]空指針引用

錯誤位置:4

public class StringUtil {
  public static final String queryParams(String param) {
    String ret = "";
    if (param != null || param.length() > 2) {
      ret = param.substring(1, param.length() - 1);
    }
  return ret;
}

存在空指針引用,會導(dǎo)致空指針異常。解決方案:

public class StringUtil {
  public static final String queryParams(String param) {
    String ret = "";
    if (param != null && param.length() > 2) {
      ret = param.substring(1, param.length() - 1);
    }
  return ret;
}

1.2.2 [內(nèi)存泄露]Stream資源關(guān)閉

錯誤位置:17

private static void write2logfile(String msg) {
    try {

        File sdCardDir = android.os.Environment
                .getExternalStorageDirectory();

        File logfile = new File(sdCardDir.getAbsolutePath()
                + File.separator + logfileName);

        if (!logfile.exists()) {
            logfile.createNewFile();
        }

        msg += "\n";

        FileOutputStream outputStream = new FileOutputStream(logfile, true);
        outputStream.write(msg.getBytes());
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

資源對象在被關(guān)閉或者Return之前可能出現(xiàn)異常,導(dǎo)致無法正常關(guān)閉或Return贱勃。比如連續(xù)關(guān)閉多個資源對象時沒有進(jìn)行異常捕獲,或者資源對象在Return之前進(jìn)行了未捕獲異常的操作谤逼。解決方案:

private static void write2logfile(String msg) {
    try {

        File sdCardDir = android.os.Environment
                .getExternalStorageDirectory();

        File logfile = new File(sdCardDir.getAbsolutePath()
                + File.separator + logfileName);

        if (!logfile.exists()) {
            logfile.createNewFile();
        }

        msg += "\n";

        FileOutputStream outputStream = new FileOutputStream(logfile, true);
        outputStream.write(msg.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally{
      if(null != outputStream){
        outputStream.close();
      }
    }
}

1.2.3 [性能]使用indexOf(字符)

錯誤位置:340

338                         int index = result.indexOf(Keyword);
339                         line = result.substring(index + Keyword.length());
340                         index = line.indexOf(" ");
341                         kernelVersion = line.substring(0, index);
342                     }
343                 } catch (IndexOutOfBoundsException e) {

當(dāng)你檢測單個字符的位置時使用String.indexOf(字符)贵扰,它執(zhí)行的很快。
解決方案:不要使用indexOf(字符串)流部。

340                         index = line.indexOf(' ');

1.2.4 [兼容]系統(tǒng)API兼容性隱患

public static String getSupportMap(Context context, String seInfo) {
    StringBuffer support = new StringBuffer("000");
    if (!"000".equals(seInfo)) {
        support.setCharAt(2, '1');
    }

    if (VERSION.SDK_INT < 10) {
        return support.toString();
    }

    NfcManager manager = (NfcManager) context
            .getSystemService(Context.NFC_SERVICE);
    NfcAdapter adapter = manager.getDefaultAdapter();
    if (null == adapter) {
        return support.toString();
    } else {
        if (adapter.isEnabled()) {
            support.setCharAt(0, '1');
        } else {
            support.setCharAt(0, '2');
        }

        if (VERSION.SDK_INT >= 19) {
            PackageManager pm = context.getPackageManager();
            boolean hasNfcHce = pm
                    .hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
            if (hasNfcHce) {
                support.setCharAt(1, '1');
            }
        }
    }

    return support.toString();
}

getDefaultAdapter方法不支持:10(android2.3.3) 以下的版本戚绕。
解決方案:加入對版本的系統(tǒng)版本的判別

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) {
       // 包含新API的代碼塊
else
{
// 包含舊的API的代碼塊
}

1.2.5 [越界]數(shù)組下標(biāo)越界隱患

private void doSelectItem(int pos) {
    mButtonViews[mSelectedButtonIndex].line.setVisibility(View.GONE);
    mButtonViews[mSelectedButtonIndex].buttonText.setTextColor(Color.BLACK);
    mButtonViews[mSelectedButtonIndex].contentView.setVisibility(View.GONE);
    mButtonViews[pos].line.setVisibility(View.VISIBLE);
    mButtonViews[pos].buttonText
            .setTextColor(KDimens.K_COLOR_PROM_INDICATOR);
    mButtonViews[pos].contentView.setVisibility(View.VISIBLE);
    mSelectedButtonIndex = pos;
}

采用下標(biāo)的方式獲取數(shù)組元素時,如果下標(biāo)越界贵涵,將產(chǎn)生java.lang.ArrayIndexOutOfBoundsException的異常列肢,導(dǎo)致app出現(xiàn)Crash选泻。
解決方案:在使用下標(biāo)的方式獲取數(shù)組元素時拉盾,需判斷下標(biāo)的有效性畅卓。

1.2.6 [異常] 使用除法或求余沒有判斷分母長度隱患

public static Drawable zoomDrawable(Context context, Drawable in, int scaledW, int scaledH) {
  Drawable zoomed = null;
  if (in instanceof BitmapDrawable) {
      Bitmap bm = ((BitmapDrawable) in).getBitmap();
      if (scaledH != -1 && scaledW == -1) {
          scaledW = (int) ((float) (bm.getWidth() / bm.getHeight()) * scaledH);
      } else if (scaledH == -1 && scaledW != -1) {
          scaledH = (int) ((float) (bm.getHeight() / bm.getWidth()) * scaledW);
      }

      Bitmap sbm = Bitmap.createScaledBitmap(bm, scaledW, scaledW, true);
      zoomed = new BitmapDrawable(context.getResources(), sbm);
  }
  return zoomed;
}

使用除法或者求余運算時举哟,如果分母是通過調(diào)用函數(shù)返回的int助赞,未對返回值進(jìn)行判斷兼犯,當(dāng)返回值為0時捂蕴,會出現(xiàn)java.lang.ArithmeticException: / by zero異常员淫。
解決方案:調(diào)用函數(shù)前端盆,對函數(shù)的返回值的長度進(jìn)行判斷怀骤。

1.2.6 [SQL]注入風(fēng)險

描述:對Content Provider進(jìn)行增刪改查操作時,程序沒有對用戶的輸入進(jìn)行過濾焕妙,未采用參數(shù)化查詢的方式蒋伦,可能導(dǎo)致sql注入攻擊。

代碼示例:

private SQLiteDatabase db;
db.rawQuery("select * from person", null);//觸發(fā)規(guī)則

推薦寫法:

  1. 服務(wù)端充分校驗參數(shù)
  2. 使用參數(shù)化查詢焚鹊,比如SQLiteStatement
  3. 避免使用rawQuery()方法
  4. 對用戶輸入進(jìn)行過濾
SQLiteStatement sqLiteStatement = db.compileStatement("insert into msgTable(uid, msg) values(?, ?)");
sqLiteStatement.bindLong(1, 12);
sqLiteStatement.bindString(3, "text");
long newRowId = sqLiteStatement.executeInsert();

1.2.7 [應(yīng)用安全] AndroidMannifest.xml文件中allowBackup設(shè)置為true時會導(dǎo)致數(shù)據(jù)泄露

描述:建議將AndroidMannifest.xml文件android:allowBackup屬性設(shè)置為false痕届。當(dāng)allowBackup標(biāo)志值為true時,攻擊者可通過adb backup和adb restore來備份和恢復(fù)應(yīng)用程序數(shù)據(jù)末患。

推薦寫法:

描述:建議將AndroidMannifest.xml文件android:allowBackup屬性設(shè)置為false研叫。當(dāng)allowBackup標(biāo)志值為true時,攻擊者可通過adb backup和adb restore來備份和恢復(fù)應(yīng)用程序數(shù)據(jù)璧针。

推薦寫法:

  1. minSdkVersion不低于9嚷炉。
  2. android:allowBackup屬性顯示設(shè)置為false。

1.3 Android 客戶端掃描流程

分為3個階段:

Android客戶端掃描流程圖

1.3.1 基礎(chǔ)內(nèi)容掃描

使用 Android Lint 對項目進(jìn)行掃描探橱,該工具已將掃描到的問題進(jìn)行了分組申屹,同時定義了問題的嚴(yán)重級別: errorwarning。在 Android StudioInspection Results 視圖窗中走搁,點擊問題標(biāo)題即可在右邊的詳情視圖中查看該問題的具體解釋等內(nèi)容独柑,針對部分內(nèi)容還有直接進(jìn)行自動修復(fù)的按鈕,如圖:

Android Lint

關(guān)注點

由于檢查的內(nèi)容繁多私植,我們重點關(guān)注以下幾個問題組的相關(guān)內(nèi)容:

  • Android 開頭的組忌栅,例如

    • Android > Lint > Correctness (可能影響程序正確性)
    • Android > Lint > Performance (可能影響程序性能)
    • Android > Lint > Security (可能影響程序安全性)
    • 等等
  • Class structure 組:指出類的設(shè)計上可能存在的問題

  • Code style issues 組:有助于提供代碼書寫規(guī)范

  • Probable bugs 組:有助于發(fā)現(xiàn)隱藏的問題

檢查通過標(biāo)準(zhǔn)

上述的列出的問題組別不出現(xiàn)或者出現(xiàn)但只包含warning類型的問題

1.3.2 可能引起Crash的問題掃描

使用360火線Godeyes對項目進(jìn)行掃描,下面將分別說明二者掃描時的關(guān)注點和檢查通過標(biāo)準(zhǔn):

360火線

360火線共有61個檢查項曲稼,按級別分為Block索绪、風(fēng)險建議優(yōu)化贫悄,檢查報告以html文件輸出瑞驱,
按規(guī)則分類查看的Tab中,可以查看具體問題位置及示例代碼窄坦。如圖

360 fireline
360 fireline
關(guān)注點

重點關(guān)注Block唤反、風(fēng)險兩類標(biāo)記的問題

檢查通過標(biāo)準(zhǔn)

掃描結(jié)果中不出現(xiàn)Block凳寺、風(fēng)險兩類問題

Godeyes

Godeyes共檢查23個錯誤,掃描結(jié)果以html文檔的方式輸出彤侍,文檔中包含了檢查問題的描述肠缨、示例以及推薦方案,方便理解盏阶。值得注意的是在掃描結(jié)果的顯示上晒奕,報告只會給出問題所在的行號。如圖

godeyes
關(guān)注點

所有列表檢查出的問題名斟。

檢查通過標(biāo)準(zhǔn)

各掃描項掃描結(jié)果為0

1.3.3 空指針和資源泄露掃描

使用Infer工具對可能的空指針可能的資源泄露進(jìn)行掃描脑慧,Infer工具會在項目的根文件夾下生成infer-out的文件夾,重點關(guān)注bugs.txt這個文件砰盐,文件中會詳細(xì)指出可能存在的問題的代碼片段及相應(yīng)的解釋闷袒,示例如下:

Found 70 issues

219: error: RESOURCE_LEAK
   resource of type `java.io.DataInputStream` acquired to `dis` by call to `new()` at line 159 is not released after line 219
**Note**: potential exception at line 164
  217.                  dis.close();
  218.                  is.close();
  219. >            } catch (IOException e) {
  220.                  e.printStackTrace();
  221.                  dr = null;

error: NULL_DEREFERENCE
  object returned by `getItemByName("instalment")` could be null and is dereferenced at line 402
  400.           } else {
  401.               ((UPDropDownWidget) getItemByName(Rules.TYPE_INSTALMENT))
  402. >                     .setmCanShow(true);
  403.               ((UPDropDownWidget) getItemByName(Rules.TYPE_INSTALMENT))
  404.                       .onCheckBoxStatusChanged(true);
關(guān)注點

所有列表檢查出的問題。

檢查通過標(biāo)準(zhǔn)

對檢查的問題盡量修復(fù)或者編寫保護(hù)語句避免拋出異常岩梳。

2. 工具使用

以下內(nèi)容均以 Android Studio 為默認(rèn)的開發(fā)環(huán)境

2.1 Android Lint

該工具已經(jīng)默認(rèn)集成 Android Studio

使用方法:

Android Stuido -> 菜單欄 -> Analyze -> Inspect Code -> 根據(jù)需要選擇相應(yīng)的掃描范圍 -> OK -> 啟動掃描

如圖:

Android Lint

Android Lint

Android Lint

參考資料

2.2 360 火線

使用方法

詳情參考 官方使用方法
火線插件目前可以在Android Studio中進(jìn)行在線搜索安裝霜运。

  • jar包版本使用

    java -jar D:\test\fireline.jar -s=D:\test\TestCase -r=E:\RedlineReport
    // 參數(shù)解釋:
    //【必填項】-s或scanSrcDir為被掃描的項目工程路徑
    //【必填項】-r或reportSaveDir為火線報告輸出路徑
    
360 fireline
  • Android stuido版本
    1. Android Studio -> 菜單欄 -> File -> Settings... -> Plugins
    2. 搜索框 -> 搜索fireline -> install -> 重啟
    3. 使用 -> Project視圖 -> 鼠標(biāo)右鍵 -> fireline -> run 生成報告
360 fireline
360 fireline

參考資料

2.3 Godeyes

使用方法

詳情參考 官方使用方法

  1. 下載 Android Studio版本插件 下載地址

  2. Android Studio-> 菜單欄 -> File -> Settings -> Plugins

  3. 選擇Install plugin from disk -> 選擇已下載的Godeyes_Android_Vx.x_(for_AndroidStudio).zip -> OK -> 安裝完成 -> 重啟

  4. Project視圖 -> 鼠標(biāo)右鍵 -> Run Godeyes -> 生成報告

godeyes
godeyes
參考資料

2.4 Infer

注意:

  1. 僅支持 Mac 和 Linux 環(huán)境
  2. 需要Python 且 Python >= 2.7

使用方法

cd {項目的根目錄}
./gradlew clean
infer -- ./gradlew build

一段時間后會在項目的根目錄下生成infer-out這個文件夾蒋腮,里面的bugs.txt文檔里記錄的就是掃描出的問題淘捡。

參考資料

3. 疑問解答

3.1 Android Studio 按照教程安裝完Godeyes后掃描項目發(fā)現(xiàn)沒有得到的報告沒有任何錯誤,說明項目沒有任何問題么池摧?

不一定焦除。Godeyes插件使用前,需要設(shè)置輸出報告類型作彤,若都兩種類型都沒選擇膘魄,則生成的html報告中就會是0錯誤。如果這里也設(shè)置了但是html報告中還是0個錯誤則說明你的代碼沒有問題竭讳。

godeyes bug

3.2 Android Lint 工具檢查的項目太多了创葡,只關(guān)注error就可以了吧?

并不是绢慢,Android Lint 檢查項目繁瑣是由于他自身也集成了一些檢查工具灿渴。例如FindBugs,單獨用Findbugs檢查的內(nèi)容基本都涵蓋在了 Android Lint檢查的 Probable bugs 分組中胰舆,但Android Lint 只將這些錯誤視為warning,而在FindBugs則可能是Scary(嚴(yán)重問題)的骚露,所以除了errorwarning也是必須關(guān)注的缚窿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棘幸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子倦零,更是在濱河造成了極大的恐慌误续,老刑警劉巖吨悍,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蹋嵌,居然都是意外死亡畜份,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門欣尼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人停蕉,你說我怎么就攤上這事愕鼓。” “怎么了慧起?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵菇晃,是天一觀的道長。 經(jīng)常有香客問我蚓挤,道長磺送,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任灿意,我火速辦了婚禮估灿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缤剧。我一直安慰自己馅袁,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布荒辕。 她就那樣靜靜地躺著汗销,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抵窒。 梳的紋絲不亂的頭發(fā)上弛针,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天,我揣著相機與錄音李皇,去河邊找鬼削茁。 笑死,一個胖子當(dāng)著我的面吹牛掉房,可吹牛的內(nèi)容都是我干的付材。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼圃阳,長吁一口氣:“原來是場噩夢啊……” “哼厌衔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捍岳,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤富寿,失蹤者是張志新(化名)和其女友劉穎睬隶,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體页徐,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡苏潜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了变勇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恤左。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖搀绣,靈堂內(nèi)的尸體忽然破棺而出飞袋,到底是詐尸還是另有隱情,我是刑警寧澤链患,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布巧鸭,位于F島的核電站,受9級特大地震影響麻捻,放射性物質(zhì)發(fā)生泄漏纲仍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一贸毕、第九天 我趴在偏房一處隱蔽的房頂上張望郑叠。 院中可真熱鬧,春花似錦明棍、人聲如沸锻拘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽署拟。三九已至,卻和暖如春歌豺,著一層夾襖步出監(jiān)牢的瞬間推穷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工类咧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留馒铃,地道東北人。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓痕惋,卻偏偏與公主長得像区宇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子值戳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容