Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 1 | // 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 | |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 15 | //! This module implements the Android Protected Confirmation (APC) service as defined |
| 16 | //! in the android.security.apc AIDL spec. |
| 17 | |
| 18 | use std::{ |
| 19 | cmp::PartialEq, |
| 20 | collections::HashMap, |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 21 | sync::{mpsc::Sender, Arc, Mutex}, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 22 | }; |
| 23 | |
Janis Danisevskis | ea03cff | 2021-12-16 08:10:17 -0800 | [diff] [blame^] | 24 | use crate::error::anyhow_error_to_cstring; |
Hasini Gunasinghe | 5a893e8 | 2021-05-05 14:32:32 +0000 | [diff] [blame] | 25 | use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd}; |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 26 | use android_security_apc::aidl::android::security::apc::{ |
| 27 | IConfirmationCallback::IConfirmationCallback, |
| 28 | IProtectedConfirmation::{BnProtectedConfirmation, IProtectedConfirmation}, |
| 29 | ResponseCode::ResponseCode, |
| 30 | }; |
| 31 | use android_security_apc::binder::{ |
Andrew Walbran | de45c8b | 2021-04-13 14:42:38 +0000 | [diff] [blame] | 32 | BinderFeatures, ExceptionCode, Interface, Result as BinderResult, SpIBinder, |
| 33 | Status as BinderStatus, Strong, ThreadState, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 34 | }; |
| 35 | use anyhow::{Context, Result}; |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 36 | use keystore2_apc_compat::ApcHal; |
| 37 | use keystore2_selinux as selinux; |
| 38 | use std::time::{Duration, Instant}; |
| 39 | |
| 40 | /// This is the main APC error type, it wraps binder exceptions and the |
| 41 | /// APC ResponseCode. |
| 42 | #[derive(Debug, thiserror::Error, PartialEq)] |
| 43 | pub enum Error { |
| 44 | /// Wraps an Android Protected Confirmation (APC) response code as defined by the |
| 45 | /// android.security.apc AIDL interface specification. |
| 46 | #[error("Error::Rc({0:?})")] |
| 47 | Rc(ResponseCode), |
| 48 | /// Wraps a Binder exception code other than a service specific exception. |
| 49 | #[error("Binder exception code {0:?}, {1:?}")] |
| 50 | Binder(ExceptionCode, i32), |
| 51 | } |
| 52 | |
| 53 | impl Error { |
| 54 | /// Short hand for `Error::Rc(ResponseCode::SYSTEM_ERROR)` |
| 55 | pub fn sys() -> Self { |
| 56 | Error::Rc(ResponseCode::SYSTEM_ERROR) |
| 57 | } |
| 58 | |
| 59 | /// Short hand for `Error::Rc(ResponseCode::OPERATION_PENDING)` |
| 60 | pub fn pending() -> Self { |
| 61 | Error::Rc(ResponseCode::OPERATION_PENDING) |
| 62 | } |
| 63 | |
| 64 | /// Short hand for `Error::Rc(ResponseCode::CANCELLED)` |
| 65 | pub fn cancelled() -> Self { |
| 66 | Error::Rc(ResponseCode::CANCELLED) |
| 67 | } |
| 68 | |
| 69 | /// Short hand for `Error::Rc(ResponseCode::ABORTED)` |
| 70 | pub fn aborted() -> Self { |
| 71 | Error::Rc(ResponseCode::ABORTED) |
| 72 | } |
| 73 | |
| 74 | /// Short hand for `Error::Rc(ResponseCode::IGNORED)` |
| 75 | pub fn ignored() -> Self { |
| 76 | Error::Rc(ResponseCode::IGNORED) |
| 77 | } |
| 78 | |
| 79 | /// Short hand for `Error::Rc(ResponseCode::UNIMPLEMENTED)` |
| 80 | pub fn unimplemented() -> Self { |
| 81 | Error::Rc(ResponseCode::UNIMPLEMENTED) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | /// This function should be used by confirmation service calls to translate error conditions |
| 86 | /// into service specific exceptions. |
| 87 | /// |
| 88 | /// All error conditions get logged by this function. |
| 89 | /// |
| 90 | /// `Error::Rc(x)` variants get mapped onto a service specific error code of `x`. |
| 91 | /// `selinux::Error::perm()` is mapped on `ResponseCode::PERMISSION_DENIED`. |
| 92 | /// |
| 93 | /// All non `Error` error conditions get mapped onto ResponseCode::SYSTEM_ERROR`. |
| 94 | /// |
| 95 | /// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed |
| 96 | /// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it |
| 97 | /// typically returns Ok(value). |
| 98 | pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> |
| 99 | where |
| 100 | F: FnOnce(U) -> BinderResult<T>, |
| 101 | { |
| 102 | result.map_or_else( |
| 103 | |e| { |
| 104 | log::error!("{:#?}", e); |
| 105 | let root_cause = e.root_cause(); |
| 106 | let rc = match root_cause.downcast_ref::<Error>() { |
| 107 | Some(Error::Rc(rcode)) => rcode.0, |
| 108 | Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0, |
| 109 | None => match root_cause.downcast_ref::<selinux::Error>() { |
| 110 | Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0, |
| 111 | _ => ResponseCode::SYSTEM_ERROR.0, |
| 112 | }, |
| 113 | }; |
Janis Danisevskis | ea03cff | 2021-12-16 08:10:17 -0800 | [diff] [blame^] | 114 | Err(BinderStatus::new_service_specific_error( |
| 115 | rc, |
| 116 | anyhow_error_to_cstring(&e).as_deref(), |
| 117 | )) |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 118 | }, |
| 119 | handle_ok, |
| 120 | ) |
| 121 | } |
| 122 | |
| 123 | /// Rate info records how many failed attempts a client has made to display a protected |
| 124 | /// confirmation prompt. Clients are penalized for attempts that get declined by the user |
| 125 | /// or attempts that get aborted by the client itself. |
| 126 | /// |
| 127 | /// After the third failed attempt the client has to cool down for 30 seconds before it |
| 128 | /// it can retry. After the sixth failed attempt, the time doubles with every failed attempt |
| 129 | /// until it goes into saturation at 24h. |
| 130 | /// |
| 131 | /// A successful user prompt resets the counter. |
| 132 | #[derive(Debug, Clone)] |
| 133 | struct RateInfo { |
| 134 | counter: u32, |
| 135 | timestamp: Instant, |
| 136 | } |
| 137 | |
| 138 | impl RateInfo { |
| 139 | const ONE_DAY: Duration = Duration::from_secs(60u64 * 60u64 * 24u64); |
| 140 | |
| 141 | fn get_remaining_back_off(&self) -> Option<Duration> { |
| 142 | let back_off = match self.counter { |
| 143 | // The first three attempts come without penalty. |
| 144 | 0..=2 => return None, |
| 145 | // The next three attempts are are penalized with 30 seconds back off time. |
| 146 | 3..=5 => Duration::from_secs(30), |
| 147 | // After that we double the back off time the with every additional attempt |
| 148 | // until we reach 1024m (~17h). |
| 149 | 6..=16 => Duration::from_secs(60) |
| 150 | .checked_mul(1u32 << (self.counter - 6)) |
| 151 | .unwrap_or(Self::ONE_DAY), |
| 152 | // After that we cap of at 24h between attempts. |
| 153 | _ => Self::ONE_DAY, |
| 154 | }; |
| 155 | let elapsed = self.timestamp.elapsed(); |
| 156 | // This does exactly what we want. |
| 157 | // `back_off - elapsed` is the remaining back off duration or None if elapsed is larger |
| 158 | // than back_off. Also, this operation cannot overflow as long as elapsed is less than |
| 159 | // back_off, which is all that we care about. |
| 160 | back_off.checked_sub(elapsed) |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | impl Default for RateInfo { |
| 165 | fn default() -> Self { |
| 166 | Self { counter: 0u32, timestamp: Instant::now() } |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /// The APC session state represents the state of an APC session. |
| 171 | struct ApcSessionState { |
| 172 | /// A reference to the APC HAL backend. |
| 173 | hal: Arc<ApcHal>, |
| 174 | /// The client callback object. |
| 175 | cb: SpIBinder, |
| 176 | /// The uid of the owner of this APC session. |
| 177 | uid: u32, |
| 178 | /// The time when this session was started. |
| 179 | start: Instant, |
| 180 | /// This is set when the client calls abort. |
| 181 | /// This is used by the rate limiting logic to determine |
| 182 | /// if the client needs to be penalized for this attempt. |
| 183 | client_aborted: bool, |
| 184 | } |
| 185 | |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 186 | struct ApcState { |
| 187 | session: Option<ApcSessionState>, |
| 188 | rate_limiting: HashMap<u32, RateInfo>, |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 189 | confirmation_token_sender: Sender<Vec<u8>>, |
| 190 | } |
| 191 | |
| 192 | impl ApcState { |
| 193 | fn new(confirmation_token_sender: Sender<Vec<u8>>) -> Self { |
| 194 | Self { session: None, rate_limiting: Default::default(), confirmation_token_sender } |
| 195 | } |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 196 | } |
| 197 | |
| 198 | /// Implementation of the APC service. |
| 199 | pub struct ApcManager { |
| 200 | state: Arc<Mutex<ApcState>>, |
| 201 | } |
| 202 | |
| 203 | impl Interface for ApcManager {} |
| 204 | |
| 205 | impl ApcManager { |
| 206 | /// Create a new instance of the Android Protected Confirmation service. |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 207 | pub fn new_native_binder( |
| 208 | confirmation_token_sender: Sender<Vec<u8>>, |
Stephen Crane | 221bbb5 | 2020-12-16 15:52:10 -0800 | [diff] [blame] | 209 | ) -> Result<Strong<dyn IProtectedConfirmation>> { |
Andrew Walbran | de45c8b | 2021-04-13 14:42:38 +0000 | [diff] [blame] | 210 | Ok(BnProtectedConfirmation::new_binder( |
| 211 | Self { state: Arc::new(Mutex::new(ApcState::new(confirmation_token_sender))) }, |
| 212 | BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, |
| 213 | )) |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 214 | } |
| 215 | |
| 216 | fn result( |
| 217 | state: Arc<Mutex<ApcState>>, |
| 218 | rc: u32, |
| 219 | data_confirmed: Option<&[u8]>, |
| 220 | confirmation_token: Option<&[u8]>, |
| 221 | ) { |
| 222 | let mut state = state.lock().unwrap(); |
| 223 | let (callback, uid, start, client_aborted) = match state.session.take() { |
| 224 | None => return, // Nothing to do |
| 225 | Some(ApcSessionState { cb: callback, uid, start, client_aborted, .. }) => { |
| 226 | (callback, uid, start, client_aborted) |
| 227 | } |
| 228 | }; |
| 229 | |
| 230 | let rc = compat_2_response_code(rc); |
| 231 | |
| 232 | // Update rate limiting information. |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 233 | match (rc, client_aborted, confirmation_token) { |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 234 | // If the user confirmed the dialog. |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 235 | (ResponseCode::OK, _, Some(confirmation_token)) => { |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 236 | // Reset counter. |
| 237 | state.rate_limiting.remove(&uid); |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 238 | // Send confirmation token to the enforcement module. |
| 239 | if let Err(e) = state.confirmation_token_sender.send(confirmation_token.to_vec()) { |
| 240 | log::error!("Got confirmation token, but receiver would not have it. {:?}", e); |
| 241 | } |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 242 | } |
| 243 | // If cancelled by the user or if aborted by the client. |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 244 | (ResponseCode::CANCELLED, _, _) | (ResponseCode::ABORTED, true, _) => { |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 245 | // Penalize. |
| 246 | let mut rate_info = state.rate_limiting.entry(uid).or_default(); |
| 247 | rate_info.counter += 1; |
| 248 | rate_info.timestamp = start; |
| 249 | } |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 250 | (ResponseCode::OK, _, None) => { |
| 251 | log::error!( |
| 252 | "Confirmation prompt was successful but no confirmation token was returned." |
| 253 | ); |
| 254 | } |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 255 | // In any other case this try does not count at all. |
| 256 | _ => {} |
| 257 | } |
| 258 | drop(state); |
| 259 | |
| 260 | if let Ok(listener) = callback.into_interface::<dyn IConfirmationCallback>() { |
| 261 | if let Err(e) = listener.onCompleted(rc, data_confirmed) { |
| 262 | log::error!( |
| 263 | "In ApcManagerCallback::result: Reporting completion to client failed {:?}", |
| 264 | e |
| 265 | ) |
| 266 | } |
| 267 | } else { |
| 268 | log::error!("In ApcManagerCallback::result: SpIBinder is not a IConfirmationCallback."); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | fn present_prompt( |
| 273 | &self, |
Andrei Homescu | 0a8291b | 2021-03-24 02:36:43 +0000 | [diff] [blame] | 274 | listener: &binder::Strong<dyn IConfirmationCallback>, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 275 | prompt_text: &str, |
| 276 | extra_data: &[u8], |
| 277 | locale: &str, |
| 278 | ui_option_flags: i32, |
| 279 | ) -> Result<()> { |
| 280 | let mut state = self.state.lock().unwrap(); |
| 281 | if state.session.is_some() { |
| 282 | return Err(Error::pending()) |
| 283 | .context("In ApcManager::present_prompt: Session pending."); |
| 284 | } |
| 285 | |
| 286 | // Perform rate limiting. |
| 287 | let uid = ThreadState::get_calling_uid(); |
| 288 | match state.rate_limiting.get(&uid) { |
| 289 | None => {} |
| 290 | Some(rate_info) => { |
| 291 | if let Some(back_off) = rate_info.get_remaining_back_off() { |
| 292 | return Err(Error::sys()).context(format!( |
| 293 | "In ApcManager::present_prompt: Cooling down. Remaining back-off: {}s", |
| 294 | back_off.as_secs() |
| 295 | )); |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | let hal = ApcHal::try_get_service(); |
| 301 | let hal = match hal { |
| 302 | None => { |
| 303 | return Err(Error::unimplemented()) |
| 304 | .context("In ApcManager::present_prompt: APC not supported.") |
| 305 | } |
| 306 | Some(h) => Arc::new(h), |
| 307 | }; |
| 308 | |
| 309 | let ui_opts = ui_opts_2_compat(ui_option_flags); |
| 310 | |
| 311 | let state_clone = self.state.clone(); |
| 312 | hal.prompt_user_confirmation( |
| 313 | prompt_text, |
| 314 | extra_data, |
| 315 | locale, |
| 316 | ui_opts, |
Janis Danisevskis | b1673db | 2021-02-08 18:11:57 -0800 | [diff] [blame] | 317 | move |rc, data_confirmed, confirmation_token| { |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 318 | Self::result(state_clone, rc, data_confirmed, confirmation_token) |
| 319 | }, |
| 320 | ) |
| 321 | .map_err(|rc| Error::Rc(compat_2_response_code(rc))) |
| 322 | .context("In present_prompt: Failed to present prompt.")?; |
| 323 | state.session = Some(ApcSessionState { |
| 324 | hal, |
| 325 | cb: listener.as_binder(), |
| 326 | uid, |
| 327 | start: Instant::now(), |
| 328 | client_aborted: false, |
| 329 | }); |
| 330 | Ok(()) |
| 331 | } |
| 332 | |
Andrei Homescu | 0a8291b | 2021-03-24 02:36:43 +0000 | [diff] [blame] | 333 | fn cancel_prompt(&self, listener: &binder::Strong<dyn IConfirmationCallback>) -> Result<()> { |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 334 | let mut state = self.state.lock().unwrap(); |
| 335 | let hal = match &mut state.session { |
| 336 | None => { |
| 337 | return Err(Error::ignored()) |
| 338 | .context("In cancel_prompt: Attempt to cancel non existing session. Ignoring.") |
| 339 | } |
| 340 | Some(session) => { |
| 341 | if session.cb != listener.as_binder() { |
| 342 | return Err(Error::ignored()).context(concat!( |
| 343 | "In cancel_prompt: Attempt to cancel session not belonging to caller. ", |
| 344 | "Ignoring." |
| 345 | )); |
| 346 | } |
| 347 | session.client_aborted = true; |
| 348 | session.hal.clone() |
| 349 | } |
| 350 | }; |
| 351 | drop(state); |
| 352 | hal.abort(); |
| 353 | Ok(()) |
| 354 | } |
| 355 | |
| 356 | fn is_supported() -> Result<bool> { |
| 357 | Ok(ApcHal::try_get_service().is_some()) |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | impl IProtectedConfirmation for ApcManager { |
| 362 | fn presentPrompt( |
| 363 | &self, |
Andrei Homescu | 0a8291b | 2021-03-24 02:36:43 +0000 | [diff] [blame] | 364 | listener: &binder::Strong<dyn IConfirmationCallback>, |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 365 | prompt_text: &str, |
| 366 | extra_data: &[u8], |
| 367 | locale: &str, |
| 368 | ui_option_flags: i32, |
| 369 | ) -> BinderResult<()> { |
Hasini Gunasinghe | 5a893e8 | 2021-05-05 14:32:32 +0000 | [diff] [blame] | 370 | // presentPrompt can take more time than other operations. |
| 371 | let _wp = wd::watch_millis("IProtectedConfirmation::presentPrompt", 3000); |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 372 | map_or_log_err( |
| 373 | self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags), |
| 374 | Ok, |
| 375 | ) |
| 376 | } |
Andrei Homescu | 0a8291b | 2021-03-24 02:36:43 +0000 | [diff] [blame] | 377 | fn cancelPrompt( |
| 378 | &self, |
| 379 | listener: &binder::Strong<dyn IConfirmationCallback>, |
| 380 | ) -> BinderResult<()> { |
Hasini Gunasinghe | 5a893e8 | 2021-05-05 14:32:32 +0000 | [diff] [blame] | 381 | let _wp = wd::watch_millis("IProtectedConfirmation::cancelPrompt", 500); |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 382 | map_or_log_err(self.cancel_prompt(listener), Ok) |
| 383 | } |
| 384 | fn isSupported(&self) -> BinderResult<bool> { |
Hasini Gunasinghe | 5a893e8 | 2021-05-05 14:32:32 +0000 | [diff] [blame] | 385 | let _wp = wd::watch_millis("IProtectedConfirmation::isSupported", 500); |
Janis Danisevskis | 7a1cf38 | 2020-11-20 11:22:14 -0800 | [diff] [blame] | 386 | map_or_log_err(Self::is_supported(), Ok) |
| 387 | } |
| 388 | } |