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}