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