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] 49 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 { 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 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)] 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. 207 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. 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. 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. 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 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. 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. 271 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 _t_outdated; 274 let _oneway_node; 275 276 let oneway = self.flags & TF_ONE_WAY != 0; 277 let process = self.to.clone(); 278 let mut process_inner = process.inner.lock(); 279 280 self.set_outstanding(&mut process_inner); 281 282 if oneway { 283 if let Some(target_node) = self.target_node.clone() { 284 crate::trace::trace_transaction(false, &self, None); 285 if process_inner.is_frozen.is_frozen() { 286 process_inner.async_recv = true; 287 if self.flags & TF_UPDATE_TXN != 0 { 288 if let Some(t_outdated) = 289 target_node.take_outdated_transaction(&self, &mut process_inner) 290 { 291 let mut alloc_guard = t_outdated.allocation.lock(); 292 if let Some(alloc) = (*alloc_guard).as_mut() { 293 // Take the oneway node to prevent `Allocation::drop` from calling 294 // `pending_oneway_finished()`, which would be incorrect as this 295 // transaction is not being submitted. 296 _oneway_node = alloc.take_oneway_node(); 297 } 298 drop(alloc_guard); 299 // Save the transaction to be dropped after locks are released. 300 _t_outdated = t_outdated; 301 } 302 } 303 } 304 match target_node.submit_oneway(self, &mut process_inner) { 305 Ok(()) => {} 306 Err((err, work)) => { 307 drop(process_inner); 308 // Drop work after releasing process lock. 309 drop(work); 310 return Err(err); 311 } 312 } 313 314 if process_inner.is_frozen.is_frozen() { 315 return Err(BinderError::new_frozen_oneway()); 316 } else { 317 return Ok(()); 318 } 319 } else { 320 pr_err!("Failed to submit oneway transaction to node."); 321 } 322 } 323 324 if process_inner.is_frozen.is_frozen() { 325 process_inner.sync_recv = true; 326 return Err(BinderError::new_frozen()); 327 } 328 329 let res = if let Some(thread) = self.find_target_thread() { 330 info.to_tid = thread.id; 331 crate::trace::trace_transaction(false, &self, Some(&thread.task)); 332 match thread.push_work(self) { 333 PushWorkRes::Ok => Ok(()), 334 PushWorkRes::FailedDead(me) => Err((BinderError::new_dead(), me)), 335 } 336 } else { 337 crate::trace::trace_transaction(false, &self, None); 338 process_inner.push_work(self) 339 }; 340 drop(process_inner); 341 342 match res { 343 Ok(()) => Ok(()), 344 Err((err, work)) => { 345 // Drop work after releasing process lock. 346 drop(work); 347 Err(err) 348 } 349 } 350 } 351 352 /// Check whether one oneway transaction can supersede another. 353 pub(crate) fn can_replace(&self, old: &Transaction) -> bool { 354 if self.from.process.task.pid() != old.from.process.task.pid() { 355 return false; 356 } 357 358 if self.flags & old.flags & (TF_ONE_WAY | TF_UPDATE_TXN) != (TF_ONE_WAY | TF_UPDATE_TXN) { 359 return false; 360 } 361 362 let target_node_match = match (self.target_node.as_ref(), old.target_node.as_ref()) { 363 (None, None) => true, 364 (Some(tn1), Some(tn2)) => Arc::ptr_eq(tn1, tn2), 365 _ => false, 366 }; 367 368 self.code == old.code && self.flags == old.flags && target_node_match 369 } 370 371 fn prepare_file_list(&self) -> Result<TranslatedFds> { 372 let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; 373 374 match alloc.translate_fds() { 375 Ok(translated) => { 376 *self.allocation.lock() = Some(alloc); 377 Ok(translated) 378 } 379 Err(err) => { 380 // Free the allocation eagerly. 381 drop(alloc); 382 Err(err) 383 } 384 } 385 } 386 } 387 388 impl DeliverToRead for Transaction { 389 fn do_work( 390 self: DArc<Self>, 391 thread: &Thread, 392 writer: &mut BinderReturnWriter<'_>, 393 ) -> Result<bool> { 394 let send_failed_reply = ScopeGuard::new(|| { 395 if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { 396 let reply = Err(BR_FAILED_REPLY); 397 self.from.deliver_reply(reply, &self); 398 } 399 self.drop_outstanding_txn(); 400 }); 401 402 let files = if let Ok(list) = self.prepare_file_list() { 403 list 404 } else { 405 // On failure to process the list, we send a reply back to the sender and ignore the 406 // transaction on the recipient. 407 return Ok(true); 408 }; 409 410 let mut tr_sec = BinderTransactionDataSecctx::default(); 411 let tr = tr_sec.tr_data(); 412 if let Some(target_node) = &self.target_node { 413 let (ptr, cookie) = target_node.get_id(); 414 tr.target.ptr = ptr as _; 415 tr.cookie = cookie as _; 416 }; 417 tr.code = self.code; 418 tr.flags = self.flags; 419 tr.data_size = self.data_size as _; 420 tr.data.ptr.buffer = self.data_address as _; 421 tr.offsets_size = self.offsets_size as _; 422 if tr.offsets_size > 0 { 423 tr.data.ptr.offsets = (self.data_address + ptr_align(self.data_size).unwrap()) as _; 424 } 425 tr.sender_euid = self.sender_euid.into_uid_in_current_ns(); 426 tr.sender_pid = 0; 427 if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { 428 // Not a reply and not one-way. 429 tr.sender_pid = self.from.process.pid_in_current_ns(); 430 } 431 let code = if self.target_node.is_none() { 432 BR_REPLY 433 } else if self.txn_security_ctx_off.is_some() { 434 BR_TRANSACTION_SEC_CTX 435 } else { 436 BR_TRANSACTION 437 }; 438 439 // Write the transaction code and data to the user buffer. 440 writer.write_code(code)?; 441 if let Some(off) = self.txn_security_ctx_off { 442 tr_sec.secctx = (self.data_address + off) as u64; 443 writer.write_payload(&tr_sec)?; 444 } else { 445 writer.write_payload(&*tr)?; 446 } 447 448 let mut alloc = self.allocation.lock().take().ok_or(ESRCH)?; 449 450 // Dismiss the completion of transaction with a failure. No failure paths are allowed from 451 // here on out. 452 send_failed_reply.dismiss(); 453 454 // Commit files, and set FDs in FDA to be closed on buffer free. 455 let close_on_free = files.commit(); 456 alloc.set_info_close_on_free(close_on_free); 457 458 // It is now the user's responsibility to clear the allocation. 459 alloc.keep_alive(); 460 461 self.drop_outstanding_txn(); 462 463 crate::trace::trace_transaction_received(&self); 464 465 // When this is not a reply and not a oneway transaction, update `current_transaction`. If 466 // it's a reply, `current_transaction` has already been updated appropriately. 467 if self.target_node.is_some() && tr_sec.transaction_data.flags & TF_ONE_WAY == 0 { 468 thread.set_current_transaction(self); 469 } 470 471 Ok(false) 472 } 473 474 fn cancel(self: DArc<Self>) { 475 let allocation = self.allocation.lock().take(); 476 drop(allocation); 477 478 // If this is not a reply or oneway transaction, then send a dead reply. 479 if self.target_node.is_some() && self.flags & TF_ONE_WAY == 0 { 480 let reply = Err(BR_DEAD_REPLY); 481 self.from.deliver_reply(reply, &self); 482 } 483 484 self.drop_outstanding_txn(); 485 } 486 487 fn should_sync_wakeup(&self) -> bool { 488 self.flags & TF_ONE_WAY == 0 489 } 490 491 fn debug_print(&self, m: &SeqFile, _prefix: &str, tprefix: &str) -> Result<()> { 492 self.debug_print_inner(m, tprefix); 493 Ok(()) 494 } 495 } 496 497 #[pinned_drop] 498 impl PinnedDrop for Transaction { 499 fn drop(self: Pin<&mut Self>) { 500 self.drop_outstanding_txn(); 501 } 502 } 503