요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
======================
Kernel Self-Protection
======================
Kernel self-protection is the design and implementation of systems and
structures within the Linux kernel to protect against security flaws in
the kernel itself. This covers a wide range of issues, including removing
entire classes of bugs, blocking security flaw exploitation methods,
and actively detecting attack attempts. Not all topics are explored in
this document, but it should serve as a reasonable starting point and
answer any frequently asked questions. (Patches welcome, of course!)
In the worst-case scenario, we assume an unprivileged local attacker
has arbitrary read and write access to the kernel's memory. In many
cases, bugs being exploited will not provide this level of access,
but with systems in place that defend against the worst case we'll
cover the more limited cases as well. A higher bar, and one that should
still be kept in mind, is protecting the kernel against a _privileged_
local attacker, since the root user has access to a vastly increased
attack surface. (Especially when they have the ability to load arbitrary
kernel modules.)
The goals for successful self-protection systems would be that they
are effective, on by default, require no opt-in by developers, have no
performance impact, do not impede kernel debugging, and have tests. It
is uncommon that all these goals can be met, but it is worth explicitly
mentioning them, since these aspects need to be explored, dealt with,
and/or accepted.
Attack Surface Reduction
========================
The most fundamental defense against security exploits is to reduce the
areas of the kernel that can be used to redirect execution. This ranges
from limiting the exposed APIs available to userspace, making in-kernel
APIs hard to use incorrectly, minimizing the areas of writable kernel
memory, etc.
Strict kernel memory permissions
--------------------------------
When all of kernel memory is writable, it becomes trivial for attacks
to redirect execution flow. To reduce the availability of these targets
the kernel needs to protect its memory with a tight set of permissions.
Executable code and read-only data must not be writable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Any areas of the kernel with executable memory must not be writable.
While this obviously includes the kernel text itself, we must consider
all additional places too: kernel modules, JIT memory, etc. (There are
temporary exceptions to this rule to support things like instruction
alternatives, breakpoints, kprobes, etc. If these must exist in a
kernel, they are implemented in a way where the memory is temporarily
made writable during the update, and then returned to the original
permissions.)
In support of this are ``CONFIG_STRICT_KERNEL_RWX`` and
``CONFIG_STRICT_MODULE_RWX``, which seek to make sure that code is not
writable, data is not executable, and read-only data is neither writable
nor executable.
Most architectures have these options on by default and not user selectable.
For some architectures like arm that wish to have these be selectable,
the architecture Kconfig can select ARCH_OPTIONAL_KERNEL_RWX to enable
a Kconfig prompt. ``CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT`` determines
the default setting when ARCH_OPTIONAL_KERNEL_RWX is enabled.
Function pointers and sensitive variables must not be writable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Vast areas of kernel memory contain function pointers that are looked
up by the kernel and used to continue execution (e.g. descriptor/vector
tables, file/network/etc operation structures, etc). The number of these
variables must be reduced to an absolute minimum.
Many such variables can be made read-only by setting them "const"
so that they live in the .rodata section instead of the .data section
of the kernel, gaining the protection of the kernel's strict memory
permissions as described above.
For variables that are initialized once at ``__init`` time, these can
be marked with the ``__ro_after_init`` attribute.
What remains are variables that are updated rarely (e.g. GDT). These
will need another infrastructure (similar to the temporary exceptions
made to kernel code mentioned above) that allow them to spend the rest
of their lifetime read-only. (For example, when being updated, only the
CPU thread performing the update would be given uninterruptible write
access to the memory.)
Segregation of kernel memory from userspace memory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The kernel must never execute userspace memory. The kernel must also never
access userspace memory without explicit expectation to do so. These
rules can be enforced either by support of hardware-based restrictions
(x86's SMEP/SMAP, ARM's PXN/PAN) or via emulation (ARM's Memory Domains).
By blocking userspace memory in this way, execution and data parsing
cannot be passed to trivially-controlled userspace memory, forcing
attacks to operate entirely in kernel memory.
Reduced access to syscalls
--------------------------
One trivial way to eliminate many syscalls for 64-bit systems is building
without ``CONFIG_COMPAT``. However, this is rarely a feasible scenario.
The "seccomp" system provides an opt-in feature made available to
userspace, which provides a way to reduce the number of kernel entry
points available to a running process. This limits the breadth of kernel
code that can be reached, possibly reducing the availability of a given
bug to an attack.
An area of improvement would be creating viable ways to keep access to
things like compat, user namespaces, BPF creation, and perf limited only
to trusted processes. This would keep the scope of kernel entry points
restricted to the more regular set of normally available to unprivileged
userspace.
Restricting access to kernel modules
------------------------------------
The kernel should never allow an unprivileged user the ability to
load specific kernel modules, since that would provide a facility to
unexpectedly extend the available attack surface. (The on-demand loading
of modules via their predefined subsystems, e.g. MODULE_ALIAS_*, is
considered "expected" here, though additional consideration should be
given even to these.) For example, loading a filesystem module via an
unprivileged socket API is nonsense: only the root or physically local
user should trigger filesystem module loading. (And even this can be up
for debate in some scenarios.)
To protect against even privileged users, systems may need to either
disable module loading entirely (e.g. monolithic kernel builds or
modules_disabled sysctl), or provide signed modules (e.g.
``CONFIG_MODULE_SIG_FORCE``, or dm-crypt with LoadPin), to keep from having
root load arbitrary kernel code via the module loader interface.
Memory integrity
================
There are many memory structures in the kernel that are regularly abused
to gain execution control during an attack, By far the most commonly
understood is that of the stack buffer overflow in which the return
address stored on the stack is overwritten. Many other examples of this
kind of attack exist, and protections exist to defend against them.
Stack buffer overflow
---------------------
The classic stack buffer overflow involves writing past the expected end
of a variable stored on the stack, ultimately writing a controlled value
to the stack frame's stored return address. The most widely used defense
is the presence of a stack canary between the stack variables and the
return address (``CONFIG_STACKPROTECTOR``), which is verified just before
the function returns. Other defenses include things like shadow stacks.
Stack depth overflow
--------------------
A less well understood attack is using a bug that triggers the
kernel to consume stack memory with deep function calls or large stack
allocations. With this attack it is possible to write beyond the end of
the kernel's preallocated stack space and into sensitive structures. Two
important changes need to be made for better protections: moving the
sensitive thread_info structure elsewhere, and adding a faulting memory
hole at the bottom of the stack to catch these overflows.
Heap memory integrity
---------------------
The structures used to track heap free lists can be sanity-checked during
allocation and freeing to make sure they aren't being used to manipulate
other memory areas.
Counter integrity
-----------------
Many places in the kernel use atomic counters to track object references
or perform similar lifetime management. When these counters can be made
to wrap (over or under) this traditionally exposes a use-after-free
flaw. By trapping atomic wrapping, this class of bug vanishes.
Size calculation overflow detection
-----------------------------------
Similar to counter overflow, integer overflows (usually size calculations)
need to be detected at runtime to kill this class of bug, which
traditionally leads to being able to write past the end of kernel buffers.
Probabilistic defenses
======================
While many protections can be considered deterministic (e.g. read-only
memory cannot be written to), some protections provide only statistical
defense, in that an attack must gather enough information about a
running system to overcome the defense. While not perfect, these do
provide meaningful defenses.
Canaries, blinding, and other secrets
-------------------------------------
It should be noted that things like the stack canary discussed earlier
are technically statistical defenses, since they rely on a secret value,
and such values may become discoverable through an information exposure
flaw.
Blinding literal values for things like JITs, where the executable
contents may be partially under the control of userspace, need a similar
secret value.
It is critical that the secret values used must be separate (e.g.
different canary per stack) and high entropy (e.g. is the RNG actually
working?) in order to maximize their success.
Kernel Address Space Layout Randomization (KASLR)
-------------------------------------------------
Since the location of kernel memory is almost always instrumental in
mounting a successful attack, making the location non-deterministic
raises the difficulty of an exploit. (Note that this in turn makes
the value of information exposures higher, since they may be used to
discover desired memory locations.)
Text and module base
~~~~~~~~~~~~~~~~~~~~
By relocating the physical and virtual base address of the kernel at
boot-time (``CONFIG_RANDOMIZE_BASE``), attacks needing kernel code will be
frustrated. Additionally, offsetting the module loading base address
means that even systems that load the same set of modules in the same
order every boot will not share a common base address with the rest of
the kernel text.
Stack base
~~~~~~~~~~
If the base address of the kernel stack is not the same between processes,
or even not the same between syscalls, targets on or beyond the stack
become more difficult to locate.
Dynamic memory base
~~~~~~~~~~~~~~~~~~~
Much of the kernel's dynamic memory (e.g. kmalloc, vmalloc, etc) ends up
being relatively deterministic in layout due to the order of early-boot
initializations. If the base address of these areas is not the same
between boots, targeting them is frustrated, requiring an information
exposure specific to the region.
Structure layout
~~~~~~~~~~~~~~~~
By performing a per-build randomization of the layout of sensitive
structures, attacks must either be tuned to known kernel builds or expose
enough kernel memory to determine structure layouts before manipulating
them.
Preventing Information Exposures
================================
Since the locations of sensitive structures are the primary target for
attacks, it is important to defend against exposure of both kernel memory
addresses and kernel memory contents (since they may contain kernel
addresses or other sensitive things like canary values).
Kernel addresses
----------------
Printing kernel addresses to userspace leaks sensitive information about
the kernel memory layout. Care should be exercised when using any printk
specifier that prints the raw address, currently %px, %p[ad], (and %p[sSb]
in certain circumstances [*]). Any file written to using one of these
specifiers should be readable only by privileged processes.
Kernels 4.14 and older printed the raw address using %p. As of 4.15-rc1
addresses printed with the specifier %p are hashed before printing.
[*] If KALLSYMS is enabled and symbol lookup fails, the raw address is
printed. If KALLSYMS is not enabled the raw address is printed.
Unique identifiers
------------------
Kernel memory addresses must never be used as identifiers exposed to
userspace. Instead, use an atomic counter, an idr, or similar unique
identifier.
Memory initialization
---------------------
Memory copied to userspace must always be fully initialized. If not
explicitly memset(), this will require changes to the compiler to make
sure structure holes are cleared.
Memory poisoning
----------------
When releasing memory, it is best to poison the contents, to avoid reuse
attacks that rely on the old contents of memory. E.g., clear stack on a
syscall return (``CONFIG_KSTACK_ERASE``), wipe heap memory on a
free. This frustrates many uninitialized variable attacks, stack content
exposures, heap content exposures, and use-after-free attacks.
Destination tracking
--------------------
To help kill classes of bugs that result in kernel addresses being
written to userspace, the destination of writes needs to be tracked. If
the buffer is destined for userspace (e.g. seq_file backed ``/proc`` files),
it should automatically censor sensitive values.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
위협 모델과 성공 조건
1-30Kernel self-protection은 커널 자체의 보안 결함을 방어하기 위한 시스템과 구조를 설계·구현하는 작업이다. 전체 bug class 제거, exploit 기법 차단, 공격 시도 능동 탐지를 포함한다. 모든 주제를 다루지는 않지만 출발점과 자주 묻는 질문에 대한 답을 제공한다.
최악의 경우 권한 없는 local attacker가 kernel memory를 임의로 읽고 쓸 수 있다고 가정한다. 실제 bug는 더 제한된 접근만 줄 수 있지만 최악을 방어하면 제한된 사례도 포괄할 수 있다. 더 높은 목표는 root가 훨씬 넓은 공격 표면, 특히 임의 kernel module 적재 권한을 가지므로 privileged local attacker에게서도 커널을 보호하는 것이다.
성공적인 자기 보호는 효과적이고 기본 활성화되며 개발자의 opt-in이 필요 없고 성능 영향과 debugging 방해가 없으며 test를 갖춰야 한다. 모든 조건을 동시에 만족하기 어렵더라도 각 조건의 비용과 tradeoff를 명시적으로 검토하고 수용해야 한다.
보호 기능이 배포 가능한 기본 방어가 되기 위한 기준이다.
======================
Kernel Self-Protection
======================
Kernel self-protection is the design and implementation of systems and
structures within the Linux kernel to protect against security flaws in
the kernel itself. This covers a wide range of issues, including removing
entire classes of bugs, blocking security flaw exploitation methods,
and actively detecting attack attempts. Not all topics are explored in
this document, but it should serve as a reasonable starting point and
answer any frequently asked questions. (Patches welcome, of course!)
In the worst-case scenario, we assume an unprivileged local attacker
has arbitrary read and write access to the kernel's memory. In many
cases, bugs being exploited will not provide this level of access,
but with systems in place that defend against the worst case we'll
cover the more limited cases as well. A higher bar, and one that should
still be kept in mind, is protecting the kernel against a _privileged_
local attacker, since the root user has access to a vastly increased
attack surface. (Especially when they have the ability to load arbitrary
kernel modules.)
The goals for successful self-protection systems would be that they
are effective, on by default, require no opt-in by developers, have no
performance impact, do not impede kernel debugging, and have tests. It
is uncommon that all these goals can be met, but it is worth explicitly
mentioning them, since these aspects need to be explored, dealt with,
and/or accepted.
공격 표면 축소와 엄격한 메모리 권한
31-69가장 근본적인 방어는 공격자가 실행 흐름을 바꾸는 데 사용할 수 있는 커널 영역을 줄이는 것이다. 사용자 공간에 노출된 API를 제한하고, 커널 내부 API를 잘못 쓰기 어렵게 만들며, writable kernel memory를 최소화해야 한다.
kernel memory 전체가 writable이면 실행 흐름을 바꾸기 쉽다. executable memory는 kernel text뿐 아니라 module과 JIT memory를 포함해 writable이어서는 안 된다. instruction alternative, breakpoint, kprobe 같은 기능이 임시 쓰기를 요구하면 update 동안만 writable로 바꾸고 즉시 원래 권한으로 되돌려야 한다.
`CONFIG_STRICT_KERNEL_RWX`와 `CONFIG_STRICT_MODULE_RWX`는 code가 writable하지 않고 data가 executable하지 않으며 read-only data가 writable·executable하지 않게 한다. 대부분 architecture는 기본으로 켜고 사용자가 선택하지 못하게 한다. ARM처럼 선택 가능하게 하려면 Kconfig에서 `ARCH_OPTIONAL_KERNEL_RWX`를 선택하며 `CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT`가 기본값을 정한다.
memory의 목적에 따라 쓰기와 실행 권한을 분리한다.
Attack Surface Reduction
========================
The most fundamental defense against security exploits is to reduce the
areas of the kernel that can be used to redirect execution. This ranges
from limiting the exposed APIs available to userspace, making in-kernel
APIs hard to use incorrectly, minimizing the areas of writable kernel
memory, etc.
Strict kernel memory permissions
--------------------------------
When all of kernel memory is writable, it becomes trivial for attacks
to redirect execution flow. To reduce the availability of these targets
the kernel needs to protect its memory with a tight set of permissions.
Executable code and read-only data must not be writable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Any areas of the kernel with executable memory must not be writable.
While this obviously includes the kernel text itself, we must consider
all additional places too: kernel modules, JIT memory, etc. (There are
temporary exceptions to this rule to support things like instruction
alternatives, breakpoints, kprobes, etc. If these must exist in a
kernel, they are implemented in a way where the memory is temporarily
made writable during the update, and then returned to the original
permissions.)
In support of this are ``CONFIG_STRICT_KERNEL_RWX`` and
``CONFIG_STRICT_MODULE_RWX``, which seek to make sure that code is not
writable, data is not executable, and read-only data is neither writable
nor executable.
Most architectures have these options on by default and not user selectable.
For some architectures like arm that wish to have these be selectable,
the architecture Kconfig can select ARCH_OPTIONAL_KERNEL_RWX to enable
a Kconfig prompt. ``CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT`` determines
the default setting when ARCH_OPTIONAL_KERNEL_RWX is enabled.
Function pointer 보호와 사용자 memory 격리
70-103descriptor/vector table과 file·network operation 구조 등 넓은 kernel memory에는 다음 실행 위치로 쓰이는 function pointer가 있다. 이런 변수의 수를 최소화해야 한다. 가능한 변수는 `const`로 선언해 `.data` 대신 `.rodata`에 두고 strict memory permission의 보호를 받게 한다.
`__init` 시 한 번만 초기화하는 변수는 `__ro_after_init`로 표시할 수 있다. GDT처럼 드물게 갱신되는 변수에는 평생 대부분을 read-only로 유지하면서 갱신 CPU thread에만 중단 불가능한 write access를 잠깐 허용하는 별도 인프라가 필요하다.
커널은 사용자 공간 memory를 절대 실행해서는 안 되고, 명시적으로 예상한 경우 외에는 접근해서도 안 된다. x86 SMEP/SMAP, ARM PXN/PAN 같은 hardware restriction이나 ARM Memory Domains 같은 emulation으로 강제한다. 사용자 memory를 차단하면 공격자는 쉽게 제어할 수 있는 영역으로 실행이나 data parsing을 넘기지 못하고 kernel memory 안에서만 공격해야 한다.
초기화와 드문 갱신 시점을 제외하고 function pointer와 민감 값을 read-only로 유지한다.
Function pointers and sensitive variables must not be writable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Vast areas of kernel memory contain function pointers that are looked
up by the kernel and used to continue execution (e.g. descriptor/vector
tables, file/network/etc operation structures, etc). The number of these
variables must be reduced to an absolute minimum.
Many such variables can be made read-only by setting them "const"
so that they live in the .rodata section instead of the .data section
of the kernel, gaining the protection of the kernel's strict memory
permissions as described above.
For variables that are initialized once at ``__init`` time, these can
be marked with the ``__ro_after_init`` attribute.
What remains are variables that are updated rarely (e.g. GDT). These
will need another infrastructure (similar to the temporary exceptions
made to kernel code mentioned above) that allow them to spend the rest
of their lifetime read-only. (For example, when being updated, only the
CPU thread performing the update would be given uninterruptible write
access to the memory.)
Segregation of kernel memory from userspace memory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The kernel must never execute userspace memory. The kernel must also never
access userspace memory without explicit expectation to do so. These
rules can be enforced either by support of hardware-based restrictions
(x86's SMEP/SMAP, ARM's PXN/PAN) or via emulation (ARM's Memory Domains).
By blocking userspace memory in this way, execution and data parsing
cannot be passed to trivially-controlled userspace memory, forcing
attacks to operate entirely in kernel memory.
Syscall과 module 접근 제한
104-14064비트 시스템에서 `CONFIG_COMPAT` 없이 빌드하면 많은 syscall을 제거할 수 있지만 현실적으로 가능한 경우는 드물다. `seccomp`는 사용자 공간이 opt-in하여 실행 프로세스가 접근할 수 있는 kernel entry point 수를 줄이고, 특정 bug를 공격에 이용할 수 있는 코드 범위를 제한한다.
compat, user namespace, BPF 생성, perf 같은 기능을 신뢰된 프로세스만 사용하도록 제한하는 실용적인 방법이 더 필요하다. 그러면 권한 없는 사용자 공간에 노출되는 kernel entry point를 일반적인 최소 집합으로 유지할 수 있다.
권한 없는 사용자가 특정 kernel module을 직접 적재하게 해서는 안 된다. `MODULE_ALIAS_*`처럼 미리 정의된 subsystem의 on-demand loading은 예상된 것으로 보지만 추가 검토가 필요하다. 예를 들어 권한 없는 socket API로 filesystem module을 적재하는 것은 부적절하며 root나 물리적으로 local인 사용자만 유발해야 한다.
privileged user까지 방어하려면 monolithic kernel이나 `modules_disabled` sysctl로 module loading을 완전히 끄거나, `CONFIG_MODULE_SIG_FORCE` 또는 dm-crypt와 LoadPin 같은 방식으로 signed module만 허용해 root가 loader interface로 임의 kernel code를 넣지 못하게 해야 한다.
기능별로 신뢰 경계를 좁히는 수단을 정리한다.
Reduced access to syscalls
--------------------------
One trivial way to eliminate many syscalls for 64-bit systems is building
without ``CONFIG_COMPAT``. However, this is rarely a feasible scenario.
The "seccomp" system provides an opt-in feature made available to
userspace, which provides a way to reduce the number of kernel entry
points available to a running process. This limits the breadth of kernel
code that can be reached, possibly reducing the availability of a given
bug to an attack.
An area of improvement would be creating viable ways to keep access to
things like compat, user namespaces, BPF creation, and perf limited only
to trusted processes. This would keep the scope of kernel entry points
restricted to the more regular set of normally available to unprivileged
userspace.
Restricting access to kernel modules
------------------------------------
The kernel should never allow an unprivileged user the ability to
load specific kernel modules, since that would provide a facility to
unexpectedly extend the available attack surface. (The on-demand loading
of modules via their predefined subsystems, e.g. MODULE_ALIAS_*, is
considered "expected" here, though additional consideration should be
given even to these.) For example, loading a filesystem module via an
unprivileged socket API is nonsense: only the root or physically local
user should trigger filesystem module loading. (And even this can be up
for debate in some scenarios.)
To protect against even privileged users, systems may need to either
disable module loading entirely (e.g. monolithic kernel builds or
modules_disabled sysctl), or provide signed modules (e.g.
``CONFIG_MODULE_SIG_FORCE``, or dm-crypt with LoadPin), to keep from having
root load arbitrary kernel code via the module loader interface.
Memory 무결성
141-193공격자는 여러 kernel memory 구조를 악용해 실행 제어권을 얻는다. 대표적인 stack buffer overflow는 stack의 return address를 덮어쓰는 방식이며, `CONFIG_STACKPROTECTOR`가 stack 변수와 return address 사이의 canary를 함수 반환 직전에 검사한다. shadow stack도 방어 수단이다.
Stack depth overflow는 깊은 함수 호출이나 큰 stack allocation으로 미리 할당된 kernel stack 끝을 넘어 민감 구조에 쓰게 한다. 더 나은 방어를 위해 민감한 `thread_info`를 다른 곳으로 옮기고 stack 아래쪽에 fault를 내는 memory hole을 추가해야 한다.
Heap free list 추적 구조는 allocation과 free 시 sanity check하여 다른 memory를 조작하는 도구로 쓰이지 않게 할 수 있다. object reference나 수명 관리를 위한 atomic counter가 overflow 또는 underflow로 wrap되면 전통적으로 use-after-free가 생기므로 atomic wrapping을 trap하면 이 bug class를 없앨 수 있다.
Counter와 마찬가지로 주로 size calculation에서 생기는 integer overflow도 runtime에 탐지해야 한다. 이를 잡지 못하면 kernel buffer 끝을 넘어 쓸 수 있는 결함으로 이어진다.
손상되는 구조와 대응 기법을 연결한다.
Memory integrity
================
There are many memory structures in the kernel that are regularly abused
to gain execution control during an attack, By far the most commonly
understood is that of the stack buffer overflow in which the return
address stored on the stack is overwritten. Many other examples of this
kind of attack exist, and protections exist to defend against them.
Stack buffer overflow
---------------------
The classic stack buffer overflow involves writing past the expected end
of a variable stored on the stack, ultimately writing a controlled value
to the stack frame's stored return address. The most widely used defense
is the presence of a stack canary between the stack variables and the
return address (``CONFIG_STACKPROTECTOR``), which is verified just before
the function returns. Other defenses include things like shadow stacks.
Stack depth overflow
--------------------
A less well understood attack is using a bug that triggers the
kernel to consume stack memory with deep function calls or large stack
allocations. With this attack it is possible to write beyond the end of
the kernel's preallocated stack space and into sensitive structures. Two
important changes need to be made for better protections: moving the
sensitive thread_info structure elsewhere, and adding a faulting memory
hole at the bottom of the stack to catch these overflows.
Heap memory integrity
---------------------
The structures used to track heap free lists can be sanity-checked during
allocation and freeing to make sure they aren't being used to manipulate
other memory areas.
Counter integrity
-----------------
Many places in the kernel use atomic counters to track object references
or perform similar lifetime management. When these counters can be made
to wrap (over or under) this traditionally exposes a use-after-free
flaw. By trapping atomic wrapping, this class of bug vanishes.
Size calculation overflow detection
-----------------------------------
Similar to counter overflow, integer overflows (usually size calculations)
need to be detected at runtime to kill this class of bug, which
traditionally leads to being able to write past the end of kernel buffers.
확률적 방어와 비밀 값
194-219Read-only memory처럼 결정론적인 보호와 달리, 일부 방어는 공격자가 실행 중인 시스템 정보를 충분히 모아야 우회할 수 있게 하는 통계적 방어다. 완벽하지 않아도 실질적인 방어를 제공한다.
Stack canary는 secret value에 의존하므로 정보 노출 결함으로 값을 알아낼 수 있는 확률적 방어다. 사용자 공간이 executable content 일부를 제어할 수 있는 JIT의 literal value blinding도 비슷한 secret이 필요하다.
성공 가능성을 높이려면 secret value를 서로 분리해야 한다. 예를 들어 stack마다 다른 canary를 사용해야 하며, RNG가 실제로 올바르게 동작하는지 확인하여 높은 entropy를 확보해야 한다.
공격자가 추측하거나 노출로 회수하기 어려운 독립 비밀을 사용한다.
Probabilistic defenses
======================
While many protections can be considered deterministic (e.g. read-only
memory cannot be written to), some protections provide only statistical
defense, in that an attack must gather enough information about a
running system to overcome the defense. While not perfect, these do
provide meaningful defenses.
Canaries, blinding, and other secrets
-------------------------------------
It should be noted that things like the stack canary discussed earlier
are technically statistical defenses, since they rely on a secret value,
and such values may become discoverable through an information exposure
flaw.
Blinding literal values for things like JITs, where the executable
contents may be partially under the control of userspace, need a similar
secret value.
It is critical that the secret values used must be separate (e.g.
different canary per stack) and high entropy (e.g. is the RNG actually
working?) in order to maximize their success.
KASLR과 layout 무작위화
220-263성공적인 공격에는 kernel memory 위치가 거의 항상 중요하므로 위치를 비결정적으로 만들면 exploit 난도가 올라간다. 반대로 주소를 찾게 해 주는 정보 노출의 가치가 더 커진다는 점도 고려해야 한다.
`CONFIG_RANDOMIZE_BASE`는 부팅 때 kernel의 physical·virtual base address를 재배치해 kernel code 위치가 필요한 공격을 방해한다. module loading base도 offset하면 같은 module을 같은 순서로 적재하는 시스템도 kernel text와 공통 base를 공유하지 않는다.
프로세스마다 또는 syscall마다 kernel stack base를 다르게 하면 stack 안팎의 target 위치를 찾기 어렵다. `kmalloc`, `vmalloc` 같은 dynamic memory는 early boot 초기화 순서 때문에 비교적 결정적인 layout이 되므로 boot마다 base를 다르게 해 해당 영역에 특화된 정보 노출 없이는 target을 찾지 못하게 해야 한다.
민감한 structure layout을 build마다 무작위화하면 공격자는 알려진 kernel build에 맞추거나 조작 전에 충분한 kernel memory를 노출시켜 실제 layout을 알아내야 한다.
서로 다른 수명 주기에 주소와 field 배치를 무작위화한다.
Kernel Address Space Layout Randomization (KASLR)
-------------------------------------------------
Since the location of kernel memory is almost always instrumental in
mounting a successful attack, making the location non-deterministic
raises the difficulty of an exploit. (Note that this in turn makes
the value of information exposures higher, since they may be used to
discover desired memory locations.)
Text and module base
~~~~~~~~~~~~~~~~~~~~
By relocating the physical and virtual base address of the kernel at
boot-time (``CONFIG_RANDOMIZE_BASE``), attacks needing kernel code will be
frustrated. Additionally, offsetting the module loading base address
means that even systems that load the same set of modules in the same
order every boot will not share a common base address with the rest of
the kernel text.
Stack base
~~~~~~~~~~
If the base address of the kernel stack is not the same between processes,
or even not the same between syscalls, targets on or beyond the stack
become more difficult to locate.
Dynamic memory base
~~~~~~~~~~~~~~~~~~~
Much of the kernel's dynamic memory (e.g. kmalloc, vmalloc, etc) ends up
being relatively deterministic in layout due to the order of early-boot
initializations. If the base address of these areas is not the same
between boots, targeting them is frustrated, requiring an information
exposure specific to the region.
Structure layout
~~~~~~~~~~~~~~~~
By performing a per-build randomization of the layout of sensitive
structures, attacks must either be tuned to known kernel builds or expose
enough kernel memory to determine structure layouts before manipulating
them.
Kernel 주소와 식별자 노출 방지
264-293민감 구조의 위치가 주요 공격 target이므로 kernel memory address와 memory content 노출을 모두 막아야 한다. memory content에는 address뿐 아니라 canary 같은 민감 값도 들어 있을 수 있다.
사용자 공간에 kernel address를 출력하면 memory layout을 누출한다. raw address를 출력하는 `%px`, `%p[ad]`, 특정 조건의 `%p[sSb]`를 사용할 때 주의해야 하며, 이런 specifier로 쓴 파일은 privileged process만 읽을 수 있어야 한다. 4.14 이하 kernel의 `%p`는 raw address를 출력했지만 4.15-rc1부터는 출력 전에 hash한다.
KALLSYMS가 켜져 있어도 symbol lookup이 실패하면 raw address를 출력하며, KALLSYMS가 없을 때도 raw address가 나온다. Kernel memory address를 사용자 공간 식별자로 사용해서는 안 되고 atomic counter, `idr`, 그 밖의 고유 identifier를 사용해야 한다.
specifier와 symbol lookup 상태에 따라 raw address 노출 여부가 달라진다.
Preventing Information Exposures
================================
Since the locations of sensitive structures are the primary target for
attacks, it is important to defend against exposure of both kernel memory
addresses and kernel memory contents (since they may contain kernel
addresses or other sensitive things like canary values).
Kernel addresses
----------------
Printing kernel addresses to userspace leaks sensitive information about
the kernel memory layout. Care should be exercised when using any printk
specifier that prints the raw address, currently %px, %p[ad], (and %p[sSb]
in certain circumstances [*]). Any file written to using one of these
specifiers should be readable only by privileged processes.
Kernels 4.14 and older printed the raw address using %p. As of 4.15-rc1
addresses printed with the specifier %p are hashed before printing.
[*] If KALLSYMS is enabled and symbol lookup fails, the raw address is
printed. If KALLSYMS is not enabled the raw address is printed.
Unique identifiers
------------------
Kernel memory addresses must never be used as identifiers exposed to
userspace. Instead, use an atomic counter, an idr, or similar unique
identifier.
초기화, poisoning, destination 추적
294-316사용자 공간으로 복사하는 memory는 항상 완전히 초기화해야 한다. 명시적으로 `memset()`하지 않는다면 compiler가 structure hole까지 지우도록 바꿔야 한다.
memory를 해제할 때 내용을 poison하면 이전 내용을 재사용하는 공격을 막을 수 있다. syscall return 때 `CONFIG_KSTACK_ERASE`로 stack을 지우고 free할 때 heap memory를 wipe하면 uninitialized variable 공격, stack·heap content 노출, use-after-free 공격을 어렵게 한다.
Kernel address가 사용자 공간에 기록되는 bug class를 제거하려면 write destination을 추적해야 한다. `seq_file` 기반 `/proc` 파일처럼 buffer 목적지가 사용자 공간이라면 민감 값을 자동으로 검열해야 한다.
데이터 생성부터 해제까지 노출 가능한 잔여 값을 관리한다.
Memory initialization
---------------------
Memory copied to userspace must always be fully initialized. If not
explicitly memset(), this will require changes to the compiler to make
sure structure holes are cleared.
Memory poisoning
----------------
When releasing memory, it is best to poison the contents, to avoid reuse
attacks that rely on the old contents of memory. E.g., clear stack on a
syscall return (``CONFIG_KSTACK_ERASE``), wipe heap memory on a
free. This frustrates many uninitialized variable attacks, stack content
exposures, heap content exposures, and use-after-free attacks.
Destination tracking
--------------------
To help kill classes of bugs that result in kernel addresses being
written to userspace, the destination of writes needs to be tracked. If
the buffer is destined for userspace (e.g. seq_file backed ``/proc`` files),
it should automatically censor sensitive values.
요약·해설
self-protection.rst:1-316커널 공격 표면 축소, strict RWX, memory integrity, KASLR, 주소·메모리 정보 노출 방지 원칙을 설명합니다.