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