[PATCH v1 1/6] PKEY: Introduce PKEY_ENFORCE_API flag

From: jeffxu
Date: Thu May 18 2023 - 21:19:37 EST


From: Jeff Xu <jeffxu@xxxxxxxxxx>

This patch introduces a new flag, PKEY_ENFORCE_API, to the pkey_alloc()
function. When a PKEY is created with this flag, it is enforced that any
thread that wants to make changes to the memory mapping (such as
mprotect/munmap) of the memory must have write access to the PKEY.
This is to prevent unauthorized access to protected memory.

PKEYs created without this flag will continue to work as they do now,
for backwards compatibility.

Signed-off-by: Jeff Xu<jeffxu@xxxxxxxxxx>
---
arch/powerpc/include/asm/pkeys.h | 11 ++++++++-
arch/x86/include/asm/mmu.h | 7 ++++++
arch/x86/include/asm/pkeys.h | 42 ++++++++++++++++++++++++++++++--
arch/x86/mm/pkeys.c | 2 +-
include/linux/pkeys.h | 9 ++++++-
include/uapi/linux/mman.h | 5 ++++
mm/mprotect.c | 6 ++---
7 files changed, 74 insertions(+), 8 deletions(-)

diff --git a/arch/powerpc/include/asm/pkeys.h b/arch/powerpc/include/asm/pkeys.h
index 59a2c7dbc78f..943333ac0fee 100644
--- a/arch/powerpc/include/asm/pkeys.h
+++ b/arch/powerpc/include/asm/pkeys.h
@@ -82,7 +82,7 @@ static inline bool mm_pkey_is_allocated(struct mm_struct *mm, int pkey)
* Relies on the mmap_lock to protect against concurrency in mm_pkey_alloc() and
* mm_pkey_free().
*/
-static inline int mm_pkey_alloc(struct mm_struct *mm)
+static inline int mm_pkey_alloc(struct mm_struct *mm, unsigned long flags)
{
/*
* Note: this is the one and only place we make sure that the pkey is
@@ -168,5 +168,14 @@ static inline bool arch_pkeys_enabled(void)
return mmu_has_feature(MMU_FTR_PKEY);
}

+static inline bool arch_check_pkey_alloc_flags(unsigned long flags)
+{
+ /* No flags supported yet. */
+ if (flags)
+ return false;
+
+ return true;
+}
+
extern void pkey_mm_init(struct mm_struct *mm);
#endif /*_ASM_POWERPC_KEYS_H */
diff --git a/arch/x86/include/asm/mmu.h b/arch/x86/include/asm/mmu.h
index 0da5c227f490..d97594b44d9a 100644
--- a/arch/x86/include/asm/mmu.h
+++ b/arch/x86/include/asm/mmu.h
@@ -66,6 +66,13 @@ typedef struct {
*/
u16 pkey_allocation_map;
s16 execute_only_pkey;
+ /*
+ * One bit per protection key.
+ * When set, thread must have write permission on corresponding
+ * PKRU in order to call memory mapping API, such as mprotect,
+ * munmap, etc.
+ */
+ u16 pkey_enforce_api_map;
#endif
} mm_context_t;

diff --git a/arch/x86/include/asm/pkeys.h b/arch/x86/include/asm/pkeys.h
index 2e6c04d8a45b..ecadf04a8251 100644
--- a/arch/x86/include/asm/pkeys.h
+++ b/arch/x86/include/asm/pkeys.h
@@ -51,6 +51,17 @@ static inline int arch_override_mprotect_pkey(struct vm_area_struct *vma,
mm_pkey_allocation_map(mm) &= ~(1U << pkey); \
} while (0)

+#define mm_pkey_enforce_api_map(mm) (mm->context.pkey_enforce_api_map)
+#define mm_set_pkey_enforce_api(mm, pkey) \
+ { \
+ mm_pkey_enforce_api_map(mm) |= (1U << pkey); \
+ }
+
+#define mm_clear_pkey_enforce_api(mm, pkey) \
+ { \
+ mm_pkey_enforce_api_map(mm) &= ~(1U << pkey); \
+ }
+
static inline
bool mm_pkey_is_allocated(struct mm_struct *mm, int pkey)
{
@@ -74,11 +85,25 @@ bool mm_pkey_is_allocated(struct mm_struct *mm, int pkey)
return mm_pkey_allocation_map(mm) & (1U << pkey);
}

