.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
---|
1 | 2 | /* |
---|
2 | 3 | * Module and Firmware Pinning Security Module |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright 2011-2016 Google Inc. |
---|
5 | 6 | * |
---|
6 | 7 | * Author: Kees Cook <keescook@chromium.org> |
---|
7 | | - * |
---|
8 | | - * This software is licensed under the terms of the GNU General Public |
---|
9 | | - * License version 2, as published by the Free Software Foundation, and |
---|
10 | | - * may be copied, distributed, and modified under those terms. |
---|
11 | | - * |
---|
12 | | - * This program is distributed in the hope that it will be useful, |
---|
13 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
14 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
15 | | - * GNU General Public License for more details. |
---|
16 | 8 | */ |
---|
17 | 9 | |
---|
18 | 10 | #define pr_fmt(fmt) "LoadPin: " fmt |
---|
19 | 11 | |
---|
20 | 12 | #include <linux/module.h> |
---|
21 | 13 | #include <linux/fs.h> |
---|
| 14 | +#include <linux/kernel_read_file.h> |
---|
22 | 15 | #include <linux/lsm_hooks.h> |
---|
23 | 16 | #include <linux/mount.h> |
---|
| 17 | +#include <linux/blkdev.h> |
---|
24 | 18 | #include <linux/path.h> |
---|
25 | 19 | #include <linux/sched.h> /* current */ |
---|
26 | 20 | #include <linux/string_helpers.h> |
---|
.. | .. |
---|
44 | 38 | kfree(pathname); |
---|
45 | 39 | } |
---|
46 | 40 | |
---|
47 | | -static int enabled = IS_ENABLED(CONFIG_SECURITY_LOADPIN_ENABLED); |
---|
| 41 | +static int enforce = IS_ENABLED(CONFIG_SECURITY_LOADPIN_ENFORCE); |
---|
| 42 | +static char *exclude_read_files[READING_MAX_ID]; |
---|
| 43 | +static int ignore_read_file_id[READING_MAX_ID] __ro_after_init; |
---|
48 | 44 | static struct super_block *pinned_root; |
---|
49 | 45 | static DEFINE_SPINLOCK(pinned_root_spinlock); |
---|
50 | 46 | |
---|
51 | 47 | #ifdef CONFIG_SYSCTL |
---|
52 | | -static int zero; |
---|
53 | | -static int one = 1; |
---|
54 | 48 | |
---|
55 | 49 | static struct ctl_path loadpin_sysctl_path[] = { |
---|
56 | 50 | { .procname = "kernel", }, |
---|
.. | .. |
---|
60 | 54 | |
---|
61 | 55 | static struct ctl_table loadpin_sysctl_table[] = { |
---|
62 | 56 | { |
---|
63 | | - .procname = "enabled", |
---|
64 | | - .data = &enabled, |
---|
| 57 | + .procname = "enforce", |
---|
| 58 | + .data = &enforce, |
---|
65 | 59 | .maxlen = sizeof(int), |
---|
66 | 60 | .mode = 0644, |
---|
67 | 61 | .proc_handler = proc_dointvec_minmax, |
---|
68 | | - .extra1 = &zero, |
---|
69 | | - .extra2 = &one, |
---|
| 62 | + .extra1 = SYSCTL_ZERO, |
---|
| 63 | + .extra2 = SYSCTL_ONE, |
---|
70 | 64 | }, |
---|
71 | 65 | { } |
---|
72 | 66 | }; |
---|
.. | .. |
---|
84 | 78 | * device, allow sysctl to change modes for testing. |
---|
85 | 79 | */ |
---|
86 | 80 | if (mnt_sb->s_bdev) { |
---|
| 81 | + char bdev[BDEVNAME_SIZE]; |
---|
| 82 | + |
---|
87 | 83 | ro = bdev_read_only(mnt_sb->s_bdev); |
---|
88 | | - pr_info("dev(%u,%u): %s\n", |
---|
| 84 | + bdevname(mnt_sb->s_bdev, bdev); |
---|
| 85 | + pr_info("%s (%u:%u): %s\n", bdev, |
---|
89 | 86 | MAJOR(mnt_sb->s_bdev->bd_dev), |
---|
90 | 87 | MINOR(mnt_sb->s_bdev->bd_dev), |
---|
91 | 88 | ro ? "read-only" : "writable"); |
---|
.. | .. |
---|
97 | 94 | loadpin_sysctl_table)) |
---|
98 | 95 | pr_notice("sysctl registration failed!\n"); |
---|
99 | 96 | else |
---|
100 | | - pr_info("load pinning can be disabled.\n"); |
---|
| 97 | + pr_info("enforcement can be disabled.\n"); |
---|
101 | 98 | } else |
---|
102 | 99 | pr_info("load pinning engaged.\n"); |
---|
103 | 100 | } |
---|
.. | .. |
---|
121 | 118 | } |
---|
122 | 119 | } |
---|
123 | 120 | |
---|
124 | | -static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) |
---|
| 121 | +static int loadpin_read_file(struct file *file, enum kernel_read_file_id id, |
---|
| 122 | + bool contents) |
---|
125 | 123 | { |
---|
126 | 124 | struct super_block *load_root; |
---|
127 | 125 | const char *origin = kernel_read_file_id_str(id); |
---|
128 | 126 | |
---|
| 127 | + /* |
---|
| 128 | + * If we will not know that we'll be seeing the full contents |
---|
| 129 | + * then we cannot trust a load will be complete and unchanged |
---|
| 130 | + * off disk. Treat all contents=false hooks as if there were |
---|
| 131 | + * no associated file struct. |
---|
| 132 | + */ |
---|
| 133 | + if (!contents) |
---|
| 134 | + file = NULL; |
---|
| 135 | + |
---|
| 136 | + /* If the file id is excluded, ignore the pinning. */ |
---|
| 137 | + if ((unsigned int)id < ARRAY_SIZE(ignore_read_file_id) && |
---|
| 138 | + ignore_read_file_id[id]) { |
---|
| 139 | + report_load(origin, file, "pinning-excluded"); |
---|
| 140 | + return 0; |
---|
| 141 | + } |
---|
| 142 | + |
---|
129 | 143 | /* This handles the older init_module API that has a NULL file. */ |
---|
130 | 144 | if (!file) { |
---|
131 | | - if (!enabled) { |
---|
| 145 | + if (!enforce) { |
---|
132 | 146 | report_load(origin, NULL, "old-api-pinning-ignored"); |
---|
133 | 147 | return 0; |
---|
134 | 148 | } |
---|
.. | .. |
---|
151 | 165 | * Unlock now since it's only pinned_root we care about. |
---|
152 | 166 | * In the worst case, we will (correctly) report pinning |
---|
153 | 167 | * failures before we have announced that pinning is |
---|
154 | | - * enabled. This would be purely cosmetic. |
---|
| 168 | + * enforcing. This would be purely cosmetic. |
---|
155 | 169 | */ |
---|
156 | 170 | spin_unlock(&pinned_root_spinlock); |
---|
157 | 171 | check_pinning_enforcement(pinned_root); |
---|
.. | .. |
---|
161 | 175 | } |
---|
162 | 176 | |
---|
163 | 177 | if (IS_ERR_OR_NULL(pinned_root) || load_root != pinned_root) { |
---|
164 | | - if (unlikely(!enabled)) { |
---|
| 178 | + if (unlikely(!enforce)) { |
---|
165 | 179 | report_load(origin, file, "pinning-ignored"); |
---|
166 | 180 | return 0; |
---|
167 | 181 | } |
---|
.. | .. |
---|
173 | 187 | return 0; |
---|
174 | 188 | } |
---|
175 | 189 | |
---|
176 | | -static int loadpin_load_data(enum kernel_load_data_id id) |
---|
| 190 | +static int loadpin_load_data(enum kernel_load_data_id id, bool contents) |
---|
177 | 191 | { |
---|
178 | | - return loadpin_read_file(NULL, (enum kernel_read_file_id) id); |
---|
| 192 | + return loadpin_read_file(NULL, (enum kernel_read_file_id) id, contents); |
---|
179 | 193 | } |
---|
180 | 194 | |
---|
181 | 195 | static struct security_hook_list loadpin_hooks[] __lsm_ro_after_init = { |
---|
.. | .. |
---|
184 | 198 | LSM_HOOK_INIT(kernel_load_data, loadpin_load_data), |
---|
185 | 199 | }; |
---|
186 | 200 | |
---|
187 | | -void __init loadpin_add_hooks(void) |
---|
| 201 | +static void __init parse_exclude(void) |
---|
188 | 202 | { |
---|
189 | | - pr_info("ready to pin (currently %sabled)", enabled ? "en" : "dis"); |
---|
190 | | - security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks), "loadpin"); |
---|
| 203 | + int i, j; |
---|
| 204 | + char *cur; |
---|
| 205 | + |
---|
| 206 | + /* |
---|
| 207 | + * Make sure all the arrays stay within expected sizes. This |
---|
| 208 | + * is slightly weird because kernel_read_file_str[] includes |
---|
| 209 | + * READING_MAX_ID, which isn't actually meaningful here. |
---|
| 210 | + */ |
---|
| 211 | + BUILD_BUG_ON(ARRAY_SIZE(exclude_read_files) != |
---|
| 212 | + ARRAY_SIZE(ignore_read_file_id)); |
---|
| 213 | + BUILD_BUG_ON(ARRAY_SIZE(kernel_read_file_str) < |
---|
| 214 | + ARRAY_SIZE(ignore_read_file_id)); |
---|
| 215 | + |
---|
| 216 | + for (i = 0; i < ARRAY_SIZE(exclude_read_files); i++) { |
---|
| 217 | + cur = exclude_read_files[i]; |
---|
| 218 | + if (!cur) |
---|
| 219 | + break; |
---|
| 220 | + if (*cur == '\0') |
---|
| 221 | + continue; |
---|
| 222 | + |
---|
| 223 | + for (j = 0; j < ARRAY_SIZE(ignore_read_file_id); j++) { |
---|
| 224 | + if (strcmp(cur, kernel_read_file_str[j]) == 0) { |
---|
| 225 | + pr_info("excluding: %s\n", |
---|
| 226 | + kernel_read_file_str[j]); |
---|
| 227 | + ignore_read_file_id[j] = 1; |
---|
| 228 | + /* |
---|
| 229 | + * Can not break, because one read_file_str |
---|
| 230 | + * may map to more than on read_file_id. |
---|
| 231 | + */ |
---|
| 232 | + } |
---|
| 233 | + } |
---|
| 234 | + } |
---|
191 | 235 | } |
---|
192 | 236 | |
---|
| 237 | +static int __init loadpin_init(void) |
---|
| 238 | +{ |
---|
| 239 | + pr_info("ready to pin (currently %senforcing)\n", |
---|
| 240 | + enforce ? "" : "not "); |
---|
| 241 | + parse_exclude(); |
---|
| 242 | + security_add_hooks(loadpin_hooks, ARRAY_SIZE(loadpin_hooks), "loadpin"); |
---|
| 243 | + return 0; |
---|
| 244 | +} |
---|
| 245 | + |
---|
| 246 | +DEFINE_LSM(loadpin) = { |
---|
| 247 | + .name = "loadpin", |
---|
| 248 | + .init = loadpin_init, |
---|
| 249 | +}; |
---|
| 250 | + |
---|
193 | 251 | /* Should not be mutable after boot, so not listed in sysfs (perm == 0). */ |
---|
194 | | -module_param(enabled, int, 0); |
---|
195 | | -MODULE_PARM_DESC(enabled, "Pin module/firmware loading (default: true)"); |
---|
| 252 | +module_param(enforce, int, 0); |
---|
| 253 | +MODULE_PARM_DESC(enforce, "Enforce module/firmware pinning"); |
---|
| 254 | +module_param_array_named(exclude, exclude_read_files, charp, NULL, 0); |
---|
| 255 | +MODULE_PARM_DESC(exclude, "Exclude pinning specific read file types"); |
---|