1 // SPDX-License-Identifier: GPL-2.0 2 3 // Copyright (C) 2025 Google LLC. 4 5 use core::sync::atomic::{AtomicBool, Ordering}; 6 use kernel::{ 7 prelude::*, 8 seq_file::SeqFile, 9 seq_print, 10 sync::{Arc, SpinLock}, 11 task::Kuid, 12 time::{Instant, Monotonic}, 13 types::ScopeGuard, 14 }; 15 16 use crate::{ 17 allocation::{Allocation, TranslatedFds}, 18 defs::*, 19 error::{BinderError, BinderResult}, 20 node::{Node, NodeRef}, 21 process::{Process, ProcessInner}, 22 ptr_align, 23 thread::{PushWorkRes, Thread}, 24 BinderReturnWriter, DArc, DLArc, DTRWrap, DeliverToRead, 25 }; 26 27 #[pin_data(PinnedDrop)] 28 pub(crate) struct Transaction { 29 pub(crate) debug_id: usize, 30 target_node: Option<DArc<Node>>, 31 pub(crate) from_parent: Option<DArc<Transaction>>, 32 pub(crate) from: Arc<Thread>, 33 pub(crate) to: Arc<Process>, 34 #[pin] 35 allocation: SpinLock<Option<Allocation>>, 36 is_outstanding: AtomicBool, 37 code: u32, 38 pub(crate) flags: u32, 39 data_size: usize, 40 offsets_size: usize, 41 data_address: usize, 42 sender_euid: Kuid, 43 txn_security_ctx_off: Option<usize>, 44 pub(crate) oneway_spam_detected: bool, 45 start_time: Instant<Monotonic>, 46 } 47 48 kernel::list::impl_list_arc_safe! { 49 impl ListArcSafe<0> for Transaction { untracked; } 50 } 51 52 impl Transaction { new( node_ref: NodeRef, from_parent: Option<DArc<Transaction>>, from: &Arc<Thread>, tr: &BinderTransactionDataSg, ) -> BinderResult<DLArc<Self>>53 pub(crate) fn new( 54 node_ref: NodeRef, 55 from_parent: Option<DArc<Transaction>>, 56 from: &Arc<Thread>, 57 tr: &BinderTransactionDataSg, 58 ) -> BinderResult<DLArc<Self>> { 59 let debug_id = super::next_debug_id(); 60 let trd = &tr.transaction_data; 61 let allow_fds = node_ref.node.flags & FLAT_BINDER_FLAG_ACCEPTS_FDS != 0; 62 let txn_security_ctx = node_ref.node.flags & FLAT_BINDER_FLAG_TXN_SECURITY_CTX != 0; 63 let mut txn_security_ctx_off = if txn_security_ctx { Some(0) } else { None }; 64 let to = node_ref.node.owner.clone(); 65 let mut alloc = match from.copy_transaction_data( 66 to.clone(), 67 tr, 68 debug_id, 69 allow_fds, 70 txn_security_ctx_off.as_mut(), 71 ) { 72 Ok(alloc) => alloc, 73 Err(err) => { 74 if !err.is_dead() { 75 pr_warn!("Failure in copy_transaction_data: {:?}", err); 76 } 77 return Err(err); 78 } 79 }; 80 let oneway_spam_detected = alloc.oneway_spam_detected; 81 if trd.flags & TF_ONE_WAY != 0 { 82 if from_parent.is_some() { 83 pr_warn!("Oneway transaction should not be in a transaction stack."); 84 return Err(EINVAL.into()); 85 } 86 alloc.set_info_oneway_node(node_ref.node.clone()); 87 } 88 if trd.flags & TF_CLEAR_BUF != 0 { 89 alloc.set_info_clear_on_drop(); 90 } 91 let target_node = node_ref.node.clone(); 92 alloc.set_info_target_node(node_ref); 93 let data_address = alloc.ptr; 94 95 Ok(DTRWrap::arc_pin_init(pin_init!(Transaction { 96 debug_id, 97 target_node: Some(target_node), 98 from_parent, 99 sender_euid: from.process.task.euid(), 100 from: from.clone(), 101 to, 102 code: trd.code, 103 flags: trd.flags, 104 data_size: trd.data_size as _, 105 offsets_size: trd.offsets_size as _, 106 data_address, 107 allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"), 108 is_outstanding: AtomicBool::new(false), 109 txn_security_ctx_off, 110 oneway_spam_detected, 111 start_time: Instant::now(), 112 }))?) 113 } 114 new_reply( from: &Arc<Thread>, to: Arc<Process>, tr: &BinderTransactionDataSg, allow_fds: bool, ) -> BinderResult<DLArc<Self>>115 pub(crate) fn new_reply( 116 from: &Arc<Thread>, 117 to: Arc<Process>, 118 tr: &BinderTransactionDataSg, 119 allow_fds: bool, 120 ) -> BinderResult<DLArc<Self>> { 121 let debug_id = super::next_debug_id(); 122 let trd = &tr.transaction_data; 123 let mut alloc = match from.copy_transaction_data(to.clone(), tr, debug_id, allow_fds, None) 124 { 125 Ok(alloc) => alloc, 126 Err(err) => { 127 pr_warn!("Failure in copy_transaction_data: {:?}", err); 128 return Err(err); 129 } 130 }; 131 let oneway_spam_detected = alloc.oneway_spam_detected; 132 if trd.flags & TF_CLEAR_BUF != 0 { 133 alloc.set_info_clear_on_drop(); 134 } 135 Ok(DTRWrap::arc_pin_init(pin_init!(Transaction { 136 debug_id, 137 target_node: None, 138 from_parent: None, 139 sender_euid: from.process.task.euid(), 140 from: from.clone(), 141 to, 142 code: trd.code, 143 flags: trd.flags, 144 data_size: trd.data_size as _, 145 offsets_size: trd.offsets_size as _, 146 data_address: alloc.ptr, 147 allocation <- kernel::new_spinlock!(Some(alloc.success()), "Transaction::new"), 148 is_outstanding: AtomicBool::new(false), 149 txn_security_ctx_off: None, 150 oneway_spam_detected, 151 start_time: Instant::now(), 152 }))?) 153 } 154 155 #[inline(never)] debug_print_inner(&self, m: &SeqFile, prefix: &str)156 pub(crate) fn debug_print_inner(&self, m: &SeqFile, prefix: &str) { 157 seq_print!( 158 m, 159 "{}{}: from {}:{} to {} code {:x} flags {:x} elapsed {}ms", 160 prefix, 161 self.debug_id, 162 self.from.process.task.pid(), 163 self.from.id, 164 self.to.task.pid(), 165 self.code, 166 self.flags, 167 self.start_time.elapsed().as_millis(), 168 ); 169 if let Some(target_node) = &self.target_node { 170 seq_print!(m, " node {}", target_node.debug_id); 171 } 172 seq_print!(m, " size {}:{}\n", self.data_size, self.offsets_size); 173 } 174 175 /// Determines if the transaction is stacked on top of the given transaction. is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool176 pub(crate) fn is_stacked_on(&self, onext: &Option<DArc<Self>>) -> bool { 177 match (&self.from_parent, onext) { 178 (None, None) => true, 179 (Some(from_parent), Some(next)) => Arc::ptr_eq(from_parent, next), 180 _ => false, 181 } 182 } 183 184 /// Returns a pointer to the next transaction on the transaction stack, if there is one. clone_next(&self) -> Option<DArc<Self>>185 pub(crate) fn clone_next(&self) -> Option<DArc<Self>> { 186 Some(self.from_parent.as_ref()?.clone()) 187 } 188 189 /// Searches in the transaction stack for a thread that belongs to the target process. This is 190 /// useful when finding a target for a new transaction: if the node belongs to a process that 191 /// is already part of the transaction stack, we reuse the thread. find_target_thread(&self) -> Option<Arc<Thread>>192 fn find_target_thread(&self) -> Option<Arc<Thread>> { 193 let mut it = &self.from_parent; 194 while let Some(transaction) = it { 195 if Arc::ptr_eq(&transaction.from.process, &self.to) { 196 return Some(transaction.from.clone()); 197 } 198 it = &transaction.from_parent; 199 } 200 None 201 } 202 203 /// Searches in the transaction stack for a transaction originating at the given thread. find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>>204 pub(crate) fn find_from(&self, thread: &Thread) -> Option<&DArc<Transaction>> { 205 let mut it = &self.from_parent; 206 while let Some(transaction) = it { 207 if core::ptr::eq(thread, transaction.from.as_ref()) { 208 return Some(transaction); 209 } 210 211 it = &transaction.from_parent; 212 } 213 None 214 } 215 set_outstanding(&self, to_process: &mut ProcessInner)216 pub(crate) fn set_outstanding(&self, to_process: &mut ProcessInner) { 217 // No race because this method is only called once. 218 if !self.is_outstanding.load(Ordering::Relaxed) { 219 self.is_outstanding.store(true, Ordering::Relaxed); 220 to_process.add_outstanding_txn(); 221 } 222 } 223 224 /// Decrement `outstanding_txns` in `to` if it hasn't already been decremented. drop_outstanding_txn(&self)225 fn drop_outstanding_txn(&self) { 226 // No race because this is called at most twice, and one of the calls are in the 227 // destructor, which is guaranteed to not race with any other operations on the 228 // transaction. It also cannot race with `set_outstanding`, since submission happens 229 // before delivery. 230 if self.is_outstanding.load(Ordering::Relaxed) { 231 self.is_outstanding.store(false, Ordering::Relaxed); 232 self.to.drop_outstanding_txn(); 233 } 234 } 235 236 /// Submits the transaction to a work queue. Uses a thread if there is one in the transaction 237 /// stack, otherwise uses the destination process. 238 /// 239 /// Not used for replies. submit(self: DLArc<Self>) -> BinderResult240 pub(crate) fn submit(self: DLArc<Self>) -> BinderResult { 241 // Defined before `process_inner` so that the destructor runs after releasing the lock. 242 let mut _t_outdated; 243 244 let oneway = self.flags & TF_ONE_WAY != 0; 245 let process = self.to.clone(); 246 let mut process_inner = process.inner.lock(); 247 248 self.set_outstanding(&mut process_inner); 249 250 if oneway { 251 if let Some(target_node) = self.target_node.clone() { 252 if process_inner.is_frozen.is_frozen() { 253 process_inner.async_recv = true; 254 if self.flags & TF_UPDATE_TXN != 0 { 255 if let Some(t_outdated) = 256 target_node.take_outdated_transaction(&self, &mut process_inner) 257 { 258 // Save the transaction to be dropped after locks are released. 259 _t_outdated = t_outdated; 260 } 261 } 262 } 263 match target_node.submit_oneway(self, &mut process_inner) { 264 Ok(()) => {} 265 Err((err, work)) => { 266 drop(process_inner); 267 // Drop work after releasing process lock. 268 drop(work); 269 return Err(err); 270 } 271 } 272 273 if process_inner.is_frozen.is_frozen() { 274 return Err(BinderError::new_frozen_oneway()); 275 } else { 276 return Ok(()); 277 } 278 } else { 279 pr_err!("Failed to submit oneway transaction to node."); 280 } 281 } 282 283 if process_inner.is_frozen.is_frozen() { 284 process_inner.sync_recv = true; 285 return Err(BinderError::new_frozen()); 286 } 287 288 let res = if let Some(thread) = self.find_target_thread() { 289 match thread.push_work(self) { 290 PushWorkRes::Ok => Ok(()), 291 PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)), 292 } 293 } else { 294 process_inner.push_work(self) 295 }; 296 drop(process_inner); 297 298 match res { 299 Ok(()) => Ok(()), 300 Err((err, work)) => { 301 // Drop work after releasing process lock. 302 drop(work); 303 Err(err) 304 } 305 } 306 } 307 308 /// Check whether one oneway transaction can supersede another. can_replace(&self, old: &Transaction) -> bool309 pub(crate) fn can_replace(&self, old: &Transaction) -> bool { 310 if self.from.process.task.pid() != old.from.process.task.pid() { 311 return false; 312 } 313 314 if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) { 315 return false; 316 } 317 318 let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) { 319 (None, None) => true, 320 (Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2), 321 _ => false, 322 }; 323 324 self.code == old.code && self.flags == old.flags && target_node_match 325 } 326 prepare_file_list(&self) -> Result<TranslatedFds>327 fn prepare_file_list(&self) -> Result<TranslatedFds> { 328 let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; 329 330 match alloc.translate_fds() { 331 Ok(translated) => { 332 *self.allocation.lock() = Some(alloc); 333 Ok(translated) 334 } 335 Err(err) => { 336 // Free the allocation eagerly. 337 drop(alloc); 338 Err(err) 339 } 340 } 341 } 342 } 343 344 impl DeliverToRead for Transaction { do_work( self: DArc<Self>, thread: &Thread, writer: &mut BinderReturnWriter<'_>, ) -> Result<bool>345 fn do_work( 346 self: DArc<Self>, 347 thread: &Thread, 348 writer: &mut BinderReturnWriter<'_>, 349 ) -> Result<bool> { 350 let send_failed_reply = ScopeGuard::new(|| { 351 if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { 352 let reply = Err(BR_FAILED_REPLY); 353 self.from.deliver_reply(reply, &self); 354 } 355 self.drop_outstanding_txn(); 356 }); 357 358 let files = if let Ok(list) = self.prepare_file_list() { 359 list 360 } else { 361 // On failure to process the list, we send a reply back to the sender and ignore the 362 // transaction on the recipient. 363 return Ok(true); 364 }; 365 366 let mut tr_sec = BinderTransactionDataSecctx::default(); 367 let tr = tr_sec.tr_data(); 368 if let Some(target_node) = &self.target_node { 369 let (ptr, cookie) = target_node.get_id(); 370 tr.target.ptr = ptr as _; 371 tr.cookie = cookie as _; 372 }; 373 tr.code = self.code; 374 tr.flags = self.flags; 375 tr.data_size = self.data_size as _; 376 tr.data.ptr.buffer = self.data_address as _; 377 tr.offsets_size = self.offsets_size as _; 378 if tr.offsets_size > 0 { 379 tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _; 380 } 381 tr.sender_euid = self.sender_euid.into_uid_in_current_ns(); 382 tr.sender_pid = 0; 383 if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { 384 // Not a reply and not one-way. 385 tr.sender_pid = self.from.process.pid_in_current_ns(); 386 } 387 let code = if self.target_node.is_none() { 388 BR_REPLY 389 } else if self.txn_security_ctx_off.is_some() { 390 BR_TRANSACTION_SEC_CTX 391 } else { 392 BR_TRANSACTION 393 }; 394 395 // Write the transaction code and data to the user buffer. 396 writer.write_code(code)?; 397 if let Some(off) = self.txn_security_ctx_off { 398 tr_sec.secctx = (self.data_address + off) as u64; 399 writer.write_payload(&tr_sec)?; 400 } else { 401 writer.write_payload(&*tr)?; 402 } 403 404 let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; 405 406 // Dismiss the completion of transaction with a failure. No failure paths are allowed from 407 // here on out. 408 send_failed_reply.dismiss(); 409 410 // Commit files, and set FDs in FDA to be closed on buffer free. 411 let close_on_free = files.commit(); 412 alloc.set_info_close_on_free(close_on_free); 413 414 // It is now the user's responsibility to clear the allocation. 415 alloc.keep_alive(); 416 417 self.drop_outstanding_txn(); 418 419 // When this is not a reply and not a oneway transaction, update `current_transaction`. If 420 // it's a reply, `current_transaction` has already been updated appropriately. 421 if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 { 422 thread.set_current_transaction(self); 423 } 424 425 Ok(false) 426 } 427 cancel(self: DArc<Self>)428 fn cancel(self: DArc<Self>) { 429 let allocation = self.allocation.lock().take(); 430 drop(allocation); 431 432 // If this is not a reply or oneway transaction, then send a dead reply. 433 if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { 434 let reply = Err(BR_DEAD_REPLY); 435 self.from.deliver_reply(reply, &self); 436 } 437 438 self.drop_outstanding_txn(); 439 } 440 should_sync_wakeup(&self) -> bool441 fn should_sync_wakeup(&self) -> bool { 442 self.flags & TF_ONE_WAY == 0 443 } 444 debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()>445 fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> { 446 self.debug_print_inner(m, tprefix); 447 Ok(()) 448 } 449 } 450 451 #[pinned_drop] 452 impl PinnedDrop for Transaction { drop(self: Pin<&mut Self>)453 fn drop(self: Pin<&mut Self>) { 454 self.drop_outstanding_txn(); 455 } 456 } 457