diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig
index 7a3b52b7e456..ea6d73241632 100644
--- a/arch/x86/kvm/Kconfig
+++ b/arch/x86/kvm/Kconfig
@@ -50,6 +50,7 @@ config KVM
 	select HAVE_KVM_PM_NOTIFIER if PM
 	select KVM_GENERIC_HARDWARE_ENABLING
 	select HYPERVISOR_SUPPORTS_HEKI
+	select SPARSEMEM
 	help
 	  Support hosting fully virtualized guest machines using hardware
 	  virtualization extensions.  You will need a fairly recent
diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile
index 80e3fe184d17..aac51a5d2cae 100644
--- a/arch/x86/kvm/Makefile
+++ b/arch/x86/kvm/Makefile
@@ -9,10 +9,12 @@ endif
 
 include $(srctree)/virt/kvm/Makefile.kvm
 
+VIRT_LIB = ../../../virt/lib
+
 kvm-y			+= x86.o emulate.o i8259.o irq.o lapic.o \
 			   i8254.o ioapic.o irq_comm.o cpuid.o pmu.o mtrr.o \
 			   hyperv.o debugfs.o mmu/mmu.o mmu/page_track.o \
-			   mmu/spte.o
+			   mmu/spte.o $(VIRT_LIB)/kvm_permissions.o
 
 ifdef CONFIG_HYPERV
 kvm-y			+= kvm_onhyperv.o
