blob: f5adc1b00b2a0433ce9a65ec1cc0d3f54c666edf [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 };
Janis Danisevskis1be7e182021-04-12 14:31:12 -070042
43 // On busy fail Immediately. It is unlikely to succeed given a bug in sqlite.
44 db.conn.busy_handler(None).context("Failed to set busy handler.")?;
45
Janis Danisevskis77d72042021-01-20 15:36:30 -080046 db.init_tables().context("Trying to initialize vpnstore db.")?;
47 Ok(db)
48 }
49
50 fn with_transaction<T, F>(&mut self, behavior: TransactionBehavior, f: F) -> Result<T>
51 where
52 F: Fn(&Transaction) -> Result<T>,
53 {
54 loop {
55 match self
56 .conn
57 .transaction_with_behavior(behavior)
58 .context("In with_transaction.")
59 .and_then(|tx| f(&tx).map(|result| (result, tx)))
60 .and_then(|(result, tx)| {
61 tx.commit().context("In with_transaction: Failed to commit transaction.")?;
62 Ok(result)
63 }) {
64 Ok(result) => break Ok(result),
65 Err(e) => {
66 if Self::is_locked_error(&e) {
67 std::thread::sleep(std::time::Duration::from_micros(500));
68 continue;
69 } else {
70 return Err(e).context("In with_transaction.");
71 }
72 }
73 }
74 }
75 }
76
77 fn is_locked_error(e: &anyhow::Error) -> bool {
78 matches!(e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
79 Some(rusqlite::ffi::Error {
80 code: rusqlite::ErrorCode::DatabaseBusy,
81 ..
82 })
83 | Some(rusqlite::ffi::Error {
84 code: rusqlite::ErrorCode::DatabaseLocked,
85 ..
86 }))
87 }
88
89 fn init_tables(&mut self) -> Result<()> {
90 self.with_transaction(TransactionBehavior::Immediate, |tx| {
91 tx.execute(
92 "CREATE TABLE IF NOT EXISTS profiles (
93 owner INTEGER,
94 alias BLOB,
95 profile BLOB,
96 UNIQUE(owner, alias));",
97 NO_PARAMS,
98 )
99 .context("Failed to initialize \"profiles\" table.")?;
100 Ok(())
101 })
102 }
103
104 fn list(&mut self, caller_uid: u32) -> Result<Vec<String>> {
105 self.with_transaction(TransactionBehavior::Deferred, |tx| {
106 let mut stmt = tx
107 .prepare("SELECT alias FROM profiles WHERE owner = ? ORDER BY alias ASC;")
108 .context("In list: Failed to prepare statement.")?;
109
110 let aliases = stmt
111 .query_map(params![caller_uid], |row| row.get(0))?
112 .collect::<rusqlite::Result<Vec<String>>>()
113 .context("In list: query_map failed.");
114 aliases
115 })
116 }
117
118 fn put(&mut self, caller_uid: u32, alias: &str, profile: &[u8]) -> Result<()> {
119 self.with_transaction(TransactionBehavior::Immediate, |tx| {
120 tx.execute(
121 "INSERT OR REPLACE INTO profiles (owner, alias, profile) values (?, ?, ?)",
122 params![caller_uid, alias, profile,],
123 )
124 .context("In put: Failed to insert or replace.")?;
125 Ok(())
126 })
127 }
128
129 fn get(&mut self, caller_uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
130 self.with_transaction(TransactionBehavior::Deferred, |tx| {
131 tx.query_row(
132 "SELECT profile FROM profiles WHERE owner = ? AND alias = ?;",
133 params![caller_uid, alias],
134 |row| row.get(0),
135 )
136 .optional()
137 .context("In get: failed loading profile.")
138 })
139 }
140
141 fn remove(&mut self, caller_uid: u32, alias: &str) -> Result<bool> {
142 let removed = self.with_transaction(TransactionBehavior::Immediate, |tx| {
143 tx.execute(
144 "DELETE FROM profiles WHERE owner = ? AND alias = ?;",
145 params![caller_uid, alias],
146 )
147 .context("In remove: Failed to delete row.")
148 })?;
149 Ok(removed == 1)
150 }
151}
152
153/// This is the main VpnProfileStore error type, it wraps binder exceptions and the
154/// VnpStore errors.
155#[derive(Debug, thiserror::Error, PartialEq)]
156pub enum Error {
157 /// Wraps a VpnProfileStore error code.
158 #[error("Error::Error({0:?})")]
159 Error(i32),
160 /// Wraps a Binder exception code other than a service specific exception.
161 #[error("Binder exception code {0:?}, {1:?}")]
162 Binder(ExceptionCode, i32),
163}
164
165impl Error {
166 /// Short hand for `Error::Error(ERROR_SYSTEM_ERROR)`
167 pub fn sys() -> Self {
168 Error::Error(ERROR_SYSTEM_ERROR)
169 }
170
171 /// Short hand for `Error::Error(ERROR_PROFILE_NOT_FOUND)`
172 pub fn not_found() -> Self {
173 Error::Error(ERROR_PROFILE_NOT_FOUND)
174 }
175}
176
177/// This function should be used by vpnprofilestore service calls to translate error conditions
178/// into service specific exceptions.
179///
180/// All error conditions get logged by this function.
181///
182/// `Error::Error(x)` variants get mapped onto a service specific error code of `x`.
183///
184/// All non `Error` error conditions get mapped onto `ERROR_SYSTEM_ERROR`.
185///
186/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
187/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
188/// typically returns Ok(value).
189fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T>
190where
191 F: FnOnce(U) -> BinderResult<T>,
192{
193 result.map_or_else(
194 |e| {
195 log::error!("{:#?}", e);
196 let root_cause = e.root_cause();
197 let rc = match root_cause.downcast_ref::<Error>() {
198 Some(Error::Error(e)) => *e,
199 Some(Error::Binder(_, _)) | None => ERROR_SYSTEM_ERROR,
200 };
201 Err(BinderStatus::new_service_specific_error(rc, None))
202 },
203 handle_ok,
204 )
205}
206
Janis Danisevskis77d72042021-01-20 15:36:30 -0800207/// Implements IVpnProfileStore AIDL interface.
208pub struct VpnProfileStore {
209 db_path: PathBuf,
Janis Danisevskis06891072021-02-11 10:28:17 -0800210 async_task: AsyncTask,
211}
212
213struct AsyncState {
214 recently_imported: HashSet<(u32, String)>,
215 legacy_loader: LegacyBlobLoader,
216 db_path: PathBuf,
Janis Danisevskis77d72042021-01-20 15:36:30 -0800217}
218
219impl VpnProfileStore {
220 /// Creates a new VpnProfileStore instance.
Janis Danisevskis06891072021-02-11 10:28:17 -0800221 pub fn new_native_binder(path: &Path) -> Strong<dyn IVpnProfileStore> {
Janis Danisevskis77d72042021-01-20 15:36:30 -0800222 let mut db_path = path.to_path_buf();
223 db_path.push("vpnprofilestore.sqlite");
Janis Danisevskis06891072021-02-11 10:28:17 -0800224
225 let result = Self { db_path, async_task: Default::default() };
226 result.init_shelf(path);
227 BnVpnProfileStore::new_binder(result)
Janis Danisevskis77d72042021-01-20 15:36:30 -0800228 }
229
230 fn open_db(&self) -> Result<DB> {
231 DB::new(&self.db_path).context("In open_db: Failed to open db.")
232 }
233
234 fn get(&self, alias: &str) -> Result<Vec<u8>> {
235 let mut db = self.open_db().context("In get.")?;
236 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800237
238 if let Some(profile) =
239 db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")?
240 {
241 return Ok(profile);
242 }
243 if self.get_legacy(calling_uid, alias).context("In get: Trying to migrate legacy blob.")? {
244 // If we were able to migrate a legacy blob try again.
245 if let Some(profile) =
246 db.get(calling_uid, alias).context("In get: Trying to load profile from DB.")?
247 {
248 return Ok(profile);
249 }
250 }
251 Err(Error::not_found()).context("In get: No such profile.")
Janis Danisevskis77d72042021-01-20 15:36:30 -0800252 }
253
254 fn put(&self, alias: &str, profile: &[u8]) -> Result<()> {
Janis Danisevskis77d72042021-01-20 15:36:30 -0800255 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800256 // In order to make sure that we don't have stale legacy profiles, make sure they are
257 // migrated before replacing them.
258 let _ = self.get_legacy(calling_uid, alias);
259 let mut db = self.open_db().context("In put.")?;
Janis Danisevskis77d72042021-01-20 15:36:30 -0800260 db.put(calling_uid, alias, profile).context("In put: Trying to insert profile into DB.")
261 }
262
263 fn remove(&self, alias: &str) -> Result<()> {
Janis Danisevskis77d72042021-01-20 15:36:30 -0800264 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800265 let mut db = self.open_db().context("In remove.")?;
266 // In order to make sure that we don't have stale legacy profiles, make sure they are
267 // migrated before removing them.
268 let _ = self.get_legacy(calling_uid, alias);
Janis Danisevskis77d72042021-01-20 15:36:30 -0800269 let removed = db
270 .remove(calling_uid, alias)
271 .context("In remove: Trying to remove profile from DB.")?;
272 if removed {
273 Ok(())
274 } else {
275 Err(Error::not_found()).context("In remove: No such profile.")
276 }
277 }
278
279 fn list(&self, prefix: &str) -> Result<Vec<String>> {
280 let mut db = self.open_db().context("In list.")?;
281 let calling_uid = ThreadState::get_calling_uid();
Janis Danisevskis06891072021-02-11 10:28:17 -0800282 let mut result = self.list_legacy(calling_uid).context("In list.")?;
283 result
284 .append(&mut db.list(calling_uid).context("In list: Trying to get list of profiles.")?);
285 result = result.into_iter().filter(|s| s.starts_with(prefix)).collect();
286 result.sort_unstable();
287 result.dedup();
288 Ok(result)
289 }
290
291 fn init_shelf(&self, path: &Path) {
292 let mut db_path = path.to_path_buf();
293 self.async_task.queue_hi(move |shelf| {
294 let legacy_loader = LegacyBlobLoader::new(&db_path);
295 db_path.push("vpnprofilestore.sqlite");
296
297 shelf.put(AsyncState { legacy_loader, db_path, recently_imported: Default::default() });
298 })
299 }
300
301 fn do_serialized<F, T: Send + 'static>(&self, f: F) -> Result<T>
302 where
303 F: FnOnce(&mut AsyncState) -> Result<T> + Send + 'static,
304 {
305 let (sender, receiver) = std::sync::mpsc::channel::<Result<T>>();
306 self.async_task.queue_hi(move |shelf| {
307 let state = shelf.get_downcast_mut::<AsyncState>().expect("Failed to get shelf.");
308 sender.send(f(state)).expect("Failed to send result.");
309 });
310 receiver.recv().context("In do_serialized: Failed to receive result.")?
311 }
312
313 fn list_legacy(&self, uid: u32) -> Result<Vec<String>> {
314 self.do_serialized(move |state| {
315 state
316 .legacy_loader
317 .list_vpn_profiles(uid)
318 .context("Trying to list legacy vnp profiles.")
319 })
320 .context("In list_legacy.")
321 }
322
323 fn get_legacy(&self, uid: u32, alias: &str) -> Result<bool> {
324 let alias = alias.to_string();
325 self.do_serialized(move |state| {
326 if state.recently_imported.contains(&(uid, alias.clone())) {
327 return Ok(true);
328 }
329 let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?;
330 let migrated =
331 Self::migrate_one_legacy_profile(uid, &alias, &state.legacy_loader, &mut db)
332 .context("Trying to migrate legacy vpn profile.")?;
333 if migrated {
334 state.recently_imported.insert((uid, alias));
335 }
336 Ok(migrated)
337 })
338 .context("In get_legacy.")
339 }
340
341 fn migrate_one_legacy_profile(
342 uid: u32,
343 alias: &str,
344 legacy_loader: &LegacyBlobLoader,
345 db: &mut DB,
346 ) -> Result<bool> {
347 let blob = legacy_loader
348 .read_vpn_profile(uid, alias)
349 .context("In migrate_one_legacy_profile: Trying to read legacy vpn profile.")?;
350 if let Some(profile) = blob {
351 db.put(uid, alias, &profile)
352 .context("In migrate_one_legacy_profile: Trying to insert profile into DB.")?;
353 legacy_loader
354 .remove_vpn_profile(uid, alias)
355 .context("In migrate_one_legacy_profile: Trying to delete legacy profile.")?;
356 Ok(true)
357 } else {
358 Ok(false)
359 }
Janis Danisevskis77d72042021-01-20 15:36:30 -0800360 }
361}
362
363impl binder::Interface for VpnProfileStore {}
364
365impl IVpnProfileStore for VpnProfileStore {
366 fn get(&self, alias: &str) -> BinderResult<Vec<u8>> {
367 map_or_log_err(self.get(alias), Ok)
368 }
369 fn put(&self, alias: &str, profile: &[u8]) -> BinderResult<()> {
370 map_or_log_err(self.put(alias, profile), Ok)
371 }
372 fn remove(&self, alias: &str) -> BinderResult<()> {
373 map_or_log_err(self.remove(alias), Ok)
374 }
375 fn list(&self, prefix: &str) -> BinderResult<Vec<String>> {
376 map_or_log_err(self.list(prefix), Ok)
377 }
378}
379
380#[cfg(test)]
381mod db_test {
382 use super::*;
383 use keystore2_test_utils::TempDir;
Janis Danisevskis1be7e182021-04-12 14:31:12 -0700384 use std::sync::Arc;
385 use std::thread;
386 use std::time::Duration;
387 use std::time::Instant;
Janis Danisevskis77d72042021-01-20 15:36:30 -0800388
Janis Danisevskis1be7e182021-04-12 14:31:12 -0700389 static TEST_ALIAS: &str = &"test_alias";
Janis Danisevskis77d72042021-01-20 15:36:30 -0800390 static TEST_BLOB1: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
391 static TEST_BLOB2: &[u8] = &[2, 2, 3, 4, 5, 6, 7, 8, 9, 0];
392 static TEST_BLOB3: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
393 static TEST_BLOB4: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
394
395 #[test]
396 fn test_profile_db() {
397 let test_dir = TempDir::new("profiledb_test_").expect("Failed to create temp dir.");
398 let mut db =
399 DB::new(&test_dir.build().push("vpnprofile.sqlite")).expect("Failed to open database.");
400
401 // Insert three profiles for owner 2.
402 db.put(2, "test1", TEST_BLOB1).expect("Failed to insert test1.");
403 db.put(2, "test2", TEST_BLOB2).expect("Failed to insert test2.");
404 db.put(2, "test3", TEST_BLOB3).expect("Failed to insert test3.");
405
406 // Check list returns all inserted aliases.
407 assert_eq!(
408 vec!["test1".to_string(), "test2".to_string(), "test3".to_string(),],
409 db.list(2).expect("Failed to list profiles.")
410 );
411
412 // There should be no profiles for owner 1.
413 assert_eq!(Vec::<String>::new(), db.list(1).expect("Failed to list profiles."));
414
415 // Check the content of the three entries.
416 assert_eq!(
417 Some(TEST_BLOB1),
418 db.get(2, "test1").expect("Failed to get profile.").as_deref()
419 );
420 assert_eq!(
421 Some(TEST_BLOB2),
422 db.get(2, "test2").expect("Failed to get profile.").as_deref()
423 );
424 assert_eq!(
425 Some(TEST_BLOB3),
426 db.get(2, "test3").expect("Failed to get profile.").as_deref()
427 );
428
429 // Remove test2 and check and check that it is no longer retrievable.
430 assert!(db.remove(2, "test2").expect("Failed to remove profile."));
431 assert!(db.get(2, "test2").expect("Failed to get profile.").is_none());
432
433 // test2 should now no longer be in the list.
434 assert_eq!(
435 vec!["test1".to_string(), "test3".to_string(),],
436 db.list(2).expect("Failed to list profiles.")
437 );
438
439 // Put on existing alias replaces it.
440 // Verify test1 is TEST_BLOB1.
441 assert_eq!(
442 Some(TEST_BLOB1),
443 db.get(2, "test1").expect("Failed to get profile.").as_deref()
444 );
445 db.put(2, "test1", TEST_BLOB4).expect("Failed to replace test1.");
446 // Verify test1 is TEST_BLOB4.
447 assert_eq!(
448 Some(TEST_BLOB4),
449 db.get(2, "test1").expect("Failed to get profile.").as_deref()
450 );
451 }
Janis Danisevskis1be7e182021-04-12 14:31:12 -0700452
453 #[test]
454 fn concurrent_vpn_profile_test() -> Result<()> {
455 let temp_dir = Arc::new(
456 TempDir::new("concurrent_vpn_profile_test_").expect("Failed to create temp dir."),
457 );
458
459 let db_path = temp_dir.build().push("vpnprofile.sqlite").to_owned();
460
461 let test_begin = Instant::now();
462
463 let mut db = DB::new(&db_path).expect("Failed to open database.");
464 const PROFILE_COUNT: u32 = 5000u32;
465 const PROFILE_DB_COUNT: u32 = 5000u32;
466
467 let mut actual_profile_count = PROFILE_COUNT;
468 // First insert PROFILE_COUNT profiles.
469 for count in 0..PROFILE_COUNT {
470 if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) {
471 actual_profile_count = count;
472 break;
473 }
474 let alias = format!("test_alias_{}", count);
475 db.put(1, &alias, TEST_BLOB1).expect("Failed to add profile (1).");
476 }
477
478 // Insert more keys from a different thread and into a different namespace.
479 let db_path1 = db_path.clone();
480 let handle1 = thread::spawn(move || {
481 let mut db = DB::new(&db_path1).expect("Failed to open database.");
482
483 for count in 0..actual_profile_count {
484 if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
485 return;
486 }
487 let alias = format!("test_alias_{}", count);
488 db.put(2, &alias, TEST_BLOB2).expect("Failed to add profile (2).");
489 }
490
491 // Then delete them again.
492 for count in 0..actual_profile_count {
493 if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
494 return;
495 }
496 let alias = format!("test_alias_{}", count);
497 db.remove(2, &alias).expect("Remove Failed (2).");
498 }
499 });
500
501 // And start deleting the first set of profiles.
502 let db_path2 = db_path.clone();
503 let handle2 = thread::spawn(move || {
504 let mut db = DB::new(&db_path2).expect("Failed to open database.");
505
506 for count in 0..actual_profile_count {
507 if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
508 return;
509 }
510 let alias = format!("test_alias_{}", count);
511 db.remove(1, &alias).expect("Remove Failed (1)).");
512 }
513 });
514
515 // While a lot of inserting and deleting is going on we have to open database connections
516 // successfully and then insert and delete a specific profile.
517 let db_path3 = db_path.clone();
518 let handle3 = thread::spawn(move || {
519 for _count in 0..PROFILE_DB_COUNT {
520 if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
521 return;
522 }
523 let mut db = DB::new(&db_path3).expect("Failed to open database.");
524
525 db.put(3, &TEST_ALIAS, TEST_BLOB3).expect("Failed to add profile (3).");
526
527 db.remove(3, &TEST_ALIAS).expect("Remove failed (3).");
528 }
529 });
530
531 // While thread 3 is inserting and deleting TEST_ALIAS, we try to get the alias.
532 // This may yield an entry or none, but it must not fail.
533 let handle4 = thread::spawn(move || {
534 for _count in 0..PROFILE_DB_COUNT {
535 if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) {
536 return;
537 }
538 let mut db = DB::new(&db_path).expect("Failed to open database.");
539
540 // This may return Some or None but it must not fail.
541 db.get(3, &TEST_ALIAS).expect("Failed to get profile (4).");
542 }
543 });
544
545 handle1.join().expect("Thread 1 panicked.");
546 handle2.join().expect("Thread 2 panicked.");
547 handle3.join().expect("Thread 3 panicked.");
548 handle4.join().expect("Thread 4 panicked.");
549
550 Ok(())
551 }
Janis Danisevskis77d72042021-01-20 15:36:30 -0800552}