Commit | Line | Data |
---|---|---|
4c6926a2 DJ |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright(c) 2018 Intel Corporation. All rights reserved. */ | |
3 | ||
4 | #include <linux/module.h> | |
5 | #include <linux/device.h> | |
6 | #include <linux/ndctl.h> | |
7 | #include <linux/slab.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/mm.h> | |
10 | #include <linux/cred.h> | |
11 | #include <linux/key.h> | |
12 | #include <linux/key-type.h> | |
13 | #include <keys/user-type.h> | |
14 | #include <keys/encrypted-type.h> | |
15 | #include "nd-core.h" | |
16 | #include "nd.h" | |
17 | ||
d2a4ac73 DJ |
18 | #define NVDIMM_BASE_KEY 0 |
19 | #define NVDIMM_NEW_KEY 1 | |
20 | ||
4c6926a2 DJ |
21 | static bool key_revalidate = true; |
22 | module_param(key_revalidate, bool, 0444); | |
23 | MODULE_PARM_DESC(key_revalidate, "Require key validation at init."); | |
24 | ||
037c8489 DJ |
25 | static const char zero_key[NVDIMM_PASSPHRASE_LEN]; |
26 | ||
4c6926a2 DJ |
27 | static void *key_data(struct key *key) |
28 | { | |
29 | struct encrypted_key_payload *epayload = dereference_key_locked(key); | |
30 | ||
31 | lockdep_assert_held_read(&key->sem); | |
32 | ||
33 | return epayload->decrypted_data; | |
34 | } | |
35 | ||
36 | static void nvdimm_put_key(struct key *key) | |
37 | { | |
64e77c8c DJ |
38 | if (!key) |
39 | return; | |
40 | ||
4c6926a2 DJ |
41 | up_read(&key->sem); |
42 | key_put(key); | |
43 | } | |
44 | ||
45 | /* | |
46 | * Retrieve kernel key for DIMM and request from user space if | |
47 | * necessary. Returns a key held for read and must be put by | |
48 | * nvdimm_put_key() before the usage goes out of scope. | |
49 | */ | |
50 | static struct key *nvdimm_request_key(struct nvdimm *nvdimm) | |
51 | { | |
52 | struct key *key = NULL; | |
53 | static const char NVDIMM_PREFIX[] = "nvdimm:"; | |
54 | char desc[NVDIMM_KEY_DESC_LEN + sizeof(NVDIMM_PREFIX)]; | |
55 | struct device *dev = &nvdimm->dev; | |
56 | ||
57 | sprintf(desc, "%s%s", NVDIMM_PREFIX, nvdimm->dimm_id); | |
028db3e2 | 58 | key = request_key(&key_type_encrypted, desc, ""); |
4c6926a2 DJ |
59 | if (IS_ERR(key)) { |
60 | if (PTR_ERR(key) == -ENOKEY) | |
37379cfc | 61 | dev_dbg(dev, "request_key() found no key\n"); |
4c6926a2 | 62 | else |
37379cfc | 63 | dev_dbg(dev, "request_key() upcall failed\n"); |
4c6926a2 DJ |
64 | key = NULL; |
65 | } else { | |
66 | struct encrypted_key_payload *epayload; | |
67 | ||
68 | down_read(&key->sem); | |
69 | epayload = dereference_key_locked(key); | |
70 | if (epayload->decrypted_datalen != NVDIMM_PASSPHRASE_LEN) { | |
71 | up_read(&key->sem); | |
72 | key_put(key); | |
73 | key = NULL; | |
74 | } | |
75 | } | |
76 | ||
77 | return key; | |
78 | } | |
79 | ||
d2e5b643 DJ |
80 | static const void *nvdimm_get_key_payload(struct nvdimm *nvdimm, |
81 | struct key **key) | |
82 | { | |
83 | *key = nvdimm_request_key(nvdimm); | |
84 | if (!*key) | |
85 | return zero_key; | |
86 | ||
87 | return key_data(*key); | |
88 | } | |
89 | ||
03b65b22 | 90 | static struct key *nvdimm_lookup_user_key(struct nvdimm *nvdimm, |
d2a4ac73 | 91 | key_serial_t id, int subclass) |
03b65b22 DJ |
92 | { |
93 | key_ref_t keyref; | |
94 | struct key *key; | |
95 | struct encrypted_key_payload *epayload; | |
96 | struct device *dev = &nvdimm->dev; | |
97 | ||
813357fe | 98 | keyref = lookup_user_key(id, 0, KEY_NEED_SEARCH); |
03b65b22 DJ |
99 | if (IS_ERR(keyref)) |
100 | return NULL; | |
101 | ||
102 | key = key_ref_to_ptr(keyref); | |
103 | if (key->type != &key_type_encrypted) { | |
104 | key_put(key); | |
105 | return NULL; | |
106 | } | |
03b65b22 | 107 | |
d2a4ac73 | 108 | dev_dbg(dev, "%s: key found: %#x\n", __func__, key_serial(key)); |
03b65b22 | 109 | |
d2a4ac73 | 110 | down_read_nested(&key->sem, subclass); |
03b65b22 DJ |
111 | epayload = dereference_key_locked(key); |
112 | if (epayload->decrypted_datalen != NVDIMM_PASSPHRASE_LEN) { | |
113 | up_read(&key->sem); | |
114 | key_put(key); | |
115 | key = NULL; | |
116 | } | |
117 | return key; | |
118 | } | |
119 | ||
d2e5b643 DJ |
120 | static const void *nvdimm_get_user_key_payload(struct nvdimm *nvdimm, |
121 | key_serial_t id, int subclass, struct key **key) | |
122 | { | |
123 | *key = NULL; | |
124 | if (id == 0) { | |
125 | if (subclass == NVDIMM_BASE_KEY) | |
126 | return zero_key; | |
127 | else | |
128 | return NULL; | |
129 | } | |
130 | ||
131 | *key = nvdimm_lookup_user_key(nvdimm, id, subclass); | |
132 | if (!*key) | |
133 | return NULL; | |
134 | ||
135 | return key_data(*key); | |
136 | } | |
137 | ||
138 | ||
139 | static int nvdimm_key_revalidate(struct nvdimm *nvdimm) | |
4c6926a2 DJ |
140 | { |
141 | struct key *key; | |
142 | int rc; | |
d2e5b643 | 143 | const void *data; |
4c6926a2 DJ |
144 | |
145 | if (!nvdimm->sec.ops->change_key) | |
d2e5b643 | 146 | return -EOPNOTSUPP; |
4c6926a2 | 147 | |
d2e5b643 | 148 | data = nvdimm_get_key_payload(nvdimm, &key); |
4c6926a2 DJ |
149 | |
150 | /* | |
151 | * Send the same key to the hardware as new and old key to | |
152 | * verify that the key is good. | |
153 | */ | |
d2e5b643 | 154 | rc = nvdimm->sec.ops->change_key(nvdimm, data, data, NVDIMM_USER); |
4c6926a2 DJ |
155 | if (rc < 0) { |
156 | nvdimm_put_key(key); | |
d2e5b643 | 157 | return rc; |
4c6926a2 | 158 | } |
d2e5b643 DJ |
159 | |
160 | nvdimm_put_key(key); | |
d78c620a | 161 | nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER); |
d2e5b643 | 162 | return 0; |
4c6926a2 DJ |
163 | } |
164 | ||
165 | static int __nvdimm_security_unlock(struct nvdimm *nvdimm) | |
166 | { | |
167 | struct device *dev = &nvdimm->dev; | |
168 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
d2e5b643 DJ |
169 | struct key *key; |
170 | const void *data; | |
4c6926a2 DJ |
171 | int rc; |
172 | ||
173 | /* The bus lock should be held at the top level of the call stack */ | |
174 | lockdep_assert_held(&nvdimm_bus->reconfig_mutex); | |
175 | ||
176 | if (!nvdimm->sec.ops || !nvdimm->sec.ops->unlock | |
d78c620a | 177 | || !nvdimm->sec.flags) |
4c6926a2 DJ |
178 | return -EIO; |
179 | ||
674f31a3 DJ |
180 | /* No need to go further if security is disabled */ |
181 | if (test_bit(NVDIMM_SECURITY_DISABLED, &nvdimm->sec.flags)) | |
182 | return 0; | |
183 | ||
7d988097 | 184 | if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) { |
37379cfc | 185 | dev_dbg(dev, "Security operation in progress.\n"); |
7d988097 DJ |
186 | return -EBUSY; |
187 | } | |
188 | ||
4c6926a2 DJ |
189 | /* |
190 | * If the pre-OS has unlocked the DIMM, attempt to send the key | |
191 | * from request_key() to the hardware for verification. Failure | |
192 | * to revalidate the key against the hardware results in a | |
193 | * freeze of the security configuration. I.e. if the OS does not | |
194 | * have the key, security is being managed pre-OS. | |
195 | */ | |
d78c620a | 196 | if (test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.flags)) { |
4c6926a2 DJ |
197 | if (!key_revalidate) |
198 | return 0; | |
199 | ||
d2e5b643 | 200 | return nvdimm_key_revalidate(nvdimm); |
4c6926a2 | 201 | } else |
d2e5b643 | 202 | data = nvdimm_get_key_payload(nvdimm, &key); |
4c6926a2 | 203 | |
d2e5b643 | 204 | rc = nvdimm->sec.ops->unlock(nvdimm, data); |
4c6926a2 DJ |
205 | dev_dbg(dev, "key: %d unlock: %s\n", key_serial(key), |
206 | rc == 0 ? "success" : "fail"); | |
207 | ||
208 | nvdimm_put_key(key); | |
d78c620a | 209 | nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER); |
4c6926a2 DJ |
210 | return rc; |
211 | } | |
212 | ||
213 | int nvdimm_security_unlock(struct device *dev) | |
214 | { | |
215 | struct nvdimm *nvdimm = to_nvdimm(dev); | |
216 | int rc; | |
217 | ||
218 | nvdimm_bus_lock(dev); | |
219 | rc = __nvdimm_security_unlock(nvdimm); | |
220 | nvdimm_bus_unlock(dev); | |
221 | return rc; | |
222 | } | |
03b65b22 | 223 | |
d78c620a DW |
224 | static int check_security_state(struct nvdimm *nvdimm) |
225 | { | |
226 | struct device *dev = &nvdimm->dev; | |
227 | ||
228 | if (test_bit(NVDIMM_SECURITY_FROZEN, &nvdimm->sec.flags)) { | |
229 | dev_dbg(dev, "Incorrect security state: %#lx\n", | |
230 | nvdimm->sec.flags); | |
231 | return -EIO; | |
232 | } | |
233 | ||
234 | if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) { | |
235 | dev_dbg(dev, "Security operation in progress.\n"); | |
236 | return -EBUSY; | |
237 | } | |
238 | ||
239 | return 0; | |
240 | } | |
241 | ||
7b60422c | 242 | static int security_disable(struct nvdimm *nvdimm, unsigned int keyid) |
03b65b22 DJ |
243 | { |
244 | struct device *dev = &nvdimm->dev; | |
245 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
246 | struct key *key; | |
247 | int rc; | |
d2e5b643 | 248 | const void *data; |
03b65b22 DJ |
249 | |
250 | /* The bus lock should be held at the top level of the call stack */ | |
251 | lockdep_assert_held(&nvdimm_bus->reconfig_mutex); | |
252 | ||
253 | if (!nvdimm->sec.ops || !nvdimm->sec.ops->disable | |
d78c620a | 254 | || !nvdimm->sec.flags) |
03b65b22 DJ |
255 | return -EOPNOTSUPP; |
256 | ||
d78c620a DW |
257 | rc = check_security_state(nvdimm); |
258 | if (rc) | |
259 | return rc; | |
7d988097 | 260 | |
d2e5b643 DJ |
261 | data = nvdimm_get_user_key_payload(nvdimm, keyid, |
262 | NVDIMM_BASE_KEY, &key); | |
263 | if (!data) | |
03b65b22 DJ |
264 | return -ENOKEY; |
265 | ||
d2e5b643 | 266 | rc = nvdimm->sec.ops->disable(nvdimm, data); |
03b65b22 DJ |
267 | dev_dbg(dev, "key: %d disable: %s\n", key_serial(key), |
268 | rc == 0 ? "success" : "fail"); | |
269 | ||
270 | nvdimm_put_key(key); | |
d78c620a | 271 | nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER); |
03b65b22 DJ |
272 | return rc; |
273 | } | |
d2a4ac73 | 274 | |
7b60422c | 275 | static int security_update(struct nvdimm *nvdimm, unsigned int keyid, |
89fa9d8e DJ |
276 | unsigned int new_keyid, |
277 | enum nvdimm_passphrase_type pass_type) | |
d2a4ac73 DJ |
278 | { |
279 | struct device *dev = &nvdimm->dev; | |
280 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
281 | struct key *key, *newkey; | |
282 | int rc; | |
d2e5b643 | 283 | const void *data, *newdata; |
d2a4ac73 DJ |
284 | |
285 | /* The bus lock should be held at the top level of the call stack */ | |
286 | lockdep_assert_held(&nvdimm_bus->reconfig_mutex); | |
287 | ||
288 | if (!nvdimm->sec.ops || !nvdimm->sec.ops->change_key | |
d78c620a | 289 | || !nvdimm->sec.flags) |
d2a4ac73 DJ |
290 | return -EOPNOTSUPP; |
291 | ||
d78c620a DW |
292 | rc = check_security_state(nvdimm); |
293 | if (rc) | |
294 | return rc; | |
d2a4ac73 | 295 | |
d2e5b643 DJ |
296 | data = nvdimm_get_user_key_payload(nvdimm, keyid, |
297 | NVDIMM_BASE_KEY, &key); | |
298 | if (!data) | |
299 | return -ENOKEY; | |
d2a4ac73 | 300 | |
d2e5b643 DJ |
301 | newdata = nvdimm_get_user_key_payload(nvdimm, new_keyid, |
302 | NVDIMM_NEW_KEY, &newkey); | |
303 | if (!newdata) { | |
d2a4ac73 DJ |
304 | nvdimm_put_key(key); |
305 | return -ENOKEY; | |
306 | } | |
307 | ||
d2e5b643 | 308 | rc = nvdimm->sec.ops->change_key(nvdimm, data, newdata, pass_type); |
89fa9d8e | 309 | dev_dbg(dev, "key: %d %d update%s: %s\n", |
d2a4ac73 | 310 | key_serial(key), key_serial(newkey), |
89fa9d8e | 311 | pass_type == NVDIMM_MASTER ? "(master)" : "(user)", |
d2a4ac73 DJ |
312 | rc == 0 ? "success" : "fail"); |
313 | ||
314 | nvdimm_put_key(newkey); | |
315 | nvdimm_put_key(key); | |
89fa9d8e | 316 | if (pass_type == NVDIMM_MASTER) |
d78c620a | 317 | nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm, |
89fa9d8e DJ |
318 | NVDIMM_MASTER); |
319 | else | |
d78c620a | 320 | nvdimm->sec.flags = nvdimm_security_flags(nvdimm, |
89fa9d8e | 321 | NVDIMM_USER); |
d2a4ac73 DJ |
322 | return rc; |
323 | } | |
64e77c8c | 324 | |
7b60422c | 325 | static int security_erase(struct nvdimm *nvdimm, unsigned int keyid, |
89fa9d8e | 326 | enum nvdimm_passphrase_type pass_type) |
64e77c8c DJ |
327 | { |
328 | struct device *dev = &nvdimm->dev; | |
329 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
037c8489 | 330 | struct key *key = NULL; |
64e77c8c | 331 | int rc; |
037c8489 | 332 | const void *data; |
64e77c8c DJ |
333 | |
334 | /* The bus lock should be held at the top level of the call stack */ | |
335 | lockdep_assert_held(&nvdimm_bus->reconfig_mutex); | |
336 | ||
337 | if (!nvdimm->sec.ops || !nvdimm->sec.ops->erase | |
d78c620a | 338 | || !nvdimm->sec.flags) |
64e77c8c DJ |
339 | return -EOPNOTSUPP; |
340 | ||
d78c620a DW |
341 | rc = check_security_state(nvdimm); |
342 | if (rc) | |
343 | return rc; | |
7d988097 | 344 | |
d78c620a | 345 | if (!test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.ext_flags) |
89fa9d8e | 346 | && pass_type == NVDIMM_MASTER) { |
37379cfc | 347 | dev_dbg(dev, |
89fa9d8e DJ |
348 | "Attempt to secure erase in wrong master state.\n"); |
349 | return -EOPNOTSUPP; | |
350 | } | |
351 | ||
d2e5b643 DJ |
352 | data = nvdimm_get_user_key_payload(nvdimm, keyid, |
353 | NVDIMM_BASE_KEY, &key); | |
354 | if (!data) | |
355 | return -ENOKEY; | |
64e77c8c | 356 | |
037c8489 | 357 | rc = nvdimm->sec.ops->erase(nvdimm, data, pass_type); |
89fa9d8e DJ |
358 | dev_dbg(dev, "key: %d erase%s: %s\n", key_serial(key), |
359 | pass_type == NVDIMM_MASTER ? "(master)" : "(user)", | |
64e77c8c DJ |
360 | rc == 0 ? "success" : "fail"); |
361 | ||
362 | nvdimm_put_key(key); | |
d78c620a | 363 | nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER); |
64e77c8c DJ |
364 | return rc; |
365 | } | |
7d988097 | 366 | |
7b60422c | 367 | static int security_overwrite(struct nvdimm *nvdimm, unsigned int keyid) |
7d988097 DJ |
368 | { |
369 | struct device *dev = &nvdimm->dev; | |
370 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev); | |
d2e5b643 | 371 | struct key *key = NULL; |
7d988097 | 372 | int rc; |
d2e5b643 | 373 | const void *data; |
7d988097 DJ |
374 | |
375 | /* The bus lock should be held at the top level of the call stack */ | |
376 | lockdep_assert_held(&nvdimm_bus->reconfig_mutex); | |
377 | ||
378 | if (!nvdimm->sec.ops || !nvdimm->sec.ops->overwrite | |
d78c620a | 379 | || !nvdimm->sec.flags) |
7d988097 DJ |
380 | return -EOPNOTSUPP; |
381 | ||
7d988097 | 382 | if (dev->driver == NULL) { |
37379cfc | 383 | dev_dbg(dev, "Unable to overwrite while DIMM active.\n"); |
7d988097 DJ |
384 | return -EINVAL; |
385 | } | |
386 | ||
d78c620a DW |
387 | rc = check_security_state(nvdimm); |
388 | if (rc) | |
389 | return rc; | |
7d988097 | 390 | |
d2e5b643 DJ |
391 | data = nvdimm_get_user_key_payload(nvdimm, keyid, |
392 | NVDIMM_BASE_KEY, &key); | |
393 | if (!data) | |
394 | return -ENOKEY; | |
7d988097 | 395 | |
d2e5b643 | 396 | rc = nvdimm->sec.ops->overwrite(nvdimm, data); |
7d988097 DJ |
397 | dev_dbg(dev, "key: %d overwrite submission: %s\n", key_serial(key), |
398 | rc == 0 ? "success" : "fail"); | |
399 | ||
400 | nvdimm_put_key(key); | |
401 | if (rc == 0) { | |
402 | set_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags); | |
403 | set_bit(NDD_WORK_PENDING, &nvdimm->flags); | |
d78c620a | 404 | set_bit(NVDIMM_SECURITY_OVERWRITE, &nvdimm->sec.flags); |
7d988097 DJ |
405 | /* |
406 | * Make sure we don't lose device while doing overwrite | |
407 | * query. | |
408 | */ | |
409 | get_device(dev); | |
410 | queue_delayed_work(system_wq, &nvdimm->dwork, 0); | |
411 | } | |
89fa9d8e | 412 | |
7d988097 DJ |
413 | return rc; |
414 | } | |
415 | ||
416 | void __nvdimm_security_overwrite_query(struct nvdimm *nvdimm) | |
417 | { | |
418 | struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(&nvdimm->dev); | |
419 | int rc; | |
420 | unsigned int tmo; | |
421 | ||
422 | /* The bus lock should be held at the top level of the call stack */ | |
423 | lockdep_assert_held(&nvdimm_bus->reconfig_mutex); | |
424 | ||
425 | /* | |
426 | * Abort and release device if we no longer have the overwrite | |
427 | * flag set. It means the work has been canceled. | |
428 | */ | |
429 | if (!test_bit(NDD_WORK_PENDING, &nvdimm->flags)) | |
430 | return; | |
431 | ||
432 | tmo = nvdimm->sec.overwrite_tmo; | |
433 | ||
434 | if (!nvdimm->sec.ops || !nvdimm->sec.ops->query_overwrite | |
d78c620a | 435 | || !nvdimm->sec.flags) |
7d988097 DJ |
436 | return; |
437 | ||
438 | rc = nvdimm->sec.ops->query_overwrite(nvdimm); | |
439 | if (rc == -EBUSY) { | |
440 | ||
441 | /* setup delayed work again */ | |
442 | tmo += 10; | |
443 | queue_delayed_work(system_wq, &nvdimm->dwork, tmo * HZ); | |
444 | nvdimm->sec.overwrite_tmo = min(15U * 60U, tmo); | |
445 | return; | |
446 | } | |
447 | ||
448 | if (rc < 0) | |
37379cfc | 449 | dev_dbg(&nvdimm->dev, "overwrite failed\n"); |
7d988097 DJ |
450 | else |
451 | dev_dbg(&nvdimm->dev, "overwrite completed\n"); | |
452 | ||
7f674025 JC |
453 | /* |
454 | * Mark the overwrite work done and update dimm security flags, | |
455 | * then send a sysfs event notification to wake up userspace | |
456 | * poll threads to picked up the changed state. | |
457 | */ | |
7d988097 DJ |
458 | nvdimm->sec.overwrite_tmo = 0; |
459 | clear_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags); | |
460 | clear_bit(NDD_WORK_PENDING, &nvdimm->flags); | |
d78c620a | 461 | nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER); |
dad42d17 | 462 | nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm, NVDIMM_MASTER); |
7f674025 JC |
463 | if (nvdimm->sec.overwrite_state) |
464 | sysfs_notify_dirent(nvdimm->sec.overwrite_state); | |
465 | put_device(&nvdimm->dev); | |
7d988097 DJ |
466 | } |
467 | ||
468 | void nvdimm_security_overwrite_query(struct work_struct *work) | |
469 | { | |
470 | struct nvdimm *nvdimm = | |
471 | container_of(work, typeof(*nvdimm), dwork.work); | |
472 | ||
473 | nvdimm_bus_lock(&nvdimm->dev); | |
474 | __nvdimm_security_overwrite_query(nvdimm); | |
475 | nvdimm_bus_unlock(&nvdimm->dev); | |
476 | } | |
7b60422c DW |
477 | |
478 | #define OPS \ | |
479 | C( OP_FREEZE, "freeze", 1), \ | |
480 | C( OP_DISABLE, "disable", 2), \ | |
481 | C( OP_UPDATE, "update", 3), \ | |
482 | C( OP_ERASE, "erase", 2), \ | |
483 | C( OP_OVERWRITE, "overwrite", 2), \ | |
484 | C( OP_MASTER_UPDATE, "master_update", 3), \ | |
485 | C( OP_MASTER_ERASE, "master_erase", 2) | |
486 | #undef C | |
487 | #define C(a, b, c) a | |
488 | enum nvdimmsec_op_ids { OPS }; | |
489 | #undef C | |
490 | #define C(a, b, c) { b, c } | |
491 | static struct { | |
492 | const char *name; | |
493 | int args; | |
494 | } ops[] = { OPS }; | |
495 | #undef C | |
496 | ||
497 | #define SEC_CMD_SIZE 32 | |
498 | #define KEY_ID_SIZE 10 | |
499 | ||
500 | ssize_t nvdimm_security_store(struct device *dev, const char *buf, size_t len) | |
501 | { | |
502 | struct nvdimm *nvdimm = to_nvdimm(dev); | |
503 | ssize_t rc; | |
504 | char cmd[SEC_CMD_SIZE+1], keystr[KEY_ID_SIZE+1], | |
505 | nkeystr[KEY_ID_SIZE+1]; | |
506 | unsigned int key, newkey; | |
507 | int i; | |
508 | ||
509 | rc = sscanf(buf, "%"__stringify(SEC_CMD_SIZE)"s" | |
510 | " %"__stringify(KEY_ID_SIZE)"s" | |
511 | " %"__stringify(KEY_ID_SIZE)"s", | |
512 | cmd, keystr, nkeystr); | |
513 | if (rc < 1) | |
514 | return -EINVAL; | |
515 | for (i = 0; i < ARRAY_SIZE(ops); i++) | |
516 | if (sysfs_streq(cmd, ops[i].name)) | |
517 | break; | |
518 | if (i >= ARRAY_SIZE(ops)) | |
519 | return -EINVAL; | |
520 | if (ops[i].args > 1) | |
521 | rc = kstrtouint(keystr, 0, &key); | |
522 | if (rc >= 0 && ops[i].args > 2) | |
523 | rc = kstrtouint(nkeystr, 0, &newkey); | |
524 | if (rc < 0) | |
525 | return rc; | |
526 | ||
527 | if (i == OP_FREEZE) { | |
528 | dev_dbg(dev, "freeze\n"); | |
529 | rc = nvdimm_security_freeze(nvdimm); | |
530 | } else if (i == OP_DISABLE) { | |
531 | dev_dbg(dev, "disable %u\n", key); | |
532 | rc = security_disable(nvdimm, key); | |
533 | } else if (i == OP_UPDATE || i == OP_MASTER_UPDATE) { | |
534 | dev_dbg(dev, "%s %u %u\n", ops[i].name, key, newkey); | |
535 | rc = security_update(nvdimm, key, newkey, i == OP_UPDATE | |
536 | ? NVDIMM_USER : NVDIMM_MASTER); | |
537 | } else if (i == OP_ERASE || i == OP_MASTER_ERASE) { | |
538 | dev_dbg(dev, "%s %u\n", ops[i].name, key); | |
539 | if (atomic_read(&nvdimm->busy)) { | |
540 | dev_dbg(dev, "Unable to secure erase while DIMM active.\n"); | |
541 | return -EBUSY; | |
542 | } | |
543 | rc = security_erase(nvdimm, key, i == OP_ERASE | |
544 | ? NVDIMM_USER : NVDIMM_MASTER); | |
545 | } else if (i == OP_OVERWRITE) { | |
546 | dev_dbg(dev, "overwrite %u\n", key); | |
547 | if (atomic_read(&nvdimm->busy)) { | |
548 | dev_dbg(dev, "Unable to overwrite while DIMM active.\n"); | |
549 | return -EBUSY; | |
550 | } | |
551 | rc = security_overwrite(nvdimm, key); | |
552 | } else | |
553 | return -EINVAL; | |
554 | ||
555 | if (rc == 0) | |
556 | rc = len; | |
557 | return rc; | |
558 | } |