summaryrefslogtreecommitdiffstats
path: root/kernel/module/strict_rwx.c
blob: 14fbea66f12f6346c5024acd3c5ec62894125c56 (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
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Module strict rwx
 *
 * Copyright (C) 2015 Rusty Russell
 */

#include <linux/module.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/set_memory.h>
#include "internal.h"

/*
 * LKM RO/NX protection: protect module's text/ro-data
 * from modification and any data from execution.
 *
 * General layout of module is:
 *          [text] [read-only-data] [ro-after-init] [writable data]
 * text_size -----^                ^               ^               ^
 * ro_size ------------------------|               |               |
 * ro_after_init_size -----------------------------|               |
 * size -----------------------------------------------------------|
 *
 * These values are always page-aligned (as is base) when
 * CONFIG_STRICT_MODULE_RWX is set.
 */

/*
 * Since some arches are moving towards PAGE_KERNEL module allocations instead
 * of PAGE_KERNEL_EXEC, keep frob_text() and module_enable_x() independent of
 * CONFIG_STRICT_MODULE_RWX because they are needed regardless of whether we
 * are strict.
 */
static void frob_text(const struct module_layout *layout,
		      int (*set_memory)(unsigned long start, int num_pages))
{
	set_memory((unsigned long)layout->base,
		   PAGE_ALIGN(layout->text_size) >> PAGE_SHIFT);
}

static void frob_rodata(const struct module_layout *layout,
		 int (*set_memory)(unsigned long start, int num_pages))
{
	set_memory((unsigned long)layout->base + layout->text_size,
		   (layout->ro_size - layout->text_size) >> PAGE_SHIFT);
}

static void frob_ro_after_init(const struct module_layout *layout,
			int (*set_memory)(unsigned long start, int num_pages))
{
	set_memory((unsigned long)layout->base + layout->ro_size,
		   (layout->ro_after_init_size - layout->ro_size) >> PAGE_SHIFT);
}

static void frob_writable_data(const struct module_layout *layout,
			int (*set_memory)(unsigned long start, int num_pages))
{
	set_memory((unsigned long)layout->base + layout->ro_after_init_size,
		   (layout->size - layout->ro_after_init_size) >> PAGE_SHIFT);
}

static bool layout_check_misalignment(const struct module_layout *layout)
{
	return WARN_ON(!PAGE_ALIGNED(layout->base)) ||
	       WARN_ON(!PAGE_ALIGNED(layout->text_size)) ||
	       WARN_ON(!PAGE_ALIGNED(layout->ro_size)) ||
	       WARN_ON(!PAGE_ALIGNED(layout->ro_after_init_size)) ||
	       WARN_ON(!PAGE_ALIGNED(layout->size));
}

bool module_check_misalignment(const struct module *mod)
{
	if (!IS_ENABLED(CONFIG_STRICT_MODULE_RWX))
		return false;

	return layout_check_misalignment(&mod->core_layout) ||
	       layout_check_misalignment(&mod->data_layout) ||
	       layout_check_misalignment(&mod->init_layout);
}

void module_enable_x(const struct module *mod)
{
	if (!PAGE_ALIGNED(mod->core_layout.base) ||
	    !PAGE_ALIGNED(mod->init_layout.base))
		return;

	frob_text(&mod->core_layout, set_memory_x);
	frob_text(&mod->init_layout, set_memory_x);
}

void module_enable_ro(const struct module *mod, bool after_init)
{
	if (!IS_ENABLED(CONFIG_STRICT_MODULE_RWX))
		return;
#ifdef CONFIG_STRICT_MODULE_RWX
	if (!rodata_enabled)
		return;
#endif

	set_vm_flush_reset_perms(mod->core_layout.base);
	set_vm_flush_reset_perms(mod->init_layout.base);
	frob_text(&mod->core_layout, set_memory_ro);

	frob_rodata(&mod->data_layout, set_memory_ro);
	frob_text(&mod->init_layout, set_memory_ro);
	frob_rodata(&mod->init_layout, set_memory_ro);

	if (after_init)
		frob_ro_after_init(&mod->data_layout, set_memory_ro);
}

void module_enable_nx(const struct module *mod)
{
	if (!IS_ENABLED(CONFIG_STRICT_MODULE_RWX))
		return;

	frob_rodata(&mod->data_layout, set_memory_nx);
	frob_ro_after_init(&mod->data_layout, set_memory_nx);
	frob_writable_data(&mod->data_layout, set_memory_nx);
	frob_rodata(&mod->init_layout, set_memory_nx);
	frob_writable_data(&mod->init_layout, set_memory_nx);
}

int module_enforce_rwx_sections(Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
				char *secstrings, struct module *mod)
{
	const unsigned long shf_wx = SHF_WRITE | SHF_EXECINSTR;
	int i;

	if (!IS_ENABLED(CONFIG_STRICT_MODULE_RWX))
		return 0;

	for (i = 0; i < hdr->e_shnum; i++) {
		if ((sechdrs[i].sh_flags & shf_wx) == shf_wx) {
			pr_err("%s: section %s (index %d) has invalid WRITE|EXEC flags\n",
			       mod->name, secstrings + sechdrs[i].sh_name, i);
			return -ENOEXEC;
		}
	}

	return 0;
}