diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 87b78a337b875..4c56d23d96877 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 8c207552f9805..91b0f17313a26 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: + - GNUExtensions + 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: + - GNUExtensions + 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 4d4c2ad376050..c7be1eddacb5e 100644 --- a/libc/src/sys/mman/CMakeLists.txt +++ b/libc/src/sys/mman/CMakeLists.txt @@ -86,6 +86,41 @@ add_entrypoint_object( .${LIBC_TARGET_OS}.msync ) +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 diff --git a/libc/src/sys/mman/linux/CMakeLists.txt b/libc/src/sys/mman/linux/CMakeLists.txt index 7181bb98a187f..1c79180cbcabb 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 @@ -166,6 +173,74 @@ add_entrypoint_object( libc.src.errno.errno ) +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_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 + .${ARCH_SUBDIRECTORY}.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.sys.mman.mprotect + libc.src.errno.errno +) + +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 + .${ARCH_SUBDIRECTORY}.pkey_common +) + add_entrypoint_object( remap_file_pages SRCS 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 0000000000000..42b6d96c8387e --- /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 0000000000000..51ffe870ce901 --- /dev/null +++ b/libc/src/sys/mman/linux/generic/pkey_common.h @@ -0,0 +1,33 @@ +//===---------- 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 pkey_get(int pkey) { + (void)pkey; + return Error(ENOSYS); +} + +LIBC_INLINE ErrorOr pkey_set(int pkey, unsigned int access_rights) { + (void)pkey; + (void)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/pkey_alloc.cpp b/libc/src/sys/mman/linux/pkey_alloc.cpp new file mode 100644 index 0000000000000..baf32013bc5c7 --- /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 // 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(SYS_pkey_alloc, flags, access_rights); + if (ret < 0) { + libc_errno = static_cast(-ret); + return -1; + } + return static_cast(ret); +#endif +} + +} // namespace LIBC_NAMESPACE_DECL 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 0000000000000..0228971bd10f6 --- /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 // 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(SYS_pkey_free, pkey); + if (ret < 0) { + libc_errno = static_cast(-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 0000000000000..623b7930c7a23 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_get.cpp @@ -0,0 +1,35 @@ +//===---------- 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 "hdr/errno_macros.h" // For ENOSYS +#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" + +#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 + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, pkey_get, (int pkey)) { + ErrorOr 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 0000000000000..15c5d9db39b33 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_mprotect.cpp @@ -0,0 +1,45 @@ +//===---------- 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/libc_errno.h" +#include "src/__support/macros/config.h" +#include "src/sys/mman/mprotect.h" + +#include // For syscall numbers. + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, pkey_mprotect, + (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(addr, len, prot); + } + +#if !defined(SYS_pkey_mprotect) + libc_errno = ENOSYS; + return -1; +#else + int ret = LIBC_NAMESPACE::syscall_impl(SYS_pkey_mprotect, addr, len, + prot, 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_set.cpp b/libc/src/sys/mman/linux/pkey_set.cpp new file mode 100644 index 0000000000000..7921443f688d3 --- /dev/null +++ b/libc/src/sys/mman/linux/pkey_set.cpp @@ -0,0 +1,35 @@ +//===---------- 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 "hdr/errno_macros.h" // For ENOSYS +#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" + +#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 + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(int, pkey_set, (int pkey, unsigned int access_rights)) { + ErrorOr 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 0000000000000..1ce23af6dbd2a --- /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 0000000000000..17b0e3684c60d --- /dev/null +++ b/libc/src/sys/mman/linux/x86_64/pkey_common.h @@ -0,0 +1,79 @@ +//===---------- 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 "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 { +namespace internal { + +constexpr int MAX_KEY = 15; +constexpr int KEY_MASK = 0x3; +constexpr int BITS_PER_KEY = 2; + +// This will SIGILL on CPUs that don't support PKU / OSPKE, +// but this case should never be reached as a prior pkey_alloc invocation +// would have failed more gracefully. +LIBC_INLINE uint32_t read_prku() { + uint32_t pkru = 0; + uint32_t edx = 0; + asm volatile("rdpkru" : "=a"(pkru), "=d"(edx) : "c"(0)); + return pkru; +} + +// This will SIGILL on CPUs that don't support PKU / OSPKE, +// but this case should never be reached as a prior pkey_alloc invocation +// would have failed more gracefully. +LIBC_INLINE void write_prku(uint32_t pkru) { + asm volatile("wrpkru" : : "a"(pkru), "d"(0), "c"(0)); +} + +} // namespace internal + +// x86_64 implementation of pkey_get. +// Returns the access rights for the given pkey on success, errno otherwise. +LIBC_INLINE ErrorOr pkey_get(int pkey) { + if (pkey < 0 || pkey > internal::MAX_KEY) { + return Error(EINVAL); + } + + uint32_t pkru = internal::read_prku(); + return (pkru >> (pkey * internal::BITS_PER_KEY)) & internal::KEY_MASK; +} + +// x86_64 implementation of pkey_set. +// Returns 0 on success, errno otherwise. +LIBC_INLINE ErrorOr pkey_set(int pkey, unsigned int access_rights) { + if (pkey < 0 || pkey > internal::MAX_KEY || + access_rights > internal::KEY_MASK) { + return Error(EINVAL); + } + + uint32_t pkru = internal::read_prku(); + pkru &= ~(internal::KEY_MASK << (pkey * internal::BITS_PER_KEY)); + pkru |= + ((access_rights & internal::KEY_MASK) << (pkey * internal::BITS_PER_KEY)); + internal::write_prku(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 0000000000000..c63c6a36c8021 --- /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 0000000000000..a357e9b8c847b --- /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 0000000000000..d41afe08ae371 --- /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 0000000000000..c02c61594ecc6 --- /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 0000000000000..55bafbd11d709 --- /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 a362c1cf61cbc..721f89961f7c0 100644 --- a/libc/test/src/sys/mman/linux/CMakeLists.txt +++ b/libc/test/src/sys/mman/linux/CMakeLists.txt @@ -67,6 +67,27 @@ add_libc_unittest( ) +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 +) + add_libc_unittest( posix_madvise_test SUITE 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 0000000000000..9c6feae2d457b --- /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 // 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)); +} diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel index a9675f4b02565..5bd45df678085 100644 --- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel @@ -6899,6 +6899,81 @@ libc_function( ], ) +libc_function( + name = "pkey_alloc", + srcs = ["src/sys/mman/linux/pkey_alloc.cpp"], + hdrs = ["src/sys/mman/pkey_alloc.h"], + deps = [ + ":__support_common", + ":__support_osutil_syscall", + ":errno", + ], +) + +libc_function( + name = "pkey_free", + srcs = ["src/sys/mman/linux/pkey_free.cpp"], + hdrs = ["src/sys/mman/pkey_free.h"], + deps = [ + ":__support_common", + ":__support_osutil_syscall", + ":errno", + ], +) + +libc_function( + name = "pkey_get", + srcs = ["src/sys/mman/linux/pkey_get.cpp"], + hdrs = ["src/sys/mman/pkey_get.h"], + deps = [ + ":__support_common", + ":__support_error_or", + ":__support_osutil_syscall", + ":errno", + ":pkey_common", + ], +) + +libc_function( + name = "pkey_mprotect", + srcs = ["src/sys/mman/linux/pkey_mprotect.cpp"], + hdrs = ["src/sys/mman/pkey_mprotect.h"], + deps = [ + ":__support_common", + ":__support_osutil_syscall", + ":errno", + ":mprotect", + ":types_size_t", + ], +) + +libc_function( + name = "pkey_set", + srcs = ["src/sys/mman/linux/pkey_set.cpp"], + hdrs = ["src/sys/mman/pkey_set.h"], + deps = [ + ":__support_common", + ":__support_error_or", + ":__support_osutil_syscall", + ":errno", + ":pkey_common", + ], +) + +libc_support_library( + name = "pkey_common", + hdrs = select({ + "@platforms//cpu:x86_64": ["src/sys/mman/linux/x86_64/pkey_common.h"], + "//conditions:default": ["src/sys/mman/linux/generic/pkey_common.h"], + }), + deps = [ + ":__support_common", + ":__support_error_or", + ":hdr_errno_macros", + ":hdr_stdint_proxy", + ], +) + libc_function( name = "posix_madvise", srcs = ["src/sys/mman/linux/posix_madvise.cpp"], diff --git a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel index b44273123dcad..4a0a81a4b2057 100644 --- a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel @@ -35,6 +35,7 @@ libc_test_library( srcs = [ "BazelFilePath.cpp", "ExecuteFunctionUnix.cpp", + "LibcDeathTestExecutors.cpp", "LibcTest.cpp", "LibcTestMain.cpp", ], diff --git a/utils/bazel/llvm-project-overlay/libc/test/src/sys/mman/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/src/sys/mman/BUILD.bazel index e2c7f7a8bf60b..13353e2a1722b 100644 --- a/utils/bazel/llvm-project-overlay/libc/test/src/sys/mman/BUILD.bazel +++ b/utils/bazel/llvm-project-overlay/libc/test/src/sys/mman/BUILD.bazel @@ -92,6 +92,24 @@ libc_test( ], ) +libc_test( + name = "pkey_test", + srcs = ["linux/pkey_test.cpp"], + deps = [ + "//libc:hdr_errno_macros", + "//libc:hdr_signal_macros", + "//libc:mmap", + "//libc:munmap", + "//libc:pkey_alloc", + "//libc:pkey_free", + "//libc:pkey_get", + "//libc:pkey_mprotect", + "//libc:pkey_set", + "//libc:types_size_t", + "//libc/test/UnitTest:test_logger", + ], +) + libc_test( name = "posix_madvise_test", srcs = ["linux/posix_madvise_test.cpp"],