commit 605fe53d83cf728ca3bc6dfe2793119daaf6c9a5
Author: Jan Pobrislo <ccx@te2000.cz>
Date: Mon, 29 Jul 2024 19:01:59 +0000
Import code from pthbs/files
Diffstat:
A | cc | | | 5 | +++++ |
A | link | | | 2 | ++ |
A | src/Makefile | | | 29 | +++++++++++++++++++++++++++++ |
A | src/applyuidgid-caps.c | | | 97 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/argv0exec.c | | | 17 | +++++++++++++++++ |
A | src/nosuid.c | | | 22 | ++++++++++++++++++++++ |
A | src/pidns_run.c | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/safelink.c | | | 118 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/spawn-pty.c | | | 142 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
9 files changed, 526 insertions(+), 0 deletions(-)
diff --git a/cc b/cc
@@ -0,0 +1,5 @@
+#!/bin/sh -xe
+exec gcc -D_GNU_SOURCE -Werror \
+ -pipe -std=c11 -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -ffunction-sections -fdata-sections \
+ "$@" -static
+# -Wall
diff --git a/link b/link
@@ -0,0 +1,2 @@
+#!/bin/sh -xe
+exec gcc -static "$@" -lskarnet
diff --git a/src/Makefile b/src/Makefile
@@ -0,0 +1,29 @@
+
+tools_simple:=argv0exec nosuid pidns_run safelink spawn-pty
+tools_libcap:=applyuidgid-caps
+tools=$(tools_simple) $(tools_libcap)
+
+all: $(tools)
+.PHONY: all
+
+clean:
+ rm *.o $(tools)
+.PHONY: clean
+
+%.o: %.c ../cc
+ ../cc -c -o '$@' '$*.c'
+
+define link_simple =
+$(1): $(1).o ../link
+ ../link -o '$$@' '$(1).o'
+endef
+
+$(foreach var,$(tools_simple),$(eval $(call link_simple,$(var))))
+
+define link_libcap =
+$(1): $(1).o ../link
+ ../link -o '$$@' '$(1).o' -lcap
+endef
+
+$(foreach var,$(tools_libcap),$(eval $(call link_libcap,$(var))))
+
diff --git a/src/applyuidgid-caps.c b/src/applyuidgid-caps.c
@@ -0,0 +1,97 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <grp.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#include <linux/securebits.h>
+
+#include <skalibs/types.h>
+#include <skalibs/setgroups.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/exec.h>
+
+#define USAGE "applyuidgid-caps [ -z ] [ -u uid ] [ -g gid ] [ -G gidlist ] [ -U ] iab_caps prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv)
+{
+ uid_t uid = 0 ;
+ gid_t gid = 0 ;
+ gid_t gids[NGROUPS_MAX+1] ;
+ size_t gidn = (size_t)-1 ;
+ int unexport = 0 ;
+ PROG = "s6-applyuidgid" ;
+ {
+ subgetopt l = SUBGETOPT_ZERO ;
+ for (;;)
+ {
+ int opt = subgetopt_r(argc, argv, "zUu:g:G:", &l) ;
+ if (opt == -1) break ;
+ switch (opt)
+ {
+ case 'z' : unexport = 1 ; break ;
+ case 'u' : if (!uid0_scan(l.arg, &uid)) dieusage() ; break ;
+ case 'g' : if (!gid0_scan(l.arg, &gid)) dieusage() ; break ;
+ case 'G' : if (!gid_scanlist(gids, NGROUPS_MAX, l.arg, &gidn) && *l.arg) dieusage() ; break ;
+ case 'U' :
+ {
+ char const *x = getenv("UID") ;
+ if (!x) strerr_dienotset(100, "UID") ;
+ if (!uid0_scan(x, &uid)) strerr_dieinvalid(100, "UID") ;
+ x = getenv("GID") ;
+ if (!x) strerr_dienotset(100, "GID") ;
+ if (!gid0_scan(x, &gid)) strerr_dieinvalid(100, "GID") ;
+ x = getenv("GIDLIST") ;
+ if (!x) strerr_dienotset(100, "GIDLIST") ;
+ if (!gid_scanlist(gids, NGROUPS_MAX+1, x, &gidn) && *x)
+ strerr_dieinvalid(100, "GIDLIST") ;
+ break ;
+ }
+ default : dieusage() ;
+ }
+ }
+ argc -= l.ind ; argv += l.ind ;
+ }
+ if (argc < 2) dieusage() ;
+
+ /*
+ The IAB 3-tuple of capability vectors (Inh, Amb and Bound),
+ captured in type cap_iab_t combine to pass capabilities
+ from one process to another through execve(2) system calls.
+ */
+
+ /* parse the first argument to obtain a set of capabilities */
+ cap_iab_t new_iab;
+ new_iab = cap_iab_from_text(argv[0]);
+ if (new_iab == NULL) {
+ strerr_dieinvalid(100, "caps") ;
+ // strerr_dief1sys(100, "requested capabilities were not recognized");
+ }
+
+ if (prctl(PR_SET_SECUREBITS,
+ SECBIT_KEEP_CAPS | /* unneeded as NO_SETUID_FIXUP is superset */
+ SECBIT_NO_SETUID_FIXUP |
+ SECBIT_NOROOT | /* disables suid and filecap privilege gain */
+ SECBIT_NOROOT_LOCKED) < 0) {
+ strerr_dief1sys(111, "Failed to set securebits via prctl()");
+ }
+ /* set these capabilities for the current process */
+ if (cap_iab_set_proc(new_iab) != 0) {
+ strerr_dief1sys(111, "Failed to set capabilities via cap_set_proc()");
+ }
+
+ if (gidn != (size_t)-1 && setgroups_and_gid(gid ? gid : getegid(), gidn, gids) < 0)
+ strerr_diefu1sys(111, "set supplementary group list") ;
+ if (gid && setgid(gid) < 0)
+ strerr_diefu1sys(111, "setgid") ;
+ if (uid && setuid(uid) < 0)
+ strerr_diefu1sys(111, "setuid") ;
+
+ if (unexport) xmexec_n(argv, "UID\0GID\0GIDLIST", 16, 3) ;
+ else xexec(&argv[1]) ;
+}
diff --git a/src/argv0exec.c b/src/argv0exec.c
@@ -0,0 +1,17 @@
+#include <skalibs/exec.h>
+
+int main (int argc, const char const **argv)
+{
+ const char *new_argv[argc + 1];
+ for(size_t i = 0; i < argc; i++) {
+ new_argv[i] = argv[i];
+ }
+ new_argv[argc] = 0;
+
+ for(size_t i = 0; argv[0][i]; i++) {
+ if(argv[0][i] == '/') {
+ new_argv[0] = &argv[0][i+1];
+ }
+ }
+ xexec(new_argv);
+}
diff --git a/src/nosuid.c b/src/nosuid.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <sys/prctl.h>
+#include <linux/securebits.h>
+
+#include <skalibs/exec.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "nosuid prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv)
+{
+ if (argc < 2) dieusage() ;
+
+ if (prctl(PR_SET_SECUREBITS, SECBIT_NOROOT | SECBIT_NOROOT_LOCKED) < 0) {
+ strerr_dief1sys(111, "Failed to set securebits via prctl()");
+ }
+
+ else xexec(&argv[1]) ;
+}
diff --git a/src/pidns_run.c b/src/pidns_run.c
@@ -0,0 +1,94 @@
+#include <fcntl.h>
+#include <errno.h>
+#include <sched.h> /* Definition of CLONE_* constants & unshare */
+#include <unistd.h> /* fork(), getpid() */
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <assert.h>
+
+#include <skalibs/exec.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/strerr2.h>
+
+#define PROG "pidns_run"
+
+void nonblock_cloexec(int fd) {
+ int flags = fcntl(fd, F_GETFD);
+ if(flags == -1) {
+ strerr_dief1sys(111, "fcntl() getfd");
+ }
+ if(fcntl(fd, F_SETFD, flags | O_NONBLOCK | FD_CLOEXEC) < 0) {
+ strerr_dief1sys(111, "fcntl() setfd");
+ }
+}
+
+int main(const int argc, const char **argv) {
+ int piperw[2];
+ #define parent_rfd piperw[0]
+ #define parent_wfd piperw[1]
+ /* returns EINVAL for some reason
+ if(pipe2(piperw, O_NONBLOCK | FD_CLOEXEC) != 0) {
+ strerr_dief1sys(111, "pipe2()");
+ }
+ */
+ if(pipe(piperw) != 0) {
+ strerr_dief1sys(111, "pipe()");
+ }
+ nonblock_cloexec(parent_rfd);
+ nonblock_cloexec(parent_wfd);
+ if(unshare(CLONE_NEWPID) != 0) {
+ strerr_dief1sys(111, "unshare()");
+ }
+ int fork1_pid = fork();
+ if(fork1_pid < 0) {
+ strerr_dief1sys(111, "first fork()");
+ }
+ if(fork1_pid == 0) {
+ /* child */
+ assert(getpid() == 1);
+ if(close(parent_wfd) != 0) {
+ strerr_dief1sys(111, "close(parent_wfd)");
+ }
+ int fork2_pid = fork();
+ if(fork2_pid < 0) {
+ strerr_dief1sys(111, "second fork()");
+ }
+ if(fork2_pid == 0) {
+ /* child */
+ exec(&argv[1]);
+ } else {
+ /* parent */
+ fd_set rfds;
+ struct timeval tv = {1, 0};
+ int retval, wstatus;
+ pid_t pid;
+ FD_ZERO(&rfds);
+ FD_SET(parent_rfd, &rfds);
+ while(1) {
+ pid = waitpid(0, &wstatus, WNOHANG);
+ if(pid == fork2_pid) {
+ exit(wait_estatus(wstatus));
+ }
+ retval = select(1, &rfds, NULL, NULL, &tv);
+ if (retval == -1 && errno != EINTR) {
+ strerr_dief1sys(111, "select()");
+ }
+ if(retval) {
+ const char term_msg[] = "pidns_run: parent died, terminating\n";
+ write(2, "pidns_run: parent died, terminating\n", sizeof(term_msg));
+ exit(111);
+ }
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ }
+ }
+ } else {
+ /* parent */
+ if(close(parent_rfd) != 0) {
+ strerr_dief1sys(111, "close(parent_rfd)");
+ }
+ int wstatus;
+ pid_t pid = waitpid(fork1_pid, &wstatus, 0);
+ exit(wait_estatus(wstatus));
+ }
+}
diff --git a/src/safelink.c b/src/safelink.c
@@ -0,0 +1,118 @@
+#include <errno.h> /* for errno */
+#include <fcntl.h> /* for mkdirat() openat() */
+#include <stdbool.h> /* for bool */
+
+#include <skalibs/strerr2.h>
+#include <skalibs/random.h>
+
+#define PROG "safelink"
+#define USAGE "safelink oldpath newpath"
+#define BUFLEN 260
+
+char* opendir_nofollow(char *pathname, int *fd, bool create_dirs)
+{
+ char next[BUFLEN];
+ int fd1, fd2;
+ const char *base; /* final part (basename) to return */
+ const char *s1 = pathname;
+ char *s2 = next;
+
+ if(pathname[0] == '/') {
+ fd1 = open("/", O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
+ if(fd1 < 0) {
+ strerr_diefu1sys(111, "open() root");
+ }
+ s1++;
+ } else {
+ fd1 = open(".", O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
+ if(fd1 < 0) {
+ strerr_diefu1sys(111, "open() CWD");
+ }
+ }
+ base = s1;
+ while (*s1) {
+ /* Copy character by character from pathname[] to next[]. */
+ *(s2++) = *(s1++);
+ if (s2 >= &next[BUFLEN]) {
+ strerr_dief1x(100, "filename exceeded buffer size");
+ }
+ if(*s1 == 0) {
+ /* end of string, return dir fd and final path component */
+ *fd = fd1;
+ return base;
+ }
+ if(*s1 == '/') {
+ if(*s1) { s1++; }
+ if(s2 == next) { continue; } /* skip empty filename or trailing slash */
+ *s2 = 0; /* null-terminate the string in next[] */
+ s2 = next; /* reset s2 pointer to the start of next[] for next dirname */
+
+ /* Try opening the directory in next[]. */
+ fd2 = openat(fd1, next, O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
+ if(fd2 < 1) {
+ if(errno == ENOENT) {
+ if(!create_dirs) {
+ strerr_dief2x(111, "directory does not exist: ", next);
+ }
+ /* Create the missing directory. */
+ if(mkdirat(fd1, next, 0777) != 0) {
+ strerr_diefu2sys(111, "mkdirat(): ", next);
+ }
+ /* Open the newly created directory. */
+ fd2 = openat(fd1, next, O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
+ if(fd2 < 1) {
+ strerr_diefu2sys(111, "openat(): ", next);
+ }
+ } else {
+ strerr_diefu2sys(111, "openat(): ", next);
+ }
+ }
+
+ /* Close the parent directory and replace reference to it with the newly opened one. */
+ close(fd1);
+ fd1 = fd2;
+ base = s1;
+ }
+ }
+}
+
+int main (int argc, char const *const *argv)
+{
+ if (argc != 3) {
+ strerr_dieusage(100, USAGE);
+ }
+ int old_fd, new_fd;
+ char *old_base, *new_base;
+ old_base = opendir_nofollow(argv[1], &old_fd, false);
+ if(old_base[0] == 0) {
+ strerr_dief2x(100, "malformed path: ", argv[1]);
+ }
+ new_base = opendir_nofollow(argv[2], &new_fd, true);
+ if(new_base[0] == 0) {
+ strerr_dief2x(100, "malformed path: ", argv[2]);
+ }
+ if(linkat(old_fd, old_base, new_fd, new_base, 0) == 0) {
+ return 0; /* created hardlink at the correct location */
+ }
+ if(errno != EEXIST) {
+ strerr_diefu1sys(111, "linkat()");
+ }
+ /* file with such name already exists, so try again with different one and atomically replace */
+ char tmp_base[BUFLEN];
+ tmp_base[0] = '.';
+ tmp_base[1] = 't';
+ tmp_base[2] = 'm';
+ tmp_base[3] = 'p';
+ tmp_base[4] = '.';
+ random_name(&tmp_base[5], 58);
+ tmp_base[64] = 0;
+ if(linkat(old_fd, old_base, new_fd, tmp_base, 0) != 0) {
+ strerr_diefu2sys(111, "linkat() to temporary name: ", tmp_base);
+ }
+ if(renameat(new_fd, tmp_base, new_fd, new_base) != 0) {
+ strerr_diefu4sys(111, "renameat() from temporary name: ", tmp_base, " to:", new_base);
+ }
+ return 0;
+}
+/* vim: sw=2 sts=2 et
+*/
diff --git a/src/spawn-pty.c b/src/spawn-pty.c
@@ -0,0 +1,142 @@
+#include <pty.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h> /* fork(), getpid(), setsid(), tcsetpgrp() */
+#include <stdlib.h> /* grantpt(), unlockpt(), ptsname() */
+
+#include <skalibs/exec.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/strerr2.h>
+
+#define PROG "spawn-pty"
+#define USAGE "spawn-pty term_name { pty-prog ... } ptmx-prog ..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+#define resetsig(S) if(signal(S, SIG_DFL) == SIG_ERR) { \
+ strerr_dief1sys(111, "resetting signal handler"); \
+ }
+
+void exec_terminal(const char *term_env, const char **ptmx_argv, const char **pty_argv) {
+ int ptmx_fd;
+ const char *pts_name;
+
+ ptmx_fd = posix_openpt(O_RDWR | O_NOCTTY);
+ if (ptmx_fd < 0) {
+ strerr_dief1sys(111, "posix_openpt()");
+ }
+ if (grantpt(ptmx_fd) != 0) {
+ strerr_dief1sys(111, "grantpt()");
+ }
+ if (unlockpt(ptmx_fd) != 0) {
+ strerr_dief1sys(111, "unlockpt()");
+ }
+ pts_name = ptsname(ptmx_fd);
+ if (pts_name == NULL) {
+ strerr_dief1sys(111, "ptsname()");
+ }
+
+ int fork_pid = fork();
+ if(fork_pid < 0) {
+ strerr_dief1sys(111, "fork()");
+ }
+ if(fork_pid == 0) {
+ /* child */
+ int pty_fd;
+
+ resetsig(SIGHUP);
+ resetsig(SIGINT);
+ resetsig(SIGQUIT);
+ resetsig(SIGPIPE);
+ resetsig(SIGALRM);
+ resetsig(SIGTERM);
+ resetsig(SIGCHLD);
+ resetsig(SIGCONT);
+ resetsig(SIGTSTP);
+ resetsig(SIGTTIN);
+ resetsig(SIGTTOU);
+
+ if(close(ptmx_fd) != 0) {
+ strerr_dief1sys(111, "close(ptmx_fd) in child");
+ }
+
+ if(setsid() < 0) { /* create new session */
+ strerr_dief1sys(111, "setsid");
+ }
+ /* open without O_NOCTTY so controlling terminal gets set */
+ pty_fd = open(pts_name, O_RDWR);
+ if (ptmx_fd < 0) {
+ strerr_dief1sys(111, "open(pty)");
+ }
+ if (tcsetpgrp(pty_fd, getpid()) < 0) { /* set controlling terminal */
+ strerr_dief1sys(111, "tcsetpgrp");
+ }
+
+ if(dup2(pty_fd, 0) < 0) {
+ strerr_dief1sys(111, "dup2(pty_fd, 0) in child");
+ }
+ if(dup2(pty_fd, 1) < 0) {
+ strerr_dief1sys(111, "dup2(pty_fd, 1) in child");
+ }
+ if(dup2(pty_fd, 2) < 0) {
+ strerr_dief1sys(111, "dup2(pty_fd, 2) in child");
+ }
+ if(close(pty_fd) != 0) {
+ strerr_dief1sys(111, "close(pty_fd) in child");
+ }
+ char envstr[6 + strlen(term_env)];
+ memcpy(envstr, "TERM=", 5);
+ strcpy(&envstr[5], term_env);
+ xmexec_n(pty_argv, envstr, strlen(envstr), 1);
+ } else {
+ /* parent */
+ if(dup2(ptmx_fd, 0) < 0) {
+ strerr_dief1sys(111, "dup2(ptmx_fd, 0) in parent");
+ }
+ if(close(ptmx_fd) != 0) {
+ strerr_dief1sys(111, "close(ptmx_fd) in parent");
+ }
+ // # parent
+ // env['PTMX_FD'] = "0"
+ // execve(terminal, env)
+ char envstr[9 + strlen(pts_name)];
+ memcpy(envstr, "PTS_NAME=", 9);
+ strcpy(&envstr[8], term_env);
+ xmexec_n(ptmx_argv, envstr, strlen(envstr), 1);
+ }
+}
+
+typedef const char * arg_t;
+
+int main(const int argc, const char **argv) {
+ if(argc < 2) {
+ dieusage();
+ }
+ arg_t pty_argv[argc];
+ const char *term_env = argv[1];
+ int n = 2;
+ if(term_env[0] == ' ') {
+ dieusage();
+ }
+ if(argv[2][0] != ' ') {
+ strerr_dieusage(100, USAGE "\nerror: missing block");
+ }
+ for(int n = 2; n < argc - 1; n++) {
+ switch(argv[n][0]) {
+ case 0:
+ pty_argv[n - 2] = 0;
+ exec_terminal(term_env, &argv[n + 1], pty_argv);
+ return 111;
+ case ' ':
+ pty_argv[n - 2] = &argv[n][1];
+ break;
+ default:
+ strerr_dieusage(100, USAGE "\nerror: improperly terminated block");
+ return 100;
+ }
+ }
+ strerr_dieusage(100, USAGE "\nerror: unterminated block");
+}
+
+/* vim: ft=c sts=2 sw=2 et
+*/