diff options
| author | Jackson Stogel <jtstogel@gmail.com> | 2025-11-18 14:30:15 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-18 14:30:15 -0800 |
| commit | db71cc58ec9471c67c6b80996930a19222dd9f03 (patch) | |
| tree | 61e6f425b2439bbff2d0e7186ff8d4a5c1b7f16a /libc | |
| parent | 6665642ce40c70b65624a5aa67566725c5a87da5 (diff) | |
[libc] Implement pkey_alloc/free/get/set/mprotect for x86_64 linux (#162362)
This patch provides definitions for `pkey_*` functions for linux x86_64.
`pkey_alloc`, `pkey_free`, and `pkey_mprotect` are simple syscall
wrappers. `pkey_set` and `pkey_get` modify architecture-specific
registers. The logic for these live in architecture specific
directories:
* `libc/src/sys/mman/linux/x86_64/pkey_common.h` has a real
implementation
* `libc/src/sys/mman/linux/generic/pkey_common.h` contains stubs that
just return `ENOSYS`.
Diffstat (limited to 'libc')
23 files changed, 896 insertions, 11 deletions
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index bbff4969cb41..4b6c10917c31 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -264,6 +264,11 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.sys.mman.munlock libc.src.sys.mman.munlockall libc.src.sys.mman.munmap + libc.src.sys.mman.pkey_alloc + libc.src.sys.mman.pkey_free + libc.src.sys.mman.pkey_get + libc.src.sys.mman.pkey_mprotect + libc.src.sys.mman.pkey_set libc.src.sys.mman.remap_file_pages libc.src.sys.mman.posix_madvise libc.src.sys.mman.shm_open diff --git a/libc/include/sys/mman.yaml b/libc/include/sys/mman.yaml index 8c207552f980..f9ab0c1001c3 100644 --- a/libc/include/sys/mman.yaml +++ b/libc/include/sys/mman.yaml @@ -101,6 +101,41 @@ functions: arguments: - type: void * - type: size_t + - name: pkey_alloc + standards: + - Linux + return_type: int + arguments: + - type: unsigned int + - type: unsigned int + - name: pkey_free + standards: + - Linux + return_type: int + arguments: + - type: int + - name: pkey_get + standards: + - GNU + return_type: int + arguments: + - type: int + - name: pkey_mprotect + standards: + - Linux + return_type: int + arguments: + - type: void * + - type: size_t + - type: int + - type: int + - name: pkey_set + standards: + - GNU + return_type: int + arguments: + - type: int + - type: unsigned int - name: posix_madvise standards: - POSIX diff --git a/libc/src/sys/mman/CMakeLists.txt b/libc/src/sys/mman/CMakeLists.txt index 4d4c2ad37605..c7be1eddacb5 100644 --- a/libc/src/sys/mman/CMakeLists.txt +++ b/libc/src/sys/mman/CMakeLists.txt @@ -87,6 +87,41 @@ add_entrypoint_object( ) add_entrypoint_object( + pkey_alloc + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.pkey_alloc +) + +add_entrypoint_object( + pkey_free + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.pkey_free +) + +add_entrypoint_object( + pkey_get + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.pkey_get +) + +add_entrypoint_object( + pkey_mprotect + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.pkey_mprotect +) + +add_entrypoint_object( + pkey_set + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.pkey_set +) + +add_entrypoint_object( remap_file_pages ALIAS DEPENDS diff --git a/libc/src/sys/mman/linux/CMakeLists.txt b/libc/src/sys/mman/linux/CMakeLists.txt index 7181bb98a187..97c116f1d2e7 100644 --- a/libc/src/sys/mman/linux/CMakeLists.txt +++ b/libc/src/sys/mman/linux/CMakeLists.txt @@ -1,3 +1,10 @@ +add_subdirectory(generic) +set(ARCH_SUBDIRECTORY generic) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE}) + add_subdirectory(${LIBC_TARGET_ARCHITECTURE}) + set(ARCH_SUBDIRECTORY ${LIBC_TARGET_ARCHITECTURE}) +endif() + add_entrypoint_object( madvise SRCS @@ -50,6 +57,17 @@ add_entrypoint_object( libc.src.errno.errno ) +add_header_library( + mprotect_common + HDRS + mprotect_common.h + DEPENDS + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno + libc.src.__support.error_or +) + add_entrypoint_object( mprotect SRCS @@ -61,6 +79,7 @@ add_entrypoint_object( libc.include.sys_syscall libc.src.__support.OSUtil.osutil libc.src.errno.errno + .mprotect_common ) add_entrypoint_object( @@ -167,6 +186,82 @@ add_entrypoint_object( ) add_entrypoint_object( + pkey_alloc + SRCS + pkey_alloc.cpp + HDRS + ../pkey_alloc.h + DEPENDS + libc.include.sys_mman + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + +add_header_library( + pkey_common + HDRS + pkey_common.h + DEPENDS + .${ARCH_SUBDIRECTORY}.pkey_common +) + +add_entrypoint_object( + pkey_free + SRCS + pkey_free.cpp + HDRS + ../pkey_free.h + DEPENDS + libc.include.sys_mman + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno +) + +add_entrypoint_object( + pkey_get + SRCS + pkey_get.cpp + HDRS + ../pkey_get.h + DEPENDS + libc.include.sys_mman + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno + .pkey_common +) + +add_entrypoint_object( + pkey_mprotect + SRCS + pkey_mprotect.cpp + HDRS + ../pkey_mprotect.h + DEPENDS + libc.include.sys_mman + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno + .mprotect_common +) + +add_entrypoint_object( + pkey_set + SRCS + pkey_set.cpp + HDRS + ../pkey_set.h + DEPENDS + libc.include.sys_mman + libc.include.sys_syscall + libc.src.__support.OSUtil.osutil + libc.src.errno.errno + .pkey_common +) + +add_entrypoint_object( remap_file_pages SRCS remap_file_pages.cpp diff --git a/libc/src/sys/mman/linux/generic/CMakeLists.txt b/libc/src/sys/mman/linux/generic/CMakeLists.txt new file mode 100644 index 000000000000..42b6d96c8387 --- /dev/null +++ b/libc/src/sys/mman/linux/generic/CMakeLists.txt @@ -0,0 +1,9 @@ +add_header_library( + pkey_common + HDRS + pkey_common.h + DEPENDS + libc.hdr.errno_macros + libc.src.__support.common + libc.src.__support.error_or +) diff --git a/libc/src/sys/mman/linux/generic/pkey_common.h b/libc/src/sys/mman/linux/generic/pkey_common.h new file mode 100644 index 000000000000..95f9a464fbd4 --- /dev/null +++ b/libc/src/sys/mman/linux/generic/pkey_common.h @@ -0,0 +1,31 @@ +//===---------- Generic stub implementations for pkey functionality. ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SYS_MMAN_LINUX_GENERIC_PKEY_COMMON_H_ +#define LLVM_SYS_MMAN_LINUX_GENERIC_PKEY_COMMON_H_ + +#include "hdr/errno_macros.h" // For ENOSYS +#include "src/__support/common.h" +#include "src/__support/error_or.h" + +namespace LIBC_NAMESPACE_DECL { +namespace pkey_common { + +LIBC_INLINE ErrorOr<int> pkey_get([[maybe_unused]] int pkey) { + return Error(ENOSYS); +} + +LIBC_INLINE ErrorOr<int> pkey_set([[maybe_unused]] int pkey, + [[maybe_unused]] unsigned int access_rights) { + return Error(ENOSYS); +} + +} // namespace pkey_common +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_SYS_MMAN_LINUX_GENERIC_PKEY_COMMON_H_ diff --git a/libc/src/sys/mman/linux/mprotect.cpp b/libc/src/sys/mman/linux/mprotect.cpp index 6b14915b60c9..c891f03a4713 100644 --- a/libc/src/sys/mman/linux/mprotect.cpp +++ b/libc/src/sys/mman/linux/mprotect.cpp @@ -11,26 +11,22 @@ #include "src/__support/OSUtil/syscall.h" // For internal syscall function. #include "src/__support/common.h" +#include "src/__support/error_or.h" #include "src/__support/libc_errno.h" #include "src/__support/macros/config.h" +#include "src/sys/mman/linux/mprotect_common.h" #include <sys/syscall.h> // For syscall numbers. namespace LIBC_NAMESPACE_DECL { -// This function is currently linux only. It has to be refactored suitably if -// mprotect is to be supported on non-linux operating systems also. LLVM_LIBC_FUNCTION(int, mprotect, (void *addr, size_t size, int prot)) { - int ret = LIBC_NAMESPACE::syscall_impl<int>( - SYS_mprotect, reinterpret_cast<long>(addr), size, prot); - - // A negative return value indicates an error with the magnitude of the - // value being the error code. - if (ret < 0) { - libc_errno = -ret; + ErrorOr<int> result = + LIBC_NAMESPACE::mprotect_common::mprotect_impl(addr, size, prot); + if (!result.has_value()) { + libc_errno = result.error(); return -1; } - - return 0; + return result.value(); } } // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/mman/linux/mprotect_common.h b/libc/src/sys/mman/linux/mprotect_common.h new file mode 100644 index 000000000000..5cd354f9919d --- /dev/null +++ b/libc/src/sys/mman/linux/mprotect_common.h @@ -0,0 +1,38 @@ +//===---------- Shared Linux implementation of POSIX mprotect. ------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" +#include "src/__support/error_or.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/attributes.h" +#include "src/__support/macros/config.h" +#include <sys/syscall.h> // For syscall numbers. + +namespace LIBC_NAMESPACE_DECL { + +namespace mprotect_common { + +// This function is currently linux only. It has to be refactored suitably if +// mprotect is to be supported on non-linux operating systems also. +LIBC_INLINE ErrorOr<int> mprotect_impl(void *addr, size_t size, int prot) { + int ret = LIBC_NAMESPACE::syscall_impl<int>( + SYS_mprotect, reinterpret_cast<long>(addr), size, prot); + + // A negative return value indicates an error with the magnitude of the + // value being the error code. + if (ret < 0) { + return Error(-ret); + } + + return 0; +} + +} // namespace mprotect_common + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/mman/linux/pkey_alloc.cpp b/libc/src/sys/mman/linux/pkey_alloc.cpp new file mode 100644 index 000000000000..6ad65f342eb5 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_alloc.cpp @@ -0,0 +1,37 @@ +//===---------- Linux implementation of the Linux pkey_alloc function -----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/sys/mman/pkey_alloc.h" + +#include "hdr/errno_macros.h" // For ENOSYS +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/config.h" + +#include <sys/syscall.h> // For syscall numbers. + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, pkey_alloc, + (unsigned int flags, unsigned int access_rights)) { +#if !defined(SYS_pkey_alloc) + libc_errno = ENOSYS; + return -1; +#else + int ret = + LIBC_NAMESPACE::syscall_impl<int>(SYS_pkey_alloc, flags, access_rights); + if (ret < 0) { + libc_errno = -ret; + return -1; + } + return static_cast<int>(ret); +#endif +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/mman/linux/pkey_common.h b/libc/src/sys/mman/linux/pkey_common.h new file mode 100644 index 000000000000..7ea7b61cdcb7 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_common.h @@ -0,0 +1,15 @@ +//===---------- Linux implementation of the Linux pkey_mprotect function --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/macros/properties/architectures.h" + +#if defined(LIBC_TARGET_ARCH_IS_X86_64) +#include "src/sys/mman/linux/x86_64/pkey_common.h" +#else +#include "src/sys/mman/linux/generic/pkey_common.h" +#endif diff --git a/libc/src/sys/mman/linux/pkey_free.cpp b/libc/src/sys/mman/linux/pkey_free.cpp new file mode 100644 index 000000000000..328ba0468252 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_free.cpp @@ -0,0 +1,35 @@ +//===---------- Linux implementation of the Linux pkey_free function ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/sys/mman/pkey_free.h" + +#include "hdr/errno_macros.h" // For ENOSYS +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/config.h" + +#include <sys/syscall.h> // For syscall numbers. + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, pkey_free, (int pkey)) { +#if !defined(SYS_pkey_free) + libc_errno = ENOSYS; + return -1; +#else + int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_pkey_free, pkey); + if (ret < 0) { + libc_errno = -ret; + return -1; + } + return 0; +#endif +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/mman/linux/pkey_get.cpp b/libc/src/sys/mman/linux/pkey_get.cpp new file mode 100644 index 000000000000..fbec5706c918 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_get.cpp @@ -0,0 +1,29 @@ +//===---------- Linux implementation of the Linux pkey_mprotect function --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/sys/mman/pkey_get.h" + +#include "src/__support/common.h" +#include "src/__support/error_or.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/config.h" +#include "src/__support/macros/properties/architectures.h" +#include "src/sys/mman/linux/pkey_common.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, pkey_get, (int pkey)) { + ErrorOr<int> ret = LIBC_NAMESPACE::pkey_common::pkey_get(pkey); + if (!ret.has_value()) { + libc_errno = ret.error(); + return -1; + } + return ret.value(); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/mman/linux/pkey_mprotect.cpp b/libc/src/sys/mman/linux/pkey_mprotect.cpp new file mode 100644 index 000000000000..daa12fa927f8 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_mprotect.cpp @@ -0,0 +1,58 @@ +//===---------- Linux implementation of the Linux pkey_mprotect function --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/sys/mman/pkey_mprotect.h" + +#include "hdr/errno_macros.h" // For ENOSYS +#include "hdr/types/size_t.h" +#include "src/__support/OSUtil/syscall.h" // For internal syscall function. +#include "src/__support/common.h" +#include "src/__support/error_or.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/config.h" +#include "src/sys/mman/linux/mprotect_common.h" + +#include <sys/syscall.h> // For syscall numbers. + +namespace LIBC_NAMESPACE_DECL { +namespace internal { + +LIBC_INLINE ErrorOr<int> pkey_mprotect_impl(void *addr, size_t len, int prot, + int pkey) { + // Fall back to mprotect if pkey is -1 + // to maintain compatibility with kernel versions that don't support pkey. + if (pkey == -1) { + return LIBC_NAMESPACE::mprotect_common::mprotect_impl(addr, len, prot); + } + +#if !defined(SYS_pkey_mprotect) + return Error(ENOSYS); +#else + int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_pkey_mprotect, addr, len, + prot, pkey); + if (ret < 0) { + return Error(-ret); + } + return 0; +#endif +} + +} // namespace internal + +LLVM_LIBC_FUNCTION(int, pkey_mprotect, + (void *addr, size_t len, int prot, int pkey)) { + ErrorOr<int> ret = + LIBC_NAMESPACE::internal::pkey_mprotect_impl(addr, len, prot, pkey); + if (!ret.has_value()) { + libc_errno = ret.error(); + return -1; + } + return ret.value(); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/mman/linux/pkey_set.cpp b/libc/src/sys/mman/linux/pkey_set.cpp new file mode 100644 index 000000000000..919a6dceeafe --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_set.cpp @@ -0,0 +1,29 @@ +//===---------- Linux implementation of the Linux pkey_mprotect function --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/sys/mman/pkey_set.h" + +#include "src/__support/common.h" +#include "src/__support/error_or.h" +#include "src/__support/libc_errno.h" +#include "src/__support/macros/attributes.h" +#include "src/__support/macros/config.h" +#include "src/sys/mman/linux/pkey_common.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, pkey_set, (int pkey, unsigned int access_rights)) { + ErrorOr<int> ret = LIBC_NAMESPACE::pkey_common::pkey_set(pkey, access_rights); + if (!ret.has_value()) { + libc_errno = ret.error(); + return -1; + } + return ret.value(); +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/sys/mman/linux/x86_64/CMakeLists.txt b/libc/src/sys/mman/linux/x86_64/CMakeLists.txt new file mode 100644 index 000000000000..1ce23af6dbd2 --- /dev/null +++ b/libc/src/sys/mman/linux/x86_64/CMakeLists.txt @@ -0,0 +1,10 @@ +add_header_library( + pkey_common + HDRS + pkey_common.h + DEPENDS + libc.hdr.errno_macros + libc.hdr.stdint_proxy + libc.src.__support.common + libc.src.__support.error_or +) diff --git a/libc/src/sys/mman/linux/x86_64/pkey_common.h b/libc/src/sys/mman/linux/x86_64/pkey_common.h new file mode 100644 index 000000000000..cb657750112c --- /dev/null +++ b/libc/src/sys/mman/linux/x86_64/pkey_common.h @@ -0,0 +1,61 @@ +//===---------- x86_64-specific implementations for pkey_{get,set}. -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SYS_MMAN_LINUX_X86_64_PKEY_COMMON_H_ +#define LLVM_SYS_MMAN_LINUX_X86_64_PKEY_COMMON_H_ + +#include <immintrin.h> + +#include "hdr/errno_macros.h" // For ENOSYS +#include "hdr/stdint_proxy.h" +#include "src/__support/common.h" +#include "src/__support/error_or.h" + +#if !defined(LIBC_TARGET_ARCH_IS_X86_64) +#error "Invalid include" +#endif + +namespace LIBC_NAMESPACE_DECL { +namespace pkey_common { + +constexpr int KEY_COUNT = 16; +constexpr int KEY_MASK = 0x3; +constexpr int BITS_PER_KEY = 2; + +// x86_64 implementation of pkey_get. +// Returns the access rights for the given pkey on success, errno otherwise. +[[gnu::target("pku")]] +LIBC_INLINE ErrorOr<int> pkey_get(int pkey) { + if (pkey < 0 || pkey >= KEY_COUNT) { + return Error(EINVAL); + } + + uint32_t pkru = _rdpkru_u32(); + return (pkru >> (pkey * BITS_PER_KEY)) & KEY_MASK; +} + +// x86_64 implementation of pkey_set. +// Returns 0 on success, errno otherwise. +[[gnu::target("pku")]] +LIBC_INLINE ErrorOr<int> pkey_set(int pkey, unsigned int access_rights) { + if (pkey < 0 || pkey >= KEY_COUNT || access_rights > KEY_MASK) { + return Error(EINVAL); + } + + uint32_t pkru = _rdpkru_u32(); + pkru &= ~(KEY_MASK << (pkey * BITS_PER_KEY)); + pkru |= ((access_rights & KEY_MASK) << (pkey * BITS_PER_KEY)); + _wrpkru(pkru); + + return 0; +} + +} // namespace pkey_common +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_SYS_MMAN_LINUX_X86_64_PKEY_COMMON_H_ diff --git a/libc/src/sys/mman/pkey_alloc.h b/libc/src/sys/mman/pkey_alloc.h new file mode 100644 index 000000000000..c63c6a36c802 --- /dev/null +++ b/libc/src/sys/mman/pkey_alloc.h @@ -0,0 +1,20 @@ +//===-- Implementation header for pkey_alloc function -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_SYS_MMAN_PKEY_ALLOC_H +#define LLVM_LIBC_SRC_SYS_MMAN_PKEY_ALLOC_H + +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int pkey_alloc(unsigned int flags, unsigned int access_rights); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_SYS_MMAN_PKEY_ALLOC_H diff --git a/libc/src/sys/mman/pkey_free.h b/libc/src/sys/mman/pkey_free.h new file mode 100644 index 000000000000..a357e9b8c847 --- /dev/null +++ b/libc/src/sys/mman/pkey_free.h @@ -0,0 +1,20 @@ +//===-- Implementation header for pkey_free function ------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_SYS_MMAN_PKEY_FREE_H +#define LLVM_LIBC_SRC_SYS_MMAN_PKEY_FREE_H + +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int pkey_free(int pkey); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_SYS_MMAN_PKEY_FREE_H diff --git a/libc/src/sys/mman/pkey_get.h b/libc/src/sys/mman/pkey_get.h new file mode 100644 index 000000000000..d41afe08ae37 --- /dev/null +++ b/libc/src/sys/mman/pkey_get.h @@ -0,0 +1,20 @@ +//===-- Implementation header for pkey_get function -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_SYS_MMAN_PKEY_GET_H +#define LLVM_LIBC_SRC_SYS_MMAN_PKEY_GET_H + +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int pkey_get(int pkey); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_SYS_MMAN_PKEY_GET_H diff --git a/libc/src/sys/mman/pkey_mprotect.h b/libc/src/sys/mman/pkey_mprotect.h new file mode 100644 index 000000000000..c02c61594ecc --- /dev/null +++ b/libc/src/sys/mman/pkey_mprotect.h @@ -0,0 +1,21 @@ +//===-- Implementation header for pkey_mprotect function --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_SYS_MMAN_PKEY_MPROTECT_H +#define LLVM_LIBC_SRC_SYS_MMAN_PKEY_MPROTECT_H + +#include "hdr/types/size_t.h" +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int pkey_mprotect(void *addr, size_t len, int prot, int pkey); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_SYS_MMAN_PKEY_MPROTECT_H diff --git a/libc/src/sys/mman/pkey_set.h b/libc/src/sys/mman/pkey_set.h new file mode 100644 index 000000000000..55bafbd11d70 --- /dev/null +++ b/libc/src/sys/mman/pkey_set.h @@ -0,0 +1,20 @@ +//===-- Implementation header for pkey_set function -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_SYS_MMAN_PKEY_SET_H +#define LLVM_LIBC_SRC_SYS_MMAN_PKEY_SET_H + +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int pkey_set(int pkey, unsigned int access_rights); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_SYS_MMAN_PKEY_SET_H diff --git a/libc/test/src/sys/mman/linux/CMakeLists.txt b/libc/test/src/sys/mman/linux/CMakeLists.txt index 32fee920321c..8a290795e67f 100644 --- a/libc/test/src/sys/mman/linux/CMakeLists.txt +++ b/libc/test/src/sys/mman/linux/CMakeLists.txt @@ -66,6 +66,31 @@ add_libc_unittest( libc.test.UnitTest.ErrnoSetterMatcher ) +# Disable sanitizers for pkey_test. +# This test intentionally triggers segfaults to verify pkey_mprotect behavior, +# and sanitizers register signal handlers that interfere with death testing. +if (NOT LLVM_USE_SANITIZER) + add_libc_unittest( + pkey_test + SUITE + libc_sys_mman_unittests + SRCS + pkey_test.cpp + DEPENDS + libc.hdr.errno_macros + libc.hdr.signal_macros + libc.hdr.types.size_t + libc.src.sys.mman.mmap + libc.src.sys.mman.munmap + libc.src.sys.mman.pkey_alloc + libc.src.sys.mman.pkey_free + libc.src.sys.mman.pkey_get + libc.src.sys.mman.pkey_mprotect + libc.src.sys.mman.pkey_set + libc.test.UnitTest.ErrnoCheckingTest + libc.test.UnitTest.ErrnoSetterMatcher + ) +endif() add_libc_unittest( posix_madvise_test diff --git a/libc/test/src/sys/mman/linux/pkey_test.cpp b/libc/test/src/sys/mman/linux/pkey_test.cpp new file mode 100644 index 000000000000..9c6feae2d457 --- /dev/null +++ b/libc/test/src/sys/mman/linux/pkey_test.cpp @@ -0,0 +1,241 @@ +//===-- Unit tests for pkey functions -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "hdr/errno_macros.h" +#include "hdr/signal_macros.h" +#include "hdr/types/size_t.h" +#include "src/sys/mman/mmap.h" +#include "src/sys/mman/munmap.h" +#include "src/sys/mman/pkey_alloc.h" +#include "src/sys/mman/pkey_free.h" +#include "src/sys/mman/pkey_get.h" +#include "src/sys/mman/pkey_mprotect.h" +#include "src/sys/mman/pkey_set.h" +#include "test/UnitTest/ErrnoCheckingTest.h" +#include "test/UnitTest/ErrnoSetterMatcher.h" +#include "test/UnitTest/LibcTest.h" +#include "test/UnitTest/TestLogger.h" + +#include <linux/param.h> // For EXEC_PAGESIZE. + +using LIBC_NAMESPACE::testing::tlog; +using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails; +using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds; + +using LlvmLibcProtectionKeyTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest; + +constexpr size_t MMAP_SIZE = EXEC_PAGESIZE; + +// Wrapper around a pkey to ensure it is freed. +class PKeyGuard { +public: + int key; + + PKeyGuard() : key(-1) {} + + PKeyGuard(int key) : key(key) {} + + ~PKeyGuard() { + if (key != -1) { + LIBC_NAMESPACE::pkey_free(key); + } + } +}; + +// Wrapper around mmap to ensure munmap is called. +class MMapPageGuard { +public: + void *addr = nullptr; + size_t size = 0; + + static MMapPageGuard mmap(int prot) { + void *addr = LIBC_NAMESPACE::mmap(nullptr, MMAP_SIZE, prot, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED) { + return MMapPageGuard(nullptr, 0); + } + return MMapPageGuard(addr, MMAP_SIZE); + } + + MMapPageGuard(void *addr, size_t size) : addr(addr), size(size) {} + + ~MMapPageGuard() { + if (addr != nullptr) { + LIBC_NAMESPACE::munmap(addr, size); + } + } +}; + +bool protection_keys_supported() { + static bool supported = []() { + PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0)); + int err = libc_errno; + libc_errno = 0; + + if (pkey.key < 0 || (err == ENOSPC || err == ENOSYS || err == EINVAL)) { + tlog << "pkey_alloc failed with errno=" << err << "\n"; + return false; + } + + int access_rights = LIBC_NAMESPACE::pkey_get(pkey.key); + err = libc_errno; + libc_errno = 0; + if (access_rights < 0 || err == ENOSYS) { + tlog << "pkey_get failed with errno=" << err << "\n"; + return false; + } + + return true; + }(); + return supported; +} + +TEST_F(LlvmLibcProtectionKeyTest, MProtectWithPKeyDisablesWrite) { + if (!protection_keys_supported()) { + tlog << "Skipping test: pkey is not available\n"; + return; + } + + PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE)); + ASSERT_NE(pkey.key, -1); + + MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE); + ASSERT_NE(page.addr, nullptr); + + volatile char *data = (char *)page.addr; + data[0] = 'a'; + + EXPECT_THAT(LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size, + PROT_READ | PROT_WRITE, pkey.key), + Succeeds()); + + // Read is still allowed. + EXPECT_EQ(data[0], 'a'); + + // Write is not allowed. + EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV)); +} + +TEST_F(LlvmLibcProtectionKeyTest, PKeySetChangesAccessRights) { + if (!protection_keys_supported()) { + tlog << "Skipping test: pkey is not available\n"; + return; + } + + PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0)); + ASSERT_NE(pkey.key, -1); + + MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE); + ASSERT_NE(page.addr, nullptr); + + EXPECT_THAT(LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size, + PROT_READ | PROT_WRITE, pkey.key), + Succeeds()); + + // Write is allowed by default. + volatile char *data = (char *)page.addr; + data[0] = 'a'; + + EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_WRITE), + Succeeds()); + + // Now read is allowed but write is not. + EXPECT_EQ(data[0], 'a'); + EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV)); + + // Now neither read nor write is allowed. + EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_ACCESS | + PKEY_DISABLE_WRITE), + Succeeds()); + EXPECT_DEATH([&data]() { (void)data[0]; }, WITH_SIGNAL(SIGSEGV)); + EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV)); +} + +TEST_F(LlvmLibcProtectionKeyTest, FallsBackToMProtectForInvalidPKey) { + MMapPageGuard page = MMapPageGuard::mmap(PROT_READ | PROT_WRITE); + ASSERT_NE(page.addr, nullptr); + + volatile char *data = (char *)page.addr; + data[0] = 'a'; + + EXPECT_THAT( + LIBC_NAMESPACE::pkey_mprotect(page.addr, page.size, PROT_READ, -1), + Succeeds()); + + // Read is still allowed. + EXPECT_EQ(data[0], 'a'); + + // Write is not allowed. + EXPECT_DEATH([&data]() { data[0] = 'b'; }, WITH_SIGNAL(SIGSEGV)); +} + +TEST_F(LlvmLibcProtectionKeyTest, ExhaustedKeysFailsWithENOSPC) { + if (!protection_keys_supported()) { + tlog << "Skipping test: pkey is not available\n"; + return; + } + + // Use an unreasonably large limit to ensure test is cross-platform. + // This limit is intended to be much larger than the actual hardware limit. + constexpr int MAX_PKEYS = 64; + PKeyGuard pkeys[MAX_PKEYS]; + for (int i = 0; i < MAX_PKEYS; ++i) { + pkeys[i].key = LIBC_NAMESPACE::pkey_alloc(0, 0); + } + + // pkey allocation should eventually fail with ENOSPC. + PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, 0)); + EXPECT_THAT(pkey.key, Fails(ENOSPC)); + libc_errno = 0; +} + +TEST_F(LlvmLibcProtectionKeyTest, Accessors) { + if (!protection_keys_supported()) { + tlog << "Skipping test: pkey is not available\n"; + return; + } + + PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE)); + ASSERT_NE(pkey.key, -1); + + // Check that pkey_alloc sets the access rights. + EXPECT_EQ(LIBC_NAMESPACE::pkey_get(pkey.key), PKEY_DISABLE_WRITE); + + // Check that pkey_set changes the access rights. + EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, PKEY_DISABLE_ACCESS), + Succeeds()); + EXPECT_EQ(LIBC_NAMESPACE::pkey_get(pkey.key), PKEY_DISABLE_ACCESS); +} + +TEST_F(LlvmLibcProtectionKeyTest, AccessorsErrorForInvalidValues) { + if (!protection_keys_supported()) { + tlog << "Skipping test: pkey is not available\n"; + return; + } + + PKeyGuard pkey(LIBC_NAMESPACE::pkey_alloc(0, PKEY_DISABLE_WRITE)); + ASSERT_NE(pkey.key, -1); + + // Pkey is out of bounds in pkey_get. + EXPECT_THAT(LIBC_NAMESPACE::pkey_get(100), Fails(EINVAL)); + EXPECT_THAT(LIBC_NAMESPACE::pkey_get(-1234), Fails(EINVAL)); + + // Pkey is out of bounds in pkey_set. + EXPECT_THAT(LIBC_NAMESPACE::pkey_set(100, PKEY_DISABLE_ACCESS), + Fails(EINVAL)); + EXPECT_THAT(LIBC_NAMESPACE::pkey_set(-1234, PKEY_DISABLE_ACCESS), + Fails(EINVAL)); + + // Non-zero flags are not supported in pkey_alloc. + EXPECT_THAT(LIBC_NAMESPACE::pkey_alloc(123, PKEY_DISABLE_WRITE), + Fails(EINVAL)); + + // Access rights are out of bounds. + EXPECT_THAT(LIBC_NAMESPACE::pkey_alloc(0, 1000), Fails(EINVAL)); + EXPECT_THAT(LIBC_NAMESPACE::pkey_set(pkey.key, 1000), Fails(EINVAL)); +} |
