Coverage Report

Created: 2026-08-31 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dropbear/src/svr-streamfwd.c
Line
Count
Source
1
#include "includes.h"
2
#include "ssh.h"
3
#include "forward.h"
4
#include "dbutil.h"
5
#include "session.h"
6
#include "buffer.h"
7
#include "packet.h"
8
#include "listener.h"
9
#include "runopts.h"
10
#include "auth.h"
11
#include "netio.h"
12
13
14
#if DROPBEAR_SVR_REMOTESTREAMFWD
15
static const struct ChanType svr_chan_streamlocalremote = {
16
    "forwarded-streamlocal@openssh.com",
17
    NULL,
18
    NULL,
19
    NULL,
20
    NULL,
21
    NULL
22
};
23
24
0
static int matchstreamlocal(const void* typedata1, const void* typedata2) {
25
26
0
    const struct FwdListener *info1 = (const struct FwdListener*)typedata1;
27
0
    const struct FwdListener *info2 = (const struct FwdListener*)typedata2;
28
29
0
    if (info1->socket_path == NULL || info2->socket_path == NULL) {
30
0
        return 0;
31
0
    }
32
33
0
    return (info1->chantype == info2->chantype)
34
0
            && (strcmp(info1->socket_path, info2->socket_path) == 0);
35
0
}
36
37
0
int svr_cancelremotestreamlocal() {
38
39
0
    int ret = DROPBEAR_FAILURE;
40
0
    char * socket_path = NULL;
41
0
    unsigned int pathlen;
42
0
    struct Listener * listener = NULL;
43
0
    struct FwdListener tcpinfo;
44
45
0
    TRACE(("enter cancelremotestreamlocal"))
46
47
0
    socket_path = buf_getstring(ses.payload, &pathlen);
48
0
    if (pathlen > MAX_HOST_LEN) {
49
0
        TRACE(("path len too long: %d", pathlen))
50
0
        goto out;
51
0
    }
52
0
    if (strlen(socket_path) != pathlen) {
53
0
        TRACE(("path has nul byte"));
54
0
        goto out;
55
0
    }
56
57
0
    tcpinfo.socket_path = socket_path;
58
0
    tcpinfo.chantype = &svr_chan_streamlocalremote;
59
0
    listener = get_listener(CHANNEL_ID_STREAMLOCALFORWARDED, &tcpinfo, matchstreamlocal);
60
0
    if (listener) {
61
0
        remove_listener( listener );
62
0
        ret = DROPBEAR_SUCCESS;
63
0
    }
64
65
0
out:
66
0
    m_free(socket_path);
67
0
    TRACE(("leave cancelremotestreamlocal"))
68
0
    return ret;
69
0
}
70
71
0
static void unlink_streamsocket(const char* path) {
72
0
    if (unlink(path) < 0 && errno != ENOENT) {
73
        /* Not fatal */
74
0
        DEBUG1(("Failed removing unix socket %s: %s",
75
0
            path, strerror(errno)));
76
0
    }
77
0
}
78
79
0
static void cleanup_streamlocal(const struct Listener *listener) {
80
81
0
    struct FwdListener *tcpinfo = (struct FwdListener*)(listener->typedata);
82
83
0
    if (tcpinfo && tcpinfo->socket_path) {
84
0
        unlink_streamsocket(tcpinfo->socket_path);
85
0
        m_free(tcpinfo->socket_path);
86
0
    }
87
0
    m_free(tcpinfo->request_listenaddr);
88
0
    m_free(tcpinfo);
89
0
}
90
91
0
static void streamlocal_acceptor(const struct Listener *listener, int sock) {
92
93
0
    int fd;
94
0
    struct FwdListener *tcpinfo = (struct FwdListener*)(listener->typedata);
95
96
0
    fd = accept(sock, NULL, NULL);
97
0
    if (fd < 0) {
98
0
        return;
99
0
    }
100
101
0
    if (send_msg_channel_open_init(fd, tcpinfo->chantype) == DROPBEAR_SUCCESS) {
102
        /* "forwarded-streamlocal@openssh.com" */
103
        /* socket path that was connected to */
104
0
        buf_putstring(ses.writepayload, tcpinfo->request_listenaddr,
105
0
                strlen(tcpinfo->request_listenaddr));
106
        /* reserved field */
107
0
        buf_putstring(ses.writepayload, "", 0);
108
109
0
        encrypt_packet();
110
111
0
    } else {
112
        /* XXX debug? */
113
0
        close(fd);
114
0
    }
115
0
}
116
117
0
int listen_streamlocal(struct FwdListener* tcpinfo, struct Listener **ret_listener) {
118
119
0
    int sock, rc, saved_errno;
120
0
    struct Listener *listener = NULL;
121
0
    struct sockaddr_un addr;
122
0
    mode_t old_umask;
123
124
0
    TRACE(("enter listen_streamlocal"))
125
126
0
    if (tcpinfo->socket_path == NULL) {
127
0
        TRACE(("leave listen_streamlocal: no socket path"))
128
0
        return DROPBEAR_FAILURE;
129
0
    }
130
131
0
    if (strlen(tcpinfo->socket_path) >= sizeof(addr.sun_path)) {
132
0
        dropbear_log(LOG_INFO, "Streamlocal forward failed: socket path too long");
133
0
        TRACE(("leave listen_streamlocal: path too long"))
134
0
        return DROPBEAR_FAILURE;
135
0
    }
136
137
0
#if DROPBEAR_FUZZ
138
0
    if (fuzz.fuzzing) {
139
        // fuzzing streamlocal is unimplemented
140
0
        return DROPBEAR_FAILURE;
141
0
    }
142
0
#endif
143
144
0
    sock = socket(PF_UNIX, SOCK_STREAM, 0);
145
0
    if (sock < 0) {
146
0
        dropbear_log(LOG_INFO, "Streamlocal forward failed: socket() failed");
147
0
        TRACE(("leave listen_streamlocal: socket() failed"))
148
0
        return DROPBEAR_FAILURE;
149
0
    }
150
151
0
    memset(&addr, 0, sizeof(addr));
152
0
    addr.sun_family = AF_UNIX;
153
0
    strlcpy(addr.sun_path, tcpinfo->socket_path, sizeof(addr.sun_path));
154
155
    /* Unlink existing socket if it exists */
156
0
    unlink_streamsocket(tcpinfo->socket_path);
157
158
    /* Set umask to allow proper permissions on the socket */
159
0
    old_umask = umask(0177);
160
0
    rc = bind(sock, (struct sockaddr*)&addr, sizeof(addr));
161
0
    saved_errno = errno;
162
0
    umask(old_umask);
163
164
0
    if (rc < 0) {
165
0
        dropbear_log(LOG_INFO, "Streamlocal forward failed: bind() failed: %s", strerror(saved_errno));
166
0
        m_close(sock);
167
0
        TRACE(("leave listen_streamlocal: bind() failed"))
168
0
        return DROPBEAR_FAILURE;
169
0
    }
170
171
172
0
    if (listen(sock, DROPBEAR_LISTEN_BACKLOG) < 0) {
173
0
        dropbear_log(LOG_INFO, "Streamlocal forward failed: listen() failed: %s", strerror(errno));
174
0
        unlink_streamsocket(tcpinfo->socket_path);
175
0
        m_close(sock);
176
0
        TRACE(("leave listen_streamlocal: listen() failed"))
177
0
        return DROPBEAR_FAILURE;
178
0
    }
179
180
0
    setnonblocking(sock);
181
182
0
    listener = new_listener(&sock, 1, LISTENER_TYPE_STREAMFORWARDED, tcpinfo,
183
0
            streamlocal_acceptor, cleanup_streamlocal);
184
185
0
    if (listener == NULL) {
186
0
        unlink_streamsocket(tcpinfo->socket_path);
187
0
        m_close(sock);
188
0
        TRACE(("leave listen_streamlocal: listener failed"))
189
0
        return DROPBEAR_FAILURE;
190
0
    }
191
192
0
    if (ret_listener) {
193
0
        *ret_listener = listener;
194
0
    }
195
196
0
    TRACE(("leave listen_streamlocal: success"))
197
0
    return DROPBEAR_SUCCESS;
198
0
}
199
200
0
int svr_remotestreamlocalreq() {
201
202
0
    int ret = DROPBEAR_FAILURE;
203
0
    char * request_path = NULL;
204
0
    unsigned int pathlen;
205
0
    struct FwdListener *tcpinfo = NULL;
206
0
    struct Listener *listener = NULL;
207
208
0
    TRACE(("enter remotestreamlocalreq"))
209
210
0
    if (svr_opts.forced_command || svr_pubkey_has_forced_command()) {
211
        /* Creating a unix socket in the right place could probably subvert
212
         * a forcedcommand, so don't allow that.
213
         * This could be relaxed if an authorized_keys "permitlisten"
214
         * equivalent were added for streamlocal */
215
0
        TRACE(("leave newstreamlocal: no unix forwarding for forced command"))
216
0
        goto out;
217
0
    }
218
219
0
    if (svr_opts.noremotefwd || !svr_pubkey_allows_tcpfwd()) {
220
0
        TRACE(("leave remotestreamlocalreq: remote forwarding disabled"))
221
0
        goto out;
222
0
    }
223
224
0
    request_path = buf_getstring(ses.payload, &pathlen);
225
0
    if (pathlen > MAX_HOST_LEN) {
226
0
        TRACE(("path len too long: %d", pathlen))
227
0
        goto out;
228
0
    }
229
0
    if (strlen(request_path) != pathlen) {
230
0
        TRACE(("path has nul byte"));
231
0
        goto out;
232
0
    }
233
234
0
    tcpinfo = (struct FwdListener*)m_malloc(sizeof(struct FwdListener));
235
0
    memset(tcpinfo, 0, sizeof(struct FwdListener));
236
0
    tcpinfo->sendaddr = NULL;
237
0
    tcpinfo->sendport = 0;
238
0
    tcpinfo->listenaddr = NULL;
239
0
    tcpinfo->listenport = 0;
240
0
    tcpinfo->chantype = &svr_chan_streamlocalremote;
241
0
    tcpinfo->fwd_type = forwarded;
242
0
    tcpinfo->interface = NULL;
243
0
    tcpinfo->socket_path = m_strdup(request_path);
244
0
    tcpinfo->request_listenaddr = request_path;
245
246
0
    ret = listen_streamlocal(tcpinfo, &listener);
247
248
0
out:
249
0
    if (ret == DROPBEAR_FAILURE) {
250
        /* we only free it if a listener wasn't created, since the listener
251
         * has to remember it if it's to be cancelled */
252
0
        m_free(request_path);
253
0
        if (tcpinfo) {
254
0
            m_free(tcpinfo->socket_path);
255
0
        }
256
0
        m_free(tcpinfo);
257
0
    }
258
259
0
    TRACE(("leave remotestreamlocalreq"))
260
0
    return ret;
261
0
}
262
#endif /* DROPBEAR_SVR_REMOTESTREAMFWD */
263
264
#if DROPBEAR_SVR_LOCALSTREAMFWD
265
266
/* Called upon creating a new stream local channel (ie we connect out to an
267
 * address */
