Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0
2
3// Copyright (C) 2025 Google LLC.
4
5use core::sync::atomic::{AtomicBool, Ordering};
6use 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
16use 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)]
28pub(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
48kernel::list::impl_list_arc_safe! {
49 impl ListArcSafe<0> for Transaction { untracked; }
50}
51
52impl Transaction {
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
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)]
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.
176 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.
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.
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.
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
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.
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.
240 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.
309 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
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
344impl DeliverToRead for Transaction {
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
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
441 fn should_sync_wakeup(&self) -> bool {
442 self.flags & TF_ONE_WAY == 0
443 }
444
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]
452impl PinnedDrop for Transaction {
453 fn drop(self: Pin<&mut Self>) {
454 self.drop_outstanding_txn();
455 }
456}