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