miniroon

Simplistic macaroon-based authorization for Unix systems
git clone https://ccx.te2000.cz/git/miniroon
Log | Files | Refs

commit 605fe53d83cf728ca3bc6dfe2793119daaf6c9a5
Author: Jan Pobrislo <ccx@te2000.cz>
Date:   Mon, 29 Jul 2024 19:01:59 +0000

Import code from pthbs/files

Diffstat:
Acc | 5+++++
Alink | 2++
Asrc/Makefile | 29+++++++++++++++++++++++++++++
Asrc/applyuidgid-caps.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/argv0exec.c | 17+++++++++++++++++
Asrc/nosuid.c | 22++++++++++++++++++++++
Asrc/pidns_run.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/safelink.c | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/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 +*/