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