]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/khash.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / basic / khash.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
0fe5f3c5
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2016 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
21#include <linux/if_alg.h>
22#include <stdbool.h>
23#include <sys/socket.h>
24
25#include "alloc-util.h"
26#include "fd-util.h"
27#include "hexdecoct.h"
28#include "khash.h"
29#include "macro.h"
30#include "missing.h"
31#include "string-util.h"
32#include "util.h"
33
34/* On current kernels the maximum digest (according to "grep digestsize /proc/crypto | sort -u") is actually 32, but
35 * let's add some extra room, the few wasted bytes don't really matter... */
36#define LONGEST_DIGEST 128
37
38struct khash {
39 int fd;
40 char *algorithm;
41 uint8_t digest[LONGEST_DIGEST+1];
42 size_t digest_size;
43 bool digest_valid;
44};
45
09b9348e
LP
46int khash_supported(void) {
47 static const union {
48 struct sockaddr sa;
49 struct sockaddr_alg alg;
50 } sa = {
51 .alg.salg_family = AF_ALG,
52 .alg.salg_type = "hash",
53 .alg.salg_name = "sha256", /* a very common algorithm */
54 };
55
56 static int cached = -1;
57
58 if (cached < 0) {
59 _cleanup_close_ int fd1 = -1, fd2 = -1;
60 uint8_t buf[LONGEST_DIGEST+1];
61
62 fd1 = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
63 if (fd1 < 0) {
64 /* The kernel returns EAFNOSUPPORT if AF_ALG is not supported at all */
65 if (IN_SET(errno, EAFNOSUPPORT, EOPNOTSUPP))
66 return (cached = false);
67
68 return -errno;
69 }
70
71 if (bind(fd1, &sa.sa, sizeof(sa)) < 0) {
72 /* The kernel returns ENOENT if the selected algorithm is not supported at all. We use a check
73 * for SHA256 as a proxy for whether the whole API is supported at all. After all it's one of
74 * the most common hash functions, and if it isn't supported, that's ample indication that
75 * something is really off. */
76
77 if (IN_SET(errno, ENOENT, EOPNOTSUPP))
78 return (cached = false);
79
80 return -errno;
81 }
82
83 fd2 = accept4(fd1, NULL, 0, SOCK_CLOEXEC);
84 if (fd2 < 0) {
85 if (errno == EOPNOTSUPP)
86 return (cached = false);
87
88 return -errno;
89 }
90
91 if (recv(fd2, buf, sizeof(buf), 0) < 0) {
92 /* On some kernels we get ENOKEY for non-keyed hash functions (such as sha256), let's refuse
93 * using the API in those cases, since the kernel is
94 * broken. https://github.com/systemd/systemd/issues/8278 */
95
96 if (IN_SET(errno, ENOKEY, EOPNOTSUPP))
97 return (cached = false);
98 }
99
100 cached = true;
101 }
102
103 return cached;
104}
105
0fe5f3c5
LP
106int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size) {
107 union {
108 struct sockaddr sa;
109 struct sockaddr_alg alg;
110 } sa = {
111 .alg.salg_family = AF_ALG,
112 .alg.salg_type = "hash",
113 };
114
115 _cleanup_(khash_unrefp) khash *h = NULL;
116 _cleanup_close_ int fd = -1;
09b9348e 117 int supported;
0fe5f3c5
LP
118 ssize_t n;
119
120 assert(ret);
121 assert(key || key_size == 0);
122
123 /* Filter out an empty algorithm early, as we do not support an algorithm by that name. */
124 if (isempty(algorithm))
125 return -EINVAL;
126
127 /* Overly long hash algorithm names we definitely do not support */
128 if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
129 return -EOPNOTSUPP;
130
09b9348e
LP
131 supported = khash_supported();
132 if (supported < 0)
133 return supported;
134 if (supported == 0)
135 return -EOPNOTSUPP;
136
0fe5f3c5
LP
137 fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
138 if (fd < 0)
139 return -errno;
140
141 strcpy((char*) sa.alg.salg_name, algorithm);
142 if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
143 if (errno == ENOENT)
144 return -EOPNOTSUPP;
145 return -errno;
146 }
147
148 if (key) {
149 if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0)
150 return -errno;
151 }
152
153 h = new0(khash, 1);
154 if (!h)
155 return -ENOMEM;
156
157 h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC);
158 if (h->fd < 0)
159 return -errno;
160
161 h->algorithm = strdup(algorithm);
162 if (!h->algorithm)
163 return -ENOMEM;
164
165 /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
166 (void) send(h->fd, NULL, 0, 0);
167
168 /* Figure out the digest size */
169 n = recv(h->fd, h->digest, sizeof(h->digest), 0);
170 if (n < 0)
171 return -errno;
172 if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */
173 return -EOPNOTSUPP;
174
175 h->digest_size = (size_t) n;
176 h->digest_valid = true;
177
178 /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
179 (void) send(h->fd, NULL, 0, 0);
180
181 *ret = h;
182 h = NULL;
183
184 return 0;
185}
186
187int khash_new(khash **ret, const char *algorithm) {
188 return khash_new_with_key(ret, algorithm, NULL, 0);
189}
190
191khash* khash_unref(khash *h) {
192 if (!h)
193 return NULL;
194
195 safe_close(h->fd);
196 free(h->algorithm);
5fecf46d 197 return mfree(h);
0fe5f3c5
LP
198}
199
200int khash_dup(khash *h, khash **ret) {
201 _cleanup_(khash_unrefp) khash *copy = NULL;
202
203 assert(h);
204 assert(ret);
205
206 copy = newdup(khash, h, 1);
207 if (!copy)
208 return -ENOMEM;
209
210 copy->fd = -1;
211 copy->algorithm = strdup(h->algorithm);
ef1fd941 212 if (!copy->algorithm)
0fe5f3c5
LP
213 return -ENOMEM;
214
215 copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
216 if (copy->fd < 0)
217 return -errno;
218
1cc6c93a 219 *ret = TAKE_PTR(copy);
0fe5f3c5
LP
220
221 return 0;
222}
223
224const char *khash_get_algorithm(khash *h) {
225 assert(h);
226
227 return h->algorithm;
228}
229
230size_t khash_get_size(khash *h) {
231 assert(h);
232
233 return h->digest_size;
234}
235
236int khash_reset(khash *h) {
237 ssize_t n;
238
239 assert(h);
240
241 n = send(h->fd, NULL, 0, 0);
242 if (n < 0)
243 return -errno;
244
245 h->digest_valid = false;
246
247 return 0;
248}
249
250int khash_put(khash *h, const void *buffer, size_t size) {
251 ssize_t n;
252
253 assert(h);
254 assert(buffer || size == 0);
255
256 if (size <= 0)
257 return 0;
258
259 n = send(h->fd, buffer, size, MSG_MORE);
260 if (n < 0)
261 return -errno;
262
263 h->digest_valid = false;
264
265 return 0;
266}
267
268int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) {
269 struct msghdr mh = {
270 mh.msg_iov = (struct iovec*) iovec,
271 mh.msg_iovlen = n,
272 };
273 ssize_t k;
274
275 assert(h);
276 assert(iovec || n == 0);
277
278 if (n <= 0)
279 return 0;
280
281 k = sendmsg(h->fd, &mh, MSG_MORE);
282 if (k < 0)
283 return -errno;
284
285 h->digest_valid = false;
286
287 return 0;
288}
289
290static int retrieve_digest(khash *h) {
291 ssize_t n;
292
293 assert(h);
294
295 if (h->digest_valid)
296 return 0;
297
298 n = recv(h->fd, h->digest, h->digest_size, 0);
299 if (n < 0)
300 return n;
301 if ((size_t) n != h->digest_size) /* digest size changed? */
302 return -EIO;
303
304 h->digest_valid = true;
305
306 return 0;
307}
308
309int khash_digest_data(khash *h, const void **ret) {
310 int r;
311
312 assert(h);
313 assert(ret);
314
315 r = retrieve_digest(h);
316 if (r < 0)
317 return r;
318
319 *ret = h->digest;
320 return 0;
321}
322
323int khash_digest_string(khash *h, char **ret) {
324 int r;
325 char *p;
326
327 assert(h);
328 assert(ret);
329
330 r = retrieve_digest(h);
331 if (r < 0)
332 return r;
333
334 p = hexmem(h->digest, h->digest_size);
335 if (!p)
336 return -ENOMEM;
337
338 *ret = p;
339 return 0;
340}