요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
설계·forced idle·신뢰 모델
core-scheduling.rst:93-163동일 cookie task 선택, IPI 전환, 신뢰 task가 없을 때 sibling을 idle로 강제하는 동작을 구조화 표와 함께 설명합니다.
보안 한계와 다른 용도
core-scheduling.rst:164-226IPI 지연, MDS·L1TF와 kernel context 보호의 한계 및 realtime 격리와 gang scheduling 용도를 정리합니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
===============
Core Scheduling
===============
Core scheduling support allows userspace to define groups of tasks that can
share a core. These groups can be specified either for security usecases (one
group of tasks don't trust another), or for performance usecases (some
workloads may benefit from running on the same core as they don't need the same
hardware resources of the shared core, or may prefer different cores if they
do share hardware resource needs). This document only describes the security
usecase.
Security usecase
----------------
A cross-HT attack involves the attacker and victim running on different Hyper
Threads of the same core. MDS and L1TF are examples of such attacks. The only
full mitigation of cross-HT attacks is to disable Hyper Threading (HT). Core
scheduling is a scheduler feature that can mitigate some (not all) cross-HT
attacks. It allows HT to be turned on safely by ensuring that only tasks in a
user-designated trusted group can share a core. This increase in core sharing
can also improve performance, however it is not guaranteed that performance
will always improve, though that is seen to be the case with a number of real
world workloads. In theory, core scheduling aims to perform at least as good as
when Hyper Threading is disabled. In practice, this is mostly the case though
not always: as synchronizing scheduling decisions across 2 or more CPUs in a
core involves additional overhead - especially when the system is lightly
loaded. When ``total_threads <= N_CPUS/2``, the extra overhead may cause core
scheduling to perform more poorly compared to SMT-disabled, where N_CPUS is the
total number of CPUs. Please measure the performance of your workloads always.
Usage
-----
Core scheduling support is enabled via the ``CONFIG_SCHED_CORE`` config option.
Using this feature, userspace defines groups of tasks that can be co-scheduled
on the same core. The core scheduler uses this information to make sure that
tasks that are not in the same group never run simultaneously on a core, while
doing its best to satisfy the system's scheduling requirements.
Core scheduling can be enabled via the ``PR_SCHED_CORE`` prctl interface.
This interface provides support for the creation of core scheduling groups, as
well as admission and removal of tasks from created groups::
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5);
option:
``PR_SCHED_CORE``
arg2:
Command for operation, must be one off:
- ``PR_SCHED_CORE_GET`` -- get core_sched cookie of ``pid``.
- ``PR_SCHED_CORE_CREATE`` -- create a new unique cookie for ``pid``.
- ``PR_SCHED_CORE_SHARE_TO`` -- push core_sched cookie to ``pid``.
- ``PR_SCHED_CORE_SHARE_FROM`` -- pull core_sched cookie from ``pid``.
arg3:
``pid`` of the task for which the operation applies.
arg4:
``pid_type`` for which the operation applies. It is one of
``PR_SCHED_CORE_SCOPE_``-prefixed macro constants. For example, if arg4
is ``PR_SCHED_CORE_SCOPE_THREAD_GROUP``, then the operation of this command
will be performed for all tasks in the task group of ``pid``.
arg5:
userspace pointer to an unsigned long long for storing the cookie returned
by ``PR_SCHED_CORE_GET`` command. Should be 0 for all other commands.
In order for a process to push a cookie to, or pull a cookie from a process, it
is required to have the ptrace access mode: `PTRACE_MODE_READ_REALCREDS` to the
process.
Building hierarchies of tasks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The simplest way to build hierarchies of threads/processes which share a
cookie and thus a core is to rely on the fact that the core-sched cookie is
inherited across forks/clones and execs, thus setting a cookie for the
'initial' script/executable/daemon will place every spawned child in the
same core-sched group.
Cookie Transferral
~~~~~~~~~~~~~~~~~~
Transferring a cookie between the current and other tasks is possible using
PR_SCHED_CORE_SHARE_FROM and PR_SCHED_CORE_SHARE_TO to inherit a cookie from a
specified task or a share a cookie with a task. In combination this allows a
simple helper program to pull a cookie from a task in an existing core
scheduling group and share it with already running tasks.
Design/Implementation
---------------------
Each task that is tagged is assigned a cookie internally in the kernel. As
mentioned in `Usage`_, tasks with the same cookie value are assumed to trust
each other and share a core.
The basic idea is that, every schedule event tries to select tasks for all the
siblings of a core such that all the selected tasks running on a core are
trusted (same cookie) at any point in time. Kernel threads are assumed trusted.
The idle task is considered special, as it trusts everything and everything
trusts it.
During a schedule() event on any sibling of a core, the highest priority task on
the sibling's core is picked and assigned to the sibling calling schedule(), if
the sibling has the task enqueued. For rest of the siblings in the core,
highest priority task with the same cookie is selected if there is one runnable
in their individual run queues. If a task with same cookie is not available,
the idle task is selected. Idle task is globally trusted.
Once a task has been selected for all the siblings in the core, an IPI is sent to
siblings for whom a new task was selected. Siblings on receiving the IPI will
switch to the new task immediately. If an idle task is selected for a sibling,
then the sibling is considered to be in a `forced idle` state. I.e., it may
have tasks on its on runqueue to run, however it will still have to run idle.
More on this in the next section.
Forced-idling of hyperthreads
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The scheduler tries its best to find tasks that trust each other such that all
tasks selected to be scheduled are of the highest priority in a core. However,
it is possible that some runqueues had tasks that were incompatible with the
highest priority ones in the core. Favoring security over fairness, one or more
siblings could be forced to select a lower priority task if the highest
priority task is not trusted with respect to the core wide highest priority
task. If a sibling does not have a trusted task to run, it will be forced idle
by the scheduler (idle thread is scheduled to run).
When the highest priority task is selected to run, a reschedule-IPI is sent to
the sibling to force it into idle. This results in 4 cases which need to be
considered depending on whether a VM or a regular usermode process was running
on either HT::
HT1 (attack) HT2 (victim)
A idle -> user space user space -> idle
B idle -> user space guest -> idle
C idle -> guest user space -> idle
D idle -> guest guest -> idle
Note that for better performance, we do not wait for the destination CPU
(victim) to enter idle mode. This is because the sending of the IPI would bring
the destination CPU immediately into kernel mode from user space, or VMEXIT
in the case of guests. At best, this would only leak some scheduler metadata
which may not be worth protecting. It is also possible that the IPI is received
too late on some architectures, but this has not been observed in the case of
x86.
Trust model
~~~~~~~~~~~
Core scheduling maintains trust relationships amongst groups of tasks by
assigning them a tag that is the same cookie value.
When a system with core scheduling boots, all tasks are considered to trust
each other. This is because the core scheduler does not have information about
trust relationships until userspace uses the above mentioned interfaces, to
communicate them. In other words, all tasks have a default cookie value of 0.
and are considered system-wide trusted. The forced-idling of siblings running
cookie-0 tasks is also avoided.
Once userspace uses the above mentioned interfaces to group sets of tasks, tasks
within such groups are considered to trust each other, but do not trust those
outside. Tasks outside the group also don't trust tasks within.
Limitations of core-scheduling
------------------------------
Core scheduling tries to guarantee that only trusted tasks run concurrently on a
core. But there could be small window of time during which untrusted tasks run
concurrently or kernel could be running concurrently with a task not trusted by
kernel.
IPI processing delays
~~~~~~~~~~~~~~~~~~~~~
Core scheduling selects only trusted tasks to run together. IPI is used to notify
the siblings to switch to the new task. But there could be hardware delays in
receiving of the IPI on some arch (on x86, this has not been observed). This may
cause an attacker task to start running on a CPU before its siblings receive the
IPI. Even though cache is flushed on entry to user mode, victim tasks on siblings
may populate data in the cache and micro architectural buffers after the attacker
starts to run and this is a possibility for data leak.
Open cross-HT issues that core scheduling does not solve
--------------------------------------------------------
1. For MDS
~~~~~~~~~~
Core scheduling cannot protect against MDS attacks between the siblings
running in user mode and the others running in kernel mode. Even though all
siblings run tasks which trust each other, when the kernel is executing
code on behalf of a task, it cannot trust the code running in the
sibling. Such attacks are possible for any combination of sibling CPU modes
(host or guest mode).
2. For L1TF
~~~~~~~~~~~
Core scheduling cannot protect against an L1TF guest attacker exploiting a
guest or host victim. This is because the guest attacker can craft invalid
PTEs which are not inverted due to a vulnerable guest kernel. The only
solution is to disable EPT (Extended Page Tables).
For both MDS and L1TF, if the guest vCPU is configured to not trust each
other (by tagging separately), then the guest to guest attacks would go away.
Or it could be a system admin policy which considers guest to guest attacks as
a guest problem.
Another approach to resolve these would be to make every untrusted task on the
system to not trust every other untrusted task. While this could reduce
parallelism of the untrusted tasks, it would still solve the above issues while
allowing system processes (trusted tasks) to share a core.
3. Protecting the kernel (IRQ, syscall, VMEXIT)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unfortunately, core scheduling does not protect kernel contexts running on
sibling hyperthreads from one another. Prototypes of mitigations have been posted
to LKML to solve this, but it is debatable whether such windows are practically
exploitable, and whether the performance overhead of the prototypes are worth
it (not to mention, the added code complexity).
Other Use cases
---------------
The main use case for Core scheduling is mitigating the cross-HT vulnerabilities
with SMT enabled. There are other use cases where this feature could be used:
- Isolating tasks that needs a whole core: Examples include realtime tasks, tasks
that uses SIMD instructions etc.
- Gang scheduling: Requirements for a group of tasks that needs to be scheduled
together could also be realized using core scheduling. One example is vCPUs of
a VM.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Core Scheduling 개요
1-13Core scheduling은 사용자 공간이 같은 core를 공유할 task group을 정의하게 합니다. 서로 신뢰하지 않는 group을 분리하는 보안 용도와 공유 hardware resource 요구가 다른 workload를 같은 core에 배치하거나 반대로 분리하는 성능 용도가 있습니다. 이 문서는 보안 용도만 설명합니다.
보안 사용 사례와 성능
14-31cross-HT 공격은 공격자와 피해자가 같은 core의 서로 다른 Hyper-Thread에서 실행될 때 발생하며 MDS와 L1TF가 예입니다. 완전한 완화는 Hyper-Threading(HT)을 끄는 것입니다.
Core scheduling은 일부 cross-HT 공격을 완화하는 scheduler 기능입니다. 사용자가 지정한 신뢰 group의 task만 core를 공유하도록 하여 HT를 더 안전하게 켤 수 있습니다. core 공유가 늘어 성능이 좋아질 수도 있지만 항상 보장되지는 않습니다.
이론적으로는 HT를 끈 경우 이상 성능을 목표로 하지만 한 core의 둘 이상 CPU에서 scheduling 결정을 동기화하는 비용 때문에, 특히 시스템 부하가 낮을 때 더 느릴 수 있습니다. `total_threads <= N_CPUS/2`이면 이 overhead로 SMT를 끈 경우보다 성능이 낮을 수 있습니다. `N_CPUS`는 전체 CPU 수이며 실제 workload 성능을 항상 측정해야 합니다.
CONFIG_SCHED_CORE와 PR_SCHED_CORE API
32-76Core scheduling은 `CONFIG_SCHED_CORE`로 활성화합니다. 사용자 공간은 같은 core에서 함께 schedule할 task group을 정의하며 scheduler는 system scheduling 요구를 최대한 충족하면서 서로 다른 group의 task가 한 core에서 동시에 실행되지 않게 합니다.
`PR_SCHED_CORE` prctl interface는 core scheduling group 생성과 task의 가입·제거를 지원합니다.
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5);
| 인자 | 값과 의미 |
|---|---|
| option | `PR_SCHED_CORE` |
| arg2 | `PR_SCHED_CORE_GET`은 `pid`의 core_sched cookie 조회, `PR_SCHED_CORE_CREATE`는 새 고유 cookie 생성, `PR_SCHED_CORE_SHARE_TO`는 cookie를 `pid`에 push, `PR_SCHED_CORE_SHARE_FROM`은 `pid`에서 cookie를 pull합니다. |
| arg3 | operation을 적용할 task의 `pid`입니다. |
| arg4 | `PR_SCHED_CORE_SCOPE_` 접두사 macro 중 하나인 `pid_type`입니다. 예를 들어 `PR_SCHED_CORE_SCOPE_THREAD_GROUP`이면 `pid`의 task group 전체에 명령을 적용합니다. |
| arg5 | `PR_SCHED_CORE_GET`이 반환한 cookie를 저장할 unsigned long long 사용자 공간 pointer입니다. 다른 명령에서는 0이어야 합니다. |
다른 process에 cookie를 push하거나 그 process에서 pull하려면 대상 process에 대한 `PTRACE_MODE_READ_REALCREDS` ptrace access mode가 필요합니다.
Task 계층과 cookie 전달
77-92cookie와 core를 공유하는 thread/process 계층을 만드는 가장 단순한 방법은 core-sched cookie가 fork/clone과 exec를 거쳐 상속된다는 점을 이용하는 것입니다. 최초 script, executable 또는 daemon에 cookie를 설정하면 모든 child가 같은 core-sched group에 들어갑니다.
`PR_SCHED_CORE_SHARE_FROM`과 `PR_SCHED_CORE_SHARE_TO`로 현재 task와 다른 task 사이에 cookie를 전달할 수 있습니다. helper 프로그램은 기존 core scheduling group의 task에서 cookie를 pull한 뒤 이미 실행 중인 task에 공유할 수 있습니다.
설계와 scheduling 결정
93-118tag된 각 task에는 커널 내부 cookie가 할당됩니다. cookie 값이 같은 task는 서로 신뢰하며 core를 공유한다고 가정합니다.
기본 원리는 모든 schedule event에서 core의 sibling 전체에 대해 같은 시점에 실행할 선택 task가 모두 신뢰 관계, 즉 같은 cookie를 갖도록 하는 것입니다. kernel thread는 신뢰한다고 가정합니다. idle task는 모든 것을 신뢰하고 모든 것이 idle task를 신뢰하는 특별한 task입니다.
한 sibling에서 `schedule()` event가 발생하면 core 전체에서 우선순위가 가장 높은 task를 고르고, 호출 sibling의 runqueue에 그 task가 있으면 그 sibling에 배정합니다. 나머지 sibling에서는 각 runqueue에서 같은 cookie를 가진 실행 가능 task 중 우선순위가 가장 높은 것을 선택합니다. 같은 cookie task가 없으면 전역적으로 신뢰받는 idle task를 선택합니다.
모든 sibling의 task를 선택한 뒤 새 task가 선택된 sibling에 IPI를 보냅니다. IPI를 받은 sibling은 즉시 새 task로 전환합니다. idle task가 선택된 sibling은 runqueue에 실행할 task가 있어도 idle을 실행해야 하는 `forced idle` 상태입니다.
Hyper-Thread forced idle
119-148scheduler는 core에서 가장 높은 우선순위를 유지하면서 서로 신뢰하는 task를 찾으려 합니다. 그러나 어떤 runqueue의 최고 우선순위 task가 core 전체 최고 우선순위 task와 호환되지 않을 수 있습니다. 공정성보다 보안을 우선하므로 sibling은 더 낮은 우선순위의 신뢰 task를 선택하거나 신뢰 task가 없으면 idle thread를 실행하도록 강제됩니다.
최고 우선순위 task가 선택되면 sibling을 idle로 강제하기 위해 reschedule IPI를 보냅니다. 두 HT에서 VM 또는 일반 사용자 process가 실행 중인지에 따라 다음 네 경우가 있습니다.
원문의 HT1 공격자와 HT2 피해자 ASCII 표를 전환 상태 표로 다시 그렸습니다.
성능을 위해 destination CPU가 idle mode에 들어갈 때까지 기다리지는 않습니다. IPI가 user space의 destination CPU를 즉시 kernel mode로 들여보내며 guest라면 VMEXIT를 일으키기 때문입니다. 최선의 공격 결과도 보호 가치가 낮을 수 있는 scheduler metadata 일부 유출 정도입니다. 일부 architecture에서는 IPI가 너무 늦게 도착할 가능성도 있으나 x86에서는 관찰되지 않았습니다.
Cookie 신뢰 모델
149-163Core scheduling은 task group에 같은 cookie tag를 할당하여 신뢰 관계를 유지합니다. 부팅 직후에는 사용자 공간이 interface로 신뢰 관계를 전달하지 않았으므로 모든 task의 기본 cookie가 0이고 시스템 전체에서 서로 신뢰한다고 봅니다. cookie 0 task를 실행하는 sibling은 forced idle도 피합니다.
사용자 공간이 task 집합을 group으로 묶으면 group 안의 task는 서로 신뢰하지만 바깥 task를 신뢰하지 않습니다. group 바깥 task도 group 안의 task를 신뢰하지 않습니다.
Core scheduling 한계와 IPI 지연
164-180Core scheduling은 신뢰하는 task만 한 core에서 동시에 실행되도록 보장하려 하지만, 짧은 시간 동안 신뢰하지 않는 task가 동시에 실행되거나 커널이 커널이 신뢰하지 않는 task와 동시에 실행될 수 있습니다.
scheduler는 sibling에게 새 task로 전환하라고 IPI로 알립니다. 일부 architecture의 hardware IPI 수신 지연 때문에 sibling이 IPI를 받기 전에 공격자 task가 먼저 실행될 수 있습니다. x86에서는 관찰되지 않았습니다.
user mode 진입 시 cache를 flush해도 공격자 실행 뒤 sibling의 피해자 task가 cache와 microarchitectural buffer에 데이터를 채울 수 있어 데이터 유출 가능성이 남습니다.
해결하지 못하는 MDS와 L1TF 문제
181-208MDS의 경우 core scheduling은 user mode에서 실행되는 sibling과 kernel mode에서 실행되는 sibling 사이 공격을 막을 수 없습니다. 모든 sibling task가 서로 신뢰하더라도 커널이 task를 대신해 코드를 실행할 때 sibling에서 실행되는 코드를 신뢰할 수 없습니다. host와 guest mode의 어떤 조합에서도 가능합니다.
L1TF의 경우 core scheduling은 guest 공격자가 guest 또는 host 피해자를 공격하는 것을 막을 수 없습니다. 취약한 guest kernel 때문에 invert되지 않은 invalid PTE를 guest 공격자가 만들 수 있기 때문입니다. 유일한 해결책은 EPT(Extended Page Tables)를 끄는 것입니다.
MDS와 L1TF 모두 guest vCPU를 서로 신뢰하지 않도록 별도 tag하면 guest-to-guest 공격은 사라집니다. 또는 관리 정책에서 guest-to-guest 공격을 guest가 해결할 문제로 간주할 수 있습니다.
다른 방법은 시스템의 모든 신뢰하지 않는 task가 서로를 신뢰하지 않게 하는 것입니다. 신뢰하지 않는 task의 병렬성은 줄지만 위 문제를 해결하면서 신뢰하는 system process는 core를 공유할 수 있습니다.
Kernel context 보호와 다른 사용 사례
209-226Core scheduling은 sibling hyperthread에서 실행되는 IRQ, syscall, VMEXIT 같은 kernel context를 서로 보호하지 못합니다. 이를 해결하는 prototype 완화가 LKML에 게시됐지만 이런 짧은 구간이 실질적으로 악용 가능한지, 성능 비용과 추가 코드 복잡성이 가치가 있는지는 논쟁 중입니다.
주요 용도는 SMT를 켠 상태에서 cross-HT 취약점을 완화하는 것입니다. 그 밖에도 core 전체가 필요한 realtime task나 SIMD instruction 사용 task를 격리하거나, VM의 vCPU처럼 task group을 함께 schedule해야 하는 gang scheduling에 사용할 수 있습니다.
보안 모델과 prctl API
core-scheduling.rst:1-92HT를 유지하면서 일부 cross-HT 공격을 줄이는 목적, 성능 비용, `PR_SCHED_CORE_*` cookie group 구성을 설명합니다.