• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 19520565317

19 Nov 2025 11:19PM UTC coverage: 72.548% (+0.1%) from 72.449%
19520565317

push

github

web-flow
core: Verify inherited FDs are writable for stdout/stderr (#39674)

When inheriting file descriptors for stdout/stderr (either from stdin or
when making stderr inherit from stdout), we previously just assumed they
would be writable and dup'd them. This could lead to broken setups if
the inherited FD was actually opened read-only.

Before dup'ing any inherited FDs to stdout/stderr, verify they are
actually writable using the new fd_is_writable() helper. If not, fall
back to /dev/null (or reopen the terminal in the TTY case) with a
warning, rather than silently creating a broken setup where output
operations would fail.

31 of 44 new or added lines in 3 files covered. (70.45%)

813 existing lines in 43 files now uncovered.

308541 of 425291 relevant lines covered (72.55%)

1188151.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/ssh-generator/ssh-generator.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <unistd.h>
4

5
#include "alloc-util.h"
6
#include "creds-util.h"
7
#include "errno-util.h"
8
#include "fd-util.h"
9
#include "fileio.h"
10
#include "generator.h"
11
#include "install.h"
12
#include "log.h"
13
#include "parse-util.h"
14
#include "path-lookup.h"
15
#include "path-util.h"
16
#include "proc-cmdline.h"
17
#include "socket-netlink.h"
18
#include "socket-util.h"
19
#include "special.h"
20
#include "ssh-util.h"
21
#include "string-util.h"
22
#include "strv.h"
23
#include "virt.h"
24

25
/* A small generator binding potentially five or more SSH sockets:
26
 *
27
 *     1. Listen on AF_VSOCK port 22 if we run in a VM with AF_VSOCK enabled
28
 *     2. Listen on AF_UNIX socket /run/host/unix-export/ssh if we run in a container with /run/host/ support
29
 *     3. Listen on AF_UNIX socket /run/ssh-unix-local/socket (always)
30
 *     4. Listen on any socket specified via kernel command line option systemd.ssh_listen=
31
 *     5. Similar, but from system credential ssh.listen
32
 *
33
 * The first two provide a nice way for hosts to connect to containers and VMs they invoke via the usual SSH
34
 * logic, but without waiting for networking or suchlike. The third allows the same for local clients. */
35

36
static const char *arg_dest = NULL;
37
static bool arg_auto = true;
38
static char **arg_listen_extra = NULL;
39

40
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
×
UNCOV
41
        int r;
×
42

UNCOV
43
        assert(key);
×
44

45
        if (proc_cmdline_key_streq(key, "systemd.ssh_auto")) {
×
46
                r = value ? parse_boolean(value) : 1;
×
47
                if (r < 0)
×
UNCOV
48
                        log_warning_errno(r, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value);
×
49
                else
UNCOV
50
                        arg_auto = r;
×
51

UNCOV
52
        } else if (proc_cmdline_key_streq(key, "systemd.ssh_listen")) {
×
53

54
                if (proc_cmdline_value_missing(key, value))
×
UNCOV
55
                        return 0;
×
56

57
                SocketAddress sa;
×
58
                r = socket_address_parse(&sa, value);
×
59
                if (r < 0)
×
UNCOV
60
                        log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value);
×
61
                else {
62
                        _cleanup_free_ char *s = NULL;
×
63
                        r = socket_address_print(&sa, &s);
×
64
                        if (r < 0)
×
UNCOV
65
                                return log_error_errno(r, "Failed to format socket address: %m");
×
66

67
                        if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
×
UNCOV
68
                                return log_oom();
×
69
                }
70
        }
71

72
        return 0;
73
}
74

