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