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