Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
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 :
20 : struct 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 :
28 7 : int 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 7 : if (cached < 0) {
41 2 : _cleanup_close_ int fd1 = -1, fd2 = -1;
42 : uint8_t buf[LONGEST_DIGEST+1];
43 :
44 2 : fd1 = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
45 2 : if (fd1 < 0) {
46 : /* The kernel returns EAFNOSUPPORT if AF_ALG is not supported at all */
47 0 : if (IN_SET(errno, EAFNOSUPPORT, EOPNOTSUPP))
48 0 : return (cached = false);
49 :
50 0 : return -errno;
51 : }
52 :
53 2 : 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 0 : if (IN_SET(errno, ENOENT, EOPNOTSUPP))
60 0 : return (cached = false);
61 :
62 0 : return -errno;
63 : }
64 :
65 2 : fd2 = accept4(fd1, NULL, 0, SOCK_CLOEXEC);
66 2 : if (fd2 < 0) {
67 0 : if (errno == EOPNOTSUPP)
68 0 : return (cached = false);
69 :
70 0 : return -errno;
71 : }
72 :
73 2 : 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 0 : if (IN_SET(errno, ENOKEY, EOPNOTSUPP))
79 0 : return (cached = false);
80 : }
81 :
82 2 : cached = true;
83 : }
84 :
85 7 : return cached;
86 : }
87 :
88 8 : int 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 8 : } sa = {
93 : .alg.salg_family = AF_ALG,
94 : .alg.salg_type = "hash",
95 : };
96 :
97 8 : _cleanup_(khash_unrefp) khash *h = NULL;
98 8 : _cleanup_close_ int fd = -1;
99 : int supported;
100 : ssize_t n;
101 :
102 8 : assert(ret);
103 8 : assert(key || key_size == 0);
104 :
105 : /* Filter out an empty algorithm early, as we do not support an algorithm by that name. */
106 8 : if (isempty(algorithm))
107 2 : return -EINVAL;
108 :
109 : /* Overly long hash algorithm names we definitely do not support */
110 6 : if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
111 0 : return -EOPNOTSUPP;
112 :
113 6 : supported = khash_supported();
114 6 : if (supported < 0)
115 0 : return supported;
116 6 : if (supported == 0)
117 0 : return -EOPNOTSUPP;
118 :
119 6 : fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
120 6 : if (fd < 0)
121 0 : return -errno;
122 :
123 6 : strcpy((char*) sa.alg.salg_name, algorithm);
124 6 : if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
125 1 : if (errno == ENOENT)
126 1 : return -EOPNOTSUPP;
127 0 : return -errno;
128 : }
129 :
130 5 : if (key) {
131 4 : if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0)
132 0 : return -errno;
133 : }
134 :
135 5 : h = new0(khash, 1);
136 5 : if (!h)
137 0 : return -ENOMEM;
138 :
139 5 : h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC);
140 5 : if (h->fd < 0)
141 0 : return -errno;
142 :
143 5 : h->algorithm = strdup(algorithm);
144 5 : if (!h->algorithm)
145 0 : return -ENOMEM;
146 :
147 : /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
148 5 : (void) send(h->fd, NULL, 0, 0);
149 :
150 : /* Figure out the digest size */
151 5 : n = recv(h->fd, h->digest, sizeof(h->digest), 0);
152 5 : if (n < 0)
153 0 : return -errno;
154 5 : if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */
155 0 : return -EOPNOTSUPP;
156 :
157 5 : h->digest_size = (size_t) n;
158 5 : h->digest_valid = true;
159 :
160 : /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
161 5 : (void) send(h->fd, NULL, 0, 0);
162 :
163 5 : *ret = h;
164 5 : h = NULL;
165 :
166 5 : return 0;
167 : }
168 :
169 4 : int khash_new(khash **ret, const char *algorithm) {
170 4 : return khash_new_with_key(ret, algorithm, NULL, 0);
171 : }
172 :
173 6 : khash* khash_unref(khash *h) {
174 6 : if (!h)
175 0 : return NULL;
176 :
177 6 : safe_close(h->fd);
178 6 : free(h->algorithm);
179 6 : return mfree(h);
180 : }
181 :
182 1 : int khash_dup(khash *h, khash **ret) {
183 1 : _cleanup_(khash_unrefp) khash *copy = NULL;
184 :
185 1 : assert(h);
186 1 : assert(ret);
187 :
188 1 : copy = newdup(khash, h, 1);
189 1 : if (!copy)
190 0 : return -ENOMEM;
191 :
192 1 : copy->fd = -1;
193 1 : copy->algorithm = strdup(h->algorithm);
194 1 : if (!copy->algorithm)
195 0 : return -ENOMEM;
196 :
197 1 : copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
198 1 : if (copy->fd < 0)
199 0 : return -errno;
200 :
201 1 : *ret = TAKE_PTR(copy);
202 :
203 1 : return 0;
204 : }
205 :
206 2 : const char *khash_get_algorithm(khash *h) {
207 2 : assert(h);
208 :
209 2 : return h->algorithm;
210 : }
211 :
212 5 : size_t khash_get_size(khash *h) {
213 5 : assert(h);
214 :
215 5 : return h->digest_size;
216 : }
217 :
218 0 : int khash_reset(khash *h) {
219 : ssize_t n;
220 :
221 0 : assert(h);
222 :
223 0 : n = send(h->fd, NULL, 0, 0);
224 0 : if (n < 0)
225 0 : return -errno;
226 :
227 0 : h->digest_valid = false;
228 :
229 0 : return 0;
230 : }
231 :
232 9 : int khash_put(khash *h, const void *buffer, size_t size) {
233 : ssize_t n;
234 :
235 9 : assert(h);
236 9 : assert(buffer || size == 0);
237 :
238 9 : if (size <= 0)
239 0 : return 0;
240 :
241 9 : n = send(h->fd, buffer, size, MSG_MORE);
242 9 : if (n < 0)
243 0 : return -errno;
244 :
245 9 : h->digest_valid = false;
246 :
247 9 : return 0;
248 : }
249 :
250 0 : int khash_put_iovec(khash *h, const struct iovec *iovec, size_t n) {
251 0 : struct msghdr mh = {
252 0 : mh.msg_iov = (struct iovec*) iovec,
253 0 : mh.msg_iovlen = n,
254 : };
255 : ssize_t k;
256 :
257 0 : assert(h);
258 0 : assert(iovec || n == 0);
259 :
260 0 : if (n <= 0)
261 0 : return 0;
262 :
263 0 : k = sendmsg(h->fd, &mh, MSG_MORE);
264 0 : if (k < 0)
265 0 : return -errno;
266 :
267 0 : h->digest_valid = false;
268 :
269 0 : return 0;
270 : }
271 :
272 10 : static int retrieve_digest(khash *h) {
273 : ssize_t n;
274 :
275 10 : assert(h);
276 :
277 10 : if (h->digest_valid)
278 2 : return 0;
279 :
280 8 : n = recv(h->fd, h->digest, h->digest_size, 0);
281 8 : if (n < 0)
282 0 : return n;
283 8 : if ((size_t) n != h->digest_size) /* digest size changed? */
284 0 : return -EIO;
285 :
286 8 : h->digest_valid = true;
287 :
288 8 : return 0;
289 : }
290 :
291 3 : int khash_digest_data(khash *h, const void **ret) {
292 : int r;
293 :
294 3 : assert(h);
295 3 : assert(ret);
296 :
297 3 : r = retrieve_digest(h);
298 3 : if (r < 0)
299 0 : return r;
300 :
301 3 : *ret = h->digest;
302 3 : return 0;
303 : }
304 :
305 7 : int khash_digest_string(khash *h, char **ret) {
306 : int r;
307 : char *p;
308 :
309 7 : assert(h);
310 7 : assert(ret);
311 :
312 7 : r = retrieve_digest(h);
313 7 : if (r < 0)
314 0 : return r;
315 :
316 7 : p = hexmem(h->digest, h->digest_size);
317 7 : if (!p)
318 0 : return -ENOMEM;
319 :
320 7 : *ret = p;
321 7 : return 0;
322 : }
|