UNCOV
75
static int make_sshd_template_unit(
×
76
                const char *dest,
77
                const char *template,
78
                const char *sshd_binary,
79
                const char *found_sshd_template_service,
80
                char **generated_sshd_template_unit) {
81

UNCOV
82
        int r;
×
83

84
        assert(dest);
×
85
        assert(template);
×
86
        assert(sshd_binary);
×
UNCOV
87
        assert(generated_sshd_template_unit);
×
88

89
        /* If the system has a suitable template already, symlink it under the name we want to use */
90
        if (found_sshd_template_service)
×
UNCOV
91
                return generator_add_symlink(
×
92
                                dest,
93
                                template,
94
                                /* dep_type= */ NULL,
95
                                found_sshd_template_service);
96

97
        if (!*generated_sshd_template_unit) {
×
UNCOV
98
                _cleanup_fclose_ FILE *f = NULL;
×
99

100
                /* We use a generic name for the unit, since we'll use it for both AF_UNIX and AF_VSOCK  */
UNCOV
101
                r = generator_open_unit_file_full(
×
102
                                dest,
103
                                /* source= */ NULL,
104
                                "sshd-generated@.service",
105
                                &f,
106
                                generated_sshd_template_unit,
107
                                /* ret_temp_path= */ NULL);
UNCOV
108
                if (r < 0)
×
109
                        return r;
110

UNCOV
111
                fprintf(f,
×
112
                        "[Unit]\n"
113
                        "Description=OpenSSH Per-Connection Server Daemon\n"
114
                        "Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n"
115
                        "\n"
116
                        "[Service]\n"
117
                        "ExecStart=-%s -i -o \"AuthorizedKeysFile ${CREDENTIALS_DIRECTORY}/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys\"\n"
118
                        "StandardInput=socket\n"
119
                        "ImportCredential=ssh.ephemeral-authorized_keys-all\n",
120
                        sshd_binary);
121

122
                r = fflush_and_check(f);
×
123
                if (r < 0)
×
UNCOV
124
                        return log_error_errno(r, "Failed to write sshd template: %m");
×
125
        }
126

UNCOV
127
        return generator_add_symlink(
×
128
                        dest,
129
                        template,
130
                        /* dep_type= */ NULL,
131
                        *generated_sshd_template_unit);
132
}
133

UNCOV
134
static int write_socket_unit(
×
135
                const char *dest,
136
                const char *unit,
137
                const char *listen_stream,
138
                const char *comment,
139
                const char *extra,
140
                bool with_ssh_access_target_dependency) {
141

UNCOV
142
        int r;
×
143

144
        assert(dest);
×
145
        assert(unit);
×
146
        assert(listen_stream);
×
UNCOV
147
        assert(comment);
×
148

149
        _cleanup_fclose_ FILE *f = NULL;
×
UNCOV
150
        r = generator_open_unit_file(
×
151
                        dest,
152
                        /* source= */ NULL,
153
                        unit,
154
                        &f);
UNCOV
155
        if (r < 0)
×
156
                return r;
157

UNCOV
158
        fprintf(f,
×
159
                "[Unit]\n"
160
                "Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n"
161
                "Documentation=man:systemd-ssh-generator(8)\n",
162
                comment);
163

164
        /* When this is a remotely accessible socket let's mark this with a milestone: ssh-access.target */
165
        if (with_ssh_access_target_dependency)
×
UNCOV
166
                fputs("Wants=ssh-access.target\n"
×
167
                      "Before=ssh-access.target\n",
168
                      f);
169

UNCOV
170
        fprintf(f,
×
171
                "\n[Socket]\n"
172
                "ListenStream=%s\n"
173
                "Accept=yes\n"
174
                "PollLimitIntervalSec=30s\n"
175
                "PollLimitBurst=50\n",
176
                listen_stream);
177

178
        if (extra)
×
UNCOV
179
                fputs(extra, f);
×
180

181
        r = fflush_and_check(f);
×
182
        if (r < 0)
×
UNCOV
183
                return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment);
×
184

UNCOV
185
        r = generator_add_symlink(
×
186
                        dest,
187
                        SPECIAL_SOCKETS_TARGET,
188
                        "wants",
189
                        unit);
