• 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

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

3
#include <getopt.h>
4
#include <sys/stat.h>
5
#include <unistd.h>
6

7
#include "alloc-util.h"
8
#include "ansi-color.h"
9
#include "build.h"
10
#include "errno-util.h"
11
#include "fd-util.h"
12
#include "fs-util.h"
13
#include "log.h"
14
#include "main-func.h"
15
#include "mkdir.h"
16
#include "parse-argument.h"
17
#include "pretty-print.h"
18
#include "ssh-util.h"
19
#include "string-util.h"
20
#include "tmpfile-util.h"
21
#include "virt.h"
22

23
static enum {
24
        ACTION_MAKE_VSOCK,
25
        ACTION_RM_VSOCK,
26
} arg_action = ACTION_MAKE_VSOCK;
27

28
static char *arg_issue_path = NULL;
29
static bool arg_issue_stdout = false;
30

31
STATIC_DESTRUCTOR_REGISTER(arg_issue_path, freep);
124✔
32

33
static int help(void) {
×
34
        _cleanup_free_ char *link = NULL;
×
35
        int r;
×
36

37
        r = terminal_urlify_man("systemd-ssh-issue", "1", &link);
×
38
        if (r < 0)
×
39
                return log_oom();
×
40

41
        printf("%s [OPTIONS...] --make-vsock\n"
×
42
               "%s [OPTIONS...] --rm-vsock\n"
43
               "\n%sCreate ssh /run/issue.d/ file reporting VSOCK address.%s\n\n"
44
               "  -h --help            Show this help\n"
45
               "     --version         Show package version\n"
46
               "     --issue-path=PATH Change path to /run/issue.d/50-ssh-vsock.issue\n"
47
               "\nSee the %s for details.\n",
48
               program_invocation_short_name,
49
               program_invocation_short_name,
50
               ansi_highlight(),
51
               ansi_normal(),
52
               link);
53

54
        return 0;
55
}
56

57
static int parse_argv(int argc, char *argv[]) {
124✔
58

59
        enum {
124✔
60
                ARG_MAKE_VSOCK = 0x100,
61
                ARG_RM_VSOCK,
62
                ARG_ISSUE_PATH,
63
                ARG_VERSION,
64
        };
65

66
        static const struct option options[] = {
124✔
67
                { "help",       no_argument,       NULL, 'h'            },
68
                { "version",    no_argument,       NULL, ARG_VERSION    },
69
                { "make-vsock", no_argument,       NULL, ARG_MAKE_VSOCK },
70
                { "rm-vsock",   no_argument,       NULL, ARG_RM_VSOCK   },
71
                { "issue-path", required_argument, NULL, ARG_ISSUE_PATH },
72
                {}
73
        };
74

75
        int c, r;
124✔
76

77
        assert(argc >= 0);
124✔
78
        assert(argv);
124✔
79

80
        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
248✔
81

82
                switch (c) {
124✔
83

84
                case 'h':
×
85
                        return help();
×
86

87
                case ARG_VERSION:
×
88
                        return version();
×
89

90
                case ARG_MAKE_VSOCK:
62✔
91
                        arg_action = ACTION_MAKE_VSOCK;
62✔
92
                        break;
62✔
93

94
                case ARG_RM_VSOCK:
62✔
95
                        arg_action = ACTION_RM_VSOCK;
62✔
96
                        break;
62✔
97

98
                case ARG_ISSUE_PATH:
×
99
                        if (isempty(optarg) || streq(optarg, "-")) {
×
100
                                arg_issue_path = mfree(arg_issue_path);
×
101
                                arg_issue_stdout = true;
×
102
                                break;
×
103
                        }
104

105
                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_issue_path);
×
106
                        if (r < 0)
×
107
                                return r;
108

109
                        arg_issue_stdout = false;
×
110
                        break;
×
111
                }
112
        }
113

114
        if (!arg_issue_path && !arg_issue_stdout) {
124✔
115
                arg_issue_path = strdup("/run/issue.d/50-ssh-vsock.issue");
124✔
116
                if (!arg_issue_path)
124✔
117
                        return log_oom();
×
118
        }
