chiark / gitweb /
80548fdfcf9a4c371c035ac050c2f42ac61bcf1a
[elogind.git] / src / libelogind / sd-id128 / sd-id128.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2011 Lennart Poettering
6 ***/
7
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11
12 #include "sd-id128.h"
13
14 #include "alloc-util.h"
15 #include "fd-util.h"
16 #include "hexdecoct.h"
17 #include "id128-util.h"
18 #include "io-util.h"
19 #include "khash.h"
20 #include "macro.h"
21 #include "missing.h"
22 #include "random-util.h"
23 #include "user-util.h"
24 #include "util.h"
25
26 _public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) {
27         unsigned n;
28
29         assert_return(s, NULL);
30
31         for (n = 0; n < 16; n++) {
32                 s[n*2] = hexchar(id.bytes[n] >> 4);
33                 s[n*2+1] = hexchar(id.bytes[n] & 0xF);
34         }
35
36         s[32] = 0;
37
38         return s;
39 }
40
41 _public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
42         unsigned n, i;
43         sd_id128_t t;
44         bool is_guid = false;
45
46         assert_return(s, -EINVAL);
47
48         for (n = 0, i = 0; n < 16;) {
49                 int a, b;
50
51                 if (s[i] == '-') {
52                         /* Is this a GUID? Then be nice, and skip over
53                          * the dashes */
54
55                         if (i == 8)
56                                 is_guid = true;
57                         else if (IN_SET(i, 13, 18, 23)) {
58                                 if (!is_guid)
59                                         return -EINVAL;
60                         } else
61                                 return -EINVAL;
62
63                         i++;
64                         continue;
65                 }
66
67                 a = unhexchar(s[i++]);
68                 if (a < 0)
69                         return -EINVAL;
70
71                 b = unhexchar(s[i++]);
72                 if (b < 0)
73                         return -EINVAL;
74
75                 t.bytes[n++] = (a << 4) | b;
76         }
77
78         if (i != (is_guid ? 36 : 32))
79                 return -EINVAL;
80
81         if (s[i] != 0)
82                 return -EINVAL;
83
84         if (ret)
85                 *ret = t;
86         return 0;
87 }
88
89 _public_ int sd_id128_get_machine(sd_id128_t *ret) {
90         static thread_local sd_id128_t saved_machine_id = {};
91         int r;
92
93         assert_return(ret, -EINVAL);
94
95         if (sd_id128_is_null(saved_machine_id)) {
96                 r = id128_read("/etc/machine-id", ID128_PLAIN, &saved_machine_id);
97                 if (r < 0)
98                         return r;
99
100                 if (sd_id128_is_null(saved_machine_id))
101                         return -ENOMEDIUM;
102         }
103
104         *ret = saved_machine_id;
105         return 0;
106 }
107
108 _public_ int sd_id128_get_boot(sd_id128_t *ret) {
109         static thread_local sd_id128_t saved_boot_id = {};
110         int r;
111
112         assert_return(ret, -EINVAL);
113
114         if (sd_id128_is_null(saved_boot_id)) {
115                 r = id128_read("/proc/sys/kernel/random/boot_id", ID128_UUID, &saved_boot_id);
116                 if (r < 0)
117                         return r;
118         }
119
120         *ret = saved_boot_id;
121         return 0;
122 }
123
124 static int get_invocation_from_keyring(sd_id128_t *ret) {
125
126         _cleanup_free_ char *description = NULL;
127         char *d, *p, *g, *u, *e;
128         unsigned long perms;
129         key_serial_t key;
130         size_t sz = 256;
131         uid_t uid;
132         gid_t gid;
133         int r, c;
134
135 #define MAX_PERMS ((unsigned long) (KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| \
136                                     KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH))
137
138         assert(ret);
139
140         key = request_key("user", "invocation_id", NULL, 0);
141         if (key == -1) {
142                 /* Keyring support not available? No invocation key stored? */
143                 if (IN_SET(errno, ENOSYS, ENOKEY))
144                         return 0;
145
146                 return -errno;
147         }
148
149         for (;;) {
150                 description = new(char, sz);
151                 if (!description)
152                         return -ENOMEM;
153
154                 c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0);
155                 if (c < 0)
156                         return -errno;
157
158                 if ((size_t) c <= sz)
159                         break;
160
161                 sz = c;
162                 free(description);
163         }
164
165         /* The kernel returns a final NUL in the string, verify that. */
166         assert(description[c-1] == 0);
167
168         /* Chop off the final description string */
169         d = strrchr(description, ';');
170         if (!d)
171                 return -EIO;
172         *d = 0;
173
174         /* Look for the permissions */
175         p = strrchr(description, ';');
176         if (!p)
177                 return -EIO;
178
179         errno = 0;
180         perms = strtoul(p + 1, &e, 16);
181         if (errno > 0)
182                 return -errno;
183         if (e == p + 1) /* Read at least one character */
184                 return -EIO;
185         if (e != d) /* Must reached the end */
186                 return -EIO;
187
188         if ((perms & ~MAX_PERMS) != 0)
189                 return -EPERM;
190
191         *p = 0;
192
193         /* Look for the group ID */
194         g = strrchr(description, ';');
195         if (!g)
196                 return -EIO;
197         r = parse_gid(g + 1, &gid);
198         if (r < 0)
199                 return r;
200         if (gid != 0)
201                 return -EPERM;
202         *g = 0;
203
204         /* Look for the user ID */
205         u = strrchr(description, ';');
206         if (!u)
207                 return -EIO;
208         r = parse_uid(u + 1, &uid);
209         if (r < 0)
210                 return r;
211         if (uid != 0)
212                 return -EPERM;
213
214         c = keyctl(KEYCTL_READ, key, (unsigned long) ret, sizeof(sd_id128_t), 0);
215         if (c < 0)
216                 return -errno;
217         if (c != sizeof(sd_id128_t))
218                 return -EIO;
219
220         return 1;
221 }
222
223 _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
224         static thread_local sd_id128_t saved_invocation_id = {};
225         int r;
226
227         assert_return(ret, -EINVAL);
228
229         if (sd_id128_is_null(saved_invocation_id)) {
230
231                 /* We first try to read the invocation ID from the kernel keyring. This has the benefit that it is not
232                  * fakeable by unprivileged code. If the information is not available in the keyring, we use
233                  * $INVOCATION_ID but ignore the data if our process was called by less privileged code
234                  * (i.e. secure_getenv() instead of getenv()).
235                  *
236                  * The kernel keyring is only relevant for system services (as for user services we don't store the
237                  * invocation ID in the keyring, as there'd be no trust benefit in that). The environment variable is
238                  * primarily relevant for user services, and sufficiently safe as no privilege boundary is involved. */
239
240                 r = get_invocation_from_keyring(&saved_invocation_id);
241                 if (r < 0)
242                         return r;
243
244                 if (r == 0) {
245                         const char *e;
246
247                         e = secure_getenv("INVOCATION_ID");
248                         if (!e)
249                                 return -ENXIO;
250
251                         r = sd_id128_from_string(e, &saved_invocation_id);
252                         if (r < 0)
253                                 return r;
254                 }
255         }
256
257         *ret = saved_invocation_id;
258         return 0;
259 }
260
261 static sd_id128_t make_v4_uuid(sd_id128_t id) {
262         /* Stolen from generate_random_uuid() of drivers/char/random.c
263          * in the kernel sources */
264
265         /* Set UUID version to 4 --- truly random generation */
266         id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
267
268         /* Set the UUID variant to DCE */
269         id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
270
271         return id;
272 }
273
274 _public_ int sd_id128_randomize(sd_id128_t *ret) {
275         sd_id128_t t;
276         int r;
277
278         assert_return(ret, -EINVAL);
279
280         r = acquire_random_bytes(&t, sizeof t, true);
281         if (r < 0)
282                 return r;
283
284         /* Turn this into a valid v4 UUID, to be nice. Note that we
285          * only guarantee this for newly generated UUIDs, not for
286          * pre-existing ones. */
287
288         *ret = make_v4_uuid(t);
289         return 0;
290 }
291
292 _public_ int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret) {
293         _cleanup_(khash_unrefp) khash *h = NULL;
294         sd_id128_t m, result;
295         const void *p;
296         int r;
297
298         assert_return(ret, -EINVAL);
299
300         r = sd_id128_get_machine(&m);
301         if (r < 0)
302                 return r;
303
304         r = khash_new_with_key(&h, "hmac(sha256)", &m, sizeof(m));
305         if (r < 0)
306                 return r;
307
308         r = khash_put(h, &app_id, sizeof(app_id));
309         if (r < 0)
310                 return r;
311
312         r = khash_digest_data(h, &p);
313         if (r < 0)
314                 return r;
315
316         /* We chop off the trailing 16 bytes */
317         memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
318
319         *ret = make_v4_uuid(result);
320         return 0;
321 }