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