Commit a3d0b7a1 authored by Alexander Gordeev's avatar Alexander Gordeev
Browse files

Merge branch 'uaccess-key' into features



Heiko Carstens says:

===================
A rather large series which is supposed to fix the crash below[1], which was
seen when running the memop kernel kvm selftest.

Problem is that cmpxchg_user_key() is executing code with a non-default
key. If a system is IPL'ed with "LOAD NORMAL", and in addition the previous
system used storage keys where the fetch-protection bit is set for some pages,
and the cmpxchg_user_key() is located within such page a protection exception
will happen when executing such code.

Idea of this series is to register all code locations running with a
non-default key at compile time. All functions, which run with a non-default
key, then must explicitly call an init function which initializes the storage
key of all pages containing such code locations with default key, which
prevents such protection exceptions.

Furthermore all functions containing code which may be executed with a
non-default access key must be marked with __kprobes to prevent out-of-line
execution of any instruction of such functions, which would result in the same
problem.

By default the kernel will not issue any storage key changing instructions
like before, which will preserve the keyless-subset mode optimizations in
hosts.

Other possible implementations which I discarded:

- Moving the code to an own section. This would require an s390 specific
  change to modpost.c, which complains about section mismatches (EX_TABLE
  entries in non-default text section). No other architecture has something
  similar, so let's keep this architecture specific hack local.

- Just apply the default storage key to the whole kprobes text
  section. However this would add special s390 semantics to the kprobes text
  section, which no other architecture has. History has shown that such hacks
  fire back sooner or later.

Furthermore, and to keep this whole stuff quite simple, this only works for
code locations in core kernel code, not within modules. After this series
there is no module code left with such code, and as of now I don't see any new
kernel code which runs with a non-default access key.

Note: the original crash can be reproduced by replacing

page_set_storage_key(real, PAGE_DEFAULT_KEY, 1);

with

page_set_storage_key(real, 8, 1);

in arch/s390/kernel/skey.c:__skey_regions_initialize()

And then run tools/testing/selftests/kvm/s390/memop from the kernel selftests.

[1]:

