[RFC PATCH v0 6/6] x86: Add prctl() options to control tagged user addresses ABI

From: Bharata B Rao
Date: Thu Mar 10 2022 - 06:17:25 EST


Provide an option for applications to opt-in for a relaxed
tagged ABI via prtcl(). This allows them to pass tagged addresses
to syscalls as pointer arguments.

The implementation is kept separate for AMD UAI as the equivalent
implementation for Intel LAM is likely to be different.

Signed-off-by: Bharata B Rao <bharata@xxxxxxx>
---
arch/x86/Kconfig | 9 +++
arch/x86/include/asm/processor.h | 12 +++
arch/x86/include/asm/uaccess.h | 3 +-
arch/x86/kernel/process.c | 134 +++++++++++++++++++++++++++++++
4 files changed, 157 insertions(+), 1 deletion(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 9f5bd41bf660..b73414eb1c01 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -2436,6 +2436,15 @@ config STRICT_SIGALTSTACK_SIZE

source "kernel/livepatch/Kconfig"

+config X86_TAGGED_ADDR_ABI
+ bool "Enable the tagged user addresses syscall ABI"
+ depends on X86_64 && CPU_SUP_AMD
+ default y
+ help
+ When this option is enabled, user applications can opt-in to a
+ relaxed ABI via prctl() allowing tagged addresses to be passed
+ to system calls as pointer arguments.
+
endmenu

config ARCH_HAS_ADD_PAGES
diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h
index 2c5f12ae7d04..414e2c039c34 100644
--- a/arch/x86/include/asm/processor.h
+++ b/arch/x86/include/asm/processor.h
@@ -863,4 +863,16 @@ bool arch_is_platform_page(u64 paddr);
#define arch_is_platform_page arch_is_platform_page
#endif

+#ifdef CONFIG_X86_TAGGED_ADDR_ABI
+/* PR_{SET,GET}_TAGGED_ADDR_CTRL prctl */
+long set_tagged_addr_ctrl(struct task_struct *task, unsigned long flags,
+ int __user *nr_bits, int __user *offset);
+long get_tagged_addr_ctrl(struct task_struct *task,
+ int __user *nr_bits, int __user *offset);
+#define SET_TAGGED_ADDR_CTRL(flags, nr_bits, offset) \
+ set_tagged_addr_ctrl(current, flags, nr_bits, offset)
+#define GET_TAGGED_ADDR_CTRL(nr_bits, offset) \
+ get_tagged_addr_ctrl(current, nr_bits, offset)
+#endif
+
#endif /* _ASM_X86_PROCESSOR_H */
diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h
index feb2e21c7e09..e415523dc425 100644
--- a/arch/x86/include/asm/uaccess.h
+++ b/arch/x86/include/asm/uaccess.h
@@ -38,7 +38,8 @@ static inline bool __chk_range_not_ok(unsigned long addr, unsigned long size, un
static inline bool __range_not_ok(const void __user *addr, unsigned long size,
unsigned long limit)
{
- if (test_thread_flag(TIF_TAGGED_ADDR))
+ if (IS_ENABLED(CONFIG_X86_TAGGED_ADDR_ABI) &&
+ (current->flags & PF_KTHREAD || test_thread_flag(TIF_TAGGED_ADDR)))
addr = untagged_addr(addr);

__chk_user_ptr(addr);
diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c
index 81d8ef036637..2bc44efdd994 100644
--- a/arch/x86/kernel/process.c
+++ b/arch/x86/kernel/process.c
@@ -26,6 +26,7 @@
#include <linux/elf-randomize.h>
#include <trace/events/power.h>
#include <linux/hw_breakpoint.h>
+#include <linux/sysctl.h>
#include <asm/cpu.h>
#include <asm/apic.h>
#include <linux/uaccess.h>
@@ -231,6 +232,12 @@ static void pkru_flush_thread(void)
pkru_write_default();
}

+static void flush_tagged_addr_state(void)
+{
+ if (IS_ENABLED(CONFIG_X86_TAGGED_ADDR_ABI))
+ clear_thread_flag(TIF_TAGGED_ADDR);
+}
+
void flush_thread(void)
{
struct task_struct *tsk = current;
@@ -240,6 +247,7 @@ void flush_thread(void)

fpu_flush_thread();
pkru_flush_thread();
+ flush_tagged_addr_state();
}

void disable_TSC(void)
@@ -1000,3 +1008,129 @@ long do_arch_prctl_common(struct task_struct *task, int option,

return -EINVAL;
}
+
+#ifdef CONFIG_X86_TAGGED_ADDR_ABI
+/*
+ * Control the relaxed ABI allowing tagged user addresses into the kernel.
+ */
+static unsigned int tagged_addr_disabled;
+
+#ifdef CONFIG_CPU_SUP_AMD
+#define UAI_TAG_BITS 7
+
+static long amd_set_tagged_addr_ctrl(unsigned long flags, int __user *nr_bits,
+ int __user *offset)
+{
+ int val;
+
+ if (!boot_cpu_has(X86_FEATURE_UAI))
+ return -EINVAL;
+
+ /* Disable tagging */
+ if (!(flags & PR_TAGGED_ADDR_ENABLE)) {
+ clear_thread_flag(TIF_TAGGED_ADDR);
+ return 0;
+ }
+
+ if (!nr_bits || !offset)
+ return -EINVAL;
+
+ /*
+ * Do not allow the enabling of the tagged address ABI if globally
+ * disabled via sysctl abi.tagged_addr_disabled.
+ */
+ if (tagged_addr_disabled)
+ return -EINVAL;
+
+ if (get_user(val, nr_bits))
+ return -EFAULT;
+
+ if (val != UAI_TAG_BITS)
+ return -EINVAL;
+
+ if (put_user(val, nr_bits) || put_user(64 - val, offset))
+ return -EFAULT;
+
+ set_thread_flag(TIF_TAGGED_ADDR);
+ return 0;
+}
+
+static long amd_get_tagged_addr_ctrl(int __user *nr_bits, int __user *offset)
+{
+ long ret = 0;
+
+ if (!boot_cpu_has(X86_FEATURE_UAI))
+ return -EINVAL;
+
+ if (test_thread_flag(TIF_TAGGED_ADDR)) {
+ if (nr_bits && put_user(UAI_TAG_BITS, nr_bits))
+ return -EFAULT;
+ if (offset && put_user(64 - UAI_TAG_BITS, offset))
+ return -EFAULT;
+ ret = PR_TAGGED_ADDR_ENABLE;
+ } else {
+ /* Report max tag size */
+ if (nr_bits && put_user(UAI_TAG_BITS, nr_bits))
+ return -EFAULT;
+ }
+ return ret;
+}
+#endif
+
+long set_tagged_addr_ctrl(struct task_struct *task, unsigned long flags,
+ int __user *nr_bits, int __user *offset)
+{
+ unsigned long valid_mask = PR_TAGGED_ADDR_ENABLE;
+
+ if (in_32bit_syscall())
+ return -EINVAL;
+
+ if (flags & ~valid_mask)
+ return -EINVAL;
+
+ if (IS_ENABLED(CONFIG_CPU_SUP_AMD))
+ return amd_set_tagged_addr_ctrl(flags, nr_bits, offset);
+ else
+ return -EINVAL;
+}
+
+long get_tagged_addr_ctrl(struct task_struct *task,
+ int __user *nr_bits, int __user *offset)
+{
+
+ if (in_32bit_syscall())
+ return -EINVAL;
+
+ if (IS_ENABLED(CONFIG_CPU_SUP_AMD))
+ return amd_get_tagged_addr_ctrl(nr_bits, offset);
+ else
+ return -EINVAL;
+}
+
+/*
+ * Global sysctl to disable the tagged user addresses support. This control
+ * only prevents the tagged address ABI enabling via prctl() and does not
+ * disable it for tasks that already opted in to the relaxed ABI.
+ */
+static struct ctl_table tagged_addr_sysctl_table[] = {
+ {
+ .procname = "tagged_addr_disabled",
+ .mode = 0644,
+ .data = &tagged_addr_disabled,
+ .maxlen = sizeof(int),
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = SYSCTL_ZERO,
+ .extra2 = SYSCTL_ONE,
+ },
+ { }
+};
+
+static int __init tagged_addr_init(void)
+{
+ if (!register_sysctl("abi", tagged_addr_sysctl_table))
+ return -EINVAL;
+ return 0;
+}
+
+core_initcall(tagged_addr_init);
+#endif /* CONFIG_X86_TAGGED_ADDR_ABI */
--
2.25.1