190
        if (r < 0)
×
UNCOV
191
                return r;
×
192

193
        return 0;
194
}
195

UNCOV
196
static int add_vsock_socket(
×
197
                const char *dest,
198
                const char *sshd_binary,
199
                const char *found_sshd_template_unit,
200
                char **generated_sshd_template_unit) {
201

UNCOV
202
        int r;
×
203

204
        assert(dest);
×
UNCOV
205
        assert(generated_sshd_template_unit);
×
206

207
        Virtualization v = detect_virtualization();
×
208
        if (v < 0)
×
209
                return log_error_errno(v, "Failed to detect if we run in a VM: %m");
×
UNCOV
210
        if (!VIRTUALIZATION_IS_VM(v)) {
×
211
                /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */
212
                log_debug("Not running in a VM, not listening on AF_VSOCK.");
×
UNCOV
213
                return 0;
×
214
        }
215

216
        r = vsock_open_or_warn(/* ret= */ NULL);
×
217
        if (r <= 0)
×
218
                return r;
219

220
        /* Determine the local CID so that we can log it to help users to connect to this VM */
UNCOV
221
        unsigned local_cid;
×
222
        r = vsock_get_local_cid_or_warn(&local_cid);
×
UNCOV
223
        if (r <= 0)
×
224
                return r;
225

UNCOV
226
        r = make_sshd_template_unit(
×
227
                        dest,
228
                        "sshd-vsock@.service",
229
                        sshd_binary,
230
                        found_sshd_template_unit,
231
                        generated_sshd_template_unit);
232
        if (r < 0)
×
233
                return r;
234

UNCOV
235
        r = write_socket_unit(
×
236
                        dest,
237
                        "sshd-vsock.socket",
238
                        "vsock::22",
239
                        "AF_VSOCK",
240
                        "ExecStartPost=-/usr/lib/systemd/systemd-ssh-issue --make-vsock\n"
241
                        "ExecStopPre=-/usr/lib/systemd/systemd-ssh-issue --rm-vsock\n",
242
                        /* with_ssh_access_target_dependency= */ true);
UNCOV
243
        if (r < 0)
×
244
                return r;
245

UNCOV
246
        log_debug("Binding SSH to AF_VSOCK vsock::22.\n"
×
247
                  "→ connect via 'ssh vsock/%u' from host", local_cid);
248
        return 0;
249
}
250

UNCOV
251
static int add_local_unix_socket(
×
252
                const char *dest,
253
                const char *sshd_binary,
254
                const char *found_sshd_template_unit,
255
                char **generated_sshd_template_unit) {
256

UNCOV
257
        int r;
×
258

259
        assert(dest);
×
UNCOV
260
        assert(sshd_binary);
×
UNCOV
261
        assert(generated_sshd_template_unit);
×
262

UNCOV
263
        r = make_sshd_template_unit(
×
264
                        dest,
265
                        "sshd-unix-local@.service",
266
                        sshd_binary,
267
                        found_sshd_template_unit,
268
                        generated_sshd_template_unit);
UNCOV
269
        if (r < 0)
×
270
                return r;
271

272
        r = write_socket_unit(
×
273
                        dest,
274
                        "sshd-unix-local.socket",
275
                        "/run/ssh-unix-local/socket",
276
                        "AF_UNIX Local",
277
                        /* extra= */ NULL,
278
                        /* with_ssh_access_target_dependency= */ false);
UNCOV
279
        if (r < 0)
×
280
                return r;
281

282
        log_debug("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n"
×
283
                  "→ connect via 'ssh .host' locally");
284
        return 0;
285
}
286

