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