Part 1
요약·해설
보호하는 공격과 의도적으로 보호하지 않는 신뢰 경계를 먼저 구분하고, memory domain·system call·object permission이 이를 어떻게 구현하는지 연결합니다.
untrusted user thread
memory · object · syscall
build-time discovery
Part 2
접을 수 있는 영어 원문 전체
영어 원문 전체 펼치기
1BBA02FA9C0CE53BA6560CD55A69F78CA7A25781E885376CC2BFF2B664ECB912
.. _usermode_overview:
Overview
########
Threat Model
************
User mode threads are considered to be untrusted by Zephyr and are therefore
isolated from other user mode threads and from the kernel. A flawed or
malicious user mode thread cannot leak or modify the private data/resources
of another thread or the kernel, and cannot interfere with or
control another user mode thread or the kernel.
Example use-cases of Zephyr's user mode features:
- The kernel can protect against many unintentional programming errors which
could otherwise silently or spectacularly corrupt the system.
- The kernel can sandbox complex data parsers such as interpreters, network
protocols, and filesystems such that malicious third-party code or data
cannot compromise the kernel or other threads.
- The kernel can support the notion of multiple logical "applications", each
with their own group of threads and private data structures, which are
isolated from each other if one crashes or is otherwise compromised.
Design Goals
============
For threads running in a non-privileged CPU state (hereafter referred to as
'user mode') we aim to protect against the following:
- We prevent access to memory not specifically granted, or incorrect access to
memory that has an incompatible policy, such as attempting to write to a
read-only area.
- Access to thread stack buffers will be controlled with a policy which
partially depends on the underlying memory protection hardware.
- A user thread will by default have read/write access to its own stack
buffer.
- A user thread will never by default have access to user thread stacks
that are not members of the same memory domain.
- A user thread will never by default have access to thread stacks owned
by a supervisor thread, or thread stacks used to handle system call
privilege elevations, interrupts, or CPU exceptions.
- A user thread may have read/write access to the stacks of other user
threads in the same memory domain, depending on hardware.
- On MPU systems, threads may only access their own stack buffer.
- On MMU systems, threads may access any user thread stack in the same
memory domain. Portable code should not assume this.
- By default, program text and read-only data are accessible to all threads
on read-only basis, kernel-wide. This policy may be adjusted.
- User threads by default are not granted default access to any memory
except what is noted above.
- We prevent use of device drivers or kernel objects not specifically granted,
with the permission granularity on a per object or per driver instance
basis.
- We validate kernel or driver API calls with incorrect parameters that would
otherwise cause a crash or corruption of data structures private to the
kernel. This includes:
- Using the wrong kernel object type.
- Using parameters outside of proper bounds or with nonsensical values.
- Passing memory buffers that the calling thread does not have sufficient
access to read or write, depending on the semantics of the API.
- Use of kernel objects that are not in a proper initialization state.
- We ensure the detection and safe handling of user mode stack overflows.
- We prevent invoking system calls to functions excluded by the kernel
configuration.
- We prevent disabling of or tampering with kernel-defined and hardware-
enforced memory protections.
- We prevent re-entry from user to supervisor mode except through the kernel-
defined system calls and interrupt handlers.
- We prevent the introduction of new executable code by user mode threads,
except to the extent to which this is supported by kernel system calls.
We are specifically not protecting against the following attacks:
- The kernel itself, and any threads that are executing in supervisor mode,
are assumed to be trusted.
- The toolchain and any supplemental programs used by the build system are
assumed to be trusted.
- The kernel build is assumed to be trusted. There is considerable build-time
logic for creating the tables of valid kernel objects, defining system calls,
and configuring interrupts. The .elf binary files that are worked with
during this process are all assumed to be trusted code.
- We can't protect against mistakes made in memory domain configuration done in
kernel mode that exposes private kernel data structures to a user thread. RAM
for kernel objects should always be configured as supervisor-only.
- It is possible to make top-level declarations of user mode threads and
assign them permissions to kernel objects. In general, all C and header
files that are part of the kernel build producing zephyr.elf are assumed to
be trusted.
- We do not protect against denial of service attacks through thread CPU
starvation. Zephyr has no thread priority aging and a user thread of a
particular priority can starve all threads of lower priority, and also other
threads of the same priority if time-slicing is not enabled.
- There are build-time defined limits on how many threads can be active
simultaneously, after which creation of new user threads will fail.
- Stack overflows for threads running in supervisor mode may be caught,
but the integrity of the system cannot be guaranteed.
High-level Policy Details
*************************
Broadly speaking, we accomplish these thread-level memory protection goals
through the following mechanisms:
- Any user thread will only have access to a subset of memory:
typically its stack, program text, read-only data, and any partitions
configured in the :ref:`memory_domain` it belongs to. Access to any other RAM
must be done on the thread's behalf through system calls, or specifically
granted by a supervisor thread using the memory domain APIs. Newly created
threads inherit the memory domain configuration of the parent. Threads may
communicate with each other by having shared membership of the same memory
domains, or via kernel objects such as semaphores and pipes.
- User threads cannot directly access memory belonging to kernel objects.
Although pointers to kernel objects are used to reference them, actual
manipulation of kernel objects is done through system call interfaces. Device
drivers and threads stacks are also considered kernel objects. This ensures
that any data inside a kernel object that is private to the kernel cannot be
tampered with.
- User threads by default have no permission to access any kernel object or
driver other than their own thread object. Such access must be granted by
another thread that is either in supervisor mode or has permission on both
the receiving thread object and the kernel object being granted access to.
The creation of new threads has an option to automatically inherit
permissions of all kernel objects granted to the parent, except the parent
thread itself.
- For performance and footprint reasons Zephyr normally does little or no
parameter error checking for kernel object or device driver APIs. Access from
user mode through system calls involves an extra layer of handler functions,
which are expected to rigorously validate access permissions and type of
the object, check the validity of other parameters through bounds checking or
other means, and verify proper read/write access to any memory buffers
involved.
- Thread stacks are defined in such a way that exceeding the specified stack
space will generate a hardware fault. The way this is done specifically
varies per architecture.
Constraints
***********
All kernel objects, thread stacks, and device driver instances must be defined
at build time if they are to be used from user mode. Dynamic use-cases for
kernel objects will need to go through pre-defined pools of available objects.
There are some constraints if additional application binary data is loaded
for execution after the kernel starts:
- Loaded object code will not be able to define any kernel objects that will be
recognized by the kernel. This code will instead need to use APIs for
requesting kernel objects from pools.
- Similarly, since the loaded object code will not be part of the kernel build
process, this code will not be able to install interrupt handlers,
instantiate device drivers, or define system calls, regardless of what
mode it runs in.
- Loaded object code that does not come from a verified source should always
be entered with the CPU already in user mode.
Part 3
한국어 전문 번역
위협 모델
Zephyr는 user mode thread를 신뢰하지 않는 대상으로 간주하므로 다른 user mode thread와 kernel에서 격리합니다. 결함이 있거나 악의적인 user thread는 다른 thread 또는 kernel의 private data와 resource를 유출하거나 변경할 수 없고, 다른 user thread나 kernel을 방해하거나 제어할 수도 없습니다.
Zephyr user mode 기능의 대표적인 사용 사례는 다음과 같습니다.
- Kernel은 방치하면 system을 조용히 또는 심각하게 손상시킬 수 있는 여러 의도치 않은 programming error로부터 자신을 보호할 수 있습니다.
- Interpreter, network protocol, filesystem처럼 복잡한 data parser를 sandbox에 넣어 악의적인 third-party code나 data가 kernel 또는 다른 thread를 침해하지 못하게 할 수 있습니다.
- 각자 thread group과 private data structure를 가진 여러 logical application을 만들고, 하나가 crash하거나 침해돼도 서로 격리되게 할 수 있습니다.
설계 목표
비특권 CPU state, 즉 여기서 user mode라고 부르는 상태에서 실행되는 thread에 대해 다음 공격과 오류를 막는 것이 목표입니다.
- 명시적으로 허용하지 않은 memory 접근, 또는 read-only area에 write하는 것처럼 policy와 맞지 않는 접근을 차단합니다.
- Thread stack buffer 접근 policy는 기반 memory-protection hardware의 특성에 일부 의존합니다.
- User thread는 기본적으로 자기 stack buffer를 read/write할 수 있습니다.
- 같은 memory domain에 속하지 않은 다른 user thread stack에는 기본적으로 절대 접근할 수 없습니다.
- Supervisor thread의 stack, system-call privilege elevation용 stack, interrupt·CPU exception 처리용 stack에도 기본적으로 접근할 수 없습니다.
- Hardware에 따라 같은 memory domain의 다른 user thread stack은 read/write할 수도 있습니다.
- MPU system에서는 자기 stack buffer만 접근할 수 있습니다.
- MMU system에서는 같은 memory domain의 모든 user thread stack을 접근할 수 있습니다. Portable code는 이 동작을 가정하면 안 됩니다.
- 기본적으로 program text와 read-only data는 kernel 전체의 모든 thread가 read-only로 접근할 수 있습니다. 이 policy는 조정할 수 있습니다.
- 위에서 언급한 영역 외 memory에는 user thread의 기본 접근 권한을 주지 않습니다.
- Thread stack buffer 접근 policy는 기반 memory-protection hardware의 특성에 일부 의존합니다.
- 명시적으로 권한을 주지 않은 device driver나 kernel object의 사용을 막습니다. 권한 granularity는 object 또는 driver instance 단위입니다.
- 그대로 실행하면 crash나 kernel-private data structure 손상을 일으킬 잘못된 kernel·driver API 호출을 검증합니다.
- 잘못된 kernel object type 사용
- 허용 범위를 벗어나거나 의미 없는 parameter
- API 의미에 필요한 read/write 권한이 없는 memory buffer 전달
- 올바른 initialization state가 아닌 kernel object 사용
- User-mode stack overflow를 검출하고 안전하게 처리합니다.
- kernel configuration에서 제외된 function을 system call로 호출하지 못하게 합니다.
- Kernel이 정의하고 hardware가 강제하는 memory protection을 끄거나 변조하지 못하게 합니다.
- Kernel-defined system call과 interrupt handler 이외의 경로로 user mode에서 supervisor mode에 재진입하지 못하게 합니다.
- Kernel system call이 지원하는 범위를 제외하고 user thread가 새 executable code를 도입하지 못하게 합니다.
보호 범위에서 제외되는 공격
다음 항목은 이 위협 모델이 방어하지 않으며 신뢰 경계 밖에 있습니다.
- Kernel 자체와 supervisor mode에서 실행되는 모든 thread는 신뢰한다고 가정합니다.
- Toolchain과 build system이 사용하는 보조 program은 신뢰한다고 가정합니다.
- Kernel build를 신뢰합니다. Valid kernel-object table 생성, system-call 정의, interrupt configuration에는 많은 build-time logic이 있고 이 과정에서 다루는 모든 ELF binary는 trusted code로 봅니다.
- Kernel mode에서 memory domain을 잘못 구성해 private kernel data structure를 user thread에 노출하는 실수는 막을 수 없습니다. Kernel object용 RAM은 항상 supervisor-only로 구성해야 합니다.
- Top-level에서 user-mode thread를 선언하고 kernel-object permission을 배정할 수 있으므로,
zephyr.elf를 만드는 kernel build의 모든 C·header file은 신뢰한다고 가정합니다. - Thread CPU starvation을 이용한 denial-of-service 공격은 막지 않습니다. Zephyr에는 thread priority aging이 없으므로 특정 priority의 user thread가 낮은 priority thread를 모두 굶길 수 있고, time slicing이 꺼져 있으면 같은 priority의 다른 thread도 굶길 수 있습니다.
- 동시에 active일 수 있는 thread 수에는 build-time limit가 있으며 한도를 넘으면 새 user thread 생성이 실패합니다.
- Supervisor-mode thread의 stack overflow를 검출할 수는 있어도 system integrity를 보장할 수는 없습니다.
상위 수준 Policy 세부사항
Thread-level memory protection 목표는 대체로 다음 mechanism으로 달성합니다.
- User thread는 memory 일부만 접근합니다. 일반적으로 자기 stack, program text, read-only data, 그리고 자신이 속한 memory domain에 구성된 partition입니다. 다른 RAM 접근은 thread를 대신해 system call이 수행하거나 supervisor thread가 memory-domain API로 명시적으로 허용해야 합니다. 새 thread는 parent의 memory-domain configuration을 상속합니다. Thread끼리는 같은 memory domain을 공유하거나 semaphore·pipe 같은 kernel object를 통해 통신할 수 있습니다.
- User thread는 kernel object에 속한 memory에 직접 접근할 수 없습니다. Kernel-object pointer는 참조에 쓰이지만 실제 조작은 system-call interface가 수행합니다. Device driver와 thread stack도 kernel object이므로 그 내부의 kernel-private data를 변조할 수 없습니다.
- User thread는 기본적으로 자기 thread object 외에는 어떤 kernel object나 driver에도 permission이 없습니다. Supervisor-mode thread, 또는 받는 thread object와 부여할 kernel object 양쪽에 permission이 있는 thread가 권한을 부여해야 합니다. 새 thread를 만들 때 parent가 가진 모든 kernel-object permission을 자동 상속하는 option이 있지만 parent thread object 자체는 제외됩니다.
- Performance와 footprint 때문에 Zephyr의 supervisor-side kernel-object·device-driver API는 보통 parameter error check를 거의 하지 않습니다. User mode에서 system call을 거치면 handler layer가 추가되며, 이 handler는 object access permission과 type을 엄격히 검증하고 bounds check 등으로 다른 parameter를 확인하며 관련 memory buffer의 read/write access도 검증해야 합니다.
- Thread stack은 지정한 stack space를 넘으면 hardware fault가 나도록 정의합니다. 구체적인 구현은 architecture마다 다릅니다.
제약사항
User mode에서 사용할 모든 kernel object, thread stack, device-driver instance는 build time에 정의해야 합니다. Kernel object를 동적으로 써야 하는 경우에는 미리 정의된 available-object pool을 거쳐야 합니다.
Kernel 시작 뒤 실행할 추가 application binary data를 load하는 경우 다음 제약이 있습니다.
- Load된 object code는 kernel이 인식하는 새 kernel object를 정의할 수 없습니다. 대신 pool에서 kernel object를 요청하는 API를 사용해야 합니다.
- Load된 code는 kernel build process에 포함되지 않았으므로 어떤 mode에서 실행되더라도 interrupt handler를 설치하거나 device driver를 instantiate하거나 system call을 정의할 수 없습니다.
- 검증된 source에서 오지 않은 loadable object code는 CPU가 이미 user mode인 상태에서 진입해야 합니다.
Source
출처
영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.