Re: [PATCH v4] security: Expand task_setscheduler LSM hook
From: Casey Schaufler
Date: Tue Jul 21 2026 - 11:12:58 EST
On 7/19/2026 6:41 PM, Aaron Tomlin wrote:
> At present, the task_setscheduler LSM hook provides security modules
> with the opportunity to mediate changes to a task's scheduling policy by
> inspecting the requested sched_attr. However, when invoked via
> sched_setaffinity(), the hook lacks visibility into the actual CPU
> affinity mask being requested. Consequently, security modules are
> entirely blind to the target CPUs and cannot make granular access
> control decisions based on spatial isolation.
>
> In modern multi-tenant and real-time environments, CPU isolation is a
> critical boundary. The inability to audit or restrict specific CPU
> pinning requests limits the effectiveness of security policies,
> particularly when attempting to shield isolated or cryptographic cores
> from unprivileged or compromised tasks.
>
> This patch expands the security_task_setscheduler() hook signature to
> include a pointer to the requested cpumask alongside the existing
> sched_attr. Because this is a shared hook used for multiple scheduling
> attribute changes, call sites that do not modify CPU affinity are
> updated to safely pass NULL for the mask. To protect against unverified
> dereferences, the parameter is annotated with __nullable in the LSM hook
> definition, ensuring the BPF verifier mandates explicit NULL checks for
> attached eBPF programs.
>
> Historically, SELinux has governed all scheduling alterations under a
> single, monolithic PROCESS__SETSCHED access vector. However, by
> inspecting fields such as attr->sched_policy or attr->sched_priority, a
> security module can introduce granular access controls. For instance,
> differentiating between standard time-sharing policies and
> latency-sensitive real-time policies.
>
> This change updates all in-tree security modules (SELinux and Smack) to
> accommodate the new parameter mechanically, while providing LSMs with
> the necessary context to enforce strict affinity policies.
>
> Signed-off-by: Aaron Tomlin <atomlin@xxxxxxxxxxx>
> ---
> Changes since v3:
>
> - Expanded the task_setscheduler LSM hook to accept the 'sched_attr'
> payload, enabling BPF and other LSMs to inspect scheduling attributes
> (Peter Zijlstra)
>
> - Updated all call sites for security_task_setscheduler() to correctly
> pass the attr structure when available, or NULL otherwise
> (Peter Zijlstra)
>
> - Added the necessary forward declaration for struct sched_attr to
> include/linux/security.h
>
> - Linked to v3: https://lore.kernel.org/lkml/20260526142838.774711-1-atomlin@xxxxxxxxxxx/
>
> Changes since v2:
>
> - Dropped patch 1. This is to be addressed by the cgroup cpuset
> maintainer (Waiman Long)
>
> - Dropped patch 3. Will be submitted as a separate patch (Paul Moore)
>
> - Linked to v2: https://lore.kernel.org/lkml/20260509213803.968464-1-atomlin@xxxxxxxxxxx/
>
> Changes since v1:
>
> - Reordered the allocation and user-copy of new_mask in the MIPS
> architecture's mipsmt_sys_sched_setaffinity() to occur before the
> LSM hook is invoked. This ensures the security modules evaluate a fully
> populated mask rather than uninitialised memory, while cleanly handling
> error unwinding
>
> - Updated cpuset_can_fork() to pass the destination cpuset's effective CPU
> mask instead of NULL
>
> - Linked to v1: https://lore.kernel.org/lkml/20260509164847.939294-1-atomlin@xxxxxxxxxxx/
>
> Signed-off-by: Aaron Tomlin <atomlin@xxxxxxxxxxx>
For the (trivial) Smack changes:
Reviewed-by: Casey Schaufler <casey@xxxxxxxxxxxxxxxx>
> ---
> arch/mips/kernel/mips-mt-fpaff.c | 2 +-
> fs/proc/base.c | 2 +-
> include/linux/lsm_hook_defs.h | 4 +++-
> include/linux/security.h | 15 +++++++++++----
> kernel/cgroup/cpuset.c | 4 ++--
> kernel/sched/syscalls.c | 4 ++--
> security/commoncap.c | 9 +++++++--
> security/security.c | 14 +++++++++-----
> security/selinux/hooks.c | 4 +++-
> security/smack/smack_lsm.c | 13 +++++++++++--
> 10 files changed, 50 insertions(+), 21 deletions(-)
>
> diff --git a/arch/mips/kernel/mips-mt-fpaff.c b/arch/mips/kernel/mips-mt-fpaff.c
> index 4fead87d2f43..c7a5d55cf7ca 100644
> --- a/arch/mips/kernel/mips-mt-fpaff.c
> +++ b/arch/mips/kernel/mips-mt-fpaff.c
> @@ -110,7 +110,7 @@ asmlinkage long mipsmt_sys_sched_setaffinity(pid_t pid, unsigned int len,
> goto out_unlock;
> }
>
> - retval = security_task_setscheduler(p);
> + retval = security_task_setscheduler(p, NULL, new_mask);
> if (retval)
> goto out_unlock;
>
> diff --git a/fs/proc/base.c b/fs/proc/base.c
> index 780f81259052..b14401030e5c 100644
> --- a/fs/proc/base.c
> +++ b/fs/proc/base.c
> @@ -2589,7 +2589,7 @@ static ssize_t timerslack_ns_write(struct file *file, const char __user *buf,
> }
> rcu_read_unlock();
>
> - err = security_task_setscheduler(p);
> + err = security_task_setscheduler(p, NULL, NULL);
> if (err) {
> count = err;
> goto out;
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..80ca02879620 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -255,7 +255,9 @@ LSM_HOOK(int, 0, task_prlimit, const struct cred *cred,
> const struct cred *tcred, unsigned int flags)
> LSM_HOOK(int, 0, task_setrlimit, struct task_struct *p, unsigned int resource,
> struct rlimit *new_rlim)
> -LSM_HOOK(int, 0, task_setscheduler, struct task_struct *p)
> +LSM_HOOK(int, 0, task_setscheduler, struct task_struct *p,
> + const struct sched_attr *attr__nullable,
> + const struct cpumask *in_mask__nullable)
> LSM_HOOK(int, 0, task_getscheduler, struct task_struct *p)
> LSM_HOOK(int, 0, task_movememory, struct task_struct *p)
> LSM_HOOK(int, 0, task_kill, struct task_struct *p, struct kernel_siginfo *info,
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 153e9043058f..82dfe4e3f8d1 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -82,6 +82,7 @@ struct ctl_table;
> struct audit_krule;
> struct user_namespace;
> struct timezone;
> +struct sched_attr;
>
> enum lsm_event {
> LSM_POLICY_CHANGE,
> @@ -196,7 +197,9 @@ extern int cap_mmap_addr(unsigned long addr);
> extern int cap_task_fix_setuid(struct cred *new, const struct cred *old, int flags);
> extern int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3,
> unsigned long arg4, unsigned long arg5);
> -extern int cap_task_setscheduler(struct task_struct *p);
> +extern int cap_task_setscheduler(struct task_struct *p,
> + const struct sched_attr *attr,
> + const struct cpumask *in_mask);
> extern int cap_task_setioprio(struct task_struct *p, int ioprio);
> extern int cap_task_setnice(struct task_struct *p, int nice);
> extern int cap_vm_enough_memory(struct mm_struct *mm, long pages);
> @@ -531,7 +534,9 @@ int security_task_prlimit(const struct cred *cred, const struct cred *tcred,
> unsigned int flags);
> int security_task_setrlimit(struct task_struct *p, unsigned int resource,
> struct rlimit *new_rlim);
> -int security_task_setscheduler(struct task_struct *p);
> +int security_task_setscheduler(struct task_struct *p,
> + const struct sched_attr *attr,
> + const struct cpumask *in_mask);
> int security_task_getscheduler(struct task_struct *p);
> int security_task_movememory(struct task_struct *p);
> int security_task_kill(struct task_struct *p, struct kernel_siginfo *info,
> @@ -1393,9 +1398,11 @@ static inline int security_task_setrlimit(struct task_struct *p,
> return 0;
> }
>
> -static inline int security_task_setscheduler(struct task_struct *p)
> +static inline int security_task_setscheduler(struct task_struct *p,
> + const struct sched_attr *attr,
> + const struct cpumask *in_mask)
> {
> - return cap_task_setscheduler(p);
> + return cap_task_setscheduler(p, attr, in_mask);
> }
>
> static inline int security_task_getscheduler(struct task_struct *p)
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index 45944b3e31ca..6ab47cf5582b 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -3042,7 +3042,7 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
> goto out_unlock;
>
> if (setsched_check) {
> - ret = security_task_setscheduler(task);
> + ret = security_task_setscheduler(task, NULL, cs->effective_cpus);
> if (ret)
> goto out_unlock;
> }
> @@ -3600,7 +3600,7 @@ static int cpuset_can_fork(struct task_struct *task, struct css_set *cset)
> if (ret)
> goto out_unlock;
>
> - ret = security_task_setscheduler(task);
> + ret = security_task_setscheduler(task, NULL, cs->effective_cpus);
> if (ret)
> goto out_unlock;
>
> diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c
> index b215b0ead9a6..86490035e815 100644
> --- a/kernel/sched/syscalls.c
> +++ b/kernel/sched/syscalls.c
> @@ -540,7 +540,7 @@ int __sched_setscheduler(struct task_struct *p,
> if (attr->sched_flags & SCHED_FLAG_SUGOV)
> return -EINVAL;
>
> - retval = security_task_setscheduler(p);
> + retval = security_task_setscheduler(p, attr, NULL);
> if (retval)
> return retval;
> }
> @@ -1213,7 +1213,7 @@ long sched_setaffinity(pid_t pid, const struct cpumask *in_mask)
> return -EPERM;
> }
>
> - retval = security_task_setscheduler(p);
> + retval = security_task_setscheduler(p, NULL, in_mask);
> if (retval)
> return retval;
>
> diff --git a/security/commoncap.c b/security/commoncap.c
> index 3399535808fe..d83eee0459fe 100644
> --- a/security/commoncap.c
> +++ b/security/commoncap.c
> @@ -1222,13 +1222,18 @@ static int cap_safe_nice(struct task_struct *p)
> /**
> * cap_task_setscheduler - Determine if scheduler policy change is permitted
> * @p: The task to affect
> + * @attr: Requested scheduling attributes (ignored)
> + * @in_mask: Requested CPU affinity mask (ignored)
> *
> * Determine if the requested scheduler policy change is permitted for the
> - * specified task.
> + * specified task. The capabilities security module does not evaluate the
> + * @attr or @in_mask parameters, relying solely on cap_safe_nice().
> *
> * Return: 0 if permission is granted, -ve if denied.
> */
> -int cap_task_setscheduler(struct task_struct *p)
> +int cap_task_setscheduler(struct task_struct *p,
> + const struct sched_attr *attr __always_unused,
> + const struct cpumask *in_mask __always_unused)
> {
> return cap_safe_nice(p);
> }
> diff --git a/security/security.c b/security/security.c
> index 71aea8fdf014..a6725dddc34f 100644
> --- a/security/security.c
> +++ b/security/security.c
> @@ -3240,17 +3240,21 @@ int security_task_setrlimit(struct task_struct *p, unsigned int resource,
> }
>
> /**
> - * security_task_setscheduler() - Check if setting sched policy/param is allowed
> + * security_task_setscheduler() - Check if setting sched policy/param/affinity is allowed
> * @p: target task
> + * @attr: requested scheduling attributes, or NULL if not changing parameters
> + * @in_mask: requested CPU affinity mask, or NULL if not changing affinity
> *
> - * Check permission before setting scheduling policy and/or parameters of
> - * process @p.
> + * Check permission before setting the scheduling policy, parameters, and/or
> + * CPU affinity of process @p.
> *
> * Return: Returns 0 if permission is granted.
> */
> -int security_task_setscheduler(struct task_struct *p)
> +int security_task_setscheduler(struct task_struct *p,
> + const struct sched_attr *attr,
> + const struct cpumask *in_mask)
> {
> - return call_int_hook(task_setscheduler, p);
> + return call_int_hook(task_setscheduler, p, attr, in_mask);
> }
>
> /**
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index 8d6945edae7a..d77a8d5c364c 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -4559,7 +4559,9 @@ static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource,
> return 0;
> }
>
> -static int selinux_task_setscheduler(struct task_struct *p)
> +static int selinux_task_setscheduler(struct task_struct *p,
> + const struct sched_attr *attr __always_unused,
> + const struct cpumask *in_mask __always_unused)
> {
> return avc_has_perm(current_sid(), task_sid_obj(p), SECCLASS_PROCESS,
> PROCESS__SETSCHED, NULL);
> diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
> index ff115068c5c0..cf28ccc28b80 100644
> --- a/security/smack/smack_lsm.c
> +++ b/security/smack/smack_lsm.c
> @@ -2338,10 +2338,19 @@ static int smack_task_getioprio(struct task_struct *p)
> /**
> * smack_task_setscheduler - Smack check on setting scheduler
> * @p: the task object
> + * @attr: Requested scheduling attributes (ignored)
> + * @in_mask: Requested CPU affinity mask (ignored)
> *
> - * Return 0 if read access is permitted
> + * Evaluate whether the current task has write access to the target task @p
> + * to change its scheduling policy. The Smack security module relies
> + * strictly on label-based access control and does not evaluate CPU
> + * affinity masks or scheduling attributes.
> + *
> + * Return: 0 if write access is permitted
> */
> -static int smack_task_setscheduler(struct task_struct *p)
> +static int smack_task_setscheduler(struct task_struct *p,
> + const struct sched_attr *attr __always_unused,
> + const struct cpumask *in_mask __always_unused)
> {
> return smk_curacc_on_task(p, MAY_WRITE, __func__);
> }