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}