blob: 7d56dc9ff69ff94b5e1110f16249c37eb40e1640 [file] [log] [blame]
Janis Danisevskis7a1cf382020-11-20 11:22:14 -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
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080015//! This module implements the Android Protected Confirmation (APC) service as defined
16//! in the android.security.apc AIDL spec.
17
18use std::{
19 cmp::PartialEq,
20 collections::HashMap,
Janis Danisevskisb1673db2021-02-08 18:11:57 -080021 sync::{mpsc::Sender, Arc, Mutex},
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080022};
23
Janis Danisevskisea03cff2021-12-16 08:10:17 -080024use crate::error::anyhow_error_to_cstring;
Hasini Gunasinghe5a893e82021-05-05 14:32:32 +000025use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd};
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080026use android_security_apc::aidl::android::security::apc::{
27 IConfirmationCallback::IConfirmationCallback,
28 IProtectedConfirmation::{BnProtectedConfirmation, IProtectedConfirmation},
29 ResponseCode::ResponseCode,
30};
31use android_security_apc::binder::{
Andrew Walbrande45c8b2021-04-13 14:42:38 +000032 BinderFeatures, ExceptionCode, Interface, Result as BinderResult, SpIBinder,
33 Status as BinderStatus, Strong, ThreadState,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080034};
35use anyhow::{Context, Result};
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080036use keystore2_apc_compat::ApcHal;
37use keystore2_selinux as selinux;
38use 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)]
43pub 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
53impl 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).
98pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T>
99where
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 Danisevskisea03cff2021-12-16 08:10:17 -0800114 Err(BinderStatus::new_service_specific_error(
115 rc,
116 anyhow_error_to_cstring(&e).as_deref(),
117 ))
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800118 },
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)]
133struct RateInfo {
134 counter: u32,
135 timestamp: Instant,
136}
137
138impl 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
164impl 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.
171struct 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 Danisevskis7a1cf382020-11-20 11:22:14 -0800186struct ApcState {
187 session: Option<ApcSessionState>,
188 rate_limiting: HashMap<u32, RateInfo>,
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800189 confirmation_token_sender: Sender<Vec<u8>>,
190}
191
192impl ApcState {
193 fn new(confirmation_token_sender: Sender<Vec<u8>>) -> Self {
194 Self { session: None, rate_limiting: Default::default(), confirmation_token_sender }
195 }
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800196}
197
198/// Implementation of the APC service.
199pub struct ApcManager {
200 state: Arc<Mutex<ApcState>>,
201}
202
203impl Interface for ApcManager {}
204
205impl ApcManager {
206 /// Create a new instance of the Android Protected Confirmation service.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800207 pub fn new_native_binder(
208 confirmation_token_sender: Sender<Vec<u8>>,
Stephen Crane221bbb52020-12-16 15:52:10 -0800209 ) -> Result<Strong<dyn IProtectedConfirmation>> {
Andrew Walbrande45c8b2021-04-13 14:42:38 +0000210 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 Danisevskis7a1cf382020-11-20 11:22:14 -0800214 }
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 Danisevskisb1673db2021-02-08 18:11:57 -0800233 match (rc, client_aborted, confirmation_token) {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800234 // If the user confirmed the dialog.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800235 (ResponseCode::OK, _, Some(confirmation_token)) => {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800236 // Reset counter.
237 state.rate_limiting.remove(&uid);
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800238 // 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 Danisevskis7a1cf382020-11-20 11:22:14 -0800242 }
243 // If cancelled by the user or if aborted by the client.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800244 (ResponseCode::CANCELLED, _, _) | (ResponseCode::ABORTED, true, _) => {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800245 // 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 Danisevskisb1673db2021-02-08 18:11:57 -0800250 (ResponseCode::OK, _, None) => {
251 log::error!(
252 "Confirmation prompt was successful but no confirmation token was returned."
253 );
254 }
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800255 // 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 Homescu0a8291b2021-03-24 02:36:43 +0000274 listener: &binder::Strong<dyn IConfirmationCallback>,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800275 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 Danisevskisb1673db2021-02-08 18:11:57 -0800317 move |rc, data_confirmed, confirmation_token| {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800318 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 Homescu0a8291b2021-03-24 02:36:43 +0000333 fn cancel_prompt(&self, listener: &binder::Strong<dyn IConfirmationCallback>) -> Result<()> {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800334 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
361impl IProtectedConfirmation for ApcManager {
362 fn presentPrompt(
363 &self,
Andrei Homescu0a8291b2021-03-24 02:36:43 +0000364 listener: &binder::Strong<dyn IConfirmationCallback>,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800365 prompt_text: &str,
366 extra_data: &[u8],
367 locale: &str,
368 ui_option_flags: i32,
369 ) -> BinderResult<()> {
Hasini Gunasinghe5a893e82021-05-05 14:32:32 +0000370 // presentPrompt can take more time than other operations.
371 let _wp = wd::watch_millis("IProtectedConfirmation::presentPrompt", 3000);
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800372 map_or_log_err(
373 self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags),
374 Ok,
375 )
376 }
Andrei Homescu0a8291b2021-03-24 02:36:43 +0000377 fn cancelPrompt(
378 &self,
379 listener: &binder::Strong<dyn IConfirmationCallback>,
380 ) -> BinderResult<()> {
Hasini Gunasinghe5a893e82021-05-05 14:32:32 +0000381 let _wp = wd::watch_millis("IProtectedConfirmation::cancelPrompt", 500);
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800382 map_or_log_err(self.cancel_prompt(listener), Ok)
383 }
384 fn isSupported(&self) -> BinderResult<bool> {
Hasini Gunasinghe5a893e82021-05-05 14:32:32 +0000385 let _wp = wd::watch_millis("IProtectedConfirmation::isSupported", 500);
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800386 map_or_log_err(Self::is_supported(), Ok)
387 }
388}