projects/linux·blob master · refs

590 lines · 20.1 KiB

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