• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.server.net.watchlist;
18 
19 import android.annotation.Nullable;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteDatabase;
24 import android.database.sqlite.SQLiteDatabaseCorruptException;
25 import android.database.sqlite.SQLiteException;
26 import android.database.sqlite.SQLiteOpenHelper;
27 import android.os.Environment;
28 import android.util.Slog;
29 
30 import com.android.internal.util.HexDump;
31 
32 import java.io.File;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Set;
36 
37 /**
38  * Helper class to process watchlist read / save watchlist reports.
39  */
40 class WatchlistReportDbHelper extends SQLiteOpenHelper {
41 
42     private static final String TAG = "WatchlistReportDbHelper";
43 
44     private static final String NAME = "watchlist_report.db";
45     private static final int VERSION = 2;
46 
47     private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
48 
49     private static class WhiteListReportContract {
50         private static final String TABLE = "records";
51         private static final String APP_DIGEST = "app_digest";
52         private static final String CNC_DOMAIN = "cnc_domain";
53         private static final String TIMESTAMP = "timestamp";
54     }
55 
56     private static final String CREATE_TABLE_MODEL = "CREATE TABLE "
57             + WhiteListReportContract.TABLE + "("
58             + WhiteListReportContract.APP_DIGEST + " BLOB,"
59             + WhiteListReportContract.CNC_DOMAIN + " TEXT,"
60             + WhiteListReportContract.TIMESTAMP + " INTEGER DEFAULT 0" + " )";
61 
62     private static final int INDEX_DIGEST = 0;
63     private static final int INDEX_CNC_DOMAIN = 1;
64     private static final int INDEX_TIMESTAMP = 2;
65 
66     private static final String[] DIGEST_DOMAIN_PROJECTION =
67             new String[] {
68                     WhiteListReportContract.APP_DIGEST,
69                     WhiteListReportContract.CNC_DOMAIN
70             };
71 
72     private static WatchlistReportDbHelper sInstance;
73 
74     /**
75      * Aggregated watchlist records.
76      */
77     public static class AggregatedResult {
78         // A list of digests that visited c&c domain or ip before.
79         final Set<String> appDigestList;
80 
81         // The c&c domain or ip visited before.
82         @Nullable final String cncDomainVisited;
83 
84         // A list of app digests and c&c domain visited.
85         final HashMap<String, String> appDigestCNCList;
86 
AggregatedResult(Set<String> appDigestList, String cncDomainVisited, HashMap<String, String> appDigestCNCList)87         public AggregatedResult(Set<String> appDigestList, String cncDomainVisited,
88                 HashMap<String, String> appDigestCNCList) {
89             this.appDigestList = appDigestList;
90             this.cncDomainVisited = cncDomainVisited;
91             this.appDigestCNCList = appDigestCNCList;
92         }
93     }
94 
getSystemWatchlistDbFile()95     static File getSystemWatchlistDbFile() {
96         return new File(Environment.getDataSystemDirectory(), NAME);
97     }
98 
WatchlistReportDbHelper(Context context)99     private WatchlistReportDbHelper(Context context) {
100         super(context, getSystemWatchlistDbFile().getAbsolutePath(), null, VERSION);
101         // Memory optimization - close idle connections after 30s of inactivity
102         setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
103     }
104 
getInstance(Context context)105     public static synchronized WatchlistReportDbHelper getInstance(Context context) {
106         if (sInstance != null) {
107             return sInstance;
108         }
109         sInstance = new WatchlistReportDbHelper(context);
110         return sInstance;
111     }
112 
113     @Override
onCreate(SQLiteDatabase db)114     public void onCreate(SQLiteDatabase db) {
115         db.execSQL(CREATE_TABLE_MODEL);
116     }
117 
118     @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)119     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
120         // TODO: For now, drop older tables and recreate new ones.
121         db.execSQL("DROP TABLE IF EXISTS " + WhiteListReportContract.TABLE);
122         onCreate(db);
123     }
124 
125     /**
126      * Insert new watchlist record.
127      *
128      * @param appDigest The digest of an app.
129      * @param cncDomain C&C domain that app visited.
130      * @return True if success.
131      */
insertNewRecord(byte[] appDigest, String cncDomain, long timestamp)132     public boolean insertNewRecord(byte[] appDigest, String cncDomain,
133             long timestamp) {
134         final SQLiteDatabase db;
135         try {
136             db = getWritableDatabase();
137         } catch (SQLiteException e) {
138             Slog.e(TAG, "Error opening the database to insert a new record", e);
139             return false;
140         }
141         final ContentValues values = new ContentValues();
142         values.put(WhiteListReportContract.APP_DIGEST, appDigest);
143         values.put(WhiteListReportContract.CNC_DOMAIN, cncDomain);
144         values.put(WhiteListReportContract.TIMESTAMP, timestamp);
145         return db.insert(WhiteListReportContract.TABLE, null, values) != -1;
146     }
147 
148     /**
149      * Aggregate all records in database before input timestamp, and return a
150      * rappor encoded result.
151      */
152     @Nullable
getAggregatedRecords(long untilTimestamp)153     public AggregatedResult getAggregatedRecords(long untilTimestamp) {
154         final String selectStatement = WhiteListReportContract.TIMESTAMP + " < ?";
155 
156         final SQLiteDatabase db;
157         try {
158             db = getReadableDatabase();
159         } catch (SQLiteException e) {
160             Slog.e(TAG, "Error opening the database", e);
161             return null;
162         }
163         Cursor c = null;
164         try {
165             c = db.query(true /* distinct */,
166                     WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
167                     new String[]{Long.toString(untilTimestamp)}, null, null,
168                     null, null);
169             if (c == null) {
170                 return null;
171             }
172             final HashSet<String> appDigestList = new HashSet<>();
173             final HashMap<String, String> appDigestCNCList = new HashMap<>();
174             String cncDomainVisited = null;
175             while (c.moveToNext()) {
176                 // We use hex string here as byte[] cannot be a key in HashMap.
177                 String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
178                 String cncDomain = c.getString(INDEX_CNC_DOMAIN);
179 
180                 appDigestList.add(digestHexStr);
181                 if (cncDomainVisited != null) {
182                     cncDomainVisited = cncDomain;
183                 }
184                 appDigestCNCList.put(digestHexStr, cncDomain);
185             }
186             return new AggregatedResult(appDigestList, cncDomainVisited, appDigestCNCList);
187         } finally {
188             if (c != null) {
189                 c.close();
190             }
191         }
192     }
193 
194     /**
195      * Remove all the records before input timestamp.
196      *
197      * @return True if success.
198      */
cleanup(long untilTimestamp)199     public boolean cleanup(long untilTimestamp) {
200         final SQLiteDatabase db;
201         try {
202             db = getWritableDatabase();
203         } catch (SQLiteException e) {
204             Slog.e(TAG, "Error opening the database to cleanup", e);
205             return false;
206         }
207         final String clause = WhiteListReportContract.TIMESTAMP + "< " + untilTimestamp;
208         try {
209             return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
210         } catch (SQLiteDatabaseCorruptException e) {
211             Slog.e(TAG, "Error deleting records", e);
212             return false;
213         }
214     }
215 }
216