summaryrefslogtreecommitdiff
path: root/drivers/misc/lkdtm/fortify.c
blob: 0159276656780d560cd9f9dffb9ef9f152950641 (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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2020 Francis Laniel <laniel_francis@privacyrequired.com>
 *
 * Add tests related to fortified functions in this file.
 */
#include "lkdtm.h"
#include <linux/string.h>
#include <linux/slab.h>

static volatile int fortify_scratch_space;

static void lkdtm_FORTIFY_STR_OBJECT(void)
{
	struct target {
		char a[10];
		int foo;
	} target[3] = {};
	/*
	 * Using volatile prevents the compiler from determining the value of
	 * 'size' at compile time. Without that, we would get a compile error
	 * rather than a runtime error.
	 */
	volatile int size = 20;

	pr_info("trying to strcmp() past the end of a struct\n");

	strncpy(target[0].a, target[1].a, size);

	/* Store result to global to prevent the code from being eliminated */
	fortify_scratch_space = target[0].a[3];

	pr_err("FAIL: fortify did not block a strncpy() object write overflow!\n");
	pr_expected_config(CONFIG_FORTIFY_SOURCE);
}

static void lkdtm_FORTIFY_STR_MEMBER(void)
{
	struct target {
		char a[10];
		char b[10];
	} target;
	volatile int size = 20;
	char *src;

	src = kmalloc(size, GFP_KERNEL);
	strscpy(src, "over ten bytes", size);
	size = strlen(src) + 1;

	pr_info("trying to strncpy() past the end of a struct member...\n");

	/*
	 * strncpy(target.a, src, 20); will hit a compile error because the
	 * compiler knows at build time that target.a < 20 bytes. Use a
	 * volatile to force a runtime error.
	 */
	strncpy(target.a, src, size);

	/* Store result to global to prevent the code from being eliminated */
	fortify_scratch_space = target.a[3];

	pr_err("FAIL: fortify did not block a strncpy() struct member write overflow!\n");
	pr_expected_config(CONFIG_FORTIFY_SOURCE);

	kfree(src);
}

static void lkdtm_FORTIFY_MEM_OBJECT(void)
{
	int before[10];
	struct target {
		char a[10];
		int foo;
	} target = {};
	int after[10];
	/*
	 * Using volatile prevents the compiler from determining the value of
	 * 'size' at compile time. Without that, we would get a compile error
	 * rather than a runtime error.
	 */
	volatile int size = 20;

	memset(before, 0, sizeof(before));
	memset(after, 0, sizeof(after));
	fortify_scratch_space = before[5];
	fortify_scratch_space = after[5];

	pr_info("trying to memcpy() past the end of a struct\n");

	pr_info("0: %zu\n", __builtin_object_size(&target, 0));
	pr_info("1: %zu\n", __builtin_object_size(&target, 1));
	pr_info("s: %d\n", size);
	memcpy(&target, &before, size);

	/* Store result to global to prevent the code from being eliminated */
	fortify_scratch_space = target.a[3];

	pr_err("FAIL: fortify did not block a memcpy() object write overflow!\n");
	pr_expected_config(CONFIG_FORTIFY_SOURCE);
}

static void lkdtm_FORTIFY_MEM_MEMBER(void)
{
	struct target {
		char a[10];
		char b[10];
	} target;
	volatile int size = 20;
	char *src;

	src = kmalloc(size, GFP_KERNEL);
	strscpy(src, "over ten bytes", size);
	size = strlen(src) + 1;

	pr_info("trying to memcpy() past the end of a struct member...\n");

	/*
	 * strncpy(target.a, src, 20); will hit a compile error because the
	 * compiler knows at build time that target.a < 20 bytes. Use a
	 * volatile to force a runtime error.
	 */
	memcpy(target.a, src, size);

	/* Store result to global to prevent the code from being eliminated */
	fortify_scratch_space = target.a[3];

	pr_err("FAIL: fortify did not block a memcpy() struct member write overflow!\n");
	pr_expected_config(CONFIG_FORTIFY_SOURCE);

	kfree(src);
}

/*
 * Calls fortified strscpy to test that it returns the same result as vanilla
 * strscpy and generate a panic because there is a write overflow (i.e. src
 * length is greater than dst length).
 */
static void lkdtm_FORTIFY_STRSCPY(void)
{
	char *src;
	char dst[5];

	struct {
		union {
			char big[10];
			char src[5];
		};
	} weird = { .big = "hello!" };
	char weird_dst[sizeof(weird.src) + 1];

	src = kstrdup("foobar", GFP_KERNEL);

	if (src == NULL)
		return;

	/* Vanilla strscpy returns -E2BIG if size is 0. */
	if (strscpy(dst, src, 0) != -E2BIG)
		pr_warn("FAIL: strscpy() of 0 length did not return -E2BIG\n");

	/* Vanilla strscpy returns -E2BIG if src is truncated. */
	if (strscpy(dst, src, sizeof(dst)) != -E2BIG)
		pr_warn("FAIL: strscpy() did not return -E2BIG while src is truncated\n");

	/* After above call, dst must contain "foob" because src was truncated. */
	if (strncmp(dst, "foob", sizeof(dst)) != 0)
		pr_warn("FAIL: after strscpy() dst does not contain \"foob\" but \"%s\"\n",
			dst);

	/* Shrink src so the strscpy() below succeeds. */
	src[3] = '\0';

	/*
	 * Vanilla strscpy returns number of character copied if everything goes
	 * well.
	 */
	if (strscpy(dst, src, sizeof(dst)) != 3)
		pr_warn("FAIL: strscpy() did not return 3 while src was copied entirely truncated\n");

	/* After above call, dst must contain "foo" because src was copied. */
	if (strncmp(dst, "foo", sizeof(dst)) != 0)
		pr_warn("FAIL: after strscpy() dst does not contain \"foo\" but \"%s\"\n",
			dst);

	/* Test when src is embedded inside a union. */
	strscpy(weird_dst, weird.src, sizeof(weird_dst));

	if (strcmp(weird_dst, "hello") != 0)
		pr_warn("FAIL: after strscpy() weird_dst does not contain \"hello\" but \"%s\"\n",
			weird_dst);

	/* Restore src to its initial value. */
	src[3] = 'b';

	/*
	 * Use strlen here so size cannot be known at compile time and there is
	 * a runtime write overflow.
	 */
	strscpy(dst, src, strlen(src));

	pr_err("FAIL: strscpy() overflow not detected!\n");
	pr_expected_config(CONFIG_FORTIFY_SOURCE);

	kfree(src);
}

static struct crashtype crashtypes[] = {
	CRASHTYPE(FORTIFY_STR_OBJECT),
	CRASHTYPE(FORTIFY_STR_MEMBER),
	CRASHTYPE(FORTIFY_MEM_OBJECT),
	CRASHTYPE(FORTIFY_MEM_MEMBER),
	CRASHTYPE(FORTIFY_STRSCPY),
};

struct crashtype_category fortify_crashtypes = {
	.crashtypes = crashtypes,
	.len	    = ARRAY_SIZE(crashtypes),
};