blob: bdde5ae4dc93e0e2f860d956221bd52729f553b7 [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;
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +000025use crate::ks_err;
Hasini Gunasinghe5a893e82021-05-05 14:32:32 +000026use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd};
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080027use android_security_apc::aidl::android::security::apc::{
28 IConfirmationCallback::IConfirmationCallback,
29 IProtectedConfirmation::{BnProtectedConfirmation, IProtectedConfirmation},
30 ResponseCode::ResponseCode,
31};
32use android_security_apc::binder::{
Andrew Walbrande45c8b2021-04-13 14:42:38 +000033 BinderFeatures, ExceptionCode, Interface, Result as BinderResult, SpIBinder,
34 Status as BinderStatus, Strong, ThreadState,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080035};
36use anyhow::{Context, Result};
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080037use keystore2_apc_compat::ApcHal;
38use keystore2_selinux as selinux;
39use std::time::{Duration, Instant};
40
41/// This is the main APC error type, it wraps binder exceptions and the
42/// APC ResponseCode.
Chris Wailes263de9f2022-08-11 15:00:51 -070043#[derive(Debug, thiserror::Error, PartialEq, Eq)]
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080044pub enum Error {
45 /// Wraps an Android Protected Confirmation (APC) response code as defined by the
46 /// android.security.apc AIDL interface specification.
47 #[error("Error::Rc({0:?})")]
48 Rc(ResponseCode),
49 /// Wraps a Binder exception code other than a service specific exception.
50 #[error("Binder exception code {0:?}, {1:?}")]
51 Binder(ExceptionCode, i32),
52}
53
54impl Error {
55 /// Short hand for `Error::Rc(ResponseCode::SYSTEM_ERROR)`
56 pub fn sys() -> Self {
57 Error::Rc(ResponseCode::SYSTEM_ERROR)
58 }
59
60 /// Short hand for `Error::Rc(ResponseCode::OPERATION_PENDING)`
61 pub fn pending() -> Self {
62 Error::Rc(ResponseCode::OPERATION_PENDING)
63 }
64
Janis Danisevskis7a1cf382020-11-20 11:22:14 -080065 /// Short hand for `Error::Rc(ResponseCode::IGNORED)`
66 pub fn ignored() -> Self {
67 Error::Rc(ResponseCode::IGNORED)
68 }
69
70 /// Short hand for `Error::Rc(ResponseCode::UNIMPLEMENTED)`
71 pub fn unimplemented() -> Self {
72 Error::Rc(ResponseCode::UNIMPLEMENTED)
73 }
74}
75
76/// This function should be used by confirmation service calls to translate error conditions
77/// into service specific exceptions.
78///
79/// All error conditions get logged by this function.
80///
81/// `Error::Rc(x)` variants get mapped onto a service specific error code of `x`.
82/// `selinux::Error::perm()` is mapped on `ResponseCode::PERMISSION_DENIED`.
83///
84/// All non `Error` error conditions get mapped onto ResponseCode::SYSTEM_ERROR`.
85///
86/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
87/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
88/// typically returns Ok(value).
89pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T>
90where
91 F: FnOnce(U) -> BinderResult<T>,
92{
93 result.map_or_else(
94 |e| {
95 log::error!("{:#?}", e);
96 let root_cause = e.root_cause();
97 let rc = match root_cause.downcast_ref::<Error>() {
98 Some(Error::Rc(rcode)) => rcode.0,
99 Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0,
100 None => match root_cause.downcast_ref::<selinux::Error>() {
101 Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0,
102 _ => ResponseCode::SYSTEM_ERROR.0,
103 },
104 };
Janis Danisevskisea03cff2021-12-16 08:10:17 -0800105 Err(BinderStatus::new_service_specific_error(
106 rc,
107 anyhow_error_to_cstring(&e).as_deref(),
108 ))
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800109 },
110 handle_ok,
111 )
112}
113
114/// Rate info records how many failed attempts a client has made to display a protected
115/// confirmation prompt. Clients are penalized for attempts that get declined by the user
116/// or attempts that get aborted by the client itself.
117///
118/// After the third failed attempt the client has to cool down for 30 seconds before it
119/// it can retry. After the sixth failed attempt, the time doubles with every failed attempt
120/// until it goes into saturation at 24h.
121///
122/// A successful user prompt resets the counter.
123#[derive(Debug, Clone)]
124struct RateInfo {
125 counter: u32,
126 timestamp: Instant,
127}
128
129impl RateInfo {
130 const ONE_DAY: Duration = Duration::from_secs(60u64 * 60u64 * 24u64);
131
132 fn get_remaining_back_off(&self) -> Option<Duration> {
133 let back_off = match self.counter {
134 // The first three attempts come without penalty.
135 0..=2 => return None,
136 // The next three attempts are are penalized with 30 seconds back off time.
137 3..=5 => Duration::from_secs(30),
138 // After that we double the back off time the with every additional attempt
139 // until we reach 1024m (~17h).
140 6..=16 => Duration::from_secs(60)
141 .checked_mul(1u32 << (self.counter - 6))
142 .unwrap_or(Self::ONE_DAY),
143 // After that we cap of at 24h between attempts.
144 _ => Self::ONE_DAY,
145 };
146 let elapsed = self.timestamp.elapsed();
147 // This does exactly what we want.
148 // `back_off - elapsed` is the remaining back off duration or None if elapsed is larger
149 // than back_off. Also, this operation cannot overflow as long as elapsed is less than
150 // back_off, which is all that we care about.
151 back_off.checked_sub(elapsed)
152 }
153}
154
155impl Default for RateInfo {
156 fn default() -> Self {
157 Self { counter: 0u32, timestamp: Instant::now() }
158 }
159}
160
161/// The APC session state represents the state of an APC session.
162struct ApcSessionState {
163 /// A reference to the APC HAL backend.
164 hal: Arc<ApcHal>,
165 /// The client callback object.
166 cb: SpIBinder,
167 /// The uid of the owner of this APC session.
168 uid: u32,
169 /// The time when this session was started.
170 start: Instant,
171 /// This is set when the client calls abort.
172 /// This is used by the rate limiting logic to determine
173 /// if the client needs to be penalized for this attempt.
174 client_aborted: bool,
175}
176
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800177struct ApcState {
178 session: Option<ApcSessionState>,
179 rate_limiting: HashMap<u32, RateInfo>,
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800180 confirmation_token_sender: Sender<Vec<u8>>,
181}
182
183impl ApcState {
184 fn new(confirmation_token_sender: Sender<Vec<u8>>) -> Self {
185 Self { session: None, rate_limiting: Default::default(), confirmation_token_sender }
186 }
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800187}
188
189/// Implementation of the APC service.
190pub struct ApcManager {
191 state: Arc<Mutex<ApcState>>,
192}
193
194impl Interface for ApcManager {}
195
196impl ApcManager {
197 /// Create a new instance of the Android Protected Confirmation service.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800198 pub fn new_native_binder(
199 confirmation_token_sender: Sender<Vec<u8>>,
Stephen Crane221bbb52020-12-16 15:52:10 -0800200 ) -> Result<Strong<dyn IProtectedConfirmation>> {
Andrew Walbrande45c8b2021-04-13 14:42:38 +0000201 Ok(BnProtectedConfirmation::new_binder(
202 Self { state: Arc::new(Mutex::new(ApcState::new(confirmation_token_sender))) },
203 BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() },
204 ))
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800205 }
206
207 fn result(
208 state: Arc<Mutex<ApcState>>,
209 rc: u32,
210 data_confirmed: Option<&[u8]>,
211 confirmation_token: Option<&[u8]>,
212 ) {
213 let mut state = state.lock().unwrap();
214 let (callback, uid, start, client_aborted) = match state.session.take() {
215 None => return, // Nothing to do
216 Some(ApcSessionState { cb: callback, uid, start, client_aborted, .. }) => {
217 (callback, uid, start, client_aborted)
218 }
219 };
220
221 let rc = compat_2_response_code(rc);
222
223 // Update rate limiting information.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800224 match (rc, client_aborted, confirmation_token) {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800225 // If the user confirmed the dialog.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800226 (ResponseCode::OK, _, Some(confirmation_token)) => {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800227 // Reset counter.
228 state.rate_limiting.remove(&uid);
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800229 // Send confirmation token to the enforcement module.
230 if let Err(e) = state.confirmation_token_sender.send(confirmation_token.to_vec()) {
231 log::error!("Got confirmation token, but receiver would not have it. {:?}", e);
232 }
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800233 }
234 // If cancelled by the user or if aborted by the client.
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800235 (ResponseCode::CANCELLED, _, _) | (ResponseCode::ABORTED, true, _) => {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800236 // Penalize.
Chris Wailes53a22af2023-07-12 17:02:47 -0700237 let rate_info = state.rate_limiting.entry(uid).or_default();
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800238 rate_info.counter += 1;
239 rate_info.timestamp = start;
240 }
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800241 (ResponseCode::OK, _, None) => {
242 log::error!(
243 "Confirmation prompt was successful but no confirmation token was returned."
244 );
245 }
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800246 // In any other case this try does not count at all.
247 _ => {}
248 }
249 drop(state);
250
251 if let Ok(listener) = callback.into_interface::<dyn IConfirmationCallback>() {
252 if let Err(e) = listener.onCompleted(rc, data_confirmed) {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000253 log::error!("Reporting completion to client failed {:?}", e)
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800254 }
255 } else {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000256 log::error!("SpIBinder is not a IConfirmationCallback.");
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800257 }
258 }
259
260 fn present_prompt(
261 &self,
Andrei Homescu0a8291b2021-03-24 02:36:43 +0000262 listener: &binder::Strong<dyn IConfirmationCallback>,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800263 prompt_text: &str,
264 extra_data: &[u8],
265 locale: &str,
266 ui_option_flags: i32,
267 ) -> Result<()> {
268 let mut state = self.state.lock().unwrap();
269 if state.session.is_some() {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000270 return Err(Error::pending()).context(ks_err!("APC Session pending."));
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800271 }
272
273 // Perform rate limiting.
274 let uid = ThreadState::get_calling_uid();
275 match state.rate_limiting.get(&uid) {
276 None => {}
277 Some(rate_info) => {
278 if let Some(back_off) = rate_info.get_remaining_back_off() {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000279 return Err(Error::sys()).context(ks_err!(
280 "APC Cooling down. Remaining back-off: {}s",
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800281 back_off.as_secs()
282 ));
283 }
284 }
285 }
286
287 let hal = ApcHal::try_get_service();
288 let hal = match hal {
289 None => {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000290 return Err(Error::unimplemented()).context(ks_err!("APC not supported."));
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800291 }
292 Some(h) => Arc::new(h),
293 };
294
295 let ui_opts = ui_opts_2_compat(ui_option_flags);
296
297 let state_clone = self.state.clone();
298 hal.prompt_user_confirmation(
299 prompt_text,
300 extra_data,
301 locale,
302 ui_opts,
Janis Danisevskisb1673db2021-02-08 18:11:57 -0800303 move |rc, data_confirmed, confirmation_token| {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800304 Self::result(state_clone, rc, data_confirmed, confirmation_token)
305 },
306 )
307 .map_err(|rc| Error::Rc(compat_2_response_code(rc)))
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000308 .context(ks_err!("APC Failed to present prompt."))?;
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800309 state.session = Some(ApcSessionState {
310 hal,
311 cb: listener.as_binder(),
312 uid,
313 start: Instant::now(),
314 client_aborted: false,
315 });
316 Ok(())
317 }
318
Andrei Homescu0a8291b2021-03-24 02:36:43 +0000319 fn cancel_prompt(&self, listener: &binder::Strong<dyn IConfirmationCallback>) -> Result<()> {
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800320 let mut state = self.state.lock().unwrap();
321 let hal = match &mut state.session {
322 None => {
323 return Err(Error::ignored())
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000324 .context(ks_err!("Attempt to cancel non existing session. Ignoring."));
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800325 }
326 Some(session) => {
327 if session.cb != listener.as_binder() {
Shaquille Johnson9da2e1c2022-09-19 12:39:01 +0000328 return Err(Error::ignored()).context(ks_err!(
329 "Attempt to cancel session not belonging to caller. Ignoring."
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800330 ));
331 }
332 session.client_aborted = true;
333 session.hal.clone()
334 }
335 };
336 drop(state);
337 hal.abort();
338 Ok(())
339 }
340
341 fn is_supported() -> Result<bool> {
342 Ok(ApcHal::try_get_service().is_some())
343 }
344}
345
346impl IProtectedConfirmation for ApcManager {
347 fn presentPrompt(
348 &self,
Andrei Homescu0a8291b2021-03-24 02:36:43 +0000349 listener: &binder::Strong<dyn IConfirmationCallback>,
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800350 prompt_text: &str,
351 extra_data: &[u8],
352 locale: &str,
353 ui_option_flags: i32,
354 ) -> BinderResult<()> {
Hasini Gunasinghe5a893e82021-05-05 14:32:32 +0000355 // presentPrompt can take more time than other operations.
356 let _wp = wd::watch_millis("IProtectedConfirmation::presentPrompt", 3000);
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800357 map_or_log_err(
358 self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags),
359 Ok,
360 )
361 }
Andrei Homescu0a8291b2021-03-24 02:36:43 +0000362 fn cancelPrompt(
363 &self,
364 listener: &binder::Strong<dyn IConfirmationCallback>,
365 ) -> BinderResult<()> {
David Drysdale541846b2024-05-23 13:16:07 +0100366 let _wp = wd::watch("IProtectedConfirmation::cancelPrompt");
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800367 map_or_log_err(self.cancel_prompt(listener), Ok)
368 }
369 fn isSupported(&self) -> BinderResult<bool> {
David Drysdale541846b2024-05-23 13:16:07 +0100370 let _wp = wd::watch("IProtectedConfirmation::isSupported");
Janis Danisevskis7a1cf382020-11-20 11:22:14 -0800371 map_or_log_err(Self::is_supported(), Ok)
372 }
373}