UNCOV
287
static int add_export_unix_socket(
×
288
                const char *dest,
289
                const char *sshd_binary,
290
                const char *found_sshd_template_unit,
291
                char **generated_sshd_template_unit) {
292

UNCOV
293
        int r;
×
294

295
        assert(dest);
×
UNCOV
296
        assert(sshd_binary);
×
UNCOV
297
        assert(generated_sshd_template_unit);
×
298

UNCOV
299
        Virtualization v = detect_container();
×
300
        if (v < 0)
×
UNCOV
301
                return log_error_errno(v, "Failed to detect if we run in a container: %m");
×
UNCOV
302
        if (v == VIRTUALIZATION_NONE) {
×
UNCOV
303
                log_debug("Not running in container, not listening on /run/host/unix-export/ssh");
×
UNCOV
304
                return 0;
×
305
        }
306

UNCOV
307
        if (access("/run/host/unix-export/", W_OK) < 0) {
×
308
                if (errno == ENOENT) {
×
309
                        log_debug("Container manager does not provide /run/host/unix-export/ mount, not binding AF_UNIX socket there.");
×
310
                        return 0;
×
311
                }
312
                if (ERRNO_IS_FS_WRITE_REFUSED(errno)) {
×
313
                        log_debug("Container manager does not provide write access to /run/host/unix-export/, not binding AF_UNIX socket there.");
×
314
                        return 0;
×
315
                }
316

317
                return log_error_errno(errno, "Unable to check if /run/host/unix-export exists: %m");
×
318
        }
319

320
        r = make_sshd_template_unit(
×
321
                        dest,
322
                        "sshd-unix-export@.service",
323
                        sshd_binary,
324
                        found_sshd_template_unit,
325
                        generated_sshd_template_unit);
326
        if (r < 0)
×
327
                return r;
328

UNCOV
329
        r = write_socket_unit(
×
330
                        dest,
331
                        "sshd-unix-export.socket",
332
                        "/run/host/unix-export/ssh",
333
                        "AF_UNIX Export",
334
                        /* extra= */ NULL,
335
                        /* with_ssh_access_target_dependency= */ true);
UNCOV
336
        if (r < 0)
×
337
                return r;
338

339
        log_debug("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n"
×
340
                  "→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host");
341

342
        return 0;
343
}
344

UNCOV
345
static int add_extra_sockets(
×
346
                const char *dest,
347
                const char *sshd_binary,
348
                const char *found_sshd_template_unit,
349
                char **generated_sshd_template_unit) {
350

UNCOV
351
        unsigned n = 1;
×
352
        int r;
×
353

UNCOV
354
        assert(dest);
×
UNCOV
355
        assert(sshd_binary);
×
UNCOV
356
        assert(generated_sshd_template_unit);
×
357

358
        if (strv_isempty(arg_listen_extra))
×
359
                return 0;
360

UNCOV
361
        STRV_FOREACH(i, arg_listen_extra) {
×
UNCOV
362
                _cleanup_free_ char *service = NULL, *socket = NULL;
×
363

364
                if (n > 1) {
×
365
                        if (asprintf(&service, "sshd-extra-%u@.service", n) < 0)
×
UNCOV
366
                                return log_oom();
×
367

368
                        if (asprintf(&socket, "sshd-extra-%u.socket", n) < 0)
×
369
                                return log_oom();
×
370
                }
371

UNCOV
372
                r = make_sshd_template_unit(
×
373
                                dest,
374
                                service ?: "sshd-extra@.service",
×
375
                                sshd_binary,
376
                                found_sshd_template_unit,
377
                                generated_sshd_template_unit);
378
                if (r < 0)
×
379
                        return r;
380

381
                r = write_socket_unit(
×
382
                                dest,
UNCOV
383
                                socket ?: "sshd-extra.socket",
×
384
                                *i,
385
                                *i,
386
                                /* extra= */ NULL,
387
                                /* with_ssh_access_target_dependency= */ true);
UNCOV
388
                if (r < 0)
×
389
                        return r;
390

391
                log_debug("Binding SSH to socket %s.", *i);
×
UNCOV
392
                n++;
×
393
        }
394

395
        return 0;
396
}
397

