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