starry-kernel 0.10.2

A Linux-compatible OS kernel built on ArceOS unikernel
Documentation
//! Resolution and authorization of `perf_event_open(2)` ownership targets.

use alloc::sync::Arc;

use super::{
    access_policy::{
        PerfAccessCapabilities, PerfCredentialIds, PerfCredentialSnapshot, perf_task_access_allowed,
    },
    target::{
        PerfContextKey, PerfCpuId, PerfTarget, PerfTargetError, PerfTargetKind, PerfTaskTarget,
    },
};
use crate::task::{Cred, TidNumber, UserTaskRef, current_user_task, get_user_task_by_number};

/// Strong target identity with its CPU selector validated in Linux order.
pub(super) enum ResolvedPerfTarget {
    /// A live task plus its optional CPU filter.
    Task {
        task: UserTaskRef,
        cpu: Option<PerfCpuId>,
    },
    /// A system-wide event owned by one logical CPU.
    Cpu(PerfCpuId),
}

/// Strong, authorized ownership target retained through event construction.
pub(crate) enum AuthorizedPerfTarget {
    /// A live task plus its optional CPU filter.
    Task {
        task: UserTaskRef,
        cpu: Option<PerfCpuId>,
    },
    /// A system-wide event owned by one logical CPU.
    Cpu(PerfCpuId),
}

impl ResolvedPerfTarget {
    /// Returns the owner class without exposing the resolved task lease.
    pub(super) const fn kind(&self) -> PerfTargetKind {
        match self {
            Self::Task { .. } => PerfTargetKind::Task,
            Self::Cpu(_) => PerfTargetKind::Cpu,
        }
    }

    /// Returns the CPU whose PMU must accept this event, when the target is
    /// CPU-affine. An unconstrained task is validated against every PMU class
    /// it may migrate onto.
    pub(super) const fn cpu_constraint(&self) -> Option<PerfCpuId> {
        match self {
            Self::Task { cpu, .. } => *cpu,
            Self::Cpu(cpu) => Some(*cpu),
        }
    }

    /// Resolves task identity before validating its CPU filter.
    pub(super) fn resolve(target: PerfTarget, cpu_count: usize) -> crate::StarryResult<Self> {
        match target {
            PerfTarget::Task { task, cpu } => {
                let current = current_user_task();
                let task = match task {
                    PerfTaskTarget::Current => current,
                    PerfTaskTarget::Tid(tid) => get_user_task_by_number(TidNumber::try_from(tid)?)?,
                };
                let cpu = cpu
                    .resolve_optional(cpu_count)
                    .map_err(target_error_to_ax)?;
                Ok(Self::Task { task, cpu })
            }
            PerfTarget::Cpu(cpu) => Ok(Self::Cpu(
                cpu.resolve_required(cpu_count)
                    .map_err(target_error_to_ax)?,
            )),
        }
    }

    /// Serializes authorization and installation against target exec.
    ///
    /// Linux retains `signal->exec_update_lock` from
    /// `perf_check_permission()` through `perf_install_in_context()`. Starry's
    /// process `exec_lock` provides the corresponding lifetime boundary.
    pub(super) fn with_authorized<R>(
        self,
        signal_delivery: bool,
        install: impl FnOnce(AuthorizedPerfTarget) -> crate::StarryResult<R>,
    ) -> crate::StarryResult<R> {
        let (task, cpu) = match self {
            Self::Task { task, cpu } => (task, cpu),
            Self::Cpu(cpu) => return install(AuthorizedPerfTarget::Cpu(cpu)),
        };

        let caller = current_user_task();
        let target_process = Arc::clone(&task.as_thread().proc_data);
        let _exec_guard = target_process.exec_lock().lock();
        let caller_thread = caller.as_thread();
        let target_thread = task.as_thread();
        let caller_cred = caller_thread.cred();
        let target_cred = target_thread.cred();
        let same_thread_group = Arc::ptr_eq(&caller_thread.proc_data, &target_thread.proc_data);
        let allowed = perf_task_access_allowed(
            credential_snapshot(&caller_cred),
            credential_snapshot(&target_cred),
            same_thread_group,
            target_thread.proc_data.dumpable() == 1,
            signal_delivery,
        );
        if !allowed {
            return Err(crate::StarryError::PermissionDenied);
        }

        install(AuthorizedPerfTarget::Task { task, cpu })
    }
}

impl AuthorizedPerfTarget {
    /// Returns the generation-bearing context used by group validation.
    pub(crate) fn context_key(&self) -> crate::StarryResult<PerfContextKey> {
        match self {
            Self::Task { task, cpu } => Ok(PerfContextKey::Task {
                scheduler_id: task.id(),
                cpu: *cpu,
            }),
            Self::Cpu(cpu) => Ok(PerfContextKey::Cpu(*cpu)),
        }
    }

    /// Returns the strongly held task target for task-specific event backends.
    ///
    /// The syscall PID was resolved in the caller's active PID namespace and
    /// authorized before this target was constructed. Backends must use this
    /// reference instead of interpreting the namespace-local syscall PID again.
    pub(crate) fn task(&self) -> Option<&UserTaskRef> {
        match self {
            Self::Task { task, .. } => Some(task),
            Self::Cpu(_) => None,
        }
    }
}

fn target_error_to_ax(error: PerfTargetError) -> crate::StarryError {
    match error {
        PerfTargetError::InvalidTuple => crate::StarryError::InvalidInput,
        PerfTargetError::NoSuchProcess => crate::StarryError::NoSuchProcess,
    }
}

fn credential_snapshot(cred: &Cred) -> PerfCredentialSnapshot {
    PerfCredentialSnapshot::new(
        PerfCredentialIds::new(
            cred.uid, cred.gid, cred.euid, cred.egid, cred.suid, cred.sgid,
        ),
        PerfAccessCapabilities::new(
            cred.has_cap_perfmon(),
            cred.has_cap_sys_ptrace(),
            cred.has_cap_kill(),
        ),
    )
}