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