Unable to handle kernel pointer dereference in virtual kernel address space
Failing address: 0000000000000000 TEID: 000000000000080b
Fault in home space mode while using kernel ASCE.
AS:0000000002528007 R3:00000001ffffc007 S:00000001ffffb801 P:000000000000013d
Oops: 0004 ilc:1 [#1]SMP
Modules linked in:
CPU: 3 UID: 0 PID: 791 Comm: memop Not tainted 6.16.0-rc1-00006-g3b568201d0a6-dirty #11 NONE
Hardware name: IBM 3931 A01 704 (z/VM 7.4.0)
Krnl PSW : 0794f00180000000 000003ffe0f4d91e (__cmpxchg_user_key1+0xbe/0x190)
           R:0 T:1 IO:1 EX:1 Key:9 M:1 W:0 P:0 AS:3 CC:3 PM:0 RI:0 EA:3
Krnl GPRS: 070003ffdfbf6af0 0000000000070000 0000000095b5a300 0000000000000000
           00000000f1000000 0000000000000000 0000000000000090 0000000000000000
           0000000000000040 0000000000000018 000003ff9b23d000 0000037fe0ef7bd8
           000003ffdfbf7500 00000000962e4000 0000037f00ffffff 0000037fe0ef7aa0
Krnl Code: 000003ffe0f4d912: ad03f0a0            stosm   160(%r15),3
           000003ffe0f4d916: a7780000            lhi     %r7,0
          #000003ffe0f4d91a: b20a6000            spka    0(%r6)
          >000003ffe0f4d91e: b2790100            sacf    256
           000003ffe0f4d922: a56f0080            llill   %r6,128
           000003ffe0f4d926: 5810a000            l       %r1,0(%r10)
           000003ffe0f4d92a: 141e                nr      %r1,%r14
           000003ffe0f4d92c: c0e7ffffffff        xilf    %r14,4294967295
Call Trace:
 [<000003ffe0f4d91e>] __cmpxchg_user_key1+0xbe/0x190
 [<000003ffe0189c6e>] cmpxchg_guest_abs_with_key+0x2fe/0x370
 [<000003ffe016d28e>] kvm_s390_vm_mem_op_cmpxchg+0x17e/0x350
 [<000003ffe0173284>] kvm_arch_vm_ioctl+0x354/0x6f0
 [<000003ffe015fedc>] kvm_vm_ioctl+0x2cc/0x6e0
 [<000003ffe05348ae>] vfs_ioctl+0x2e/0x70
 [<000003ffe0535e70>] __s390x_sys_ioctl+0xe0/0x100
 [<000003ffe0f40f06>] __do_syscall+0x136/0x340
 [<000003ffe0f4cb2e>] system_call+0x6e/0x90
Last Breaking-Event-Address:
 [<000003ffe0f4d896>] __cmpxchg_user_key1+0x36/0x190
===================

Signed-off-by: default avatarAlexander Gordeev <agordeev@linux.ibm.com>
parents fbb3bdf5 82d6229e
Loading
Loading
Loading
Loading
+13 −5
Original line number Diff line number Diff line
@@ -130,11 +130,19 @@ typedef pte_t *pgtable_t;
static inline void page_set_storage_key(unsigned long addr,
					unsigned char skey, int mapped)
{
	if (!mapped)
		asm volatile(".insn rrf,0xb22b0000,%0,%1,8,0"
			     : : "d" (skey), "a" (addr));
	else
		asm volatile("sske %0,%1" : : "d" (skey), "a" (addr));
	if (!mapped) {
		asm volatile(
			"	.insn	rrf,0xb22b0000,%[skey],%[addr],8,0"
			:
			: [skey] "d" (skey), [addr] "a" (addr)
			: "memory");
	} else {
		asm volatile(
			"	sske	 %[skey],%[addr]"
			:
			: [skey] "d" (skey), [addr] "a" (addr)
			: "memory");
	}
}

static inline unsigned char page_get_storage_key(unsigned long addr)
+32 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_SKEY_H
#define __ASM_SKEY_H

#include <asm/rwonce.h>

struct skey_region {
	unsigned long start;
	unsigned long end;
};

#define SKEY_REGION(_start, _end)			\
	stringify_in_c(.section .skey_region,"a";)	\
	stringify_in_c(.balign 8;)			\
	stringify_in_c(.quad (_start);)			\
	stringify_in_c(.quad (_end);)			\
	stringify_in_c(.previous)

extern int skey_regions_initialized;
extern struct skey_region __skey_region_start[];
extern struct skey_region __skey_region_end[];

void __skey_regions_initialize(void);

static inline void skey_regions_initialize(void)
{
	if (READ_ONCE(skey_regions_initialized))
		return;
	__skey_regions_initialize();
}

#endif /* __ASM_SKEY_H */
+23 −181
Original line number Diff line number Diff line
@@ -473,188 +473,30 @@ do { \

void __cmpxchg_user_key_called_with_bad_pointer(void);

#define CMPXCHG_USER_KEY_MAX_LOOPS 128

static __always_inline int __cmpxchg_user_key(unsigned long address, void *uval,
int __cmpxchg_user_key1(unsigned long address, unsigned char *uval,
			unsigned char old, unsigned char new, unsigned long key);
int __cmpxchg_user_key2(unsigned long address, unsigned short *uval,
			unsigned short old, unsigned short new, unsigned long key);
int __cmpxchg_user_key4(unsigned long address, unsigned int *uval,
			unsigned int old, unsigned int new, unsigned long key);
int __cmpxchg_user_key8(unsigned long address, unsigned long *uval,
			unsigned long old, unsigned long new, unsigned long key);
int __cmpxchg_user_key16(unsigned long address, __uint128_t *uval,
			 __uint128_t old, __uint128_t new, unsigned long key);

static __always_inline int _cmpxchg_user_key(unsigned long address, void *uval,
					     __uint128_t old, __uint128_t new,
					     unsigned long key, int size)
{
	bool sacf_flag;
	int rc = 0;

	switch (size) {
	case 1: {
		unsigned int prev, shift, mask, _old, _new;
		unsigned long count;

		shift = (3 ^ (address & 3)) << 3;
		address ^= address & 3;
		_old = ((unsigned int)old & 0xff) << shift;
		_new = ((unsigned int)new & 0xff) << shift;
		mask = ~(0xff << shift);
		sacf_flag = enable_sacf_uaccess();
		asm_inline volatile(
			"	spka	0(%[key])\n"
			"	sacf	256\n"
			"	llill	%[count],%[max_loops]\n"
			"0:	l	%[prev],%[address]\n"
			"1:	nr	%[prev],%[mask]\n"
			"	xilf	%[mask],0xffffffff\n"
			"	or	%[new],%[prev]\n"
			"	or	%[prev],%[tmp]\n"
			"2:	lr	%[tmp],%[prev]\n"
			"3:	cs	%[prev],%[new],%[address]\n"
			"4:	jnl	5f\n"
			"	xr	%[tmp],%[prev]\n"
			"	xr	%[new],%[tmp]\n"
			"	nr	%[tmp],%[mask]\n"
			"	jnz	5f\n"
			"	brct	%[count],2b\n"
			"5:	sacf	768\n"
			"	spka	%[default_key]\n"
			EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(3b, 5b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(4b, 5b, %[rc], %[prev])
			: [rc] "+&d" (rc),
			  [prev] "=&d" (prev),
			  [address] "+Q" (*(int *)address),
			  [tmp] "+&d" (_old),
			  [new] "+&d" (_new),
			  [mask] "+&d" (mask),
			  [count] "=a" (count)
			: [key] "%[count]" (key << 4),
			  [default_key] "J" (PAGE_DEFAULT_KEY),
			  [max_loops] "J" (CMPXCHG_USER_KEY_MAX_LOOPS)
			: "memory", "cc");
		disable_sacf_uaccess(sacf_flag);
		*(unsigned char *)uval = prev >> shift;
		if (!count)
			rc = -EAGAIN;
		return rc;
	}
	case 2: {
		unsigned int prev, shift, mask, _old, _new;
		unsigned long count;

		shift = (2 ^ (address & 2)) << 3;
		address ^= address & 2;
		_old = ((unsigned int)old & 0xffff) << shift;
		_new = ((unsigned int)new & 0xffff) << shift;
		mask = ~(0xffff << shift);
		sacf_flag = enable_sacf_uaccess();
		asm_inline volatile(
			"	spka	0(%[key])\n"
			"	sacf	256\n"
			"	llill	%[count],%[max_loops]\n"
			"0:	l	%[prev],%[address]\n"
			"1:	nr	%[prev],%[mask]\n"
			"	xilf	%[mask],0xffffffff\n"
			"	or	%[new],%[prev]\n"
			"	or	%[prev],%[tmp]\n"
			"2:	lr	%[tmp],%[prev]\n"
			"3:	cs	%[prev],%[new],%[address]\n"
			"4:	jnl	5f\n"
			"	xr	%[tmp],%[prev]\n"
			"	xr	%[new],%[tmp]\n"
			"	nr	%[tmp],%[mask]\n"
			"	jnz	5f\n"
			"	brct	%[count],2b\n"
			"5:	sacf	768\n"
			"	spka	%[default_key]\n"
			EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(3b, 5b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(4b, 5b, %[rc], %[prev])
			: [rc] "+&d" (rc),
			  [prev] "=&d" (prev),
			  [address] "+Q" (*(int *)address),
			  [tmp] "+&d" (_old),
			  [new] "+&d" (_new),
			  [mask] "+&d" (mask),
			  [count] "=a" (count)
			: [key] "%[count]" (key << 4),
			  [default_key] "J" (PAGE_DEFAULT_KEY),
			  [max_loops] "J" (CMPXCHG_USER_KEY_MAX_LOOPS)
			: "memory", "cc");
		disable_sacf_uaccess(sacf_flag);
		*(unsigned short *)uval = prev >> shift;
		if (!count)
			rc = -EAGAIN;
		return rc;
	}
	case 4:	{
		unsigned int prev = old;

		sacf_flag = enable_sacf_uaccess();
		asm_inline volatile(
			"	spka	0(%[key])\n"
			"	sacf	256\n"
			"0:	cs	%[prev],%[new],%[address]\n"
			"1:	sacf	768\n"
			"	spka	%[default_key]\n"
			EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
			: [rc] "+&d" (rc),
			  [prev] "+&d" (prev),
			  [address] "+Q" (*(int *)address)
			: [new] "d" ((unsigned int)new),
			  [key] "a" (key << 4),
			  [default_key] "J" (PAGE_DEFAULT_KEY)
			: "memory", "cc");
		disable_sacf_uaccess(sacf_flag);
		*(unsigned int *)uval = prev;
		return rc;
	}
	case 8: {
		unsigned long prev = old;

		sacf_flag = enable_sacf_uaccess();
		asm_inline volatile(
			"	spka	0(%[key])\n"
			"	sacf	256\n"
			"0:	csg	%[prev],%[new],%[address]\n"
			"1:	sacf	768\n"
			"	spka	%[default_key]\n"
			EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev])
			: [rc] "+&d" (rc),
			  [prev] "+&d" (prev),
			  [address] "+QS" (*(long *)address)
			: [new] "d" ((unsigned long)new),
			  [key] "a" (key << 4),
			  [default_key] "J" (PAGE_DEFAULT_KEY)
			: "memory", "cc");
		disable_sacf_uaccess(sacf_flag);
		*(unsigned long *)uval = prev;
		return rc;
	}
	case 16: {
		__uint128_t prev = old;

		sacf_flag = enable_sacf_uaccess();
		asm_inline volatile(
			"	spka	0(%[key])\n"
			"	sacf	256\n"
			"0:	cdsg	%[prev],%[new],%[address]\n"
			"1:	sacf	768\n"
			"	spka	%[default_key]\n"
			EX_TABLE_UA_LOAD_REGPAIR(0b, 1b, %[rc], %[prev])
			EX_TABLE_UA_LOAD_REGPAIR(1b, 1b, %[rc], %[prev])
			: [rc] "+&d" (rc),
			  [prev] "+&d" (prev),
			  [address] "+QS" (*(__int128_t *)address)
			: [new] "d" (new),
			  [key] "a" (key << 4),
			  [default_key] "J" (PAGE_DEFAULT_KEY)
			: "memory", "cc");
		disable_sacf_uaccess(sacf_flag);
		*(__uint128_t *)uval = prev;
		return rc;
	}
	case 1:  return __cmpxchg_user_key1(address, uval, old, new, key);
	case 2:  return __cmpxchg_user_key2(address, uval, old, new, key);
	case 4:  return __cmpxchg_user_key4(address, uval, old, new, key);
	case 8:  return __cmpxchg_user_key8(address, uval, old, new, key);
	case 16: return __cmpxchg_user_key16(address, uval, old, new, key);
	default: __cmpxchg_user_key_called_with_bad_pointer();
	}
	__cmpxchg_user_key_called_with_bad_pointer();
	return rc;
	return 0;
}

/**
@@ -686,7 +528,7 @@ static __always_inline int __cmpxchg_user_key(unsigned long address, void *uval,
	BUILD_BUG_ON(sizeof(*(__ptr)) != sizeof(*(__uval)));		\
	might_fault();							\
	__chk_user_ptr(__ptr);						\
	__cmpxchg_user_key((unsigned long)(__ptr), (void *)(__uval),	\
	_cmpxchg_user_key((unsigned long)(__ptr), (void *)(__uval),	\
			  (old), (new), (key), sizeof(*(__ptr)));	\
})

+1 −1
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@ obj-y += processor.o syscall.o ptrace.o signal.o cpcmd.o ebcdic.o nmi.o
obj-y	+= debug.o irq.o ipl.o dis.o vdso.o cpufeature.o
obj-y	+= sysinfo.o lgr.o os_info.o ctlreg.o
obj-y	+= runtime_instr.o cache.o fpu.o dumpstack.o guarded_storage.o sthyi.o
obj-y	+= entry.o reipl.o kdebugfs.o alternative.o
obj-y	+= entry.o reipl.o kdebugfs.o alternative.o skey.o
obj-y	+= nospec-branch.o ipl_vmparm.o machine_kexec_reloc.o unwind_bc.o
obj-y	+= smp.o text_amode31.o stacktrace.o abs_lowcore.o facility.o uv.o wti.o
obj-y	+= diag/
+48 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#include <asm/rwonce.h>
#include <asm/page.h>
#include <asm/skey.h>

int skey_regions_initialized;

static inline unsigned long load_real_address(unsigned long address)
{
	unsigned long real;

	asm volatile(
		"	lra	%[real],0(%[address])\n"
		: [real] "=d" (real)
		: [address] "a" (address)
		: "cc");
	return real;
}

/*
 * Initialize storage keys of registered memory regions with the
 * default key. This is useful for code which is executed with a
 * non-default access key.
 */
void __skey_regions_initialize(void)
{
	unsigned long address, real;
	struct skey_region *r, *end;

	r = __skey_region_start;
	end = __skey_region_end;
	while (r < end) {
		address = r->start & PAGE_MASK;
		do {
			real = load_real_address(address);
			page_set_storage_key(real, PAGE_DEFAULT_KEY, 1);
			address += PAGE_SIZE;
		} while (address < r->end);
		r++;
	}
	/*
	 * Make sure storage keys are initialized before
	 * skey_regions_initialized is changed.
	 */
	barrier();
	WRITE_ONCE(skey_regions_initialized, 1);
}
Loading