119

120
        return 1;
121
}
122

123
static int acquire_cid(unsigned *ret_cid) {
62✔
124
        int r;
62✔
125

126
        assert(ret_cid);
62✔
127

128
        Virtualization v = detect_virtualization();
62✔
129
        if (v < 0)
62✔
130
                return log_error_errno(v, "Failed to detect if we run in a VM: %m");
×
131
        if (!VIRTUALIZATION_IS_VM(v)) {
62✔
132
                /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */
133
                log_debug("Not running in a VM, not creating issue file.");
×
134
                *ret_cid = 0;
×
135
                return 0;
×
136
        }
137

138
        r = vsock_open_or_warn(/* ret= */ NULL);
62✔
139
        if (r <= 0)
62✔
140
                return r;
141

142
        return vsock_get_local_cid_or_warn(ret_cid);
62✔
143
}
144

145
static int run(int argc, char* argv[]) {
124✔
146
        int r;
124✔
147

148
        log_setup();
124✔
149

150
        r = parse_argv(argc, argv);
124✔
151
        if (r <= 0)
124✔
152
                return r;
153

154
        switch (arg_action) {
124✔
155
        case ACTION_MAKE_VSOCK: {
62✔
156
                unsigned cid;
62✔
157

158
                r = acquire_cid(&cid);
62✔
159
                if (r < 0)
62✔
160
                        return r;
×
161
                if (r == 0) {
62✔
UNCOV
162
                        log_debug("Not running in a VSOCK enabled VM, skipping.");
×
163
                        break;
62✔
164
                }
165

UNCOV
166
                _cleanup_(unlink_and_freep) char *t = NULL;
×
167
                _cleanup_(fclosep) FILE *f = NULL;
62✔
168
                FILE *out;
62✔
169

170
                if (arg_issue_path)  {
62✔
171
                        r = mkdir_parents(arg_issue_path, 0755);
62✔
172
                        if (r < 0)
62✔
UNCOV
173
                                return log_error_errno(r, "Failed to create parent directories of '%s': %m", arg_issue_path);
×
174

175
                        r = fopen_tmpfile_linkable(arg_issue_path, O_WRONLY|O_CLOEXEC, &t, &f);
62✔
176
                        if (r < 0)
62✔
UNCOV
177
                                return log_error_errno(r, "Failed to create '%s': %m", arg_issue_path);
×
178

179
                        out = f;
62✔
180
                } else
UNCOV
181
                        out = stdout;
×
182

183
                fprintf(out,
62✔
184
                        "Try contacting this VM's SSH server via 'ssh vsock%%%u' from host.\n"
185
                        "\n", cid);
186

187
                if (f) {
62✔
188
                        if (fchmod(fileno(f), 0644) < 0)
62✔
UNCOV
189
                                return log_error_errno(errno, "Failed to adjust access mode of '%s': %m", arg_issue_path);
×
190

191
                        r = flink_tmpfile(f, t, arg_issue_path, LINK_TMPFILE_REPLACE);
62✔
192
                        if (r < 0)
62✔
UNCOV
193
                                return log_error_errno(r, "Failed to move '%s' into place: %m", arg_issue_path);
×
194
                }
195

196
                break;
62✔
197
        }
198

199
        case ACTION_RM_VSOCK:
62✔
200
                if (arg_issue_path) {
62✔
201
                        if (unlink(arg_issue_path) < 0) {
62✔
UNCOV
202
                                if (errno != ENOENT)
×
203
                                        return log_error_errno(errno, "Failed to remove '%s': %m", arg_issue_path);
×
204

205
                                log_debug_errno(errno, "File '%s' does not exist, no operation executed.", arg_issue_path);
124✔
206
                        } else
207
                                log_debug("Successfully removed '%s'.", arg_issue_path);
62✔
208
                } else
UNCOV
209
                        log_notice("STDOUT selected for issue file, not removing.");
×
210

211
                break;
212

UNCOV
213
        default:
×
UNCOV
214
                assert_not_reached();
×
215
        }
216

217
        return 0;
218
}
219

220
DEFINE_MAIN_FUNCTION(run);
124✔
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