요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
==================================
Introduction of non-executable mfd
==================================
:Author:
Daniel Verkamp <dverkamp@chromium.org>
Jeff Xu <jeffxu@chromium.org>
:Contributor:
Aleksa Sarai <cyphar@cyphar.com>
Since Linux introduced the memfd feature, memfds have always had their
execute bit set, and the memfd_create() syscall doesn't allow setting
it differently.
However, in a secure-by-default system, such as ChromeOS, (where all
executables should come from the rootfs, which is protected by verified
boot), this executable nature of memfd opens a door for NoExec bypass
and enables “confused deputy attack”. E.g, in VRP bug [1]: cros_vm
process created a memfd to share the content with an external process,
however the memfd is overwritten and used for executing arbitrary code
and root escalation. [2] lists more VRP of this kind.
On the other hand, executable memfd has its legit use: runc uses memfd’s
seal and executable feature to copy the contents of the binary then
execute them. For such a system, we need a solution to differentiate runc's
use of executable memfds and an attacker's [3].
To address those above:
- Let memfd_create() set X bit at creation time.
- Let memfd be sealed for modifying X bit when NX is set.
- Add a new pid namespace sysctl: vm.memfd_noexec to help applications in
migrating and enforcing non-executable MFD.
User API
========
``int memfd_create(const char *name, unsigned int flags)``
``MFD_NOEXEC_SEAL``
When MFD_NOEXEC_SEAL bit is set in the ``flags``, memfd is created
with NX. F_SEAL_EXEC is set and the memfd can't be modified to
add X later. MFD_ALLOW_SEALING is also implied.
This is the most common case for the application to use memfd.
``MFD_EXEC``
When MFD_EXEC bit is set in the ``flags``, memfd is created with X.
Note:
``MFD_NOEXEC_SEAL`` implies ``MFD_ALLOW_SEALING``. In case that
an app doesn't want sealing, it can add F_SEAL_SEAL after creation.
Sysctl:
========
``pid namespaced sysctl vm.memfd_noexec``
The new pid namespaced sysctl vm.memfd_noexec has 3 values:
- 0: MEMFD_NOEXEC_SCOPE_EXEC
memfd_create() without MFD_EXEC nor MFD_NOEXEC_SEAL acts like
MFD_EXEC was set.
- 1: MEMFD_NOEXEC_SCOPE_NOEXEC_SEAL
memfd_create() without MFD_EXEC nor MFD_NOEXEC_SEAL acts like
MFD_NOEXEC_SEAL was set.
- 2: MEMFD_NOEXEC_SCOPE_NOEXEC_ENFORCED
memfd_create() without MFD_NOEXEC_SEAL will be rejected.
The sysctl allows finer control of memfd_create for old software that
doesn't set the executable bit; for example, a container with
vm.memfd_noexec=1 means the old software will create non-executable memfd
by default while new software can create executable memfd by setting
MFD_EXEC.
The value of vm.memfd_noexec is passed to child namespace at creation
time. In addition, the setting is hierarchical, i.e. during memfd_create,
we will search from current ns to root ns and use the most restrictive
setting.
[1] https://crbug.com/1305267
[2] https://bugs.chromium.org/p/chromium/issues/list?q=type%3Dbug-security%20memfd%20escalation&can=1
[3] https://lwn.net/Articles/781013/
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
배경과 보안 목적
1-35Linux에 memfd가 도입된 뒤 memfd에는 항상 실행 비트가 설정되었고, `memfd_create()` 시스템 호출에는 이를 다르게 지정하는 방법이 없었습니다. 그러나 검증 부팅으로 보호되는 rootfs에서만 실행 파일이 와야 하는 ChromeOS 같은 기본 보안 시스템에서는 이 성질이 NoExec 우회 통로가 될 수 있습니다.
문서의 VRP 사례에서는 `cros_vm` 프로세스가 외부 프로세스와 내용을 공유하려고 만든 memfd가 덮어써진 뒤 임의 코드 실행과 root 권한 상승에 사용되었습니다. 반면 runc는 바이너리 내용을 복사하고 실행하기 위해 memfd의 sealing과 실행 기능을 정당하게 사용하므로, 실행 가능한 memfd를 무조건 없애는 대신 정상 용도와 공격 용도를 구별할 수 있어야 합니다.
기존 호환성과 기본 보안을 함께 다루는 세 가지 변화입니다.
.. SPDX-License-Identifier: GPL-2.0
==================================
Introduction of non-executable mfd
==================================
:Author:
Daniel Verkamp <dverkamp@chromium.org>
Jeff Xu <jeffxu@chromium.org>
:Contributor:
Aleksa Sarai <cyphar@cyphar.com>
Since Linux introduced the memfd feature, memfds have always had their
execute bit set, and the memfd_create() syscall doesn't allow setting
it differently.
However, in a secure-by-default system, such as ChromeOS, (where all
executables should come from the rootfs, which is protected by verified
boot), this executable nature of memfd opens a door for NoExec bypass
and enables “confused deputy attack”. E.g, in VRP bug [1]: cros_vm
process created a memfd to share the content with an external process,
however the memfd is overwritten and used for executing arbitrary code
and root escalation. [2] lists more VRP of this kind.
On the other hand, executable memfd has its legit use: runc uses memfd’s
seal and executable feature to copy the contents of the binary then
execute them. For such a system, we need a solution to differentiate runc's
use of executable memfds and an attacker's [3].
To address those above:
- Let memfd_create() set X bit at creation time.
- Let memfd be sealed for modifying X bit when NX is set.
- Add a new pid namespace sysctl: vm.memfd_noexec to help applications in
migrating and enforcing non-executable MFD.
memfd_create 사용자 API
36-52`MFD_NOEXEC_SEAL`을 `flags`에 지정하면 NX인 memfd를 만들고 `F_SEAL_EXEC`를 설정하여 나중에 X 비트를 추가하지 못하게 합니다. 이 플래그는 `MFD_ALLOW_SEALING`도 암시하며, 일반적인 애플리케이션에 권장되는 경로입니다. `MFD_EXEC`는 반대로 X가 설정된 memfd를 만듭니다.
생성 시점에 실행 속성과 변경 가능성을 결정합니다.
원문 주의 사항은 sealing 자체를 원하지 않는 애플리케이션이 생성 뒤 `F_SEAL_SEAL`을 추가할 수 있다고 설명합니다. `F_SEAL_SEAL`은 이후 seal 집합 변경을 막는 최종 봉인이므로, 이 문장은 로컬 v6.18.37 원문의 표현을 그대로 기준으로 이해해야 합니다.
User API
========
``int memfd_create(const char *name, unsigned int flags)``
``MFD_NOEXEC_SEAL``
When MFD_NOEXEC_SEAL bit is set in the ``flags``, memfd is created
with NX. F_SEAL_EXEC is set and the memfd can't be modified to
add X later. MFD_ALLOW_SEALING is also implied.
This is the most common case for the application to use memfd.
``MFD_EXEC``
When MFD_EXEC bit is set in the ``flags``, memfd is created with X.
Note:
``MFD_NOEXEC_SEAL`` implies ``MFD_ALLOW_SEALING``. In case that
an app doesn't want sealing, it can add F_SEAL_SEAL after creation.
pid namespace sysctl
53-80pid namespace별 `vm.memfd_noexec`는 실행 플래그를 지정하지 않는 기존 소프트웨어의 기본 동작을 조정합니다. 값이 커질수록 더 엄격하며, 새 프로그램은 정책이 허용하는 범위에서 플래그를 명시해 의도를 드러낼 수 있습니다.
`MFD_EXEC`와 `MFD_NOEXEC_SEAL`을 생략한 호출에 적용되는 정책입니다.
예를 들어 컨테이너에서 값을 1로 두면 플래그를 모르는 구형 프로그램은 기본적으로 실행 불가능한 memfd를 만들고, 새 프로그램은 `MFD_EXEC`를 명시해 실행 가능한 memfd를 요청할 수 있습니다.
namespace 생성 시 상속한 뒤 호출 시 가장 제한적인 값을 택합니다.
Sysctl:
========
``pid namespaced sysctl vm.memfd_noexec``
The new pid namespaced sysctl vm.memfd_noexec has 3 values:
- 0: MEMFD_NOEXEC_SCOPE_EXEC
memfd_create() without MFD_EXEC nor MFD_NOEXEC_SEAL acts like
MFD_EXEC was set.
- 1: MEMFD_NOEXEC_SCOPE_NOEXEC_SEAL
memfd_create() without MFD_EXEC nor MFD_NOEXEC_SEAL acts like
MFD_NOEXEC_SEAL was set.
- 2: MEMFD_NOEXEC_SCOPE_NOEXEC_ENFORCED
memfd_create() without MFD_NOEXEC_SEAL will be rejected.
The sysctl allows finer control of memfd_create for old software that
doesn't set the executable bit; for example, a container with
vm.memfd_noexec=1 means the old software will create non-executable memfd
by default while new software can create executable memfd by setting
MFD_EXEC.
The value of vm.memfd_noexec is passed to child namespace at creation
time. In addition, the setting is hierarchical, i.e. during memfd_create,
we will search from current ns to root ns and use the most restrictive
setting.
관련 사례와 참고 자료
81-86참고 링크는 Chrome VRP 사례, memfd를 이용한 권한 상승 이슈 검색 결과, runc의 실행 가능한 memfd 사용 배경을 설명하는 LWN 기사로 이어집니다.
[1] https://crbug.com/1305267
[2] https://bugs.chromium.org/p/chromium/issues/list?q=type%3Dbug-security%20memfd%20escalation&can=1
[3] https://lwn.net/Articles/781013/
요약·해설
mfd_noexec.rst:1-86보안 경계는 단일 플래그가 아니라 생성 시 의도 표시와 namespace 계층의 가장 엄격한 정책을 함께 적용해 형성됩니다. 구형 호출자에 대한 기본값과 새 호출자의 명시적 `MFD_EXEC` 허용 여부를 배포 정책에 맞춰 단계적으로 조정해야 합니다.