summaryrefslogtreecommitdiff
path: root/arch/s390/kernel/stackprotector.c
blob: d4e40483f00887e58ddbf1872645b5b3a42c661d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// SPDX-License-Identifier: GPL-2.0

#ifndef pr_fmt
#define pr_fmt(fmt)	"stackprot: " fmt
#endif

#include <linux/export.h>
#include <linux/uaccess.h>
#include <linux/printk.h>
#include <asm/abs_lowcore.h>
#include <asm/sections.h>
#include <asm/machine.h>
#include <asm/asm-offsets.h>
#include <asm/arch-stackprotector.h>

#ifdef __DECOMPRESSOR

#define DEBUGP		boot_debug
#define EMERGP		boot_emerg
#define PANIC		boot_panic

#else /* __DECOMPRESSOR */

#define DEBUGP		pr_debug
#define EMERGP		pr_emerg
#define PANIC		panic

#endif /* __DECOMPRESSOR */

int __bootdata_preserved(stack_protector_debug);

unsigned long __stack_chk_guard;
EXPORT_SYMBOL(__stack_chk_guard);

struct insn_ril {
	u8 opc1 : 8;
	u8 r1	: 4;
	u8 opc2 : 4;
	u32 imm;
} __packed;

/*
 * Convert a virtual instruction address to a real instruction address. The
 * decompressor needs to patch instructions within the kernel image based on
 * their virtual addresses, while dynamic address translation is still
 * disabled. Therefore a translation from virtual kernel image addresses to
 * the corresponding physical addresses is required.
 *
 * After dynamic address translation is enabled and when the kernel needs to
 * patch instructions such a translation is not required since the addresses
 * are identical.
 */
static struct insn_ril *vaddress_to_insn(unsigned long vaddress)
{
#ifdef __DECOMPRESSOR
	return (struct insn_ril *)__kernel_pa(vaddress);
#else
	return (struct insn_ril *)vaddress;
#endif
}

static unsigned long insn_to_vaddress(struct insn_ril *insn)
{
#ifdef __DECOMPRESSOR
	return (unsigned long)__kernel_va(insn);
#else
	return (unsigned long)insn;
#endif
}

#define INSN_RIL_STRING_SIZE (sizeof(struct insn_ril) * 2 + 1)

static void insn_ril_to_string(char *str, struct insn_ril *insn)
{
	u8 *ptr = (u8 *)insn;
	int i;

	for (i = 0; i < sizeof(*insn); i++)
		hex_byte_pack(&str[2 * i], ptr[i]);
	str[2 * i] = 0;
}

static void stack_protector_dump(struct insn_ril *old, struct insn_ril *new)
{
	char ostr[INSN_RIL_STRING_SIZE];
	char nstr[INSN_RIL_STRING_SIZE];

	insn_ril_to_string(ostr, old);
	insn_ril_to_string(nstr, new);
	DEBUGP("%016lx: %s -> %s\n", insn_to_vaddress(old), ostr, nstr);
}

static int stack_protector_verify(struct insn_ril *insn, unsigned long kernel_start)
{
	char istr[INSN_RIL_STRING_SIZE];
	unsigned long vaddress, offset;

	/* larl */
	if (insn->opc1 == 0xc0 && insn->opc2 == 0x0)
		return 0;
	/* lgrl */
	if (insn->opc1 == 0xc4 && insn->opc2 == 0x8)
		return 0;
	insn_ril_to_string(istr, insn);
	vaddress = insn_to_vaddress(insn);
	if (__is_defined(__DECOMPRESSOR)) {
		offset = (unsigned long)insn - kernel_start + TEXT_OFFSET;
		EMERGP("Unexpected instruction at %016lx/%016lx: %s\n", vaddress, offset, istr);
		PANIC("Stackprotector error\n");
	} else {
		EMERGP("Unexpected instruction at %016lx: %s\n", vaddress, istr);
	}
	return -EINVAL;
}

int __stack_protector_apply(unsigned long *start, unsigned long *end, unsigned long kernel_start)
{
	unsigned long canary, *loc;
	struct insn_ril *insn, new;
	int rc;

	/*
	 * Convert LARL/LGRL instructions to LLILF so register R1 contains the
	 * address of the per-cpu / per-process stack canary:
	 *
	 * LARL/LGRL R1,__stack_chk_guard => LLILF R1,__lc_stack_canary
	 */
	canary = __LC_STACK_CANARY;
	if (machine_has_relocated_lowcore())
		canary += LOWCORE_ALT_ADDRESS;
	for (loc = start; loc < end; loc++) {
		insn = vaddress_to_insn(*loc);
		rc = stack_protector_verify(insn, kernel_start);
		if (rc)
			return rc;
		new = *insn;
		new.opc1 = 0xc0;
		new.opc2 = 0xf;
		new.imm = canary;
		if (stack_protector_debug)
			stack_protector_dump(insn, &new);
		s390_kernel_write(insn, &new, sizeof(*insn));
	}
	return 0;
}

#ifdef __DECOMPRESSOR
void __stack_protector_apply_early(unsigned long kernel_start)
{
	unsigned long *start, *end;

	start = (unsigned long *)vmlinux.stack_prot_start;
	end = (unsigned long *)vmlinux.stack_prot_end;
	__stack_protector_apply(start, end, kernel_start);
}
#endif