268
0
static int newstreamlocal(struct Channel * channel) {
269
270
    /*
271
    https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL#rev1.30
272
273
    byte        SSH_MSG_CHANNEL_OPEN
274
    string      "direct-streamlocal@openssh.com"
275
    uint32      sender channel
276
    uint32      initial window size
277
    uint32      maximum packet size
278
    string      socket path
279
    string      reserved
280
    uint32      reserved
281
    */
282
283
0
    char* destsocket = NULL;
284
0
    unsigned int len;
285
0
    int err = SSH_OPEN_ADMINISTRATIVELY_PROHIBITED;
286
287
0
    TRACE(("streamlocal channel %d", channel->index))
288
289
0
    if (svr_opts.forced_command || svr_pubkey_has_forced_command()) {
290
0
        TRACE(("leave newstreamlocal: no unix forwarding for forced command"))
291
0
        goto out;
292
0
    }
293
294
0
    if (svr_opts.nolocaltcp || !svr_pubkey_allows_tcpfwd()) {
295
0
        TRACE(("leave newstreamlocal: local unix forwarding disabled"))
296
0
        goto out;
297
0
    }
298
299
0
    destsocket = buf_getstring(ses.payload, &len);
300
0
    if (len > MAX_HOST_LEN) {
301
0
        TRACE(("leave streamlocal: destsocket too long"))
302
0
        goto out;
303
0
    }
304
305
0
    channel->conn_pending = connect_streamlocal(destsocket, channel_connect_done,
306
0
        channel, DROPBEAR_PRIO_NORMAL);
307
308
0
    err = SSH_OPEN_IN_PROGRESS;
309
310
0
out:
311
    m_free(destsocket);
312
0
    TRACE(("leave streamlocal: err %d", err))
313
0
    return err;
314
0
}
315
316
const struct ChanType svr_chan_streamlocal = {
317
    "direct-streamlocal@openssh.com",
318
    newstreamlocal, /* init */
319
    NULL, /* checkclose */
320
    NULL, /* reqhandler */
321
    NULL, /* closehandler */
322
    NULL /* cleanup */
323
};
324
325
#endif /* DROPBEAR_SVR_LOCALSTREAMFWD */