diff options
author | David Howells <dhowells@redhat.com> | 2018-02-22 09:38:34 -0500 |
---|---|---|
committer | David Howells <dhowells@redhat.com> | 2018-02-22 09:58:38 -0500 |
commit | d9f4bb1a0f4db493efe6d7c58ffe696a57de7eb3 (patch) | |
tree | 259ac728946dd076a4b118f6e504929fe42b08d4 | |
parent | 4b34968e77ad09628cfb3c4a7daf2adc2cefc6e8 (diff) |
KEYS: Use individual pages in big_key for crypto buffers
kmalloc() can't always allocate large enough buffers for big_key to use for
crypto (1MB + some metadata) so we cannot use that to allocate the buffer.
Further, vmalloc'd pages can't be passed to sg_init_one() and the aead
crypto accessors cannot be called progressively and must be passed all the
data in one go (which means we can't pass the data in one block at a time).
Fix this by allocating the buffer pages individually and passing them
through a multientry scatterlist to the crypto layer. This has the bonus
advantage that we don't have to allocate a contiguous series of pages.
We then vmap() the page list and pass that through to the VFS read/write
routines.
This can trigger a warning:
WARNING: CPU: 0 PID: 60912 at mm/page_alloc.c:3883 __alloc_pages_nodemask+0xb7c/0x15f8
([<00000000002acbb6>] __alloc_pages_nodemask+0x1ee/0x15f8)
[<00000000002dd356>] kmalloc_order+0x46/0x90
[<00000000002dd3e0>] kmalloc_order_trace+0x40/0x1f8
[<0000000000326a10>] __kmalloc+0x430/0x4c0
[<00000000004343e4>] big_key_preparse+0x7c/0x210
[<000000000042c040>] key_create_or_update+0x128/0x420
[<000000000042e52c>] SyS_add_key+0x124/0x220
[<00000000007bba2c>] system_call+0xc4/0x2b0
from the keyctl/padd/useradd test of the keyutils testsuite on s390x.
Note that it might be better to shovel data through in page-sized lumps
instead as there's no particular need to use a monolithic buffer unless the
kernel itself wants to access the data.
Fixes: 13100a72f40f ("Security: Keys: Big keys stored encrypted")
Reported-by: Paul Bunyan <pbunyan@redhat.com>
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Kirill Marinushkin <k.marinushkin@gmail.com>
-rw-r--r-- | security/keys/big_key.c | 110 |
1 files changed, 87 insertions, 23 deletions
diff --git a/security/keys/big_key.c b/security/keys/big_key.c index 929e14978c42..fa728f662a6f 100644 --- a/security/keys/big_key.c +++ b/security/keys/big_key.c | |||
@@ -22,6 +22,13 @@ | |||
22 | #include <keys/big_key-type.h> | 22 | #include <keys/big_key-type.h> |
23 | #include <crypto/aead.h> | 23 | #include <crypto/aead.h> |
24 | 24 | ||
25 | struct big_key_buf { | ||
26 | unsigned int nr_pages; | ||
27 | void *virt; | ||
28 | struct scatterlist *sg; | ||
29 | struct page *pages[]; | ||
30 | }; | ||
31 | |||
25 | /* | 32 | /* |
26 | * Layout of key payload words. | 33 | * Layout of key payload words. |
27 | */ | 34 | */ |
@@ -91,10 +98,9 @@ static DEFINE_MUTEX(big_key_aead_lock); | |||
91 | /* | 98 | /* |
92 | * Encrypt/decrypt big_key data | 99 | * Encrypt/decrypt big_key data |
93 | */ | 100 | */ |
94 | static int big_key_crypt(enum big_key_op op, u8 *data, size_t datalen, u8 *key) | 101 | static int big_key_crypt(enum big_key_op op, struct big_key_buf *buf, size_t datalen, u8 *key) |
95 | { | 102 | { |
96 | int ret; | 103 | int ret; |
97 | struct scatterlist sgio; | ||
98 | struct aead_request *aead_req; | 104 | struct aead_request *aead_req; |
99 | /* We always use a zero nonce. The reason we can get away with this is | 105 | /* We always use a zero nonce. The reason we can get away with this is |
100 | * because we're using a different randomly generated key for every | 106 | * because we're using a different randomly generated key for every |
@@ -109,8 +115,7 @@ static int big_key_crypt(enum big_key_op op, u8 *data, size_t datalen, u8 *key) | |||
109 | return -ENOMEM; | 115 | return -ENOMEM; |
110 | 116 | ||
111 | memset(zero_nonce, 0, sizeof(zero_nonce)); | 117 | memset(zero_nonce, 0, sizeof(zero_nonce)); |
112 | sg_init_one(&sgio, data, datalen + (op == BIG_KEY_ENC ? ENC_AUTHTAG_SIZE : 0)); | 118 | aead_request_set_crypt(aead_req, buf->sg, buf->sg, datalen, zero_nonce); |
113 | aead_request_set_crypt(aead_req, &sgio, &sgio, datalen, zero_nonce); | ||
114 | aead_request_set_callback(aead_req, CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL); | 119 | aead_request_set_callback(aead_req, CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL); |
115 | aead_request_set_ad(aead_req, 0); | 120 | aead_request_set_ad(aead_req, 0); |
116 | 121 | ||
@@ -130,21 +135,81 @@ error: | |||
130 | } | 135 | } |
131 | 136 | ||
132 | /* | 137 | /* |
138 | * Free up the buffer. | ||
139 | */ | ||
140 | static void big_key_free_buffer(struct big_key_buf *buf) | ||
141 | { | ||
142 | unsigned int i; | ||
143 | |||
144 | if (buf->virt) { | ||
145 | memset(buf->virt, 0, buf->nr_pages * PAGE_SIZE); | ||
146 | vunmap(buf->virt); | ||
147 | } | ||
148 | |||
149 | for (i = 0; i < buf->nr_pages; i++) | ||
150 | if (buf->pages[i]) | ||
151 | __free_page(buf->pages[i]); | ||
152 | |||
153 | kfree(buf); | ||
154 | } | ||
155 | |||
156 | /* | ||
157 | * Allocate a buffer consisting of a set of pages with a virtual mapping | ||
158 | * applied over them. | ||
159 | */ | ||
160 | static void *big_key_alloc_buffer(size_t len) | ||
161 | { | ||
162 | struct big_key_buf *buf; | ||
163 | unsigned int npg = (len + PAGE_SIZE - 1) >> PAGE_SHIFT; | ||
164 | unsigned int i, l; | ||
165 | |||
166 | buf = kzalloc(sizeof(struct big_key_buf) + | ||
167 | sizeof(struct page) * npg + | ||
168 | sizeof(struct scatterlist) * npg, | ||
169 | GFP_KERNEL); | ||
170 | if (!buf) | ||
171 | return NULL; | ||
172 | |||
173 | buf->nr_pages = npg; | ||
174 | buf->sg = (void *)(buf->pages + npg); | ||
175 | sg_init_table(buf->sg, npg); | ||
176 | |||
177 | for (i = 0; i < buf->nr_pages; i++) { | ||
178 | buf->pages[i] = alloc_page(GFP_KERNEL); | ||
179 | if (!buf->pages[i]) | ||
180 | goto nomem; | ||
181 | |||
182 | l = min_t(size_t, len, PAGE_SIZE); | ||
183 | sg_set_page(&buf->sg[i], buf->pages[i], l, 0); | ||
184 | len -= l; | ||
185 | } | ||
186 | |||
187 | buf->virt = vmap(buf->pages, buf->nr_pages, VM_MAP, PAGE_KERNEL); | ||
188 | if (!buf->virt) | ||
189 | goto nomem; | ||
190 | |||
191 | return buf; | ||
192 | |||
193 | nomem: | ||
194 | big_key_free_buffer(buf); | ||
195 | return NULL; | ||
196 | } | ||
197 | |||
198 | /* | ||
133 | * Preparse a big key | 199 | * Preparse a big key |
134 | */ | 200 | */ |
135 | int big_key_preparse(struct key_preparsed_payload *prep) | 201 | int big_key_preparse(struct key_preparsed_payload *prep) |
136 | { | 202 | { |
203 | struct big_key_buf *buf; | ||
137 | struct path *path = (struct path *)&prep->payload.data[big_key_path]; | 204 | struct path *path = (struct path *)&prep->payload.data[big_key_path]; |
138 | struct file *file; | 205 | struct file *file; |
139 | u8 *enckey; | 206 | u8 *enckey; |
140 | u8 *data = NULL; | ||
141 | ssize_t written; | 207 | ssize_t written; |
142 | size_t datalen = prep->datalen; | 208 | size_t datalen = prep->datalen, enclen = datalen + ENC_AUTHTAG_SIZE; |
143 | int ret; | 209 | int ret; |
144 | 210 | ||
145 | ret = -EINVAL; | ||
146 | if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data) | 211 | if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data) |
147 | goto error; | 212 | return -EINVAL; |
148 | 213 | ||
149 | /* Set an arbitrary quota */ | 214 | /* Set an arbitrary quota */ |
150 | prep->quotalen = 16; | 215 | prep->quotalen = 16; |
@@ -157,13 +222,12 @@ int big_key_preparse(struct key_preparsed_payload *prep) | |||
157 | * | 222 | * |
158 | * File content is stored encrypted with randomly generated key. | 223 | * File content is stored encrypted with randomly generated key. |
159 | */ | 224 | */ |
160 | size_t enclen = datalen + ENC_AUTHTAG_SIZE; | ||
161 | loff_t pos = 0; | 225 | loff_t pos = 0; |
162 | 226 | ||
163 | data = kmalloc(enclen, GFP_KERNEL); | 227 | buf = big_key_alloc_buffer(enclen); |
164 | if (!data) | 228 | if (!buf) |
165 | return -ENOMEM; | 229 | return -ENOMEM; |
166 | memcpy(data, prep->data, datalen); | 230 | memcpy(buf->virt, prep->data, datalen); |
167 | 231 | ||
168 | /* generate random key */ | 232 | /* generate random key */ |
169 | enckey = kmalloc(ENC_KEY_SIZE, GFP_KERNEL); | 233 | enckey = kmalloc(ENC_KEY_SIZE, GFP_KERNEL); |
@@ -176,7 +240,7 @@ int big_key_preparse(struct key_preparsed_payload *prep) | |||
176 | goto err_enckey; | 240 | goto err_enckey; |
177 | 241 | ||
178 | /* encrypt aligned data */ | 242 | /* encrypt aligned data */ |
179 | ret = big_key_crypt(BIG_KEY_ENC, data, datalen, enckey); | 243 | ret = big_key_crypt(BIG_KEY_ENC, buf, datalen, enckey); |
180 | if (ret) | 244 | if (ret) |
181 | goto err_enckey; | 245 | goto err_enckey; |
182 | 246 | ||
@@ -187,7 +251,7 @@ int big_key_preparse(struct key_preparsed_payload *prep) | |||
187 | goto err_enckey; | 251 | goto err_enckey; |
188 | } | 252 | } |
189 | 253 | ||
190 | written = kernel_write(file, data, enclen, &pos); | 254 | written = kernel_write(file, buf->virt, enclen, &pos); |
191 | if (written != enclen) { | 255 | if (written != enclen) { |
192 | ret = written; | 256 | ret = written; |
193 | if (written >= 0) | 257 | if (written >= 0) |
@@ -202,7 +266,7 @@ int big_key_preparse(struct key_preparsed_payload *prep) | |||
202 | *path = file->f_path; | 266 | *path = file->f_path; |
203 | path_get(path); | 267 | path_get(path); |
204 | fput(file); | 268 | fput(file); |
205 | kzfree(data); | 269 | big_key_free_buffer(buf); |
206 | } else { | 270 | } else { |
207 | /* Just store the data in a buffer */ | 271 | /* Just store the data in a buffer */ |
208 | void *data = kmalloc(datalen, GFP_KERNEL); | 272 | void *data = kmalloc(datalen, GFP_KERNEL); |
@@ -220,7 +284,7 @@ err_fput: | |||
220 | err_enckey: | 284 | err_enckey: |
221 | kzfree(enckey); | 285 | kzfree(enckey); |
222 | error: | 286 | error: |
223 | kzfree(data); | 287 | big_key_free_buffer(buf); |
224 | return ret; | 288 | return ret; |
225 | } | 289 | } |
226 | 290 | ||
@@ -298,15 +362,15 @@ long big_key_read(const struct key *key, char __user *buffer, size_t buflen) | |||
298 | return datalen; | 362 | return datalen; |
299 | 363 | ||
300 | if (datalen > BIG_KEY_FILE_THRESHOLD) { | 364 | if (datalen > BIG_KEY_FILE_THRESHOLD) { |
365 | struct big_key_buf *buf; | ||
301 | struct path *path = (struct path *)&key->payload.data[big_key_path]; | 366 | struct path *path = (struct path *)&key->payload.data[big_key_path]; |
302 | struct file *file; | 367 | struct file *file; |
303 | u8 *data; | ||
304 | u8 *enckey = (u8 *)key->payload.data[big_key_data]; | 368 | u8 *enckey = (u8 *)key->payload.data[big_key_data]; |
305 | size_t enclen = datalen + ENC_AUTHTAG_SIZE; | 369 | size_t enclen = datalen + ENC_AUTHTAG_SIZE; |
306 | loff_t pos = 0; | 370 | loff_t pos = 0; |
307 | 371 | ||
308 | data = kmalloc(enclen, GFP_KERNEL); | 372 | buf = big_key_alloc_buffer(enclen); |
309 | if (!data) | 373 | if (!buf) |
310 | return -ENOMEM; | 374 | return -ENOMEM; |
311 | 375 | ||
312 | file = dentry_open(path, O_RDONLY, current_cred()); | 376 | file = dentry_open(path, O_RDONLY, current_cred()); |
@@ -316,26 +380,26 @@ long big_key_read(const struct key *key, char __user *buffer, size_t buflen) | |||
316 | } | 380 | } |
317 | 381 | ||
318 | /* read file to kernel and decrypt */ | 382 | /* read file to kernel and decrypt */ |
319 | ret = kernel_read(file, data, enclen, &pos); | 383 | ret = kernel_read(file, buf->virt, enclen, &pos); |
320 | if (ret >= 0 && ret != enclen) { | 384 | if (ret >= 0 && ret != enclen) { |
321 | ret = -EIO; | 385 | ret = -EIO; |
322 | goto err_fput; | 386 | goto err_fput; |
323 | } | 387 | } |
324 | 388 | ||
325 | ret = big_key_crypt(BIG_KEY_DEC, data, enclen, enckey); | 389 | ret = big_key_crypt(BIG_KEY_DEC, buf, enclen, enckey); |
326 | if (ret) | 390 | if (ret) |
327 | goto err_fput; | 391 | goto err_fput; |
328 | 392 | ||
329 | ret = datalen; | 393 | ret = datalen; |
330 | 394 | ||
331 | /* copy decrypted data to user */ | 395 | /* copy decrypted data to user */ |
332 | if (copy_to_user(buffer, data, datalen) != 0) | 396 | if (copy_to_user(buffer, buf->virt, datalen) != 0) |
333 | ret = -EFAULT; | 397 | ret = -EFAULT; |
334 | 398 | ||
335 | err_fput: | 399 | err_fput: |
336 | fput(file); | 400 | fput(file); |
337 | error: | 401 | error: |
338 | kzfree(data); | 402 | big_key_free_buffer(buf); |
339 | } else { | 403 | } else { |
340 | ret = datalen; | 404 | ret = datalen; |
341 | if (copy_to_user(buffer, key->payload.data[big_key_data], | 405 | if (copy_to_user(buffer, key->payload.data[big_key_data], |