Branch data 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 : 28 : 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 [ + + ]: 28 : if (cached < 0) {
41 [ + - + - ]: 8 : _cleanup_close_ int fd1 = -1, fd2 = -1;
42 : : uint8_t buf[LONGEST_DIGEST+1];
43 : :
44 : 8 : fd1 = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
45 [ - + ]: 8 : 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 [ - + ]: 8 : 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 : 8 : fd2 = accept4(fd1, NULL, 0, SOCK_CLOEXEC);
66 [ - + ]: 8 : if (fd2 < 0) {
67 [ # # ]: 0 : if (errno == EOPNOTSUPP)
68 : 0 : return (cached = false);
69 : :
70 : 0 : return -errno;
71 : : }
72 : :
73 [ - + ]: 8 : 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 : 8 : cached = true;
83 : : }
84 : :
85 : 28 : return cached;
86 : : }
87 : :
88 : 32 : 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 : 32 : } sa = {
93 : : .alg.salg_family = AF_ALG,
94 : : .alg.salg_type = "hash",
95 : : };
96 : :
97 : 32 : _cleanup_(khash_unrefp) khash *h = NULL;
98 : 32 : _cleanup_close_ int fd = -1;
99 : : int supported;
100 : : ssize_t n;
101 : :
102 [ - + ]: 32 : assert(ret);
103 [ + + - + ]: 32 : assert(key || key_size == 0);
104 : :
105 : : /* Filter out an empty algorithm early, as we do not support an algorithm by that name. */
106 [ + + ]: 32 : if (isempty(algorithm))
107 : 8 : return -EINVAL;
108 : :
109 : : /* Overly long hash algorithm names we definitely do not support */
110 [ - + ]: 24 : if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
111 : 0 : return -EOPNOTSUPP;
112 : :
113 : 24 : supported = khash_supported();
114 [ - + ]: 24 : if (supported < 0)
115 : 0 : return supported;
116 [ - + ]: 24 : if (supported == 0)
117 : 0 : return -EOPNOTSUPP;
118 : :
119 : 24 : fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
120 [ - + ]: 24 : if (fd < 0)
121 : 0 : return -errno;
122 : :
123 : 24 : strcpy((char*) sa.alg.salg_name, algorithm);
124 [ + + ]: 24 : if (bind(fd, &sa.sa, sizeof(sa)) < 0) {
125 [ + - ]: 4 : if (errno == ENOENT)
126 : 4 : return -EOPNOTSUPP;
127 : 0 : return -errno;
128 : : }
129 : :
130 [ + + ]: 20 : if (key) {
131 [ - + ]: 16 : if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_size) < 0)
132 : 0 : return -errno;
133 : : }
134 : :
135 : 20 : h = new0(khash, 1);
136 [ - + ]: 20 : if (!h)
137 : 0 : return -ENOMEM;
138 : :
139 : 20 : h->fd = accept4(fd, NULL, 0, SOCK_CLOEXEC);
140 [ - + ]: 20 : if (h->fd < 0)
141 : 0 : return -errno;
142 : :
143 : 20 : h->algorithm = strdup(algorithm);
144 [ - + ]: 20 : 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 : 20 : (void) send(h->fd, NULL, 0, 0);
149 : :
150 : : /* Figure out the digest size */
151 : 20 : n = recv(h->fd, h->digest, sizeof(h->digest), 0);
152 [ - + ]: 20 : if (n < 0)
153 : 0 : return -errno;
154 [ - + ]: 20 : if (n >= LONGEST_DIGEST) /* longer than what we expected? If so, we don't support this */
155 : 0 : return -EOPNOTSUPP;
156 : :
157 : 20 : h->digest_size = (size_t) n;
158 : 20 : h->digest_valid = true;
159 : :
160 : : /* Temporary fix for rc kernel bug: https://bugzilla.redhat.com/show_bug.cgi?id=1395896 */
161 : 20 : (void) send(h->fd, NULL, 0, 0);
162 : :
163 : 20 : *ret = h;
164 : 20 : h = NULL;
165 : :
166 : 20 : return 0;
167 : : }
168 : :
169 : 16 : int khash_new(khash **ret, const char *algorithm) {
170 : 16 : return khash_new_with_key(ret, algorithm, NULL, 0);
171 : : }
172 : :
173 : 24 : khash* khash_unref(khash *h) {
174 [ - + ]: 24 : if (!h)
175 : 0 : return NULL;
176 : :
177 : 24 : safe_close(h->fd);
178 : 24 : free(h->algorithm);
179 : 24 : return mfree(h);
180 : : }
181 : :
182 : 4 : int khash_dup(khash *h, khash **ret) {
183 : 4 : _cleanup_(khash_unrefp) khash *copy = NULL;
184 : :
185 [ - + ]: 4 : assert(h);
186 [ - + ]: 4 : assert(ret);
187 : :
188 : 4 : copy = newdup(khash, h, 1);
189 [ - + ]: 4 : if (!copy)
190 : 0 : return -ENOMEM;
191 : :
192 : 4 : copy->fd = -1;
193 : 4 : copy->algorithm = strdup(h->algorithm);
194 [ - + ]: 4 : if (!copy->algorithm)
195 : 0 : return -ENOMEM;
196 : :
197 : 4 : copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
198 [ - + ]: 4 : if (copy->fd < 0)
199 : 0 : return -errno;
200 : :
201 : 4 : *ret = TAKE_PTR(copy);
202 : :
203 : 4 : return 0;
204 : : }
205 : :
206 : 8 : const char *khash_get_algorithm(khash *h) {
207 [ - + ]: 8 : assert(h);
208 : :
209 : 8 : return h->algorithm;
210 : : }
211 : :
212 : 20 : size_t khash_get_size(khash *h) {
213 [ - + ]: 20 : assert(h);
214 : :
215 : 20 : 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 : 36 : int khash_put(khash *h, const void *buffer, size_t size) {
233 : : ssize_t n;
234 : :
235 [ - + ]: 36 : assert(h);
236 [ - + # # ]: 36 : assert(buffer || size == 0);
237 : :
238 [ - + ]: 36 : if (size <= 0)
239 : 0 : return 0;
240 : :
241 : 36 : n = send(h->fd, buffer, size, MSG_MORE);
242 [ - + ]: 36 : if (n < 0)
243 : 0 : return -errno;
244 : :
245 : 36 : h->digest_valid = false;
246 : :
247 : 36 : 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 : 40 : static int retrieve_digest(khash *h) {
273 : : ssize_t n;
274 : :
275 [ - + ]: 40 : assert(h);
276 : :
277 [ + + ]: 40 : if (h->digest_valid)
278 : 8 : return 0;
279 : :
280 : 32 : n = recv(h->fd, h->digest, h->digest_size, 0);
281 [ - + ]: 32 : if (n < 0)
282 : 0 : return n;
283 [ - + ]: 32 : if ((size_t) n != h->digest_size) /* digest size changed? */
284 : 0 : return -EIO;
285 : :
286 : 32 : h->digest_valid = true;
287 : :
288 : 32 : return 0;
289 : : }
290 : :
291 : 12 : int khash_digest_data(khash *h, const void **ret) {
292 : : int r;
293 : :
294 [ - + ]: 12 : assert(h);
295 [ - + ]: 12 : assert(ret);
296 : :
297 : 12 : r = retrieve_digest(h);
298 [ - + ]: 12 : if (r < 0)
299 : 0 : return r;
300 : :
301 : 12 : *ret = h->digest;
302 : 12 : return 0;
303 : : }
304 : :
305 : 28 : int khash_digest_string(khash *h, char **ret) {
306 : : int r;
307 : : char *p;
308 : :
309 [ - + ]: 28 : assert(h);
310 [ - + ]: 28 : assert(ret);
311 : :
312 : 28 : r = retrieve_digest(h);
313 [ - + ]: 28 : if (r < 0)
314 : 0 : return r;
315 : :
316 : 28 : p = hexmem(h->digest, h->digest_size);
317 [ - + ]: 28 : if (!p)
318 : 0 : return -ENOMEM;
319 : :
320 : 28 : *ret = p;
321 : 28 : return 0;
322 : : }
|