Add query to get installed apps with known vuln + tests.

Note that I don't think the query will work correctly across multiple repos,
because it is currently only querying the app with the "preferred
metadata".
This commit is contained in:
Peter Serwylo 2017-07-05 13:56:45 +10:00
parent 1fc8828122
commit 504854547b
4 changed files with 165 additions and 16 deletions

View File

@ -11,6 +11,7 @@ import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.ApkAntiFeatureJoinTable;
import org.fdroid.fdroid.data.Schema.ApkTable;
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
@ -133,6 +134,12 @@ public class AppProvider extends FDroidProvider {
Uri uri = Uri.withAppendedPath(AppProvider.getContentUri(), PATH_CALC_PREFERRED_METADATA);
context.getContentResolver().query(uri, null, null, null, null);
}
public static List<App> findInstalledAppsWithKnownVulns(Context context) {
Uri uri = getInstalledWithKnownVulnsUri();
Cursor cursor = context.getContentResolver().query(uri, Cols.ALL, null, null, null);
return cursorToList(cursor);
}
}
/**
@ -150,6 +157,7 @@ public class AppProvider extends FDroidProvider {
protected static class AppQuerySelection extends QuerySelection {
private boolean naturalJoinToInstalled;
private boolean naturalJoinAntiFeatures;
private boolean leftJoinPrefs;
AppQuerySelection() {
@ -170,6 +178,10 @@ public class AppProvider extends FDroidProvider {
return naturalJoinToInstalled;
}
public boolean naturalJoinAntiFeatures() {
return naturalJoinAntiFeatures;
}
/**
* Tells the query selection that it will need to join onto the installed apps table
* when used. This should be called when your query makes use of fields from that table
@ -182,6 +194,11 @@ public class AppProvider extends FDroidProvider {
return this;
}
public AppQuerySelection requireNatrualJoinAntiFeatures() {
naturalJoinAntiFeatures = true;
return this;
}
public boolean leftJoinToPrefs() {
return leftJoinPrefs;
}
@ -201,6 +218,11 @@ public class AppProvider extends FDroidProvider {
if (this.leftJoinToPrefs() || query.leftJoinToPrefs()) {
bothWithJoin.requireLeftJoinPrefs();
}
if (this.naturalJoinAntiFeatures() || query.naturalJoinAntiFeatures()) {
bothWithJoin.requireNatrualJoinAntiFeatures();
}
return bothWithJoin;
}
@ -210,6 +232,7 @@ public class AppProvider extends FDroidProvider {
private boolean isSuggestedApkTableAdded;
private boolean requiresInstalledTable;
private boolean requiresAntiFeatures;
private boolean requiresLeftJoinToPrefs;
private boolean countFieldAppended;
@ -243,6 +266,9 @@ public class AppProvider extends FDroidProvider {
if (selection.leftJoinToPrefs()) {
leftJoinToPrefs();
}
if (selection.naturalJoinAntiFeatures()) {
naturalJoinAntiFeatures();
}
}
// TODO: What if the selection requires a natural join, but we first get a left join
@ -277,6 +303,22 @@ public class AppProvider extends FDroidProvider {
}
}
public void naturalJoinAntiFeatures() {
if (!requiresAntiFeatures) {
join(
getApkAntiFeatureJoinTableName(),
"apkAntiFeature",
"apkAntiFeature." + ApkAntiFeatureJoinTable.Cols.APK_ID + " = " + getApkTableName() + "." + ApkTable.Cols.ROW_ID);
join(
Schema.AntiFeatureTable.NAME,
"antiFeature",
"antiFeature." + Schema.AntiFeatureTable.Cols.ROW_ID + " = " + "apkAntiFeature." + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID);
requiresAntiFeatures = true;
}
}
@Override
public void addField(String field) {
switch (field) {
@ -370,6 +412,7 @@ public class AppProvider extends FDroidProvider {
private static final String PATH_CALC_PREFERRED_METADATA = "calcPreferredMetadata";
private static final String PATH_CALC_SUGGESTED_APKS = "calcNonRepoDetailsFromIndex";
private static final String PATH_TOP_FROM_CATEGORY = "topFromCategory";
private static final String PATH_INSTALLED_WITH_KNOWN_VULNS = "installedWithKnownVulns";
private static final int CAN_UPDATE = CODE_SINGLE + 1;
private static final int INSTALLED = CAN_UPDATE + 1;
@ -383,6 +426,7 @@ public class AppProvider extends FDroidProvider {
private static final int HIGHEST_PRIORITY = SEARCH_REPO + 1;
private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1;
private static final int TOP_FROM_CATEGORY = CALC_PREFERRED_METADATA + 1;
private static final int INSTALLED_WITH_KNOWN_VULNS = TOP_FROM_CATEGORY + 1;
static {
MATCHER.addURI(getAuthority(), null, CODE_LIST);
@ -400,6 +444,7 @@ public class AppProvider extends FDroidProvider {
MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
MATCHER.addURI(getAuthority(), PATH_TOP_FROM_CATEGORY + "/#/*", TOP_FROM_CATEGORY);
MATCHER.addURI(getAuthority(), PATH_INSTALLED_WITH_KNOWN_VULNS, INSTALLED_WITH_KNOWN_VULNS);
}
public static Uri getContentUri() {
@ -421,6 +466,12 @@ public class AppProvider extends FDroidProvider {
.build();
}
public static Uri getInstalledWithKnownVulnsUri() {
return getContentUri().buildUpon()
.appendPath(PATH_INSTALLED_WITH_KNOWN_VULNS)
.build();
}
public static Uri getTopFromCategoryUri(String category, int limit) {
return getContentUri().buildUpon()
.appendPath(PATH_TOP_FROM_CATEGORY)
@ -505,6 +556,10 @@ public class AppProvider extends FDroidProvider {
return ApkTable.NAME;
}
protected String getApkAntiFeatureJoinTableName() {
return ApkAntiFeatureJoinTable.NAME;
}
@Override
protected String getProviderName() {
return "AppProvider";
@ -652,6 +707,14 @@ public class AppProvider extends FDroidProvider {
return new AppQuerySelection(selection, args);
}
private AppQuerySelection queryInstalledWithKnownVulns() {
// Include the hash in this check because otherwise any app with any vulnerable version will
// get returned.
String selection = " antiFeature." + Schema.AntiFeatureTable.Cols.NAME + " = 'KnownVuln' AND " +
getApkTableName() + "." + ApkTable.Cols.HASH + " = installed." + InstalledAppTable.Cols.HASH;
return new AppQuerySelection(selection).requireNaturalInstalledTable().requireNatrualJoinAntiFeatures();
}
static AppQuerySelection queryPackageNames(String packageNames, String packageNameField) {
String[] args = packageNames.split(",");
String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
@ -739,6 +802,11 @@ public class AppProvider extends FDroidProvider {
includeSwap = false;
break;
case INSTALLED_WITH_KNOWN_VULNS:
selection = selection.add(queryInstalledWithKnownVulns());
includeSwap = false;
break;
case RECENTLY_UPDATED:
String table = getTableName();
String isNew = table + "." + Cols.LAST_UPDATED + " <= " + table + "." + Cols.ADDED + " DESC";

View File

@ -126,6 +126,10 @@ public class TempAppProvider extends AppProvider {
return TempApkProvider.TABLE_TEMP_APK;
}
protected String getApkAntiFeatureJoinTableName() {
return TempApkProvider.TABLE_TEMP_APK;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
switch (MATCHER.match(uri)) {

View File

@ -6,14 +6,17 @@ import android.content.ContentValues;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.data.InstalledAppTestUtils;
import org.fdroid.fdroid.data.Schema;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.io.IOException;
import java.util.List;
import static org.junit.Assert.assertNull;
@ -24,26 +27,88 @@ import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
public class AntiFeaturesTest extends FDroidProviderTest {
@Test
public void testPerApkAntiFeatures() throws IOException, RepoUpdater.UpdateException {
private App notVuln;
private App allVuln;
private App vulnAtV2;
@Before
public void setup() {
Preferences.setup(context);
ContentValues vulnValues = new ContentValues(1);
vulnValues.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
App vulnAtV2 = Assert.insertApp(context, "com.vuln", "Fixed it");
Assert.insertApk(context, vulnAtV2, 1);
Assert.insertApk(context, vulnAtV2, 2, vulnValues);
Assert.insertApk(context, vulnAtV2, 3);
vulnAtV2 = Assert.insertApp(context, "com.vuln", "Fixed it");
insertApk(vulnAtV2, 1, false);
insertApk(vulnAtV2, 2, true);
insertApk(vulnAtV2, 3, false);
App notVuln = Assert.insertApp(context, "com.not-vuln", "It's Fine");
Assert.insertApk(context, notVuln, 5);
Assert.insertApk(context, notVuln, 10);
Assert.insertApk(context, notVuln, 15);
notVuln = Assert.insertApp(context, "com.not-vuln", "It's Fine");
insertApk(notVuln, 5, false);
insertApk(notVuln, 10, false);
insertApk(notVuln, 15, false);
App allVuln = Assert.insertApp(context, "com.all-vuln", "Oops");
Assert.insertApk(context, allVuln, 100, vulnValues);
Assert.insertApk(context, allVuln, 101, vulnValues);
Assert.insertApk(context, allVuln, 105, vulnValues);
allVuln = Assert.insertApp(context, "com.all-vuln", "Oops");
insertApk(allVuln, 100, true);
insertApk(allVuln, 101, true);
insertApk(allVuln, 105, true);
AppProvider.Helper.recalculatePreferredMetadata(context);
}
@After
public void tearDown() {
Preferences.clearSingletonForTesting();
}
private static String generateHash(String packageName, int versionCode) {
return packageName + "-" + versionCode;
}
private void insertApk(App app, int versionCode, boolean isVuln) {
ContentValues values = new ContentValues();
values.put(Schema.ApkTable.Cols.HASH, generateHash(app.packageName, versionCode));
if (isVuln) {
values.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
}
Assert.insertApk(context, app, versionCode, values);
}
private void install(App app, int versionCode) {
String hash = generateHash(app.packageName, versionCode);
InstalledAppTestUtils.install(context, app.packageName, versionCode, "v" + versionCode, null, hash);
}
@Test
public void noVulnerableApps() {
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(0, installed.size());
}
@Test
public void futureVersionIsVulnerable() {
install(vulnAtV2, 1);
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(0, installed.size());
}
@Test
public void vulnerableAndAbleToBeUpdated() {
install(vulnAtV2, 2);
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(1, installed.size());
assertEquals(vulnAtV2.packageName, installed.get(0).packageName);
}
@Test
public void vulnerableButUpToDate() {
install(vulnAtV2, 3);
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
assertEquals(0, installed.size());
}
@Test
public void antiFeaturesSaveCorrectly() {
List<Apk> notVulnApks = ApkProvider.Helper.findByPackageName(context, notVuln.packageName);
assertEquals(3, notVulnApks.size());

View File

@ -22,6 +22,14 @@ public class InstalledAppTestUtils {
String packageName,
int versionCode, String versionName,
@Nullable String signingCert) {
install(context, packageName, versionCode, versionName, signingCert, null);
}
public static void install(Context context,
String packageName,
int versionCode, String versionName,
@Nullable String signingCert,
@Nullable String hash) {
PackageInfo info = new PackageInfo();
info.packageName = packageName;
info.versionCode = versionCode;
@ -31,8 +39,12 @@ public class InstalledAppTestUtils {
if (signingCert != null) {
info.signatures = new Signature[]{new Signature(signingCert)};
}
String hashType = "sha256";
String hash = "00112233445566778899aabbccddeeff";
if (hash == null) {
hash = "00112233445566778899aabbccddeeff";
}
InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
}