chiark / gitweb /
khash: try to detect broken AF_ALG support in centos kernels
[elogind.git] / src / basic / khash.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
38 struct 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
46 int 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
106 int 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;
117         int supported;
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
131         supported = khash_supported();
132         if (supported < 0)
133                 return supported;
134         if (supported == 0)
135                 return -EOPNOTSUPP;
136
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
187 int khash_new(khash **ret, const char *algorithm) {
188         return khash_new_with_key(ret, algorithm, NULL, 0);
189 }
190
191 khash* khash_unref(khash *h) {
192         if (!h)
193                 return NULL;
194
195         safe_close(h->fd);
196         free(h->algorithm);
197         return mfree(h);
198 }
199
200 int 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);
212         if (!copy->algorithm)
213                 return -ENOMEM;
214
215         copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
216         if (copy->fd < 0)
217                 return -errno;
218
219         *ret = copy;
220         copy = NULL;
221
222         return 0;
223 }
224
225 const char *khash_get_algorithm(khash *h) {
226         assert(h);
227
228         return h->algorithm;
229 }
230
231 size_t khash_get_size(khash *h) {
232         assert(h);
233
234         return h->digest_size;
235 }
236
237 int khash_reset(khash *h) {
238         ssize_t n;
239
240         assert(h);
241
242         n = send(h->fd, NULL, 0, 0);
243         if (n < 0)
244                 return -errno;
245
246         h->digest_valid = false;
247
248         return 0;
249 }
250
251 int khash_put(khash *h, const void *buffer, size_t size) {
252         ssize_t n;
253
254         assert(h);
255         assert(buffer || size == 0);
256
257         if (size <= 0)
258                 return 0;
259
260         n = send(h->fd, buffer, size, MSG_MORE);
261         if (n < 0)
262                 return -errno;
263
264         h->digest_valid = false;
265
266         return 0;
267 }
268
269 int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) {
270         struct msghdr mh = {
271                 mh.msg_iov = (struct iovec*) iovec,
272                 mh.msg_iovlen = n,
273         };
274         ssize_t k;
275
276         assert(h);
277         assert(iovec || n == 0);
278
279         if (n <= 0)
280                 return 0;
281
282         k = sendmsg(h->fd, &mh, MSG_MORE);
283         if (k < 0)
284                 return -errno;
285
286         h->digest_valid = false;
287
288         return 0;
289 }
290
291 static int retrieve_digest(khash *h) {
292         ssize_t n;
293
294         assert(h);
295
296         if (h->digest_valid)
297                 return 0;
298
299         n = recv(h->fd, h->digest, h->digest_size, 0);
300         if (n < 0)
301                 return n;
302         if ((size_t) n != h->digest_size) /* digest size changed? */
303                 return -EIO;
304
305         h->digest_valid = true;
306
307         return 0;
308 }
309
310 int khash_digest_data(khash *h, const void **ret) {
311         int r;
312
313         assert(h);
314         assert(ret);
315
316         r = retrieve_digest(h);
317         if (r < 0)
318                 return r;
319
320         *ret = h->digest;
321         return 0;
322 }
323
324 int khash_digest_string(khash *h, char **ret) {
325         int r;
326         char *p;
327
328         assert(h);
329         assert(ret);
330
331         r = retrieve_digest(h);
332         if (r < 0)
333                 return r;
334
335         p = hexmem(h->digest, h->digest_size);
336         if (!p)
337                 return -ENOMEM;
338
339         *ret = p;
340         return 0;
341 }