Coverage Report

Created: 2026-07-30 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/shared/osc-context.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include <sys/auxv.h>
4
5
#include "escape.h"
6
#include "format-util.h"
7
#include "hostname-setup.h"
8
#include "id128-util.h"
9
#include "osc-context.h"
10
#include "pidfd-util.h"
11
#include "process-util.h"
12
#include "string-util.h"
13
#include "strv.h"
14
#include "terminal-util.h"
15
#include "user-util.h"
16
17
/* This currently generates open sequences for OSC 3008 types "boot", "container", "vm", "elevate", "chpriv",
18
 * "subcontext", "session", "service".
19
 *
20
 * NB: Not generated by systemd: "remote" (would have to be generated from the SSH client), "app". */
21
22
0
static int strextend_escaped(char **s, const char *prefix, const char *value) {
23
0
        assert(s);
24
0
        assert(value);
25
26
0
        if (!strextend(s, prefix))
27
0
                return -ENOMEM;
28
29
0
        _cleanup_free_ char *e = xescape(value, ";\\");
30
0
        if (!e)
31
0
                return -ENOMEM;
32
33
0
        if (!strextend(s, e))
34
0
                return -ENOMEM;
35
36
0
        return 0;
37
0
}
38
39
0
static int osc_append_identity(char **s) {
40
0
        int r;
41
42
0
        assert(s);
43
44
0
        _cleanup_free_ char *u = getusername_malloc();
45
0
        if (u) {
46
0
                r = strextend_escaped(s, ";user=", u);
47
0
                if (r < 0)
48
0
                        return r;
49
0
        }
50
51
0
        _cleanup_free_ char *h = gethostname_malloc();
52
0
        if (h) {
53
0
                r = strextend_escaped(s, ";hostname=", h);
54
0
                if (r < 0)
55
0
                        return r;
56
0
        }
57
58
0
        sd_id128_t id;
59
0
        if (sd_id128_get_machine(&id) >= 0) {
60
0
                r = strextendf(s, ";machineid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
61
0
                if (r < 0)
62
0
                        return r;
63
0
        }
64
65
0
        if (sd_id128_get_boot(&id) >= 0) {
66
0
                r = strextendf(s, ";bootid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
67
0
                if (r < 0)
68
0
                        return r;
69
0
        }
70
71
0
        r = strextendf(s, ";pid=" PID_FMT, getpid_cached());
72
0
        if (r < 0)
73
0
                return r;
74
75
0
        uint64_t pidfdid;
76
0
        if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0) {
77
0
                r = strextendf(s, ";pidfdid=%" PRIu64, pidfdid);
78
0
                if (r < 0)
79
0
                        return r;
80
0
        }
81
82
0
        r = strextend_escaped(s, ";comm=", program_invocation_short_name);
83
0
        if (r < 0)
84
0
                return r;
85
86
0
        return 0;
87
0
}
88
89
0
static void osc_context_default_id(sd_id128_t *ret_id) {
90
91
        /* Usually we only want one context ID per tool. Since we don't want to store the ID let's just hash
92
         * one from process credentials */
93
94
0
        struct {
95
0
                const char label[32];
96
0
                uint64_t pidfdid;
97
0
                uint8_t auxval[16];
98
0
                pid_t pid;
99
0
        } data = {
100
0
                .label = "systemd context hash bytes v1",
101
0
                .pid = getpid_cached(),
102
0
        };
103
104
0
        assert(ret_id);
105
106
0
        memcpy(data.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(data.auxval));
107
0
        (void) pidfd_get_inode_id_self_cached(&data.pidfdid);
108
109
0
        *ret_id = id128_digest(&data, sizeof(data));
110
0
}
111
112
0
static int osc_context_intro_raw(sd_id128_t id, char **ret_seq) {
113
0
        int r;
114
115
0
        assert(ret_seq);
116
117
0
        _cleanup_free_ char *seq = NULL;
118
0
        if (asprintf(&seq, ANSI_OSC "3008;start=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0)
119
0
                return -ENOMEM;
120
121
0
        r = osc_append_identity(&seq);
122
0
        if (r < 0)
123
0
                return r;
124
125
0
        *ret_seq = TAKE_PTR(seq);
126
0
        return 0;
127
0
}
128
129
0
static int osc_context_intro(char **ret_seq, sd_id128_t *ret_context_id) {
130
0
        int r;
131
132
0
        assert(ret_seq);
133
134
        /* If the user passed us a buffer for the context ID generate a randomized one, since we have a place
135
         * to store it. The user should pass the ID back to osc_context_close() later on. If the user did not
136
         * pass us a buffer, we'll use a session ID hashed from process properties that remain stable as long
137
         * our process exists. It hence also remains stable across reexec and similar. */
138
0
        sd_id128_t id;
139
0
        if (ret_context_id) {
140
0
                r = sd_id128_randomize(&id);
141
0
                if (r < 0)
142
0
                        return r;
143
0
        } else
144
0
                osc_context_default_id(&id);
145
146
0
        r = osc_context_intro_raw(id, ret_seq);
147
0
        if (r < 0)
148
0
                return r;
149
150
0
        if (ret_context_id)
151
0
                *ret_context_id = id;
152
153
0
        return 0;
154
0
}
155
156
0
static int osc_context_outro(char *_seq, sd_id128_t id, char **ret_seq, sd_id128_t *ret_context_id) {
157
0
        _cleanup_free_ char *seq = TAKE_PTR(_seq); /* We take possession of the string no matter what */
158
159
0
        if (ret_seq)
160
0
                *ret_seq = TAKE_PTR(seq);
161
0
        else {
162
0
                fputs(seq, stdout);
163
0
                fflush(stdout);
164
0
        }
165
166
0
        if (ret_context_id)
167
0
                *ret_context_id = id;
168
169
0
        return 0;
170
0
}
171
172
0
int osc_context_open_boot(char **ret_seq) {
173
0
        int r;
174
175
0
        _cleanup_free_ char *seq = NULL;
176
0
        r = osc_context_intro(&seq, /* ret_context_id= */ NULL);
177
0
        if (r < 0)
178
0
                return r;
179
180
0
        if (!strextend(&seq, ";type=boot" ANSI_ST))
181
0
                return -ENOMEM;
182
183
0
        return osc_context_outro(TAKE_PTR(seq), /* context_id= */ SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL);
184
0
}
185
186
0
int osc_context_open_container(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
187
0
        int r;
188
189
0
        _cleanup_free_ char *seq = NULL;
190
0
        sd_id128_t id = SD_ID128_NULL;
191
0
        r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
192
0
        if (r < 0)
193
0
                return r;
194
195
0
        if (name) {
196
0
                r = strextend_escaped(&seq, ";container=", name);
197
0
                if (r < 0)
198
0
                        return r;
199
0
        }
200
201
0
        if (!strextend(&seq, ";type=container" ANSI_ST))
202
0
                return -ENOMEM;
203
204
0
        return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
205
0
}
206
207
0
int osc_context_open_vm(const char *name, char **ret_seq, sd_id128_t *ret_context_id) {
208
0
        int r;
209
210
0
        assert(name);
211
212
0
        _cleanup_free_ char *seq = NULL;
213
0
        sd_id128_t id = SD_ID128_NULL;
214
0
        r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
215
0
        if (r < 0)
216
0
                return r;
217
218
0
        r = strextend_escaped(&seq, ";vm=", name);
219
0
        if (r < 0)
220
0
                return r;
221
222
0
        if (!strextend(&seq, ";type=vm" ANSI_ST))
223
0
                return -ENOMEM;
224
225
0
        return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
226
0
}
227
228
0
int osc_context_open_chpriv(const char *target_user, char **ret_seq, sd_id128_t *ret_context_id) {
229
0
        int r;
230
231
0
        assert(target_user);
232
233
0
        _cleanup_free_ char *seq = NULL;
234
0
        sd_id128_t id = SD_ID128_NULL;
235
0
        r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
236
0
        if (r < 0)
237
0
                return r;
238
239
0
        if (is_this_me(target_user) > 0) {
240
0
                if (!strextend(&seq, ";type=subcontext" ANSI_ST))
241
0
                        return -ENOMEM;
242
0
        } else if (STR_IN_SET(target_user, "root", "0")) {
243
0
                if (!strextend(&seq, ";type=elevate" ANSI_ST))
244
0
                        return -ENOMEM;
245
0
        } else {
246
0
                r = strextend_escaped(&seq, ";targetuser=", target_user);
247
0
                if (r < 0)
248
0
                        return r;
249
250
0
                if (!strextend(&seq, ";type=chpriv" ANSI_ST))
251
0
                        return -ENOMEM;
252
0
        }
253
254
0
        return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
255
0
}
256
257
0
int osc_context_open_session(const char *user, const char *session_id, char **ret_seq, sd_id128_t *ret_context_id) {
258
0
        int r;
259
260
0
        _cleanup_free_ char *seq = NULL;
261
0
        sd_id128_t id = SD_ID128_NULL;
262
0
        r = osc_context_intro(&seq, ret_context_id ? &id : NULL);
263
0
        if (r < 0)
264
0
                return r;
265
266
0
        if (user) {
267
0
                r = strextend_escaped(&seq, ";targetuser=", user);
268
0
                if (r < 0)
269
0
                        return r;
270
0
        }
271
272
0
        if (session_id) {
273
0
                r = strextend_escaped(&seq, ";sessionid=", session_id);
274
0
                if (r < 0)
275
0
                        return r;
276
0
        }
277
278
0
        if (!strextend(&seq, ";type=session" ANSI_ST))
279
0
                return -ENOMEM;
280
281
0
        return osc_context_outro(TAKE_PTR(seq), id, ret_seq, ret_context_id);
282
0
}
283
284
0
int osc_context_open_service(const char *unit, sd_id128_t invocation_id, char **ret_seq) {
285
0
        int r;
286
287
0
        sd_id128_t id = SD_ID128_NULL;
288
0
        r = osc_context_id_from_invocation_id(invocation_id, &id);
289
0
        if (r < 0)
290
0
                return r;
291
292
0
        _cleanup_free_ char *seq = NULL;
293
0
        r = osc_context_intro_raw(id, &seq);
294
0
        if (r < 0)
295
0
                return r;
296
297
0
        if (unit) {
298
0
                r = strextend_escaped(&seq, ";servicename=", unit);
299
0
                if (r < 0)
300
0
                        return r;
301
0
        }
302
303
0
        r = strextendf(&seq, ";invocationid=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(invocation_id));
304
0
        if (r < 0)
305
0
                return r;
306
307
0
        if (!strextend(&seq, ";type=service" ANSI_ST))
308
0
                return -ENOMEM;
309
310
0
        return osc_context_outro(TAKE_PTR(seq), id, ret_seq, /* ret_context_id= */ NULL);
311
0
}
312
313
0
int osc_context_close(sd_id128_t id, char **ret_seq) {
314
315
0
        if (sd_id128_is_null(id)) {
316
                /* nil uuid: no session opened */
317
0
                if (ret_seq)
318
0
                        *ret_seq = NULL;
319
0
                return 0;
320
0
        }
321
322
0
        if (sd_id128_is_allf(id)) /* max uuid: default session opened */
323
0
                osc_context_default_id(&id);
324
325
0
        _cleanup_free_ char *seq = NULL;
326
0
        if (asprintf(&seq, ANSI_OSC "3008;end=" SD_ID128_FORMAT_STR ANSI_ST, SD_ID128_FORMAT_VAL(id)) < 0)
327
0
                return -ENOMEM;
328
329
0
        return osc_context_outro(TAKE_PTR(seq), SD_ID128_NULL, ret_seq, /* ret_context_id= */ NULL);
330
0
}
331
332
0
int osc_context_id_from_invocation_id(sd_id128_t id, sd_id128_t *ret) {
333
0
        static const sd_id128_t app_id = SD_ID128_MAKE(5d,63,a5,8d,96,fd,45,d0,a0,f0,63,50,fc,d8,9a,cd);
334
335
0
        assert(ret);
336
337
        /* Hash the context id from the invocation id, so that we don't have to store the context id (as long
338
         * as we have the invocation ID). We don't use the invocation ID literally in order not to make it too
339
         * easy to cancel our service context because the invocation ID is reported literally via the
340
         * $INVOCATION_ID env var. */
341
0
        return sd_id128_get_app_specific(id, app_id, ret);
342
0
}