diff --git a/include/linux/kvm_mem_attr.h b/include/linux/kvm_mem_attr.h
new file mode 100644
index 000000000000..0a755025e553
--- /dev/null
+++ b/include/linux/kvm_mem_attr.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * KVM guest page permissions - Definitions.
+ *
+ * Copyright © 2023 Microsoft Corporation.
+ */
+#ifndef __KVM_MEM_ATTR_H__
+#define __KVM_MEM_ATTR_H__
+
+#include <linux/kvm_host.h>
+#include <linux/kvm_types.h>
+
+/* clang-format off */
+
+#define MEM_ATTR_READ			BIT(0)
+#define MEM_ATTR_WRITE			BIT(1)
+#define MEM_ATTR_EXEC			BIT(2)
+#define MEM_ATTR_IMMUTABLE		BIT(3)
+
+#define MEM_ATTR_PROT ( \
+	MEM_ATTR_READ | \
+	MEM_ATTR_WRITE | \
+	MEM_ATTR_EXEC | \
+	MEM_ATTR_IMMUTABLE)
+
+/* clang-format on */
+
+int kvm_permissions_set(struct kvm *kvm, gfn_t gfn_start, gfn_t gfn_end,
+			unsigned long heki_attr);
+unsigned long kvm_permissions_get(struct kvm *kvm, gfn_t gfn);
+
+#endif /* __KVM_MEM_ATTR_H__ */
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 2477b4a16126..2b5b90216565 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -2319,6 +2319,11 @@ struct kvm_memory_attributes {
 
 #define KVM_MEMORY_ATTRIBUTE_PRIVATE           (1ULL << 3)
 
+#define KVM_MEMORY_ATTRIBUTE_HEKI_READ         (1ULL << 4)
+#define KVM_MEMORY_ATTRIBUTE_HEKI_WRITE        (1ULL << 5)
+#define KVM_MEMORY_ATTRIBUTE_HEKI_EXEC         (1ULL << 6)
+#define KVM_MEMORY_ATTRIBUTE_HEKI_IMMUTABLE    (1ULL << 7)
+
 #define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
 
 struct kvm_create_guest_memfd {
diff --git a/virt/heki/Kconfig b/virt/heki/Kconfig
index 5ea75b595667..75a784653e31 100644
--- a/virt/heki/Kconfig
+++ b/virt/heki/Kconfig
@@ -5,6 +5,7 @@
 config HEKI
 	bool "Hypervisor Enforced Kernel Integrity (Heki)"
 	depends on ARCH_SUPPORTS_HEKI && HYPERVISOR_SUPPORTS_HEKI
+	select KVM_GENERIC_MEMORY_ATTRIBUTES
 	help
 	  This feature enhances guest virtual machine security by taking
 	  advantage of security features provided by the hypervisor for guests.
diff --git a/virt/lib/kvm_permissions.c b/virt/lib/kvm_permissions.c
new file mode 100644
index 000000000000..9f4e8027d21c
--- /dev/null
+++ b/virt/lib/kvm_permissions.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KVM guest page permissions - functions.
+ *
+ * Copyright © 2023 Microsoft Corporation.
+ */
+#include <linux/kvm_host.h>
+#include <linux/kvm_mem_attr.h>
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+
+#define pr_fmt(fmt) "kvm: heki: " fmt
+
+/* clang-format off */
+
+static unsigned long kvm_default_permissions =
+	MEM_ATTR_READ |
+	MEM_ATTR_WRITE |
+	MEM_ATTR_EXEC;
+
+static unsigned long kvm_memory_attributes_heki =
+	KVM_MEMORY_ATTRIBUTE_HEKI_READ |
+	KVM_MEMORY_ATTRIBUTE_HEKI_WRITE |
+	KVM_MEMORY_ATTRIBUTE_HEKI_EXEC |
+	KVM_MEMORY_ATTRIBUTE_HEKI_IMMUTABLE;
+
+/* clang-format on */
+
+static unsigned long heki_attr_to_kvm_attr(unsigned long heki_attr)
+{
+	unsigned long kvm_attr = 0;
+
+	if (WARN_ON_ONCE((heki_attr | MEM_ATTR_PROT) != MEM_ATTR_PROT))
+		return 0;
+
+	if (heki_attr & MEM_ATTR_READ)
+		kvm_attr |= KVM_MEMORY_ATTRIBUTE_HEKI_READ;
+	if (heki_attr & MEM_ATTR_WRITE)
+		kvm_attr |= KVM_MEMORY_ATTRIBUTE_HEKI_WRITE;
+	if (heki_attr & MEM_ATTR_EXEC)
+		kvm_attr |= KVM_MEMORY_ATTRIBUTE_HEKI_EXEC;
+	if (heki_attr & MEM_ATTR_IMMUTABLE)
+		kvm_attr |= KVM_MEMORY_ATTRIBUTE_HEKI_IMMUTABLE;
+	return kvm_attr;
+}
+
+static unsigned long kvm_attr_to_heki_attr(unsigned long kvm_attr)
+{
+	unsigned long heki_attr = 0;
+
+	if (kvm_attr & KVM_MEMORY_ATTRIBUTE_HEKI_READ)
+		heki_attr |= MEM_ATTR_READ;
+	if (kvm_attr & KVM_MEMORY_ATTRIBUTE_HEKI_WRITE)
+		heki_attr |= MEM_ATTR_WRITE;
+	if (kvm_attr & KVM_MEMORY_ATTRIBUTE_HEKI_EXEC)
+		heki_attr |= MEM_ATTR_EXEC;
+	if (kvm_attr & KVM_MEMORY_ATTRIBUTE_HEKI_IMMUTABLE)
+		heki_attr |= MEM_ATTR_IMMUTABLE;
+	return heki_attr;
+}
+
+unsigned long kvm_permissions_get(struct kvm *kvm, gfn_t gfn)
+{
+	unsigned long kvm_attr = 0;
+
+	/*
+	 * Retrieve the permissions for a guest page. If not present (i.e., no
+	 * attribute), then return default permissions (RWX).  This means
+	 * setting permissions to 0 resets them to RWX. We might want to
+	 * revisit that in a future version.
+	 */
+	kvm_attr = kvm_get_memory_attributes(kvm, gfn);
+	if (kvm_attr)
+		return kvm_attr_to_heki_attr(kvm_attr);
+	else
+		return kvm_default_permissions;
+}
+EXPORT_SYMBOL_GPL(kvm_permissions_get);
+
+int kvm_permissions_set(struct kvm *kvm, gfn_t gfn_start, gfn_t gfn_end,
+			unsigned long heki_attr)
+{
+	if ((heki_attr | MEM_ATTR_PROT) != MEM_ATTR_PROT)
+		return -EINVAL;
+
+	if (gfn_end <= gfn_start)
+		return -EINVAL;
+
+	if (kvm_range_has_memory_attributes(kvm, gfn_start, gfn_end,
+					    KVM_MEMORY_ATTRIBUTE_HEKI_IMMUTABLE,
+					    false)) {
+		pr_warn_ratelimited(
+			"Guest tried to change immutable permission for GFNs %llx-%llx\n",
+			gfn_start, gfn_end);
+		return -EPERM;
+	}
+
+	return kvm_vm_set_mem_attributes(kvm, gfn_start, gfn_end,
+					 heki_attr_to_kvm_attr(heki_attr),
+					 kvm_memory_attributes_heki);
+}
+EXPORT_SYMBOL_GPL(kvm_permissions_set);
