1. 靜態(tài)掃描流程
1.1 版本發(fā)布流程
大致分為5個階段萌京,靜態(tài)代碼掃描的工作在第3步進(jìn)行,如圖:
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ī)則
推薦寫法:
- 服務(wù)端充分校驗參數(shù)
- 使用參數(shù)化查詢焚鹊,比如SQLiteStatement
- 避免使用rawQuery()方法
- 對用戶輸入進(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ù)璧针。
推薦寫法:
- minSdkVersion不低于9嚷炉。
- android:allowBackup屬性顯示設(shè)置為false。
1.3 Android 客戶端掃描流程
分為3個階段:
1.3.1 基礎(chǔ)內(nèi)容掃描
使用 Android Lint 對項目進(jìn)行掃描探橱,該工具已將掃描到的問題進(jìn)行了分組申屹,同時定義了問題的嚴(yán)重級別: error
和 warning
。在 Android Studio 的 Inspection Results 視圖窗中走搁,點擊問題標(biāo)題即可在右邊的詳情視圖中查看該問題的具體解釋等內(nèi)容独柑,針對部分內(nèi)容還有直接進(jìn)行自動修復(fù)的按鈕,如圖:
關(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中,可以查看具體問題位置及示例代碼窄坦。如圖
關(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é)果的顯示上晒奕,報告只會給出問題所在的行號。如圖
關(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 -> 啟動掃描
如圖:
參考資料
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為火線報告輸出路徑
- Android stuido版本
- Android Studio -> 菜單欄 -> File -> Settings... -> Plugins
- 搜索框 -> 搜索fireline -> install -> 重啟
- 使用 -> Project視圖 -> 鼠標(biāo)右鍵 -> fireline -> run 生成報告
參考資料
2.3 Godeyes
使用方法
詳情參考 官方使用方法
下載 Android Studio版本插件 下載地址
Android Studio
-> 菜單欄 ->File
->Settings
->Plugins
選擇
Install plugin from disk
-> 選擇已下載的Godeyes_Android_Vx.x_(for_AndroidStudio).zip
->OK
-> 安裝完成 -> 重啟Project視圖 -> 鼠標(biāo)右鍵 ->
Run Godeyes
-> 生成報告
參考資料
2.4 Infer
注意:
- 僅支持 Mac 和 Linux 環(huán)境
- 需要Python 且 Python >= 2.7
使用方法
cd {項目的根目錄}
./gradlew clean
infer -- ./gradlew build
一段時間后會在項目的根目錄下生成infer-out這個文件夾蒋腮,里面的bugs.txt文檔里記錄的就是掃描出的問題淘捡。
參考資料
- https://infer.liaohuqiu.net/
- http://blog.csdn.net/itfootball/article/details/46474235
- https://github.com/facebook/infer/blob/master/INSTALL.md
- https://github.com/facebook/infer/releases
3. 疑問解答
3.1 Android Studio 按照教程安裝完Godeyes后掃描項目發(fā)現(xiàn)沒有得到的報告沒有任何錯誤,說明項目沒有任何問題么池摧?
不一定焦除。Godeyes插件使用前,需要設(shè)置輸出報告類型作彤,若都兩種類型都沒選擇膘魄,則生成的html報告中就會是0錯誤。如果這里也設(shè)置了但是html報告中還是0個錯誤則說明你的代碼沒有問題竭讳。
3.2 Android Lint 工具檢查的項目太多了创葡,只關(guān)注error
就可以了吧?
并不是绢慢,Android Lint 檢查項目繁瑣是由于他自身也集成了一些檢查工具灿渴。例如FindBugs,單獨用Findbugs檢查的內(nèi)容基本都涵蓋在了 Android Lint檢查的 Probable bugs 分組中胰舆,但Android Lint 只將這些錯誤視為
warning
,而在FindBugs則可能是Scary(嚴(yán)重問題)
的骚露,所以除了error
,warning
也是必須關(guān)注的缚窿。