lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251106160735.2638485-2-andrealmeid@igalia.com>
Date: Thu,  6 Nov 2025 13:07:35 -0300
From: André Almeida <andrealmeid@...lia.com>
To: Catalin Marinas <catalin.marinas@....com>,
	Will Deacon <will@...nel.org>,
	Mark Rutland <mark.rutland@....com>,
	Mark Brown <broonie@...nel.org>
Cc: linux-arm-kernel@...ts.infradead.org,
	linux-kernel@...r.kernel.org,
	kernel-dev@...lia.com,
	Ryan Houdek <houdek.ryan@...-emu.org>,
	Billy Laws <blaws05@...il.com>,
	André Almeida <andrealmeid@...lia.com>
Subject: [RFC PATCH 1/1] arch: arm64: Implement unaligned atomic emulation

Implement support for emulating unaligned atomic operations on arm64.
User applications that wish to enable support for this should use the
pctrl() flag `PR_ARM64_UNALIGN_ATOMIC_EMULATE`.

Signed-off-by: André Almeida <andrealmeid@...lia.com>
---
 arch/arm64/include/asm/exception.h   |   1 +
 arch/arm64/include/asm/processor.h   |   3 +
 arch/arm64/include/asm/thread_info.h |   1 +
 arch/arm64/kernel/Makefile           |   2 +-
 arch/arm64/kernel/process.c          |  15 +
 arch/arm64/kernel/unaligned_atomic.c | 520 +++++++++++++++++++++++++++
 arch/arm64/mm/fault.c                |  10 +
 include/uapi/linux/prctl.h           |   5 +
 kernel/sys.c                         |   7 +-
 9 files changed, 562 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm64/kernel/unaligned_atomic.c

diff --git a/arch/arm64/include/asm/exception.h b/arch/arm64/include/asm/exception.h
index a2da3cb21c24..f6dd1b9afe69 100644
--- a/arch/arm64/include/asm/exception.h
+++ b/arch/arm64/include/asm/exception.h
@@ -82,6 +82,7 @@ void do_sp_pc_abort(unsigned long addr, unsigned long esr, struct pt_regs *regs)
 void bad_el0_sync(struct pt_regs *regs, int reason, unsigned long esr);
 void do_el0_cp15(unsigned long esr, struct pt_regs *regs);
 int do_compat_alignment_fixup(unsigned long addr, struct pt_regs *regs);
+int do_unaligned_atomic_fixup(struct pt_regs *regs, u64 *fault_addr);
 void do_el0_svc(struct pt_regs *regs);
 void do_el0_svc_compat(struct pt_regs *regs);
 void do_el0_fpac(struct pt_regs *regs, unsigned long esr);
diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h
index 61d62bfd5a7b..bc62a690cd00 100644
--- a/arch/arm64/include/asm/processor.h
+++ b/arch/arm64/include/asm/processor.h
@@ -437,5 +437,8 @@ int set_tsc_mode(unsigned int val);
 #define GET_TSC_CTL(adr)        get_tsc_mode((adr))
 #define SET_TSC_CTL(val)        set_tsc_mode((val))
 
+int set_unalign_atomic_ctl(unsigned int val);
+#define ARM64_SET_UNALIGN_ATOMIC_CTL(val)	set_unalign_atomic_ctl((val))
+
 #endif /* __ASSEMBLY__ */
 #endif /* __ASM_PROCESSOR_H */
diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h
index f241b8601ebd..636a44d72064 100644
--- a/arch/arm64/include/asm/thread_info.h
+++ b/arch/arm64/include/asm/thread_info.h
@@ -86,6 +86,7 @@ void arch_setup_new_exec(void);
 #define TIF_TSC_SIGSEGV		30	/* SIGSEGV on counter-timer access */
 #define TIF_LAZY_MMU		31	/* Task in lazy mmu mode */
 #define TIF_LAZY_MMU_PENDING	32	/* Ops pending for lazy mmu mode exit */
