blob: f92eacd63ec8b22e9520e99f6a18f127f7b1a2aa [file] [log] [blame]
Janis Danisevskis77d72042021-01-20 15:36:30 -08001// Copyright 2020, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Implements the android.security.vpnprofilestore interface.
16
17use android_security_vpnprofilestore::aidl::android::security::vpnprofilestore::{
18 IVpnProfileStore::BnVpnProfileStore, IVpnProfileStore::IVpnProfileStore,
19 IVpnProfileStore::ERROR_PROFILE_NOT_FOUND, IVpnProfileStore::ERROR_SYSTEM_ERROR,
20};
21use android_security_vpnprofilestore::binder::{Result as BinderResult, Status as BinderStatus};
22use anyhow::{Context, Result};
23use binder::{ExceptionCode, Strong, ThreadState};
Janis Danisevskis06891072021-02-11 10:28:17 -080024use keystore2::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
Janis Danisevskis77d72042021-01-20 15:36:30 -080025use rusqlite::{
26 params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS,
27};
Janis Danisevskis06891072021-02-11 10:28:17 -080028use std::{
29 collections::HashSet,
30 path::{Path, PathBuf},
31};
Janis Danisevskis77d72042021-01-20 15:36:30 -080032
33struct DB {
34 conn: Connection,
35}
36
37impl DB {
38 fn new(db_file: &Path) -> Result<Self> {
39 let mut db = Self {
40 conn: Connection::open(db_file).context("Failed to initialize SQLite connection.")?,
41 };
42 db.init_tables().context("Trying to initialize vpnstore db.")?;
43 Ok(db)
44 }
45
46 fn with_transaction<T, F>(&mut self, behavior: TransactionBehavior, f: F) -> Result<T>
47 where
48 F: Fn(&Transaction) -> Result<T>,
49 {
50 loop {
51 match self
52 .conn
53 .transaction_with_behavior(behavior)
54 .context("In with_transaction.")
55 .and_then(|tx| f(&tx).map(|result| (result, tx)))
56 .and_then(|(result, tx)| {
57 tx.commit().context("In with_transaction: Failed to commit transaction.")?;
58 Ok(result)
59 }) {
60 Ok(result) => break Ok(result),
61 Err(e) => {
62 if Self::is_locked_error(&e) {
63 std::thread::sleep(std::time::Duration::from_micros(500));
64 continue;
65 } else {
66 return Err(e).context("In with_transaction.");
67 }
68 }
69 }
70 }
71 }
72
73 fn is_locked_error(e: &anyhow::Error) -> bool {
74 matches!(e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
75 Some(rusqlite::ffi::Error {
76 code: rusqlite::ErrorCode::DatabaseBusy,
77 ..
78 })
79 | Some(rusqlite::ffi::Error {
80 code: rusqlite::ErrorCode::DatabaseLocked,
81 ..
82 }))
83 }
84
85 fn init_tables(&mut self) -> Result<()> {
86 self.with_transaction(TransactionBehavior::Immediate, |tx| {
87 tx.execute(
88 "CREATE TABLE IF NOT EXISTS profiles (
89 owner INTEGER,
90 alias BLOB,
91 profile BLOB,
92 UNIQUE(owner, alias));",
93 NO_PARAMS,
94 )
95 .context("Failed to initialize \"profiles\" table.")?;
96 Ok(())
97 })
98 }
99
100 fn list(&mut self, caller_uid: u32) -> Result<Vec<String>> {
101 self.with_transaction(TransactionBehavior::Deferred, |tx| {
102 let mut stmt = tx
103 .prepare("SELECT alias FROM profiles WHERE owner = ? ORDER BY alias ASC;")
104 .context("In list: Failed to prepare statement.")?;
105
106 let aliases = stmt
107 .query_map(params![caller_uid], |row| row.get(0))?
108 .collect::<rusqlite::Result<Vec<String>>>()
109 .context("In list: query_map failed.");
110 aliases
111 })
112 }
113
114 fn put(&mut self, caller_uid: u32, alias: &str, profile: &[u8]) -> Result<()> {
115 self.with_transaction(TransactionBehavior::Immediate, |tx| {
116 tx.execute(
117 "INSERT OR REPLACE INTO profiles (owner, alias, profile) values (?, ?, ?)",
118 params![caller_uid, alias, profile,],
119 )
120 .context("In put: Failed to insert or replace.")?;
121 Ok(())
122 })
123 }
124
125 fn get(&mut self, caller_uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
126 self.with_transaction(TransactionBehavior::Deferred, |tx| {
127 tx.query_row(
128 "SELECT profile FROM profiles WHERE owner = ? AND alias = ?;",
129 params![caller_uid, alias],
130 |row| row.get(0),
131 )
132 .optional()
133 .context("In get: failed loading profile.")
134 })
135 }
136
137 fn remove(&mut self, caller_uid: u32, alias: &str) -> Result<bool> {
138 let removed = self.with_transaction(TransactionBehavior::Immediate, |tx| {
139 tx.execute(
140 "DELETE FROM profiles WHERE owner = ? AND alias = ?;",
141 params![caller_uid, alias],
142 )
143 .context("In remove: Failed to delete row.")
144 })?;
145 Ok(removed == 1)
146 }
147}
148
149/// This is the main VpnProfileStore error type, it wraps binder exceptions and the
150/// VnpStore errors.
151#[derive(Debug, thiserror::Error, PartialEq)]
152pub enum Error {
153 /// Wraps a VpnProfileStore error code.
154 #[error("Error::Error({0:?})")]
155 Error(i32),
156 /// Wraps a Binder exception code other than a service specific exception.
157 #[error("Binder exception code {0:?}, {1:?}")]
158 Binder(ExceptionCode, i32),
159}
160
161impl Error {
162 /// Short hand for `Error::Error(ERROR_SYSTEM_ERROR)`
163 pub fn sys() -> Self {
164 Error::Error(ERROR_SYSTEM_ERROR)
165 }
166
167 /// Short hand for `Error::Error(ERROR_PROFILE_NOT_FOUND)`
168 pub fn not_found() -> Self {
169 Error::Error(ERROR_PROFILE_NOT_FOUND)
170 }
171}
172
173/// This function should be used by vpnprofilestore service calls to translate error conditions
174/// into service specific exceptions.
175///
176/// All error conditions get logged by this function.
177///
178/// `Error::Error(x)` variants get mapped onto a service specific error code of `x`.
179///
180/// All non `Error` error conditions get mapped onto `ERROR_SYSTEM_ERROR`.
181///
182/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
183/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
184/// typically returns Ok(value).
185fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T>
186where
187 F: FnOnce(U) -> BinderResult<T>,
188{
189 result.map_or_else(
190 |e| {
191 log::error!("{:#?}", e);
192 let root_cause = e.root_cause();
193 let rc = match root_cause.downcast_ref::<Error>() {
194 Some(Error::Error(e)) => *e,
195 Some(Error::Binder(_, _)) | None => ERROR_SYSTEM_ERROR,
196 };
197 Err(BinderStatus::new_service_specific_error(rc, None))
198 },
199 handle_ok,
200 )
201}
202
Janis Danisevskis77d72042021-01-20 15:36:30 -0800203/// Implements IVpnProfileStore AIDL interface.
204pub struct VpnProfileStore {
205 db_path: PathBuf,
Janis Danisevskis06891072021-02-11 10:28:17 -0800206 async_task: AsyncTask,
207}
208
209struct AsyncState {
210 recently_imported: HashSet<(u32, String)>,
211 legacy_loader: LegacyBlobLoader,
212 db_path: PathBuf,
Janis Danisevskis77d72042021-01-20 15:36:30 -0800213}
214
215impl VpnProfileStore {
216 /// Creates a new VpnProfileStore instance.
Janis Danisevskis06891072021-02-11 10:28:17 -0800217 pub fn new_native_binder(path: &Path) -> Strong<dyn IVpnProfileStore> {
Janis Danisevskis77d72042021-01-20 15:36:30 -0800218 let mut db_path = path.to_path_buf();
219 db_path.push("vpnprofilestore.sqlite");
Janis Danisevskis06891072021-02-11 10:28:17 -0800220
221 let result = Self { db_path, async_task: Default::default() };
222 result.init_shelf(path);
223 BnVpnProfileStore::new_binder(result)
Janis Danisevskis77d72042021-01-20 15:36:30 -0800224 }
225
226 fn open_db(&self) -> Result<DB> {
227 DB::new(&self.db_path).context("In open_db: Failed to open db.")
228 }
229
230 fn get(&self, alias: &str) -> Result<Vec<u8>> {
231 let mut db = self.open_db().context("In get.")?;
232 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800233
234 if let Some(profile) =
235 db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")?
236 {
237 return Ok(profile);
238 }
239 if self.get_legacy(calling_uid, alias).context("In get: Trying to migrate legacy blob.")? {
240 // If we were able to migrate a legacy blob try again.
241 if let Some(profile) =
242 db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")?
243 {
244 return Ok(profile);
245 }
246 }
247 Err(Error::not_found()).context("In get: No such profile.")
Janis Danisevskis77d72042021-01-20 15:36:30 -0800248 }
249
250 fn put(&self, alias: &str, profile: &[u8]) -> Result<()> {
Janis Danisevskis77d72042021-01-20 15:36:30 -0800251 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800252 // In order to make sure that we don't have stale legacy profiles, make sure they are
253 // migrated before replacing them.
254 let _ = self.get_legacy(calling_uid, alias);
255 let mut db = self.open_db().context("In put.")?;
Janis Danisevskis77d72042021-01-20 15:36:30 -0800256 db.put(calling_uid, alias, profile).context("In put: Trying to insert profile into DB.")
257 }
258
259 fn remove(&self, alias: &str) -> Result<()> {
Janis Danisevskis77d72042021-01-20 15:36:30 -0800260 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800261 let mut db = self.open_db().context("In remove.")?;
262 // In order to make sure that we don't have stale legacy profiles, make sure they are
263 // migrated before removing them.
264 let _ = self.get_legacy(calling_uid, alias);
Janis Danisevskis77d72042021-01-20 15:36:30 -0800265 let removed = db
266 .remove(calling_uid, alias)
267 .context("In remove: Trying to remove profile from DB.")?;
268 if removed {
269 Ok(())
270 } else {
271 Err(Error::not_found()).context("In remove: No such profile.")
272 }
273 }
274
275 fn list(&self, prefix: &str) -> Result<Vec<String>> {
276 let mut db = self.open_db().context("In list.")?;
277 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800278 let mut result = self.list_legacy(calling_uid).context("In list.")?;
279 result
280 .append(&mut db.list(calling_uid).context("In list: Trying to get list of profiles.")?);
281 result = result.into_iter().filter(|s| s.starts_with(prefix)).collect();
282 result.sort_unstable();
283 result.dedup();
284 Ok(result)
285 }
286
287 fn init_shelf(&self, path: &Path) {
288 let mut db_path = path.to_path_buf();
289 self.async_task.queue_hi(move |shelf| {
290 let legacy_loader = LegacyBlobLoader::new(&db_path);
291 db_path.push("vpnprofilestore.sqlite");
292
293 shelf.put(AsyncState { legacy_loader, db_path, recently_imported: Default::default() });
294 })
295 }
296
297 fn do_serialized<F, T: Send + 'static>(&self, f: F) -> Result<T>
298 where
299 F: FnOnce(&mut AsyncState) -> Result<T> + Send + 'static,
300 {
301 let (sender, receiver) = std::sync::mpsc::channel::<Result<T>>();
302 self.async_task.queue_hi(move |shelf| {
303 let state = shelf.get_downcast_mut::<AsyncState>().expect("Failed to get shelf.");
304 sender.send(f(state)).expect("Failed to send result.");
305 });
306 receiver.recv().context("In do_serialized: Failed to receive result.")?
307 }
308
309 fn list_legacy(&self, uid: u32) -> Result<Vec<String>> {
310 self.do_serialized(move |state| {
311 state
312 .legacy_loader
313 .list_vpn_profiles(uid)
314 .context("Trying to list legacy vnp profiles.")
315 })
316 .context("In list_legacy.")
317 }
318
319 fn get_legacy(&self, uid: u32, alias: &str) -> Result<bool> {
320 let alias = alias.to_string();
321 self.do_serialized(move |state| {
322 if state.recently_imported.contains(&(uid, alias.clone())) {
323 return Ok(true);
324 }
325 let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?;
326 let migrated =
327 Self::migrate_one_legacy_profile(uid, &alias, &state.legacy_loader, &mut db)
328 .context("Trying to migrate legacy vpn profile.")?;
329 if migrated {
330 state.recently_imported.insert((uid, alias));
331 }
332 Ok(migrated)
333 })
334 .context("In get_legacy.")
335 }
336
337 fn migrate_one_legacy_profile(
338 uid: u32,
339 alias: &str,
340 legacy_loader: &LegacyBlobLoader,
341 db: &mut DB,
342 ) -> Result<bool> {
343 let blob = legacy_loader
344 .read_vpn_profile(uid, alias)
345 .context("In migrate_one_legacy_profile: Trying to read legacy vpn profile.")?;
346 if let Some(profile) = blob {
347 db.put(uid, alias, &profile)
348 .context("In migrate_one_legacy_profile: Trying to insert profile into DB.")?;
349 legacy_loader
350 .remove_vpn_profile(uid, alias)
351 .context("In migrate_one_legacy_profile: Trying to delete legacy profile.")?;
352 Ok(true)
353 } else {
354 Ok(false)
355 }
Janis Danisevskis77d72042021-01-20 15:36:30 -0800356 }
357}
358
359impl binder::Interface for VpnProfileStore {}
360
361impl IVpnProfileStore for VpnProfileStore {
362 fn get(&self, alias: &str) -> BinderResult<Vec<u8>> {
363 map_or_log_err(self.get(alias), Ok)
364 }
365 fn put(&self, alias: &str, profile: &[u8]) -> BinderResult<()> {
366 map_or_log_err(self.put(alias, profile), Ok)
367 }
368 fn remove(&self, alias: &str) -> BinderResult<()> {
369 map_or_log_err(self.remove(alias), Ok)
370 }
371 fn list(&self, prefix: &str) -> BinderResult<Vec<String>> {
372 map_or_log_err(self.list(prefix), Ok)
373 }
374}
375
376#[cfg(test)]
377mod db_test {
378 use super::*;
379 use keystore2_test_utils::TempDir;
380
381 static TEST_BLOB1: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
382 static TEST_BLOB2: &[u8] = &[2, 2, 3, 4, 5, 6, 7, 8, 9, 0];
383 static TEST_BLOB3: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
384 static TEST_BLOB4: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
385
386 #[test]
387 fn test_profile_db() {
388 let test_dir = TempDir::new("profiledb_test_").expect("Failed to create temp dir.");
389 let mut db =
390 DB::new(&test_dir.build().push("vpnprofile.sqlite")).expect("Failed to open database.");
391
392 // Insert three profiles for owner 2.
393 db.put(2, "test1", TEST_BLOB1).expect("Failed to insert test1.");
394 db.put(2, "test2", TEST_BLOB2).expect("Failed to insert test2.");
395 db.put(2, "test3", TEST_BLOB3).expect("Failed to insert test3.");
396
397 // Check list returns all inserted aliases.
398 assert_eq!(
399 vec!["test1".to_string(), "test2".to_string(), "test3".to_string(),],
400 db.list(2).expect("Failed to list profiles.")
401 );
402
403 // There should be no profiles for owner 1.
404 assert_eq!(Vec::<String>::new(), db.list(1).expect("Failed to list profiles."));
405
406 // Check the content of the three entries.
407 assert_eq!(
408 Some(TEST_BLOB1),
409 db.get(2, "test1").expect("Failed to get profile.").as_deref()
410 );
411 assert_eq!(
412 Some(TEST_BLOB2),
413 db.get(2, "test2").expect("Failed to get profile.").as_deref()
414 );
415 assert_eq!(
416 Some(TEST_BLOB3),
417 db.get(2, "test3").expect("Failed to get profile.").as_deref()
418 );
419
420 // Remove test2 and check and check that it is no longer retrievable.
421 assert!(db.remove(2, "test2").expect("Failed to remove profile."));
422 assert!(db.get(2, "test2").expect("Failed to get profile.").is_none());
423
424 // test2 should now no longer be in the list.
425 assert_eq!(
426 vec!["test1".to_string(), "test3".to_string(),],
427 db.list(2).expect("Failed to list profiles.")
428 );
429
430 // Put on existing alias replaces it.
431 // Verify test1 is TEST_BLOB1.
432 assert_eq!(
433 Some(TEST_BLOB1),
434 db.get(2, "test1").expect("Failed to get profile.").as_deref()
435 );
436 db.put(2, "test1", TEST_BLOB4).expect("Failed to replace test1.");
437 // Verify test1 is TEST_BLOB4.
438 assert_eq!(
439 Some(TEST_BLOB4),
440 db.get(2, "test1").expect("Failed to get profile.").as_deref()
441 );
442 }
443}