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