starnix_core/security/yama.rs
1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This module implements the YAMA LSM functionality, used to lock down ptrace access.
6
7use std::borrow::Cow;
8use std::sync::atomic::Ordering;
9
10use crate::security;
11use crate::task::{CurrentTask, PtraceAllowedPtracers, Task};
12use crate::vfs::FsNodeOps;
13use crate::vfs::pseudo::simple_file::{BytesFile, BytesFileOps, parse_unsigned_file};
14use starnix_uapi::auth::{CAP_SYS_ADMIN, CAP_SYS_PTRACE, PtraceAccessMode};
15use starnix_uapi::error;
16use starnix_uapi::errors::Errno;
17
18/// Scope definitions for Yama. For full details, see ptrace(2).
19/// 0 means classic ptrace checks, without additional restrictions.
20/// This is the Starnix default (i.e. YAMA is not active).
21pub const SCOPE_CLASSIC: u8 = 0;
22/// 1 means tracer needs to have CAP_SYS_PTRACE or be a parent / child
23/// process. This is the default with YAMA active.
24pub const SCOPE_RESTRICTED: u8 = 1;
25/// 2 means tracer needs to have CAP_SYS_PTRACE
26pub const SCOPE_ADMIN_ONLY: u8 = 2;
27/// 3 means no process can attach.
28pub const SCOPE_NO_ATTACH: u8 = 3;
29
30/// Corresponds to the `ptrace_access_check()` LSM hook.
31pub(super) fn ptrace_access_check(
32 current_task: &CurrentTask,
33 tracee: &Task,
34 mode: PtraceAccessMode,
35) -> Result<(), Errno> {
36 if !mode.contains(PtraceAccessMode::ATTACH) {
37 // YAMA controls read access checks but not attach.
38 return Ok(());
39 }
40
41 let ptrace_scope = current_task.kernel().ptrace_scope.load(Ordering::Relaxed);
42
43 // From the `ptrace.2` man page description of YAMA's `ptrace_scope`:
44 match ptrace_scope {
45 // classic ptrace permissions:
46 //
47 // No additional restrictions on operations that perform
48 // PTRACE_MODE_ATTACH checks (beyond those imposed by the
49 // commoncap and other LSMs).
50 //
51 //
52 // The use of PTRACE_TRACEME is unchanged.
53 SCOPE_CLASSIC => Ok(()),
54
55 // restricted ptrace: (the YAMA default)
56 //
57 // When performing an operation that requires a
58 // PTRACE_MODE_ATTACH check, the calling process must either
59 // have the CAP_SYS_PTRACE capability in the user namespace of
60 // the target process or it must have a predefined
61 // relationship with the target process. By default, the
62 // predefined relationship is that the target process must be
63 // a descendant of the caller.
64 //
65 // A target process can employ the prctl(2) PR_SET_PTRACER
66 // operation to declare an additional PID that is allowed to
67 // perform PTRACE_MODE_ATTACH operations on the target. See
68 // the kernel source file
69 // Documentation/admin-guide/LSM/Yama.rst (or
70 // Documentation/security/Yama.txt before Linux 4.13) for
71 // further details.
72 //
73 // The use of PTRACE_TRACEME is unchanged.
74 SCOPE_RESTRICTED => {
75 // This only allows us to attach to descendants and tasks that have
76 // explicitly allowlisted us with PR_SET_PTRACER.
77 let mut ttg = tracee.thread_group().read().parent.clone();
78 let my_pid = current_task.thread_group().leader;
79 while let Some(target) = ttg {
80 let target = target.upgrade();
81 if target.leader == my_pid {
82 return Ok(());
83 }
84 ttg = target.read().parent.clone();
85 }
86
87 match tracee.thread_group().read().allowed_ptracers {
88 PtraceAllowedPtracers::None => (),
89 PtraceAllowedPtracers::Some(pid) => {
90 if my_pid == pid {
91 return Ok(());
92 }
93 }
94 PtraceAllowedPtracers::Any => return Ok(()),
95 }
96
97 security::check_task_capable(current_task, CAP_SYS_PTRACE)
98 }
99
100 // admin-only attach:
101 //
102 // Only processes with the CAP_SYS_PTRACE capability in the
103 // user namespace of the target process may perform
104 // PTRACE_MODE_ATTACH operations or trace children that employ
105 // PTRACE_TRACEME.
106 SCOPE_ADMIN_ONLY => security::check_task_capable(current_task, CAP_SYS_PTRACE),
107
108 // no attach:
109 //
110 // No process may perform PTRACE_MODE_ATTACH operations or
111 // trace children that employ PTRACE_TRACEME.
112 //
113 // Once this value has been written to the file, it cannot be
114 // changed.
115 _ => error!(EPERM),
116 }
117}
118
119/// Corresponds to the `ptrace_traceme()` LSM hook.
120pub(super) fn ptrace_traceme(
121 current_task: &CurrentTask,
122 parent_tracer_task: &Task,
123) -> Result<(), Errno> {
124 let ptrace_scope = current_task.kernel().ptrace_scope.load(Ordering::Relaxed);
125
126 match ptrace_scope {
127 SCOPE_CLASSIC | SCOPE_RESTRICTED => Ok(()),
128 SCOPE_ADMIN_ONLY => {
129 if parent_tracer_task.real_creds().has_capability(CAP_SYS_PTRACE) {
130 Ok(())
131 } else {
132 error!(EPERM)
133 }
134 }
135 _ => error!(EPERM),
136 }
137}
138pub struct PtraceScopeFile {}
139
140impl PtraceScopeFile {
141 pub fn new_node() -> impl FsNodeOps {
142 BytesFile::new_node(Self {})
143 }
144}
145
146impl BytesFileOps for PtraceScopeFile {
147 fn write(&self, current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
148 security::check_task_capable(current_task, CAP_SYS_ADMIN)?;
149
150 let new_scope = parse_unsigned_file::<u8>(&data)?;
151 if new_scope > SCOPE_NO_ATTACH {
152 return error!(EINVAL);
153 }
154
155 let kernel = current_task.kernel();
156 loop {
157 let old_scope = kernel.ptrace_scope.load(Ordering::Relaxed);
158
159 if old_scope == SCOPE_NO_ATTACH && new_scope != SCOPE_NO_ATTACH {
160 return error!(EINVAL);
161 }
162
163 if kernel
164 .ptrace_scope
165 .compare_exchange(old_scope, new_scope, Ordering::Relaxed, Ordering::Relaxed)
166 .is_ok()
167 {
168 return Ok(());
169 }
170 }
171 }
172
173 fn read(&self, current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
174 let mut scope = current_task.kernel().ptrace_scope.load(Ordering::Relaxed).to_string();
175 scope.push('\n');
176 Ok(scope.into_bytes().into())
177 }
178}