UNCOV
398
static int parse_credentials(void) {
×
UNCOV
399
        _cleanup_free_ char *b = NULL;
×
UNCOV
400
        size_t sz = 0;
×
401
        int r;
×
402

UNCOV
403
        r = read_credential_with_decryption("ssh.listen", (void**) &b, &sz);
×
404
        if (r <= 0)
×
405
                return r;
406

UNCOV
407
        _cleanup_fclose_ FILE *f = NULL;
×
UNCOV
408
        f = fmemopen_unlocked(b, sz, "r");
×
UNCOV
409
        if (!f)
×
UNCOV
410
                return log_oom();
×
411

412
        for (;;) {
×
413
                _cleanup_free_ char *item = NULL;
×
414

UNCOV
415
                r = read_stripped_line(f, LINE_MAX, &item);
×
416
                if (r == 0)
×
417
                        break;
UNCOV
418
                if (r < 0) {
×
UNCOV
419
                        log_error_errno(r, "Failed to parse credential 'ssh.listen': %m");
×
420
                        break;
421
                }
422

423
                if (startswith(item, "#"))
×
UNCOV
424
                        continue;
×
425

426
                SocketAddress sa;
×
UNCOV
427
                r = socket_address_parse(&sa, item);
×
428
                if (r < 0) {
×
429
                        log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item);
×
UNCOV
430
                        continue;
×
431
                }
432

UNCOV
433
                _cleanup_free_ char *s = NULL;
×
UNCOV
434
                r = socket_address_print(&sa, &s);
×
UNCOV
435
                if (r < 0)
×
436
                        return log_error_errno(r, "Failed to format socket address: %m");
×
437

UNCOV
438
                if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0)
×
439
                        return log_oom();
×
440
        }
441

442
        return 0;
×
443
}
444

UNCOV
445
static int run(const char *dest, const char *dest_early, const char *dest_late) {
×
446
        int r;
×
447

448
        assert_se(arg_dest = dest);
×
449

UNCOV
450
        r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, /* flags= */ 0);
×
451
        if (r < 0)
×
452
                log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
×
453

UNCOV
454
        (void) parse_credentials();
×
455

UNCOV
456
        strv_sort_uniq(arg_listen_extra);
×
457

458
        if (!arg_auto && strv_isempty(arg_listen_extra)) {
×
459
                log_debug("Disabling SSH generator logic, because it has been turned off explicitly.");
×
UNCOV
460
                return 0;
×
461
        }
462

463
        _cleanup_free_ char *sshd_binary = NULL;
×
464
        r = find_executable("sshd", &sshd_binary);
×
465
        if (r == -ENOENT) {
×
UNCOV
466
                log_debug("Disabling SSH generator logic, since sshd is not installed.");
×
467
                return 0;
×
468
        }
469
        if (r < 0)
×
UNCOV
470
                return log_error_errno(r, "Failed to determine if sshd is installed: %m");
×
471

472
        _cleanup_(lookup_paths_done) LookupPaths lp = {};
×
473
        r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, /* root_dir= */ NULL);
×
UNCOV
474
        if (r < 0)
×
475
                return r;
476

477
        _cleanup_free_ char *found_sshd_template_unit = NULL;
×
478
        r = unit_file_exists_full(RUNTIME_SCOPE_SYSTEM, &lp, "sshd@.service", &found_sshd_template_unit);
×
479
        if (r < 0)
×
480
                return log_error_errno(r, "Unable to detect if sshd@.service exists: %m");
×
481

482
        _cleanup_free_ char *generated_sshd_template_unit = NULL;
×
483
        RET_GATHER(r, add_extra_sockets(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
484

485
        if (arg_auto) {
×
486
                RET_GATHER(r, add_vsock_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
487
                RET_GATHER(r, add_local_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
UNCOV
488
                RET_GATHER(r, add_export_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit));
×
489
        }
490

491
        return r;
×
492
}
493

UNCOV
494
DEFINE_MAIN_GENERATOR_FUNCTION(run);
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc