Skip to content

Conversation

@jtstogel
Copy link
Contributor

@jtstogel jtstogel commented Oct 7, 2025

This patch provides definitions for pkey_* functions for linux x86_64.

alloc, free, and 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.

This is my first addition to LLVM libc. It's likely I've missed something that needs updating -- ample feedback is welcome!

@llvmbot llvmbot added libc bazel "Peripheral" support tier build system: utils/bazel labels Oct 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 7, 2025

@llvm/pr-subscribers-libc

Author: Jackson Stogel (jtstogel)

Changes

This patch provides definitions for pkey_* functions for linux x86_64.

alloc, free, and 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.

This is my first addition to LLVM libc. It's likely I've missed something that needs updating -- ample feedback is welcome!


Patch is 30.82 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/162362.diff

22 Files Affected:

  • (modified) libc/config/linux/x86_64/entrypoints.txt (+5)
  • (modified) libc/src/sys/mman/CMakeLists.txt (+35)
  • (modified) libc/src/sys/mman/linux/CMakeLists.txt (+75)
  • (added) libc/src/sys/mman/linux/generic/CMakeLists.txt (+9)
  • (added) libc/src/sys/mman/linux/generic/pkey_common.h (+25)
  • (added) libc/src/sys/mman/linux/pkey_alloc.cpp (+37)
  • (added) libc/src/sys/mman/linux/pkey_free.cpp (+35)
  • (added) libc/src/sys/mman/linux/pkey_get.cpp (+35)
  • (added) libc/src/sys/mman/linux/pkey_mprotect.cpp (+45)
  • (added) libc/src/sys/mman/linux/pkey_set.cpp (+35)
  • (added) libc/src/sys/mman/linux/x86_64/CMakeLists.txt (+10)
  • (added) libc/src/sys/mman/linux/x86_64/pkey_common.h (+71)
  • (added) libc/src/sys/mman/pkey_alloc.h (+20)
  • (added) libc/src/sys/mman/pkey_free.h (+20)
  • (added) libc/src/sys/mman/pkey_get.h (+20)
  • (added) libc/src/sys/mman/pkey_mprotect.h (+21)
  • (added) libc/src/sys/mman/pkey_set.h (+20)
  • (modified) libc/test/src/sys/mman/linux/CMakeLists.txt (+21)
  • (added) libc/test/src/sys/mman/linux/pkey_test.cpp (+241)
  • (modified) utils/bazel/llvm-project-overlay/libc/BUILD.bazel (+75)
  • (modified) utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel (+1)
  • (modified) utils/bazel/llvm-project-overlay/libc/test/src/sys/mman/BUILD.bazel (+18)
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/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..0811cfb77d4b0
--- /dev/null
+++ b/libc/src/sys/mman/linux/generic/pkey_common.h
@@ -0,0 +1,25 @@
+#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(int pkey) {
+  (void)pkey;
+  return Error(ENOSYS);
+}
+
+LIBC_INLINE ErrorOr<int> 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 <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 = static_cast<int>(-ret);
+    return -1;
+  }
+  return static_cast<int>(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 <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 = static_cast<int>(-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<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 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 <sys/syscall.h> // 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<int>(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<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 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..bffa9feaed06c
--- /dev/null
+++ b/libc/src/sys/mman/linux/x86_64/pkey_common.h
@@ -0,0 +1,71 @@
+#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;
+  LIBC_INLINE_ASM("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) {
+  LIBC_INLINE_ASM("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<int> 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<int> 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..4d19348ef09db
--- /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 "src/__support/macros/config.h"
+#include "hdr/types/size_t.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.
...
[truncated]

@github-actions
Copy link

github-actions bot commented Oct 7, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@michaelrj-google michaelrj-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks good, but you need to update the headergen file in include/sys/mman.yaml

LIBC_INLINE uint32_t read_prku() {
uint32_t pkru = 0;
uint32_t edx = 0;
LIBC_INLINE_ASM("rdpkru" : "=a"(pkru), "=d"(edx) : "c"(0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we migrating back to the macro approach for ASM?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize there was a preference. Switched to use asm volatile directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For setjmp/longjmp we used [[gnu::naked]] so that the whole function was assembly. I'd need to dig up the discussion but I'd say either is probably fine in this case.

@jtstogel jtstogel changed the title Implement pkey_alloc/free/get/set/mprotect for x86_64 linux [libc] Implement pkey_alloc/free/get/set/mprotect for x86_64 linux Oct 7, 2025
// 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this entrypoint dependency be broken up?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. I added a file mprotect_common.h that just exposes the syscall wrapper. WDYT of that vs just duplicating the syscall line?

LIBC_INLINE uint32_t read_prku() {
uint32_t pkru = 0;
uint32_t edx = 0;
asm volatile("rdpkru" : "=a"(pkru), "=d"(edx) : "c"(0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! switched, much nicer than asm

// 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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, ty!

}

uint32_t pkru = internal::read_prku();
return (pkru >> (pkey * internal::BITS_PER_KEY)) & internal::KEY_MASK;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to overflow on this shift?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not possible, since we check bounds on pkey and pkru is always 32 bits

Comment on lines 68 to 70
pkru &= ~(internal::KEY_MASK << (pkey * internal::BITS_PER_KEY));
pkru |=
((access_rights & internal::KEY_MASK) << (pkey * internal::BITS_PER_KEY));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar question about potential overflow here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likewise should not be possible, since we check bounds on pkey and also mask it against 0x3

@jtstogel jtstogel requested a review from lntue November 3, 2025 20:58
- type: int
- name: pkey_get
standards:
- GNUExtensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: gnu

- type: int
- name: pkey_set
standards:
- GNUExtensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: gnu

Copy link
Contributor

@lntue lntue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few nits.

Copy link
Contributor

@michaelrj-google michaelrj-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall design looks good, just a few things I think could be simplified

Comment on lines +24 to +25
libc_errno = ENOSYS;
return -1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally for syscall wrappers like this we don't bother with the ENOSYS and leave it as a compile time error when building the library.

Comment on lines 17 to 21
#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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would make sense to have a top level pkey_common.h so you don't need this logic in multiple files.

Copy link
Contributor

@michaelrj-google michaelrj-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-actions
Copy link

🐧 Linux x64 Test Results

The build succeeded and no tests ran. This is expected in some build configurations.

@jtstogel jtstogel merged commit db71cc5 into llvm:main Nov 18, 2025
27 of 52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bazel "Peripheral" support tier build system: utils/bazel libc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants