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:
parent
1fc8828122
commit
504854547b
@ -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";
|
||||
|
@ -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)) {
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user