seatd/seatd-launch/seatd-launch.c
Kenny Levinsen 907b75de1a seatd-launch: Use absolute path for seatd
We previously used execlp to execute seatd, which had the effect of
searching PATH for the executable. This allowed the caller to control
what executable was run, which is bad if SUID has been set.

Instead, expose the absolute install path for seatd from meason as a
define, and use that in a call to execv.
2021-09-16 00:46:49 +02:00

185 lines
3.8 KiB
C

#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
(void)argc;
const char *usage = "Usage: seatd-launch [options] [--] command\n"
"\n"
" -h Show this help message\n"
" -s <path> Where to create the seatd socket\n"
" -v Show the version number\n"
"\n";
int c;
char *sockpath = NULL;
while ((c = getopt(argc, argv, "vhs:")) != -1) {
switch (c) {
case 's':
sockpath = optarg;
break;
case 'v':
printf("seatd-launch version %s\n", SEATD_VERSION);
return 0;
case 'h':
printf("%s", usage);
return 0;
case '?':
fprintf(stderr, "Try '%s -h' for more information.\n", argv[0]);
return 1;
default:
abort();
}
}
if (optind >= argc) {
fprintf(stderr, "A command must be specified\n\n%s", usage);
return 1;
}
char **command = &argv[optind];
char sockbuf[256];
if (sockpath == NULL) {
sprintf(sockbuf, "/tmp/seatd.%d.sock", getpid());
sockpath = sockbuf;
}
unlink(sockpath);
int fds[2];
if (pipe(fds) == -1) {
perror("Could not create pipe");
goto error;
}
pid_t seatd_child = fork();
if (seatd_child == -1) {
perror("Could not fork seatd process");
goto error;
} else if (seatd_child == 0) {
close(fds[0]);
char pipebuf[16] = {0};
snprintf(pipebuf, sizeof pipebuf, "%d", fds[1]);
char *command[] = {"seatd", "-n", pipebuf, "-s", sockpath, NULL};
execv(SEATD_INSTALLPATH, command);
perror("Could not start seatd");
_exit(1);
}
close(fds[1]);
// Wait for seatd to be ready
char buf[1] = {0};
while (true) {
pid_t p = waitpid(seatd_child, NULL, WNOHANG);
if (p == seatd_child) {
fprintf(stderr, "seatd exited prematurely\n");
goto error_seatd;
} else if (p == -1 && (errno != EINTR && errno != ECHILD)) {
perror("Could not wait for seatd process");
goto error_seatd;
}
struct pollfd fd = {
.fd = fds[0],
.events = POLLIN,
};
// We poll with timeout to avoid a racing on a blocking read
if (poll(&fd, 1, 1000) == -1) {
if (errno == EAGAIN || errno == EINTR) {
continue;
} else {
perror("Could not poll notification fd");
goto error_seatd;
}
}
if (fd.revents & POLLIN) {
ssize_t n = read(fds[0], buf, 1);
if (n == -1 && errno != EINTR) {
perror("Could not read from pipe");
goto error_seatd;
} else if (n > 0) {
break;
}
}
}
close(fds[0]);
uid_t uid = getuid();
gid_t gid = getgid();
// Restrict access to the socket to just us
if (chown(sockpath, uid, gid) == -1) {
perror("Could not chown seatd socket");
goto error_seatd;
}
if (chmod(sockpath, 0700) == -1) {
perror("Could not chmod socket");
goto error_seatd;
}
// Drop privileges
if (setgid(gid) == -1) {
perror("Could not set gid to drop privileges");
goto error_seatd;
}
if (setuid(uid) == -1) {
perror("Could not set uid to drop privileges");
goto error_seatd;
}
pid_t child = fork();
if (child == -1) {
perror("Could not fork target process");
goto error_seatd;
} else if (child == 0) {
setenv("SEATD_SOCK", sockpath, 1);
execvp(command[0], command);
perror("Could not start target");
_exit(1);
}
int status = 0;
while (true) {
pid_t p = waitpid(child, &status, 0);
if (p == child) {
break;
} else if (p == -1 && errno != EINTR) {
perror("Could not wait for target process");
goto error_seatd;
}
}
if (unlink(sockpath) != 0) {
perror("Could not unlink socket");
}
if (kill(seatd_child, SIGTERM) != 0) {
perror("Could not kill seatd");
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
return 128 + WTERMSIG(status);
} else {
abort(); // unreachable
}
error_seatd:
unlink(sockpath);
kill(seatd_child, SIGTERM);
error:
return 1;
}