1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* Large capacity key type |
3 | * |
4 | * Copyright (C) 2017-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. |
5 | * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved. |
6 | * Written by David Howells (dhowells@redhat.com) |
7 | */ |
8 | |
9 | #define pr_fmt(fmt) "big_key: "fmt |
10 | #include <linux/init.h> |
11 | #include <linux/seq_file.h> |
12 | #include <linux/file.h> |
13 | #include <linux/shmem_fs.h> |
14 | #include <linux/err.h> |
15 | #include <linux/random.h> |
16 | #include <keys/user-type.h> |
17 | #include <keys/big_key-type.h> |
18 | #include <crypto/chacha20poly1305.h> |
19 | |
20 | /* |
21 | * Layout of key payload words. |
22 | */ |
23 | struct big_key_payload { |
24 | u8 *data; |
25 | struct path path; |
26 | size_t length; |
27 | }; |
28 | #define to_big_key_payload(payload) \ |
29 | (struct big_key_payload *)((payload).data) |
30 | |
31 | /* |
32 | * If the data is under this limit, there's no point creating a shm file to |
33 | * hold it as the permanently resident metadata for the shmem fs will be at |
34 | * least as large as the data. |
35 | */ |
36 | #define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry)) |
37 | |
38 | /* |
39 | * big_key defined keys take an arbitrary string as the description and an |
40 | * arbitrary blob of data as the payload |
41 | */ |
42 | struct key_type key_type_big_key = { |
43 | .name = "big_key" , |
44 | .preparse = big_key_preparse, |
45 | .free_preparse = big_key_free_preparse, |
46 | .instantiate = generic_key_instantiate, |
47 | .revoke = big_key_revoke, |
48 | .destroy = big_key_destroy, |
49 | .describe = big_key_describe, |
50 | .read = big_key_read, |
51 | .update = big_key_update, |
52 | }; |
53 | |
54 | /* |
55 | * Preparse a big key |
56 | */ |
57 | int big_key_preparse(struct key_preparsed_payload *prep) |
58 | { |
59 | struct big_key_payload *payload = to_big_key_payload(prep->payload); |
60 | struct file *file; |
61 | u8 *buf, *enckey; |
62 | ssize_t written; |
63 | size_t datalen = prep->datalen; |
64 | size_t enclen = datalen + CHACHA20POLY1305_AUTHTAG_SIZE; |
65 | int ret; |
66 | |
67 | BUILD_BUG_ON(sizeof(*payload) != sizeof(prep->payload.data)); |
68 | |
69 | if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data) |
70 | return -EINVAL; |
71 | |
72 | /* Set an arbitrary quota */ |
73 | prep->quotalen = 16; |
74 | |
75 | payload->length = datalen; |
76 | |
77 | if (datalen > BIG_KEY_FILE_THRESHOLD) { |
78 | /* Create a shmem file to store the data in. This will permit the data |
79 | * to be swapped out if needed. |
80 | * |
81 | * File content is stored encrypted with randomly generated key. |
82 | * Since the key is random for each file, we can set the nonce |
83 | * to zero, provided we never define a ->update() call. |
84 | */ |
85 | loff_t pos = 0; |
86 | |
87 | buf = kvmalloc(size: enclen, GFP_KERNEL); |
88 | if (!buf) |
89 | return -ENOMEM; |
90 | |
91 | /* generate random key */ |
92 | enckey = kmalloc(size: CHACHA20POLY1305_KEY_SIZE, GFP_KERNEL); |
93 | if (!enckey) { |
94 | ret = -ENOMEM; |
95 | goto error; |
96 | } |
97 | ret = get_random_bytes_wait(buf: enckey, nbytes: CHACHA20POLY1305_KEY_SIZE); |
98 | if (unlikely(ret)) |
99 | goto err_enckey; |
100 | |
101 | /* encrypt data */ |
102 | chacha20poly1305_encrypt(dst: buf, src: prep->data, src_len: datalen, NULL, ad_len: 0, |
103 | nonce: 0, key: enckey); |
104 | |
105 | /* save aligned data to file */ |
106 | file = shmem_kernel_file_setup(name: "" , size: enclen, flags: 0); |
107 | if (IS_ERR(ptr: file)) { |
108 | ret = PTR_ERR(ptr: file); |
109 | goto err_enckey; |
110 | } |
111 | |
112 | written = kernel_write(file, buf, enclen, &pos); |
113 | if (written != enclen) { |
114 | ret = written; |
115 | if (written >= 0) |
116 | ret = -EIO; |
117 | goto err_fput; |
118 | } |
119 | |
120 | /* Pin the mount and dentry to the key so that we can open it again |
121 | * later |
122 | */ |
123 | payload->data = enckey; |
124 | payload->path = file->f_path; |
125 | path_get(&payload->path); |
126 | fput(file); |
127 | kvfree_sensitive(addr: buf, len: enclen); |
128 | } else { |
129 | /* Just store the data in a buffer */ |
130 | void *data = kmalloc(size: datalen, GFP_KERNEL); |
131 | |
132 | if (!data) |
133 | return -ENOMEM; |
134 | |
135 | payload->data = data; |
136 | memcpy(data, prep->data, prep->datalen); |
137 | } |
138 | return 0; |
139 | |
140 | err_fput: |
141 | fput(file); |
142 | err_enckey: |
143 | kfree_sensitive(objp: enckey); |
144 | error: |
145 | kvfree_sensitive(addr: buf, len: enclen); |
146 | return ret; |
147 | } |
148 | |
149 | /* |
150 | * Clear preparsement. |
151 | */ |
152 | void big_key_free_preparse(struct key_preparsed_payload *prep) |
153 | { |
154 | struct big_key_payload *payload = to_big_key_payload(prep->payload); |
155 | |
156 | if (prep->datalen > BIG_KEY_FILE_THRESHOLD) |
157 | path_put(&payload->path); |
158 | kfree_sensitive(objp: payload->data); |
159 | } |
160 | |
161 | /* |
162 | * dispose of the links from a revoked keyring |
163 | * - called with the key sem write-locked |
164 | */ |
165 | void big_key_revoke(struct key *key) |
166 | { |
167 | struct big_key_payload *payload = to_big_key_payload(key->payload); |
168 | |
169 | /* clear the quota */ |
170 | key_payload_reserve(key, datalen: 0); |
171 | if (key_is_positive(key) && payload->length > BIG_KEY_FILE_THRESHOLD) |
172 | vfs_truncate(&payload->path, 0); |
173 | } |
174 | |
175 | /* |
176 | * dispose of the data dangling from the corpse of a big_key key |
177 | */ |
178 | void big_key_destroy(struct key *key) |
179 | { |
180 | struct big_key_payload *payload = to_big_key_payload(key->payload); |
181 | |
182 | if (payload->length > BIG_KEY_FILE_THRESHOLD) { |
183 | path_put(&payload->path); |
184 | payload->path.mnt = NULL; |
185 | payload->path.dentry = NULL; |
186 | } |
187 | kfree_sensitive(objp: payload->data); |
188 | payload->data = NULL; |
189 | } |
190 | |
191 | /* |
192 | * Update a big key |
193 | */ |
194 | int big_key_update(struct key *key, struct key_preparsed_payload *prep) |
195 | { |
196 | int ret; |
197 | |
198 | ret = key_payload_reserve(key, datalen: prep->datalen); |
199 | if (ret < 0) |
200 | return ret; |
201 | |
202 | if (key_is_positive(key)) |
203 | big_key_destroy(key); |
204 | |
205 | return generic_key_instantiate(key, prep); |
206 | } |
207 | |
208 | /* |
209 | * describe the big_key key |
210 | */ |
211 | void big_key_describe(const struct key *key, struct seq_file *m) |
212 | { |
213 | struct big_key_payload *payload = to_big_key_payload(key->payload); |
214 | |
215 | seq_puts(m, s: key->description); |
216 | |
217 | if (key_is_positive(key)) |
218 | seq_printf(m, fmt: ": %zu [%s]" , |
219 | payload->length, |
220 | payload->length > BIG_KEY_FILE_THRESHOLD ? "file" : "buff" ); |
221 | } |
222 | |
223 | /* |
224 | * read the key data |
225 | * - the key's semaphore is read-locked |
226 | */ |
227 | long big_key_read(const struct key *key, char *buffer, size_t buflen) |
228 | { |
229 | struct big_key_payload *payload = to_big_key_payload(key->payload); |
230 | size_t datalen = payload->length; |
231 | long ret; |
232 | |
233 | if (!buffer || buflen < datalen) |
234 | return datalen; |
235 | |
236 | if (datalen > BIG_KEY_FILE_THRESHOLD) { |
237 | struct file *file; |
238 | u8 *buf, *enckey = payload->data; |
239 | size_t enclen = datalen + CHACHA20POLY1305_AUTHTAG_SIZE; |
240 | loff_t pos = 0; |
241 | |
242 | buf = kvmalloc(size: enclen, GFP_KERNEL); |
243 | if (!buf) |
244 | return -ENOMEM; |
245 | |
246 | file = dentry_open(path: &payload->path, O_RDONLY, current_cred()); |
247 | if (IS_ERR(ptr: file)) { |
248 | ret = PTR_ERR(ptr: file); |
249 | goto error; |
250 | } |
251 | |
252 | /* read file to kernel and decrypt */ |
253 | ret = kernel_read(file, buf, enclen, &pos); |
254 | if (ret != enclen) { |
255 | if (ret >= 0) |
256 | ret = -EIO; |
257 | goto err_fput; |
258 | } |
259 | |
260 | ret = chacha20poly1305_decrypt(dst: buf, src: buf, src_len: enclen, NULL, ad_len: 0, nonce: 0, |
261 | key: enckey) ? 0 : -EBADMSG; |
262 | if (unlikely(ret)) |
263 | goto err_fput; |
264 | |
265 | ret = datalen; |
266 | |
267 | /* copy out decrypted data */ |
268 | memcpy(buffer, buf, datalen); |
269 | |
270 | err_fput: |
271 | fput(file); |
272 | error: |
273 | kvfree_sensitive(addr: buf, len: enclen); |
274 | } else { |
275 | ret = datalen; |
276 | memcpy(buffer, payload->data, datalen); |
277 | } |
278 | |
279 | return ret; |
280 | } |
281 | |
282 | /* |
283 | * Register key type |
284 | */ |
285 | static int __init big_key_init(void) |
286 | { |
287 | return register_key_type(ktype: &key_type_big_key); |
288 | } |
289 | |
290 | late_initcall(big_key_init); |
291 | |