Tuesday, August 10, 2021

Re: Transferring ownership of SSH connection from process A to B, letting A quit nicely?

On Monday, August 9th, 2021 at 5:36 AM, Philip Guenther <guenther@gmail.com> wrote:

> If you're 100% sure you have it right, then it should be easy to provide a
> program that demonstrates
> 1. passing an fd between processes
> 2. using it successfully in the receiving process
> 3. the sending process exiting
> 4. attempts to us it failing the receiving process

Not 100%, but I'm out of ideas, so here goes nothing.

client.c (process A):

#include<unistd.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<string.h>
#include<assert.h>

int sendfd(int sock, int fd) {
struct msghdr msg;
struct iovec iov[1];
struct cmsghdr *cmsg = NULL;
char ctrl_buf[CMSG_SPACE(sizeof(int))];
char data[1];

memset(&msg, 0, sizeof(struct msghdr));
memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int)));

data[0] = ' ';
iov[0].iov_base = data;
iov[0].iov_len = sizeof(data);

msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_controllen = CMSG_SPACE(sizeof(int));
msg.msg_control = ctrl_buf;

cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));

*((int*) CMSG_DATA(cmsg)) = fd;

return sendmsg(sock, &msg, 0);
}

int main(int argc, char **argv) {
int c = socket(AF_UNIX, SOCK_STREAM, 0);
assert(c != -1);

struct sockaddr_un a;
a.sun_family = AF_UNIX;
strcpy(a.sun_path, "/service/sock");

assert(connect(c, (struct sockaddr*) &a, sizeof(a)) != -1);

sendfd(c, 0);
sendfd(c, 1);

close(c);

/* The SSH conn should stay after returning, but it doesn't. */
}

server.c (process B):

#include<unistd.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<string.h>
#include<assert.h>
#include<stdio.h>
#include<sys/stat.h>

int recvfd(int socket) {
int len;
int fd;
char buf[1];
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsg;
char cms[CMSG_SPACE(sizeof(int))];

iov.iov_base = buf;
iov.iov_len = sizeof(buf);

msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
msg.msg_control = (caddr_t) cms;
msg.msg_controllen = sizeof(cms);

len = recvmsg(socket, &msg, 0);

if(len <= 0) {
return -1;
}

cmsg = CMSG_FIRSTHDR(&msg);
memmove(&fd, CMSG_DATA(cmsg), sizeof(int));

return fd;
}

int main(int argc, char **argv) {
unlink("/service/sock");

int s = socket(AF_UNIX, SOCK_STREAM, 0);
assert(s != -1);

struct sockaddr_un a;
a.sun_family = AF_UNIX;
strcpy(a.sun_path, "/service/sock");
assert(bind(s, (struct sockaddr*) &a, sizeof(a)) != -1);

assert(chmod("/service/sock", 0777) != -1); /* Quick workaround. */

assert(listen(s, 20) != -1);

while(1) {
puts("Waiting..");

int c = accept(s, NULL, NULL);
assert(c != -1);

puts("Accepted");

int in = recvfd(c);
int out = recvfd(c);

printf("Received: in=%i out=%i\n", in, out);

char *outstr = "Hello from the Server\n";
assert(write(out, outstr, strlen(outstr)) != -1);

assert(close(c) != -1);

/* Intentionally leaking SSH FDs */
}
}

Compiled with:
cc -std=c99 -o server server.c
cc -std=c99 -o client client.c

`client` is also the shell of the user, but the results are the same if
I call it from within a "real" shell, too.

The server receives the correct FDs, and prints
"Hello from the Server\n" correctly, too. But as soon as `client`
exits, the SSH connection goes with it, instead of staying (as in,
I get "Connection to localhost closed").

No comments:

Post a Comment