Commit message: seatd-launch: Use snprintf for socket path

We also reduce the size of the buffer from 256 bytes to a much more
reasonable 32 bytes.
CVE: None
Vulnerability description: None

Project: seatd
File: seatd-launch.c

int main(int argc, char *argv[]) {
	(void)argc;

	const char *usage = "Usage: seatd-launch [options] [--] command\n"
			    "\n"
			    "  -h	Show this help message\n"
			    "  -v	Show the version number\n"
			    "\n";

	int c;
	while ((c = getopt(argc, argv, "vh")) != -1) {
		switch (c) {
		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 sockpath[32];
	snprintf(sockpath, sizeof sockpath, "/tmp/seatd.%d.sock", getpid());

	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 *env[2] = {NULL, NULL};
		char loglevelbuf[32] = {0};
		char *cur_loglevel = getenv("SEATD_LOGLEVEL");
		if (cur_loglevel != NULL) {
			snprintf(loglevelbuf, sizeof loglevelbuf, "SEATD_LOGLEVEL=%s", cur_loglevel);
			env[0] = loglevelbuf;
		}

		char *command[] = {"seatd", "-n", pipebuf, "-s", sockpath, NULL};
		execve(SEATD_INSTALLPATH, command, env);
		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;
}