+#define TIF_UNALIGN_ATOMIC_EMULATE	33
 
 #define _TIF_SIGPENDING		(1 << TIF_SIGPENDING)
 #define _TIF_NEED_RESCHED	(1 << TIF_NEED_RESCHED)
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 76f32e424065..54ce606d0552 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -34,7 +34,7 @@ obj-y			:= debug-monitors.o entry.o irq.o fpsimd.o		\
 			   cpufeature.o alternative.o cacheinfo.o		\
 			   smp.o smp_spin_table.o topology.o smccc-call.o	\
 			   syscall.o proton-pack.o idle.o patching.o pi/	\
-			   rsi.o jump_label.o
+			   rsi.o jump_label.o unaligned_atomic.o
 
 obj-$(CONFIG_COMPAT)			+= sys32.o signal32.o			\
 					   sys_compat.o
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 95d4bb848096..c47ec1ac8fe2 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -967,3 +967,18 @@ int set_tsc_mode(unsigned int val)
 
 	return do_set_tsc_mode(val);
 }
+
+int set_unalign_atomic_ctl(unsigned int val)
+{
+	unsigned long valid_mask = PR_ARM64_UNALIGN_ATOMIC_EMULATE;
+	if (val & ~valid_mask)
+		return -EINVAL;
+
+	/*
+	 * TODO: check if this is running in a ARM v8.1 or greater.
+	 * Refuse otherwise.
+	 */
+
+	update_thread_flag(TIF_UNALIGN_ATOMIC_EMULATE, val & PR_ARM64_UNALIGN_ATOMIC_EMULATE);
+	return 0;
+}
diff --git a/arch/arm64/kernel/unaligned_atomic.c b/arch/arm64/kernel/unaligned_atomic.c
new file mode 100644
index 000000000000..fc1a7f4ddfdd
--- /dev/null
+++ b/arch/arm64/kernel/unaligned_atomic.c
@@ -0,0 +1,520 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Unaligned atomic emulation by André Almeida <andrealmeid@...lia.com>
+ * Derived from original work by Billy Laws <blaws05@...il.com>
+ */
+
+#include <linux/cacheflush.h>
+#include <linux/compiler.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/semaphore.h>
+#include <linux/uaccess.h>
+
+#include <asm/alternative-macros.h>
+#include <asm/asm-extable.h>
+#include <asm/exception.h>
+#include <asm/ptrace.h>
+#include <asm/traps.h>
+
+#define __LOAD_ACQUIRE_RCPC(sfx, regs...) \
+	ALTERNATIVE("ldar" #sfx "\t" #regs,     \
+			".arch_extension rcpc\n"    \
+			"ldapr" #sfx "\t" #regs,    \
+			ARM64_HAS_LDAPR)
+
+struct fault_info {
+	int error;
+	u64 addr;
+	u64 size;
+};
+
+#define ATOMIC_MEM_MASK 0x3b200c00
+#define ATOMIC_MEM_INST 0x38200000
+
+#define RCPC2_MASK  0x3fe00c00
+#define LDAPUR_INST 0x19400000
+#define STLUR_INST  0x19000000
+
+#define LDAXR_MASK 0x3ffffc00
+
+#define LDAXP_MASK 0xbfff8000
+#define LDAXP_INST 0x887f8000
+
+#define LDAR_INST  0x08dffc00
+#define LDAPR_INST 0x38bfc000
+#define STLR_INST  0x089ffc00
+
+#define ATOMIC_ADD_OP	0x0
+#define ATOMIC_CLR_OP	0x1
+#define ATOMIC_EOR_OP	0x2
+#define ATOMIC_SET_OP	0x3
+#define ATOMIC_SWAP_OP	0x8
+
+#define get_reg(gprs, reg) (reg == 31 ? 0 : gprs[reg])
+#define set_reg(gprs, reg, val) do { if (reg != 31) gprs[reg] = val; } while (0)
+
+#define get_addr_reg(instr) ((instr >> 5) & 0b11111)
+#define get_size(instr) (1 << (instr >> 30))
+
+static DEFINE_MUTEX(buslock);
+
+static struct fault_info do_load_acquire_128(u64 addr, u128 *result)
+{
+	u64 lower, upper, orig_addr = addr;
+	int ret;
+
+	addr = (u64)__uaccess_mask_ptr((void __user *)addr);
+
+	if (!access_ok((void __user *)orig_addr, 16))
+		return (struct fault_info) {.error = -EFAULT, .addr = orig_addr, .size = 16};
+
+	uaccess_enable_privileged();
+	asm volatile("1: ldaxp %[resultlower], %[resultupper], [%[addr]]\n"
+			"   clrex\n"
+			"2:\n"
+			_ASM_EXTABLE_UACCESS_ERR(1b, 2b, %w[ret])
+			: [resultlower] "=r"(lower), [resultupper] "=r"(upper), [ret] "+r"(ret)
+			: [addr] "r"(addr)
+			: "memory");
+	uaccess_disable_privileged();
+
+	if (ret)
+		return (struct fault_info) {.error = ret, .addr = orig_addr, .size = 16};
+
+	*result = (u128)upper << 64 | lower;
+
+	return (struct fault_info) {0};
+}
+
+/*
+ * Do a load acquire at address `addr` and save it at `result`
+ */
+static struct fault_info do_load_acquire_64(u64 addr, u64 *result)
+{
+	u64 orig_addr = addr;
+	int ret = 0;
+
+	addr = (u64)__uaccess_mask_ptr((void __user *)addr);
+
+	if (!access_ok((void __user *)orig_addr, 8))
+		return (struct fault_info) {.error = -EFAULT, .addr = orig_addr, .size = 8};
+
+	uaccess_enable_privileged();
+	asm volatile("1: " __LOAD_ACQUIRE_RCPC(, %[result], [%[addr]]) "\n"
+			"2:\n"
+			_ASM_EXTABLE_UACCESS_ERR(1b, 2b, %w[ret])
+			: [result] "=r"(*result), [ret] "+r"(ret)
+			: [addr] "r"(addr)
+			: "memory");
+	uaccess_disable_privileged();
+
+	return (struct fault_info) {.error = ret, .addr = orig_addr, .size = 8};
+}
+
+/*
+ * If *expected value is found in addr, swap it for val
+ * Otherwise, write the value found at addr at expected address
+ */
+static struct fault_info do_store_cas_64(u64 *expected, u64 val, u64 addr)
+{
+	u64 orig_addr = addr, tmp, oldval;
+	int ret = 0;
+
+	addr = (u64)__uaccess_mask_ptr((void __user *)addr);
+
+	if (!access_ok((void __user *)orig_addr, 8))
+		return (struct fault_info) {.error = -EFAULT, .addr = orig_addr, .size = 8};
+
+	uaccess_enable_privileged();
+	asm volatile("1: ldxr %[oldval], [%[addr]]\n"
+			"   cmp  %[oldval], %[expected]\n"
+			"   b.ne 3f\n"
+			"2: stlxr %w[tmp], %[val], [%[addr]]\n"
+			"   cbnz %w[tmp], 1b\n"
+			"3:\n"
+			_ASM_EXTABLE_UACCESS_ERR(1b, 3b, %w[ret])
+			_ASM_EXTABLE_UACCESS_ERR(2b, 3b, %w[ret])
+			: [tmp] "=&r"(tmp), [oldval] "=&r"(oldval), [ret] "+r"(ret)
+			: [addr] "r"(addr), [expected] "r"(*expected), [val] "r"(val)
+			: "memory", "cc");
+	uaccess_disable_privileged();
+
+	if (ret)
+		return (struct fault_info) {.error = ret, .addr = orig_addr, .size = 8};
+
+	if (oldval != *expected) {
+		*expected = oldval;
+		return (struct fault_info) {.error = -EAGAIN};
+	}
+
+	return (struct fault_info) {0};
+}
+
+/*
+ * If possible, do one 128 bit load. Otherwise, do two 64 bit loads and combine
+ * the results.
+ */
+static struct fault_info do_load_64(u64 addr, u64 *result)
+{
+	u64 alignment_mask = 0b1111, align_offset = addr & alignment_mask;
+	struct fault_info fi = {0};
+	u128 tmp;
+
+	/* The address crosses a 16 byte boundary and needs two loads */
+	if (align_offset > 8) {
+		u64 alignment = addr & 0b111, upper_val, lower_val;
+
+		addr &= ~0b111ULL;
+
+		fi = do_load_acquire_64(addr + 8, &upper_val);
+		if (fi.error)
+			return fi;
+
+		fi = do_load_acquire_64(addr, &lower_val);
+		if (fi.error)
+			return fi;
+
+		tmp = ((u128) upper_val << 64) | lower_val;
+		*result = tmp >> (alignment * 8);
+	} else {
+		addr &= ~alignment_mask;
+
+		fi = do_load_acquire_128(addr, &tmp);
+		if (fi.error)
+			return fi;
+
+		*result = tmp >> (align_offset * 8);
+	}
+
+	return fi;
+}
+
+static u64 do_atomic_mem_op(u8 op, u64 src_val, u64 desired)
+{
+	switch (op) {
+	case ATOMIC_ADD_OP:
+		return src_val + desired;
+	case ATOMIC_CLR_OP:
+		return src_val & ~desired;
+	case ATOMIC_EOR_OP:
+		return src_val & desired;
+	case ATOMIC_SET_OP:
+		return src_val ^ desired;
+	case ATOMIC_SWAP_OP:
+		return desired;
+	default:
+		BUG();
+	}
+
+	/* Unreachable */
+	return 0;
+}
+
+static struct fault_info load_cas(u64 desired_src, u64 addr, u8 op, u64 alignment,
+				     u128 *aux_desired, u128 *aux_expected,
+				     u128 *aux_actual)
+{
+	u128 tmp_expected, tmp_desired, tmp_actual, mask = ~0ULL, desired, expected, neg_mask;
+	u64 addr_upper, load_order_upper, load_order_lower;
+	struct fault_info fi;
+
+	mask <<= alignment * 8;
+	addr_upper = addr + 8;
+	neg_mask = ~mask;
+
+	fi = do_load_acquire_64(addr_upper, &load_order_upper);
+	if (fi.error)
+		return fi;
+	fi = do_load_acquire_64(addr, &load_order_lower);
+	if (fi.error)
+		return fi;
+
+	tmp_actual = (u128)load_order_upper << 64 | load_order_lower;
+
+	desired = do_atomic_mem_op(op, tmp_actual >> (alignment * 8), desired_src);
+	expected = (tmp_actual >> (alignment * 8));
+
+	tmp_expected = tmp_actual;
+	tmp_expected &= neg_mask;
+	tmp_expected |= expected << (alignment * 8);
+
+	tmp_desired = tmp_expected;
+	tmp_desired &= neg_mask;
+	tmp_desired |= desired << (alignment * 8);
+
+	*aux_desired = tmp_desired;
+	*aux_expected = tmp_expected;
+	*aux_actual = tmp_actual;
+
+	return (struct fault_info) {0};
+}
+
+/*
+ * After CAS failed, check if we need to try again or if we should return error.
+ * Returns true if needs to retry.
+ */
+static bool handle_fail(u128 tmp_expected, u128 tmp_desired, u128 mask,
+			u64 *result, bool retry, bool tear,
+			u64 alignment)
+{
+	u128 neg_mask = ~mask,
+	     failed_result_our_bits = tmp_expected & mask,
+	     failed_result_not_our_bits = tmp_expected & neg_mask,
+	     failed_desired_not_our_bits = tmp_desired & neg_mask;
+	u64 failed_result = failed_result_our_bits >> (alignment * 8);
+
+	/*
+	 * If the bits changed weren't part of our regular CAS, then we retry.
+	 */
+	if ((failed_result_not_our_bits ^ failed_desired_not_our_bits) != 0)
+		return true;
+
+	/*
+	 * This happens in the case that between load and CAS that something has
+	 * store our desired in to the memory location. This means our CAS fails
+	 * because what we wanted to store was already stored.
+	 */
+	if (retry) {
+		/* If we are retrying and tearing then we can't do anything */
+		if (tear) {
+			*result = failed_result;
+			return false;
+		} else {
+			return true;
+		}
+	} else {
+		/*
+		 * With we were called without retry, then we have failed
+		 * regardless of tear. CAS failed but handled successfully
+		 */
+		*result = failed_result;
+	}
+
+	return false;
+}
+
+/*
+ * This instruction reads a 64-bit doubleword from memory, and compares it
+ * against the value held in a first register. If the comparison is equal, the
+ * value in a second register is written to memory. If the comparison is not
+ * equal, the architecture permits writing the value read from the location to
+ * memory.
+ *
+ * To handle an unaligned CAS, the code first loads two 64-bit words. Then, if
+ * what is found in the word is the same as the expected value, the code tries
+ * to do two 64-bit writes in the address. If both stores works then return
+ * success. If only the first store works, then the code is in a "tear" state
+ * and returns with the word found in the address.
+ *
+ * TODO: here we threat every operation as it's a cross cache address,
+ * meaning that we need two 64 bit ops to make it work. That works for
+ * all cases, but it's slower and unnecessary for the cases that doesn't
+ * cross it and can do a single 128 bit operation.
+ */
+static struct fault_info do_cas_64(bool retry, u64 desired_src, u64 expected_src,
+				   u64 addr, u8 op, u64 *result)
+{
+	u128 tmp_expected, tmp_desired, tmp_actual, mask = ~0ULL;
+	u64 alignment = addr & 0b111, addr_upper;
+	struct fault_info fi;
+	bool tear = false;
+
+	mask <<= alignment * 8;
+	addr &= ~0b111ULL;
+	addr_upper = addr + 8;
+
+	/*
+	 * TODO: take this lock only if we need to emulate a split lock
+	 */
+	guard(mutex)(&buslock);
+
+retry:
+	fi = load_cas(desired_src, addr, op, alignment, &tmp_desired, &tmp_expected, &tmp_actual);
+	if (fi.error)
+		return fi;
+
+	if (tmp_expected == tmp_actual) {
+		u128 expected = (tmp_actual >> (alignment * 8));
+		u64 tmp_expected_lower = tmp_expected,
+		    tmp_expected_upper = tmp_expected >> 64,
+		    tmp_desired_lower = tmp_desired,
+		    tmp_desired_upper = tmp_desired >> 64;
+
+		fi = do_store_cas_64(&tmp_expected_upper, tmp_desired_upper, addr_upper);
+		if (fi.error && fi.error != -EAGAIN)
+			return fi;
+
+		if (fi.error == 0) {
+			fi = do_store_cas_64(&tmp_expected_lower, tmp_desired_lower, addr);
+			if (fi.error && fi.error != -EAGAIN)
+				return fi;
+
+			/* Both store() worked, CAS succeeded */
+			if (fi.error == 0) {
+				*result = expected;
+				return fi;
+			/* A partial store() happened, tear state */
+			} else {
+				tear = true;
+			}
+		}
+
+		tmp_expected = tmp_expected_upper;
+		tmp_expected <<= 64;
+		tmp_expected |= tmp_expected_lower;
+	} else {
+		tmp_expected = tmp_actual;
+	}
+
+	if (handle_fail(tmp_expected, tmp_desired, mask, result, retry, tear, alignment))
+		goto retry;
+
+	return (struct fault_info) {0};
+}
+
+/*
+ * For a giving atomic memory operation, parse it and call the desired op
+ */
+static struct fault_info handle_atomic_mem_op(u32 instr, u64 *gprs)
+{
+	u32 size = get_size(instr), result_reg = instr & 0b11111,
+		   source_reg = (instr >> 16) & 0b11111,
+		   addr_reg = get_addr_reg(instr);
+	u64 addr = get_reg(gprs, addr_reg);
+	u8 op = (instr >> 12) & 0xf;
+	struct fault_info fi = {0};
+
+	if (size == 8) {
+		u64 res = 0;
+
+		switch (op) {
+		case ATOMIC_ADD_OP:
+		case ATOMIC_CLR_OP:
+		case ATOMIC_EOR_OP:
+		case ATOMIC_SET_OP:
+		case ATOMIC_SWAP_OP:
+			break;
+		default:
+			pr_warn("Unhandled atomic mem op 0x%02x\n", op);
+			return (struct fault_info) {.error = -EINVAL};
+		}
+
+		fi = do_cas_64(true, get_reg(gprs, source_reg), 0, addr, op, &res);
+
+		/*
+		 * If the operation succeeded and our dest reg is not zero, we
+		 * need to update the result reg with what was in memory
+		 */
+		if (!fi.error)
+			set_reg(gprs, result_reg, res);
+	} else {
+		fi.error = -EINVAL;
+	}
+
+	return fi;
+}
+
+static struct fault_info handle_atomic_load(u32 instr, u64 *gprs, s64 offset)
+{
+	u32 size = get_size(instr), result_reg = instr & 0b11111,
+		   addr_reg = get_addr_reg(instr);
+	u64 addr = get_reg(gprs, addr_reg) + offset, res;
+
+	struct fault_info fi = {0};
+
+	if (size == 8) {
+		fi = do_load_64(addr, &res);
+		if (!fi.error)
+			set_reg(gprs, result_reg, res);
+	} else {
+		fi.error = -EINVAL;
+	}
+
+	return fi;
+}
+
+static struct fault_info handle_atomic_store(u32 instr, u64 *gprs, s64 offset)
+{
+	u32 size = get_size(instr), data_reg = instr & 0x1F, addr_reg = get_addr_reg(instr);
+	u64 addr = get_reg(gprs, addr_reg) + offset;
+	struct fault_info fi = {0};
+
+	if (size == 8) {
+		u64 res;
+
+		fi = do_cas_64(false, get_reg(gprs, data_reg), 0, addr, ATOMIC_SWAP_OP, &res);
+	} else {
+		fi.error = -EINVAL;
+	}
+
+	return fi;
+}
+
+static struct fault_info decode_instruction(u32 instr, u64 *gprs)
+{
+	struct fault_info fi = {0};
+	s32 offset = (s32)(instr) << 11 >> 23, size = get_size(instr);
+
+	/* TODO: We only support 64-bit instructions for now */
+	if (size != 8)
+		goto exit;
+
+	if ((instr & LDAXR_MASK) == LDAR_INST || (instr & LDAXR_MASK) == LDAPR_INST)
+		return handle_atomic_load(instr, gprs, 0);
+
+	if ((instr & LDAXR_MASK) == STLR_INST)
+		return  handle_atomic_store(instr, gprs, 0);
+
+	if ((instr & RCPC2_MASK) == LDAPUR_INST)
+		return handle_atomic_load(instr, gprs, offset);
+
+	if ((instr & RCPC2_MASK) == STLUR_INST)
+		return handle_atomic_store(instr, gprs, offset);
+
+	if ((instr & ATOMIC_MEM_MASK) == ATOMIC_MEM_INST)
+		return handle_atomic_mem_op(instr, gprs);
+
+	/* TODO: Handle CASAL and CASPAL as well */
+
+exit:
+	fi.error = -EINVAL;
+	return fi;
+}
+
+/*
+ * After a fault access happened, move the pointer to the end of the bad section
+ */
+static void increment_fault_address(u64 **fault_addr, int size)
+{
+	u64 *addr = *fault_addr;
+	u8 tmp;
+	int i;
+
+	for (i = 0; i < size - 1 && !get_user(tmp, (u8 __user *)(addr + i)); i++)
+		(*fault_addr)++;
+}
+
+int do_unaligned_atomic_fixup(struct pt_regs *regs, u64 *fault_addr)
+{
+	u32 *pc = (u32 *)regs->pc, instr;
+	u64 *gprs = (u64 *)regs->regs;
+	struct fault_info fi;
+
+	if (get_user(instr, (u32 __user *)(pc)))
+		return -EFAULT;
+
+	fi = decode_instruction(instr, gprs);
+
+	if (fi.error) {
+		*fault_addr = fi.addr;
+		if (fi.error == -EFAULT)
+			increment_fault_address(&fault_addr, fi.size);
+
+		return fi.error;
+	}
+
+	arm64_skip_faulting_instruction(regs, 4);
+	return 0;
+}
diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c
index d816ff44faff..7bd93aaa5140 100644
--- a/arch/arm64/mm/fault.c
+++ b/arch/arm64/mm/fault.c
@@ -798,6 +798,16 @@ static int do_alignment_fault(unsigned long far, unsigned long esr,
 	if (IS_ENABLED(CONFIG_COMPAT_ALIGNMENT_FIXUPS) &&
 	    compat_user_mode(regs))
 		return do_compat_alignment_fixup(far, regs);
+
+	if (user_mode(regs) && test_thread_flag(TIF_UNALIGN_ATOMIC_EMULATE)) {
+		u64 page_fault_address;
+		int ret = do_unaligned_atomic_fixup(regs, &page_fault_address);
+
+		if (!ret)
+			return 0;
+		else if (ret == -EFAULT)
+			return do_translation_fault(page_fault_address, ESR_ELx_FSC_FAULT | ESR_ELx_WNR, regs);
+	}
 	do_bad_area(far, esr, regs);
 	return 0;
 }
diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index 51c4e8c82b1e..1202ce10e386 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -386,4 +386,9 @@ struct prctl_mm_map {
 # define PR_FUTEX_HASH_SET_SLOTS	1
 # define PR_FUTEX_HASH_GET_SLOTS	2
 
+#define PR_ARM64_SET_UNALIGN_ATOMIC 0x46455849
+# define PR_ARM64_UNALIGN_ATOMIC_EMULATE	(1UL << 0)
+# define PR_ARM64_UNALIGN_ATOMIC_BACKPATCH	(1UL << 1)
+# define PR_ARM64_UNALIGN_ATOMIC_STRICT_SPLIT_LOCKS	(1UL << 2)
+
 #endif /* _LINUX_PRCTL_H */
diff --git a/kernel/sys.c b/kernel/sys.c
index 8b58eece4e58..5966ce4a075c 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -158,7 +158,9 @@
 #ifndef PPC_SET_DEXCR_ASPECT
 # define PPC_SET_DEXCR_ASPECT(a, b, c)	(-EINVAL)
 #endif
-
+#ifndef ARM64_SET_UNALIGN_ATOMIC_CTL
+# define ARM64_SET_UNALIGN_ATOMIC_CTL(a)		(-EINVAL)
+#endif
 /*
  * this is where the system-wide overflow UID and GID are defined, for
  * architectures that now have 32-bit UID/GID but didn't in the past
@@ -2868,6 +2870,9 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case PR_FUTEX_HASH:
 		error = futex_hash_prctl(arg2, arg3, arg4);
 		break;
+	case PR_ARM64_SET_UNALIGN_ATOMIC:
+		error = ARM64_SET_UNALIGN_ATOMIC_CTL(arg2);
+		break;
 	default:
 		trace_task_prctl_unknown(option, arg2, arg3, arg4, arg5);
 		error = -EINVAL;
-- 
2.51.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