+/*
+ * Return true if the pkey has ENFORCE_API flag during allocation.
+ */
+static inline bool mm_pkey_enforce_api(struct mm_struct *mm, int pkey)
+{
+ /*
+ * Only pkey created by user space has the flag.
+ * execute_only_pkey check is in mm_pkey_is_allocated().
+ */
+ if (pkey != ARCH_DEFAULT_PKEY && mm_pkey_is_allocated(mm, pkey))
+ return mm_pkey_enforce_api_map(mm) & (1U << pkey);
+
+ return false;
+}
+
/*
* Returns a positive, 4-bit key on success, or -1 on failure.
*/
-static inline
-int mm_pkey_alloc(struct mm_struct *mm)
+static inline int mm_pkey_alloc(struct mm_struct *mm, unsigned long flags)
{
/*
* Note: this is the one and only place we make sure
@@ -101,6 +126,9 @@ int mm_pkey_alloc(struct mm_struct *mm)

mm_set_pkey_allocated(mm, ret);

+ if (flags & PKEY_ENFORCE_API)
+ mm_set_pkey_enforce_api(mm, ret);
+
return ret;
}

@@ -110,6 +138,7 @@ int mm_pkey_free(struct mm_struct *mm, int pkey)
if (!mm_pkey_is_allocated(mm, pkey))
return -EINVAL;

+ mm_clear_pkey_enforce_api(mm, pkey);
mm_set_pkey_free(mm, pkey);

return 0;
@@ -123,4 +152,13 @@ static inline int vma_pkey(struct vm_area_struct *vma)
return (vma->vm_flags & vma_pkey_mask) >> VM_PKEY_SHIFT;
}

+static inline bool arch_check_pkey_alloc_flags(unsigned long flags)
+{
+ unsigned long valid_flags = PKEY_ENFORCE_API;
+
+ if (flags & ~valid_flags)
+ return false;
+
+ return true;
+}
#endif /*_ASM_X86_PKEYS_H */
diff --git a/arch/x86/mm/pkeys.c b/arch/x86/mm/pkeys.c
index 7418c367e328..a76981f44acf 100644
--- a/arch/x86/mm/pkeys.c
+++ b/arch/x86/mm/pkeys.c
@@ -20,7 +20,7 @@ int __execute_only_pkey(struct mm_struct *mm)
/* Do we need to assign a pkey for mm's execute-only maps? */
if (execute_only_pkey == -1) {
/* Go allocate one to use, which might fail */
- execute_only_pkey = mm_pkey_alloc(mm);
+ execute_only_pkey = mm_pkey_alloc(mm, 0);
if (execute_only_pkey < 0)
return -1;
need_to_set_mm_pkey = true;
diff --git a/include/linux/pkeys.h b/include/linux/pkeys.h
index 86be8bf27b41..81a482c3e051 100644
--- a/include/linux/pkeys.h
+++ b/include/linux/pkeys.h
@@ -3,6 +3,7 @@
#define _LINUX_PKEYS_H

#include <linux/mm.h>
+#include <linux/mman.h>

#define ARCH_DEFAULT_PKEY 0

@@ -25,7 +26,7 @@ static inline bool mm_pkey_is_allocated(struct mm_struct *mm, int pkey)
return (pkey == 0);
}

-static inline int mm_pkey_alloc(struct mm_struct *mm)
+static inline int mm_pkey_alloc(struct mm_struct *mm, unsigned long flags)
{
return -1;
}
@@ -46,6 +47,12 @@ static inline bool arch_pkeys_enabled(void)
return false;
}

+static inline bool arch_check_pkey_alloc_flags(unsigned long flags)
+{
+ if (flags)
+ return false;
+ return true;
+}
#endif /* ! CONFIG_ARCH_HAS_PKEYS */

#endif /* _LINUX_PKEYS_H */
diff --git a/include/uapi/linux/mman.h b/include/uapi/linux/mman.h
index f55bc680b5b0..8c69b9a7ff5b 100644
--- a/include/uapi/linux/mman.h
+++ b/include/uapi/linux/mman.h
@@ -41,4 +41,9 @@
#define MAP_HUGE_2GB HUGETLB_FLAG_ENCODE_2GB
#define MAP_HUGE_16GB HUGETLB_FLAG_ENCODE_16GB

+/*
+ * Flags for pkey_alloc
+ */
+#define PKEY_ENFORCE_API (1 << 0)
+
#endif /* _UAPI_LINUX_MMAN_H */
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 92d3d3ca390a..8a68fdca8487 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -894,15 +894,15 @@ SYSCALL_DEFINE2(pkey_alloc, unsigned long, flags, unsigned long, init_val)
int pkey;
int ret;

- /* No flags supported yet. */
- if (flags)
+ if (!arch_check_pkey_alloc_flags(flags))
return -EINVAL;
+
/* check for unsupported init values */
if (init_val & ~PKEY_ACCESS_MASK)
return -EINVAL;

mmap_write_lock(current->mm);
- pkey = mm_pkey_alloc(current->mm);
+ pkey = mm_pkey_alloc(current->mm, flags);

ret = -ENOSPC;
if (pkey == -1)
--
2.40.1.606.ga4b1b128d6-goog