On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote: > From: Alexey Gladkov > > All privileged operations moved to the daemon. Commands to the server Suggested replacement: "All privileged operations are moved to the daemon. Commands to the server" > are sent through the unix domain socket. The credentials which the sender > specifies are checked by the kernel. The hasher-priv no longer SUID. Suggested replacement for the last sentence: "Neither hasher-priv nor its daemon are installed with SUID on" > > For each user server creates a separate session process that executes Suggested replacement: s/server/the server/ > commands only from the user who created it. The session process ends > after a certain period of inactivity. > > Signed-off-by: Alexey Gladkov > --- > hasher-priv/.gitignore | 1 + > hasher-priv/DESIGN | 281 ++++++++++++++++---------- > hasher-priv/Makefile | 28 ++- > hasher-priv/caller.c | 81 ++++---- > hasher-priv/caller_server.c | 373 ++++++++++++++++++++++++++++++++++ > hasher-priv/caller_task.c | 214 ++++++++++++++++++++ > hasher-priv/cmdline.c | 27 ++- > hasher-priv/communication.c | 392 ++++++++++++++++++++++++++++++++++++ > hasher-priv/communication.h | 77 +++++++ > hasher-priv/config.c | 143 ++++++++++++- > hasher-priv/epoll.c | 39 ++++ > hasher-priv/epoll.h | 18 ++ > hasher-priv/hasher-priv.c | 78 +++++++ > hasher-priv/hasher-privd.c | 375 ++++++++++++++++++++++++++++++++++ > hasher-priv/io_log.c | 2 +- > hasher-priv/io_x11.c | 2 +- > hasher-priv/killuid.c | 2 +- > hasher-priv/logging.c | 64 ++++++ > hasher-priv/logging.h | 55 +++++ > hasher-priv/main.c | 75 ------- > hasher-priv/pass.c | 117 ++++++++++- > hasher-priv/pidfile.c | 128 ++++++++++++ > hasher-priv/pidfile.h | 44 ++++ > hasher-priv/priv.h | 33 +-- > hasher-priv/server.conf | 13 ++ > hasher-priv/sockets.c | 183 +++++++++++++++++ > hasher-priv/sockets.h | 32 +++ > hasher-priv/x11.c | 1 + > 28 files changed, 2632 insertions(+), 246 deletions(-) > create mode 100644 hasher-priv/caller_server.c > create mode 100644 hasher-priv/caller_task.c > create mode 100644 hasher-priv/communication.c > create mode 100644 hasher-priv/communication.h > create mode 100644 hasher-priv/epoll.c > create mode 100644 hasher-priv/epoll.h > create mode 100644 hasher-priv/hasher-priv.c > create mode 100644 hasher-priv/hasher-privd.c > create mode 100644 hasher-priv/logging.c > create mode 100644 hasher-priv/logging.h > delete mode 100644 hasher-priv/main.c > create mode 100644 hasher-priv/pidfile.c > create mode 100644 hasher-priv/pidfile.h > create mode 100644 hasher-priv/server.conf > create mode 100644 hasher-priv/sockets.c > create mode 100644 hasher-priv/sockets.h > > diff --git a/hasher-priv/.gitignore b/hasher-priv/.gitignore > index 5ca24b0..8f0f658 100644 > --- a/hasher-priv/.gitignore > +++ b/hasher-priv/.gitignore > @@ -4,6 +4,7 @@ getconf.sh > getugid1.sh > getugid2.sh > hasher-priv > +hasher-privd > hasher-priv.8 > hasher-priv.conf.5 > hasher-useradd > diff --git a/hasher-priv/DESIGN b/hasher-priv/DESIGN This file desperately needs lots of fixes throughout, to be fair. > index 1470ce7..310667d 100644 > --- a/hasher-priv/DESIGN > +++ b/hasher-priv/DESIGN > @@ -1,35 +1,63 @@ > > -Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow: > +Here is a hasher-priv (euid=user,egid=hashman,gid!=egid) control flow: Suggested replacement: s/a hasher-priv/the client's/ A reword of all the header phrases would also suffice. > + sanitize file descriptors > + set safe umask > + ensure 0,1,2 are valid file descriptors > + close all the rest > + parse command line arguments > - + check for non-zero argument list > - + parse -h, --help and -number options > - + caller_num initialized here > - + parse arguments, abort if wrong > - + chroot_path and chroot_argv are initialized here > - + valid arguments are: > - getconf > - killuid > - getugid1 > - chrootuid1 [program args] > - getugid2 > - chrootuid2 [program args] > ++ connect to /var/run/hasher-priv socket > + + wait for the creation of a session server > + + close socket > ++ connect to session server by /var/run/hasher-priv-UID socket > ++ send command to start new task > + + receive a result code from the server > ++ send current stdout and stderr to server > + + receive a result code from the server > ++ send task arguments > + + receive a result code from the server > ++ send environment variables > + + receive a result code from the server > ++ send command to run task > + + receive a task result code from the server > + > +Here is a hasher-privd (euid=root,egid=hashman,gid==egid) control flow: Same as above. > ++ parse command line arguments > + + parse -h and --help options > ++ set safe umask > ++ check pidfile, abort if server already running > ++ daemonize > + + change current working directory to "/" > + + close all file descriptors > ++ initialize logger > ++ create pidfile > ++ examine and change blocked signals > ++ I/O event notification and add file descriptors > + + create a file descriptor for accepting signals > + + create and listen server socket > ++ wait for incomming caller connections > + + handle signal if signal is received > + + close caller's session > + + handle connection if the caller opened a new connection > + + get connection credentials > + + fork new process for caller if don't have any > + + notify the client if the session server already running > + + close caller connection > ++ close all descriptors in notification poll > ++ close and remove pidfile > + > +Here is control flow for session server: > + initialize data related to caller > - + caller_uid initialized here from getuid() > + + caller_uid initialized here by uid received via the socket > + caller_uid must be valid uid > - + caller_gid initialized here from getgid() > + + caller_gid initialized here by uid received via the socket > + caller_gid must be valid gid > - + caller_user initialized here from getpwnam(LOGNAME)->pw_name or > - getpwuid(caller_uid)->pw_name > + + caller_user initialized here from getpwuid(caller_uid)->pw_name > + must be true: caller_uid == pw->pw_uid > + must be true: caller_gid == pw->pw_gid > + caller_home initialized here > + caller_user's home directory must exist > -+ read work limit hints from environment variables > -+ drop all environment variables > ++ set safe umask > ++ open a caller-specific socket > + load configuration > + safe chdir to /etc/hasher-priv > + safe load "system" file > @@ -40,7 +68,7 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow: > prefix > umask > nice > - allowed_devices > + allow_ttydev > allowed_mountpoints > rlimit_(hard|soft)_* > wlimit_(time_elapsed|time_idle|bytes_written) > @@ -53,85 +81,136 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow: > + change_user1 and change_user2 should be initialized here > + change_uid1 and change_gid1 initialized from change_user1 > + change_uid2 and change_gid2 initialized from change_user2 > -+ execute choosen task > - + getconf: print config file /etc/hasher-priv/user.d/caller_user[:caller_num] > - + getugid1: print change_uid1:change_gid1 pair > - + getugid2: print change_uid2:change_gid2 pair > - + killuid > - + check for valid uids specified > - + drop dumpable flag (just in case - not required) > - + setuid to specified uid pair > - + kill (-1, SIGKILL) > - + purge all SYSV IPC objects belonging to specified uid pair > - + chrootuid1/chrootuid2 > - + check for valid uid specified > - + setup mounts and devices > - + unshare mount namespace > - + safe chdir to chroot_path > - + safe chdir to dev > - + mount dev > - + create devices available to all users: null, zero, full, random, urandom > - + if makedev_console environment variable is true, > - create devices available to root only: console, tty0, fb0 > - + mount /dev/shm > - + mount all mountpoints specified by requested_mountpoints environment variable > - + if /dev/pts was mounted, create devices: tty, ptmx > - + safe chdir to chroot_path > - + sanitize file descriptors again > - + if use_pty is disabled, create pipe to handle child's stdout and stderr > - + if X11 forwarding is requested, create socketpair and > - open /tmp/.X11-unix directory readonly for later use with fchdir() > - + unless share_ipc is enabled, isolate System V IPC namespace > - + unless share_uts is enabled, unshare UTS namespace > - + if X11 forwarding to a tcp address was not requested, > - unless share_network is enabled, unshare network > - + clear supplementary group access list > - + create pty: > - + temporarily switch to called_uid:caller_gid > - + open /dev/ptmx > - + fetch pts number > - + unlock pts pair > - + open pts slave > - + switch uid:gid back > - + chroot to "." > - + create another pty if possible: > - + temporarily switch to called_uid:caller_gid > - + safely chdir to "dev" > - + if "pts/ptmx" is available with right permissions: > - + replace "ptmx" with a symlink to "pts/ptmx" > - + open "ptmx" > - + fetch pts number > - + unlock pts pair > - + open pts slave > - + chdir back to "/" > - + switch uid:gid back > - + if another pty was crated, close pts pair that was opened earlier > - + set rlimits > - + set close-on-exec flag on all non-standard descriptors > - + fork > - + in parent: > - + setgid/setuid to caller user > - + install CHLD signal handler > - + unblock master pty and pipe descriptors > - + if use_pty is enabled, initialize tty and install WINCH signal handler > - + listen to "/dev/log" > - + while work limits are not exceeded, handle child input/output > - + close master pty descriptor, thus sending HUP to child session > - + wait for child process termination > - + remove CHLD signal handler > - + return child proccess exit code > - + in child: > - + if X11 forwarding to a tcp address was requested, > ++ I/O event notification and add file descriptors > + + create a file descriptor for accepting signals > + + create and listen the session socket > ++ notify the client that the session server is ready > ++ wait for incomming caller connections > + + handle signal if signal is received > + + handle connection if the caller opened a new connection > + + task handler > + + reset timeout timer > + + finish the server if the task doesn't arrive from the caller > + for more than a minute. > + > +Here is control flow for the task handler: > ++ receive task header > + + receive client's stdin, stdout and stderr > + + check number of arguments > ++ receive task arguments if we expect them > ++ receive environment variables if client want to send them > ++ fork process to handle task > + + in parent: > + + wait exit status > + + in child: > + + replace stdin, stdout and stderr with those that were received > + + drop all environment variables > + + apply the client's environment variables > + + sanitize file descriptors > + + set safe umask > + + ensure 0,1,2 are valid file descriptors > + + close all the rest > + + parse task arguments > + + check for non-zero argument list > + + parse -h, --help and -number options > + + caller_num initialized here > + + parse arguments, abort if wrong > + + chroot_path and chroot_argv are initialized here > + + valid arguments are: > + getconf > + killuid > + getugid1 > + chrootuid1 [program args] > + getugid2 > + chrootuid2 [program args] > + + caller_num initialized from task > + + chroot_path and chroot_argv are initialized here > + + read work limit hints from environment variables > + + drop all environment variables > + + execute choosen task > + + getconf: print config file /etc/hasher-priv/user.d/caller_user[:caller_num] > + + getugid1: print change_uid1:change_gid1 pair > + + getugid2: print change_uid2:change_gid2 pair > + + killuid > + + check for valid uids specified > + + drop dumpable flag (just in case - not required) > + + setuid to specified uid pair > + + kill (-1, SIGKILL) > + + purge all SYSV IPC objects belonging to specified uid pair > + + chrootuid1/chrootuid2 > + + fork to kill the processes that were left from the previous chrootuid call > + + in parent: > + + wait exit status > + + in child: > + + killuid > + + check for valid uid specified > + + setup mounts and devices > + + unshare mount namespace > + + safe chdir to chroot_path > + + safe chdir to dev > + + mount dev > + + create devices available to all users: null, zero, full, random, urandom > + + if makedev_console environment variable is true, > + create devices available to root only: console, tty0, fb0 > + + mount /dev/shm > + + mount all mountpoints specified by requested_mountpoints environment variable > + + if /dev/pts was mounted, create devices: tty, ptmx > + + safe chdir to chroot_path > + + sanitize file descriptors again > + + if use_pty is disabled, create pipe to handle child's stdout and stderr > + + if X11 forwarding is requested, create socketpair and > + open /tmp/.X11-unix directory readonly for later use with fchdir() > + + unless share_ipc is enabled, isolate System V IPC namespace > + + unless share_uts is enabled, unshare UTS namespace > + + if X11 forwarding to a tcp address was not requested, > unless share_network is enabled, unshare network > - + setgid/setuid to specified user > - + setsid > - + change controlling terminal to pty > - + redirect stdin if required, either to null or to pty > - + redirect stdout and stderr either to pipe or to pty > - + set nice > - + if X11 forwarding is requested, > - + add X11 auth entry using xauth utility > - + create and bind unix socket for X11 forwarding > - + send listening descriptor and fake auth data to the parent > - + set umask > - + execute specified program > + + clear supplementary group access list > + + create pty: > + + temporarily switch to called_uid:caller_gid > + + open /dev/ptmx > + + fetch pts number > + + unlock pts pair > + + open pts slave > + + switch uid:gid back > + + chroot to "." > + + create another pty if possible: > + + temporarily switch to called_uid:caller_gid > + + safely chdir to "dev" > + + if "pts/ptmx" is available with right permissions: > + + replace "ptmx" with a symlink to "pts/ptmx" > + + open "ptmx" > + + fetch pts number > + + unlock pts pair > + + open pts slave > + + chdir back to "/" > + + switch uid:gid back > + + if another pty was crated, close pts pair that was opened earlier > + + set rlimits > + + set close-on-exec flag on all non-standard descriptors > + + fork > + + in parent: > + + setgid/setuid to caller user > + + install CHLD signal handler > + + unblock master pty and pipe descriptors > + + if use_pty is enabled, initialize tty and install WINCH signal handler > + + listen to "/dev/log" > + + while work limits are not exceeded, handle child input/output > + + close master pty descriptor, thus sending HUP to child session > + + wait for child process termination > + + remove CHLD signal handler > + + return child proccess exit code > + + in child: > + + if X11 forwarding to a tcp address was requested, > + unless share_network is enabled, unshare network > + + setgid/setuid to specified user > + + setsid > + + change controlling terminal to pty > + + redirect stdin if required, either to null or to pty > + + redirect stdout and stderr either to pipe or to pty > + + set nice > + + if X11 forwarding is requested, > + + add X11 auth entry using xauth utility > + + create and bind unix socket for X11 forwarding > + + send listening descriptor and fake auth data to the parent > + + set umask > + + execute specified program > diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile > index a815e9e..82aa385 100644 > --- a/hasher-priv/Makefile > +++ b/hasher-priv/Makefile > @@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec) > HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh > MAN5PAGES = $(PROJECT).conf.5 > MAN8PAGES = $(PROJECT).8 hasher-useradd.8 > -TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES) > +TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES) > > sysconfdir = /etc > libexecdir = /usr/lib > @@ -21,6 +21,7 @@ man5dir = $(mandir)/man5 > man8dir = $(mandir)/man8 > configdir = $(sysconfdir)/$(PROJECT) > helperdir = $(libexecdir)/$(PROJECT) > +socketdir = /var/run > DESTDIR = > > MKDIR_P = mkdir -p > @@ -33,17 +34,25 @@ WARNINGS = -Wall -W -Wshadow -Wpointer-arith -Wwrite-strings \ > -Wmissing-prototypes -Wmissing-declarations -Wmissing-noreturn \ > -Wmissing-format-attribute -Wredundant-decls -Wdisabled-optimization > CPPFLAGS = -std=gnu99 -D_GNU_SOURCE $(CHDIRUID_FLAGS) \ > - $(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\" > + $(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\" \ > + -DSOCKETDIR=\"$(socketdir)\" -DPROJECT=\"$(PROJECT)\" > CFLAGS = -pipe -O2 > override CFLAGS += $(WARNINGS) > LDLIBS = > > -SRC = caller.c chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \ > +SRC = hasher-priv.c cmdline.c fds.c sockets.c logging.c communication.c xmalloc.c pass.c > +OBJ = $(SRC:.c=.o) > + > +server_SRC = hasher-privd.c \ > + communication.c epoll.c pidfile.c logging.c sockets.c \ > + caller.c caller_server.c caller_task.c \ > + chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \ > config.c fds.c getconf.c getugid.c ipc.c killuid.c io_log.c io_x11.c \ > - main.c makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \ > + makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \ > unshare.c xmalloc.c x11.c > -OBJ = $(SRC:.c=.o) > -DEP = $(SRC:.c=.d) > +server_OBJ = $(server_SRC:.c=.o) > + > +DEP = $(SRC:.c=.d) $(server_SRC:.c=.d) > > .PHONY: all install clean indent > > @@ -52,14 +61,19 @@ all: $(TARGETS) > $(PROJECT): $(OBJ) > $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ > > +hasher-privd: $(server_OBJ) > + $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ > + > install: all > $(MKDIR_P) -m710 $(DESTDIR)$(configdir)/user.d > $(INSTALL) -p -m640 fstab $(DESTDIR)$(configdir)/fstab > $(INSTALL) -p -m640 system.conf $(DESTDIR)$(configdir)/system > + $(INSTALL) -p -m640 server.conf $(DESTDIR)$(configdir)/server > $(MKDIR_P) -m750 $(DESTDIR)$(helperdir) > $(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/ > $(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/ > $(MKDIR_P) -m755 $(DESTDIR)$(sbindir) > + $(INSTALL) -p -m755 hasher-privd $(DESTDIR)$(sbindir)/ > $(INSTALL) -p -m755 hasher-useradd $(DESTDIR)$(sbindir)/ > $(MKDIR_P) -m755 $(DESTDIR)$(man5dir) > $(INSTALL) -p -m644 $(MAN5PAGES) $(DESTDIR)$(man5dir)/ > @@ -67,7 +81,7 @@ install: all > $(INSTALL) -p -m644 $(MAN8PAGES) $(DESTDIR)$(man8dir)/ > > clean: > - $(RM) $(TARGETS) $(DEP) $(OBJ) core *~ > + $(RM) $(TARGETS) $(DEP) $(OBJ) $(server_OBJ) core *~ > > indent: > indent *.h *.c > diff --git a/hasher-priv/caller.c b/hasher-priv/caller.c > index e83084a..031ddef 100644 > --- a/hasher-priv/caller.c > +++ b/hasher-priv/caller.c > @@ -19,62 +19,67 @@ > > #include "priv.h" > #include "xmalloc.h" > +#include "logging.h" > > -const char *caller_user, *caller_home; > -uid_t caller_uid; > -gid_t caller_gid; > +char *caller_user = NULL; > +char *caller_home = NULL; > +uid_t caller_uid; > +gid_t caller_gid; > > /* > * Initialize caller_user, caller_uid, caller_gid and caller_home. > */ > -void > -init_caller_data(void) > +int > +init_caller_data(uid_t uid, gid_t gid) > { > - const char *logname; > struct passwd *pw = 0; > > - caller_uid = getuid(); > - if (caller_uid < MIN_CHANGE_UID) > - error(EXIT_FAILURE, 0, "caller has invalid uid: %u", > - caller_uid); > - > - caller_gid = getgid(); > - if (caller_gid < MIN_CHANGE_GID) > - error(EXIT_FAILURE, 0, "caller has invalid gid: %u", > - caller_gid); > - > - if ((logname = getenv("LOGNAME"))) > - if (!*logname || strchr(logname, ':')) > - logname = 0; > - > - if (logname) > - { > - pw = getpwnam(logname); > - if (caller_uid != pw->pw_uid || caller_gid != pw->pw_gid) > - pw = 0; > + caller_uid = uid; > + if (caller_uid < MIN_CHANGE_UID) { > + err("caller has invalid uid: %u", caller_uid); > + return -1; > + } > + > + caller_gid = gid; > + if (caller_gid < MIN_CHANGE_GID) { > + err("caller has invalid gid: %u", caller_gid); > + return -1; > } > > - if (!pw) > - pw = getpwuid(caller_uid); > + pw = getpwuid(caller_uid); > > - if (!pw || !pw->pw_name) > - error(EXIT_FAILURE, 0, "caller lookup failure"); > + if (!pw || !pw->pw_name) { > + err("caller lookup failure"); > + return -1; > + } > > caller_user = xstrdup(pw->pw_name); > > - if (caller_uid != pw->pw_uid) > - error(EXIT_FAILURE, 0, "caller %s: uid mismatch", > - caller_user); > + if (caller_uid != pw->pw_uid) { > + err("caller %s: uid mismatch", caller_user); > + return -1; > + } > > - if (caller_gid != pw->pw_gid) > - error(EXIT_FAILURE, 0, "caller %s: gid mismatch", > - caller_user); > + if (caller_gid != pw->pw_gid) { > + err("caller %s: gid mismatch", caller_user); > + return -1; > + } > > errno = 0; > if (pw->pw_dir && *pw->pw_dir) > caller_home = canonicalize_file_name(pw->pw_dir); > > - if (!caller_home || !*caller_home) > - error(EXIT_FAILURE, errno, "caller %s: invalid home", > - caller_user); > + if (!caller_home || !*caller_home) { > + err("caller %s: invalid home: %m", caller_user); > + return -1; > + } > + > + return 0; > +} > + > +void > +free_caller_data(void) > +{ > + free(caller_user); > + free(caller_home); > } > diff --git a/hasher-priv/caller_server.c b/hasher-priv/caller_server.c > new file mode 100644 > index 0000000..8182c69 > --- /dev/null > +++ b/hasher-priv/caller_server.c > @@ -0,0 +1,373 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + The part of the hasher-privd program. Which part? > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#include > + > +#include /* SOCK_CLOEXEC */ > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > + > +#include "priv.h" > +#include "xmalloc.h" > +#include "sockets.h" > +#include "logging.h" > +#include "epoll.h" > +#include "communication.h" > + > +static char socketpath[UNIX_PATH_MAX]; > + > +static int > +validate_arguments(task_t task, char **argv) > +{ > + int required_args = 0, more_args = 0; > + int rc = -1; > + int argc = 0; > + > + while (argv && argv[argc]) > + argc++; > + > + switch (task) { > + case TASK_GETCONF: > + case TASK_KILLUID: > + case TASK_GETUGID1: > + case TASK_GETUGID2: > + required_args = 0; > + break; > + case TASK_CHROOTUID1: > + case TASK_CHROOTUID2: > + more_args = 1; > + required_args = 2; > + break; > + default: > + err("unknown task type: %u", task); > + return rc; > + } > + > + if (argc < 0) > + err("number of arguments must be a positive but got %d", > + argc); > + > + else if (argc < required_args) > + err("%s task requires at least %d arguments but got %d", > + task2str(task), required_args, argc); > + > + else if (argc > required_args && !more_args) > + err("%s task requires exactly %d arguments but got %d", > + task2str(task), required_args, argc); > + > + else > + rc = 0; > + > + return rc; > +} > + > +static void > +free_task(struct task *task) > +{ > + if (task->env) { > + free(task->env[0]); > + free(task->env); > + } > + > + if (task->argv) { > + free(task->argv[0]); > + free(task->argv); > + } > +} > + > +static int > +cancel_task(int conn, struct task *task) > +{ > + free_task(task); > + send_command_response(conn, CMD_STATUS_FAILED, "command failed"); > + return 0; > +} > + > +static int > +process_task(int conn) > +{ > + uid_t uid; > + gid_t gid; > + > + if (get_peercred(conn, NULL, &uid, &gid) < 0) > + return -1; > + > + if (caller_uid != uid || caller_gid != gid) { > + err("user (uid=%d) has no permissions to send commands" Suggested replacement: "has no permissions" -> "has no permission" > + " to the session of another user (uid=%d)", > + uid, caller_uid); > + return -1; > + } > + > + struct task task = { 0 }; > + int fds[3]; > + > + while (1) { > + task_t type = TASK_NONE; > + struct cmd hdr = { 0 }; > + > + if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0) > + return cancel_task(conn, &task); > + > + switch (hdr.type) { > + case CMD_TASK_BEGIN: > + if (hdr.datalen != sizeof(type)) > + return cancel_task(conn, &task); > + > + if (xrecvmsg(conn, &type, hdr.datalen) < 0) > + return cancel_task(conn, &task); > + > + task.type = type; > + > + break; > + > + case CMD_TASK_FDS: > + if (hdr.datalen != sizeof(int) * 3) > + return cancel_task(conn, &task); > + > + if (task.stdin) > + close(task.stdin); > + > + if (task.stdout) > + close(task.stdout); > + > + if (task.stderr) > + close(task.stderr); > + > + if (fds_recv(conn, fds, 3) < 0) > + return cancel_task(conn, &task); > + > + task.stdin = fds[0]; > + task.stdout = fds[1]; > + task.stderr = fds[2]; > + > + break; > + > + case CMD_TASK_ARGUMENTS: > + if (task.argv) { > + free(task.argv[0]); > + free(task.argv); > + } > + > + if (recv_list(conn, &task.argv, hdr.datalen) < 0) > + return cancel_task(conn, &task); > + > + if (validate_arguments(task.type, task.argv) < 0) > + return cancel_task(conn, &task); > + > + break; > + > + case CMD_TASK_ENVIRON: > + if (task.env) { > + free(task.env[0]); > + free(task.env); > + } > + > + if (recv_list(conn, &task.env, hdr.datalen) < 0) > + return cancel_task(conn, &task); > + > + break; > + > + case CMD_TASK_RUN: > + process_caller_task(conn, &task); > + free_task(&task); > + return 0; > + > + default: > + err("unsupported command: %d", hdr.type); > + } > + > + send_command_response(conn, CMD_STATUS_DONE, NULL); > + } > + > + return 0; > +} > + > +static int > +caller_server(int cl_conn) > +{ > + int ret = 0; > + > + umask(077); > + > + snprintf(socketpath, sizeof(socketpath), > + "hasher-priv-%d-%u", > + caller_uid, caller_num); > + > + int fd_conn = -1; > + int fd_signal = -1; > + int fd_ep = -1; > + > + if ((fd_conn = srv_listen(SOCKETDIR, socketpath)) < 0) > + return -1; > + > + snprintf(socketpath, sizeof(socketpath), > + "%s/hasher-priv-%d-%u", > + SOCKETDIR, caller_uid, caller_num); > + > + if (chown(socketpath, caller_uid, caller_gid)) { > + err("fchown: %s: %m", socketpath); > + ret = -1; > + goto fail; > + } > + > + /* Load config according to caller information. */ > + configure(); > + > + sigset_t mask; > + > + sigfillset(&mask); > + sigprocmask(SIG_SETMASK, &mask, NULL); > + > + if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0) { > + err("signalfd: %m"); > + ret = -1; > + goto fail; > + } > + > + if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0) { > + err("epoll_create1: %m"); > + ret = -1; > + goto fail; > + } > + > + if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0) { > + err("epollin_add: failed"); > + ret = -1; > + goto fail; > + } > + > + /* Tell client that caller server is ready */ Suggested replacement: "client" -> "the client" > + send_command_response(cl_conn, CMD_STATUS_DONE, NULL); > + close(cl_conn); > + > + unsigned long nsec = 0; > + int finish_server = 0; > + > + while (!finish_server) { > + struct epoll_event ev[42]; > + int fdcount; > + > + errno = 0; > + if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), 1000)) < 0) { > + if (errno == EINTR) > + continue; > + err("epoll_wait: %m"); > + break; > + } > + > + if (fdcount == 0) { > + nsec++; > + > + if (nsec >= server_session_timeout) > + break; > + > + } else for (int i = 0; i < fdcount; i++) { > + if (!(ev[i].events & EPOLLIN)) { > + continue; > + > + } else if (ev[i].data.fd == fd_signal) { > + struct signalfd_siginfo fdsi; > + ssize_t size; > + > + size = read_retry(fd_signal, &fdsi, sizeof(fdsi)); > + > + if (size != sizeof(fdsi)) { > + err("unable to read signal info"); Suggested error message: "unable to read signal info from signalfd" The form present in the patch is only obvious to a developer. > + continue; > + } > + > + int status; > + > + switch (fdsi.ssi_signo) { > + case SIGINT: > + case SIGTERM: > + finish_server = 1; > + break; > + case SIGCHLD: > + if (waitpid(-1, &status, 0) < 0) > + err("waitpid: %m"); > + break; > + } > + > + } else if (ev[i].data.fd == fd_conn) { > + int conn; > + > + if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) { > + err("accept4: %m"); > + continue; > + } > + > + if (set_recv_timeout(conn, 3) < 0) { > + close(conn); > + continue; > + } > + > + if (!process_task(conn)) { > + /* reset timer */ > + nsec = 0; > + } > + > + close(conn); > + } > + } > + } > +fail: > + epollin_remove(fd_ep, fd_signal); > + epollin_remove(fd_ep, fd_conn); > + > + if (fd_ep >= 0) > + close(fd_ep); > + > + unlink(socketpath); > + > + return ret; > +} > + > +pid_t > +fork_server(int cl_conn, uid_t uid, gid_t gid, unsigned num) > +{ > + pid_t pid = fork(); > + > + if (pid != 0) { > + if (pid < 0) { > + err("fork: %m"); > + return -1; > + } > + return pid; > + } > + > + caller_num = num; > + > + int ret = init_caller_data(uid, gid); > + > + if (ret < 0) { > + info("%s(%d) num=%u: start session server", caller_user, caller_uid, caller_num); > + ret = caller_server(cl_conn); > + info("%s(%d): finish session server", caller_user, caller_uid); > + } > + > + if (ret < 0) { > + send_command_response(cl_conn, CMD_STATUS_FAILED, NULL); > + ret = EXIT_FAILURE; > + } else { > + ret = EXIT_SUCCESS; > + } > + > + free_caller_data(); > + close(cl_conn); > + > + exit(ret); > +} > diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c > new file mode 100644 > index 0000000..d8f2dd5 > --- /dev/null > +++ b/hasher-priv/caller_task.c > @@ -0,0 +1,214 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + The part of the hasher-privd program. Which part? The same anonymous part as caller_server.c? =) > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > + > +#include "communication.h" > +#include "xmalloc.h" > +#include "logging.h" > +#include "sockets.h" > +#include "priv.h" > + > +static int > +killprevious(void) > +{ > + int rc = 0; > + pid_t pid = fork(); > + > + if (pid < 0) { > + err("fork: %m"); > + return -1; > + } > + > + if (!pid) > + exit(do_killuid()); > + > + while (1) { > + int wstatus; > + > + if (waitpid(pid, &wstatus, WUNTRACED | WCONTINUED) < 0) { > + err("waitpid: %m"); > + rc = -1; > + break; > + } > + > + if (WIFEXITED(wstatus)) { > + rc = WEXITSTATUS(wstatus); > + break; > + } > + } > + > + return rc; > +} > + > +static int > +reopen_fd(int oldfd, int newfd) > +{ > + if (oldfd < 0) > + return 0; > + > + close(newfd); > + > + if (dup2(oldfd, newfd) < 0) { > + err("dup2: %m"); > + return -1; > + } > + > + close(oldfd); > + > + return 0; > +} > + > +static int > +reopen_iostreams(int stdin, int stdout, int stderr) > +{ > + return (reopen_fd(stdin, STDIN_FILENO) < 0 || > + reopen_fd(stdout, STDOUT_FILENO) < 0 || > + reopen_fd(stderr, STDERR_FILENO) < 0) ? -1 : 0; > +} > + > +static int > +caller_task(struct task *task) > +{ > + int rc = EXIT_FAILURE; > + int i = 0; > + pid_t pid; > + > + if ((pid = fork()) != 0) { > + if (pid < 0) { > + err("fork: %m"); > + return -1; > + } > + return pid; > + } > + > + if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0) > + exit(rc); > + > + /* cleanup environment to avoid side effects. */ s/cleanup/clean up/ > + if (clearenv() != 0) > + fatal("clearenv: %m"); > + > + while (task->env && task->env[i]) { > + if (putenv(task->env[i++]) != 0) > + fatal("putenv: %m"); > + } > + > + /* First, check and sanitize file descriptors. */ > + sanitize_fds(); > + > + /* Second, parse task arguments. */ > + parse_task_args(task->type, (const char **) task->argv); > + > + if (chroot_path && *chroot_path != '/') > + fatal("%s: invalid chroot path", chroot_path); > + > + /* Fourth, parse environment for config options. */ This reminds me of Michael Zadornov and his numbered pigs. > + parse_env(); > + > + /* We don't need environment variables any longer. */ Suggested replacement: "We don't need environment variables any longer." > + if (clearenv() != 0) > + fatal("clearenv: %m"); > + > + /* Finally, execute choosen task. */ s/choosen/chosen/; even further: is it actually chosen in this function? It's actually passed in an argument by pointer. > + switch (task->type) { > + case TASK_GETCONF: > + rc = do_getconf(); > + break; > + case TASK_KILLUID: > + rc = do_killuid(); > + break; > + case TASK_GETUGID1: > + rc = do_getugid1(); > + break; > + case TASK_CHROOTUID1: > + rc = !killprevious() > + ? do_chrootuid1() > + : EXIT_FAILURE; > + break; > + case TASK_GETUGID2: > + rc = do_getugid2(); > + break; > + case TASK_CHROOTUID2: > + rc = !killprevious() > + ? do_chrootuid2() > + : EXIT_FAILURE; > + break; > + default: > + fatal("unknown task %d", task->type); > + } > + > + /* Write of all user-space buffered data */ Suggested replacement: "Write down all the user-space buffered data" This is the closest option to the original suggestion. > + fflush(stdout); > + fflush(stderr); > + > + exit(rc); > +} > + > +int > +process_caller_task(int conn, struct task *task) > +{ > + int rc = EXIT_FAILURE; > + pid_t pid, cpid; > + > + if ((pid = fork()) != 0) { > + if (pid < 0) { > + err("fork: %m"); > + return -1; > + } > + return 0; > + } > + > + if ((cpid = caller_task(task)) > 0) { > + while (1) { > + pid_t w; > + int wstatus; > + > + if ((w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED)) < 0) { > + err("waitpid: %m"); > + break; > + } > + > + if (WIFEXITED(wstatus)) { > + rc = WEXITSTATUS(wstatus); > + info("%s: process %d exited, status=%d", task2str(task->type), cpid, WEXITSTATUS(wstatus)); > + break; > + } > + > + if (WIFSIGNALED(wstatus)) { > + info("%s: process %d killed by signal %d", task2str(task->type), cpid, WTERMSIG(wstatus)); > + break; > + } > + } > + } > + > + if (task->env) { > + free(task->env[0]); > + free(task->env); > + } > + > + if (task->argv) { > + free(task->argv[0]); > + free(task->argv); > + } > + > + /* Notify client about result */ Suggested closest replacement: "Notify the client about the results" > + (rc == EXIT_FAILURE) > + ? send_command_response(conn, CMD_STATUS_FAILED, "command failed") > + : send_command_response(conn, CMD_STATUS_DONE, NULL); > + > + exit(rc); > +} > diff --git a/hasher-priv/cmdline.c b/hasher-priv/cmdline.c > index 1cdfe23..aba802f 100644 > --- a/hasher-priv/cmdline.c > +++ b/hasher-priv/cmdline.c > @@ -80,6 +80,8 @@ const char *chroot_path; > const char **chroot_argv; > unsigned caller_num; > > +const char **task_args; > + > static unsigned > get_caller_num(const char *str) > { > @@ -126,40 +128,57 @@ parse_cmdline(int argc, const char *argv[]) > if (ac < 1) > show_usage("insufficient arguments"); > > + task_args = NULL; > + > if (!strcmp("getconf", av[0])) > { > if (ac != 1) > show_usage("%s: invalid usage", av[0]); > + task_args = av + 1; > return TASK_GETCONF; > } else if (!strcmp("killuid", av[0])) > { > if (ac != 1) > show_usage("%s: invalid usage", av[0]); > + task_args = av + 1; > return TASK_KILLUID; > } else if (!strcmp("getugid1", av[0])) > { > if (ac != 1) > show_usage("%s: invalid usage", av[0]); > + task_args = av + 1; > return TASK_GETUGID1; > } else if (!strcmp("chrootuid1", av[0])) > { > if (ac < 3) > show_usage("%s: invalid usage", av[0]); > - chroot_path = av[1]; > - chroot_argv = av + 2; > + task_args = av + 1; > return TASK_CHROOTUID1; > } else if (!strcmp("getugid2", av[0])) > { > if (ac != 1) > show_usage("%s: invalid usage", av[0]); > + task_args = av + 1; > return TASK_GETUGID2; > } else if (!strcmp("chrootuid2", av[0])) > { > if (ac < 3) > show_usage("%s: invalid usage", av[0]); > - chroot_path = av[1]; > - chroot_argv = av + 2; > + task_args = av + 1; > return TASK_CHROOTUID2; > } else > show_usage("%s: invalid argument", av[0]); > } > + > +void > +parse_task_args(task_t task, const char *argv[]) > +{ > + switch (task) { > + case TASK_CHROOTUID1: > + case TASK_CHROOTUID2: > + chroot_path = argv[0]; > + chroot_argv = (const char **)argv + 1; > + default: > + break; > + } > +} > diff --git a/hasher-priv/communication.c b/hasher-priv/communication.c > new file mode 100644 > index 0000000..77f93e1 > --- /dev/null > +++ b/hasher-priv/communication.c > @@ -0,0 +1,392 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + Functions for communication between the hasher-priv and the hasher-privd. The proposed phrase is a bit clunky but OK. I'll still give a suggested replacement: "This file offers some helper functions for client-server communication." > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > + > +#include "priv.h" > +#include "logging.h" > +#include "xmalloc.h" > +#include "sockets.h" > +#include "communication.h" > + > +struct cmd_resp { > + cmd_status_t status; > + size_t msglen; > +}; > + > +#define taskbuflen 20 > +static char taskbuf[taskbuflen]; > + > +static const struct cm { > + const char *name; > + task_t value; > +} taskmap[] = { > + { "none", TASK_NONE }, > + { "getconf", TASK_GETCONF }, > + { "killuid", TASK_KILLUID }, > + { "getugid1", TASK_GETUGID1 }, > + { "getugid2", TASK_GETUGID2 }, > + { "chrootuid1", TASK_CHROOTUID1 }, > + { "chrootuid2", TASK_CHROOTUID2 } > +}; > + > +static const size_t taskmap_size = ARRAY_SIZE(taskmap); > + > +char * > +task2str(task_t type) > +{ > + size_t i; > + > + for (i = 0; i < taskmap_size; i++) { > + if (taskmap[i].value == type) > + return strncpy(taskbuf, taskmap[i].name, taskbuflen - 1); > + } > + return NULL; > +} > + > +task_t > +str2task(char *s) > +{ > + size_t i; > + > + for (i = 0; i < taskmap_size; i++) { > + if (!strcmp(taskmap[i].name, s)) > + return taskmap[i].value; > + } > + return TASK_NONE; > +} > + > +static int > +send_list(int conn, cmd_t cmd, const char **argv) > +{ > + int i = 0; > + struct cmd hdr = { .type = cmd }; > + > + while (argv && argv[i]) > + hdr.datalen += strlen(argv[i++]) + 1; > + > + if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0) > + return -1; > + > + i = 0; > + while (argv && argv[i]) { > + if (xsendmsg(conn, (char *) argv[i], strlen(argv[i]) + 1) < 0) > + return -1; > + i++; > + } > + > + return 0; > +} > + > +int > +recv_list(int conn, char ***argv, size_t datalen) > +{ > + char *args = calloc(1UL, datalen); > + if (!args) { > + err("calloc: %m"); > + return -1; > + } > + > + if (datalen > 0 && xrecvmsg(conn, args, datalen) < 0) { > + free(args); > + return -1; > + } > + > + if (!argv) { > + free(args); > + return 0; > + } > + > + size_t i = 0, n = 0; > + > + while (args && n < datalen) { > + i++; > + n += strnlen(args + n, datalen - n) + 1; > + } > + > + char **av = malloc(sizeof(char *) * (i + 1)); > + if (!av) { > + err("malloc: %m"); > + return -1; > + } > + av[i] = NULL; > + > + i = n = 0; > + > + while (args && n < datalen) { > + av[i++] = args + n; > + n += strnlen(args + n, datalen - n) + 1; > + } > + > + *argv = av; > + > + return 0; > +} > + > +long int > +send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...) > +{ > + va_list ap; > + > + struct cmd_resp rs = { 0 }; > + struct iovec iov = { 0 }; > + struct msghdr msg = { 0 }; > + > + rs.status = retcode; > + rs.msglen = 0; > + > + if (fmt && *fmt) { > + int len; > + > + va_start(ap, fmt); > + len = vsnprintf(NULL, 0, fmt, ap); > + va_end(ap); > + > + if (len < 0) { > + err("unable to calculate message size"); > + return -1; > + } > + > + rs.msglen = (size_t) len + 1; > + } > + > + iov.iov_base = &rs; > + iov.iov_len = sizeof(rs); > + > + msg.msg_iov = &iov; > + msg.msg_iovlen = 1; > + > + errno = 0; > + if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0) { > + /* The client left without waiting for an answer */ > + if (errno == EPIPE) > + return 0; > + > + err("sendmsg: %m"); > + return -1; > + } > + > + if (!rs.msglen) > + return 0; > + > + msg.msg_iov[0].iov_base = malloc(rs.msglen); > + > + if (!msg.msg_iov[0].iov_base) { > + err("malloc: %m"); > + return -1; > + } > + > + msg.msg_iov[0].iov_len = rs.msglen; > + > + va_start(ap, fmt); > + vsnprintf(msg.msg_iov[0].iov_base, msg.msg_iov[0].iov_len, fmt, ap); > + va_end(ap); > + > + long int rc = 0; > + errno = 0; > + if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0 && errno != EPIPE) { > + err("sendmsg: %m"); > + rc = -1; > + } > + > + free(msg.msg_iov[0].iov_base); > + > + return rc; > +} > + > +static int > +recv_command_response(int conn, cmd_status_t *retcode, char **m) > +{ > + struct cmd_resp rs = { 0 }; > + > + if (xrecvmsg(conn, &rs, sizeof(rs)) < 0) > + return -1; > + > + if (retcode) > + *retcode = rs.status; > + > + if (!m || !rs.msglen) > + return 0; > + > + char *x = xcalloc(1UL, rs.msglen); > + if (xrecvmsg(conn, x, rs.msglen) < 0) > + return -1; > + > + *m = x; > + return 0; > +} > + > +int > +server_command(int conn, cmd_t cmd, const char **args) > +{ > + cmd_status_t status; > + char *msg = NULL; > + > + if (send_list(conn, cmd, args) < 0) > + return -1; > + > + if (recv_command_response(conn, &status, &msg) < 0) { > + free(msg); > + return -1; > + } > + > + if (msg && *msg) { > + err("%s", msg); > + free(msg); > + } > + > + return status == CMD_STATUS_FAILED ? -1 : 0; > +} > + > +int > +server_open_session(const char *dir_name, const char *file_name, unsigned num) > +{ > + int conn = srv_connect(dir_name, file_name); > + > + if (conn < 0) > + return -1; > + > + struct cmd hdr = { > + .type = CMD_OPEN_SESSION, > + .datalen = sizeof(num), > + }; > + > + if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0) > + return -1; > + > + if (xsendmsg(conn, &num, sizeof(num)) < 0) > + return -1; > + > + cmd_status_t status; > + char *msg = NULL; > + > + if (recv_command_response(conn, &status, &msg) < 0) { > + free(msg); > + return -1; > + } > + > + close(conn); > + > + if (msg && *msg) { > + err("%s", msg); > + free(msg); > + } > + > + return status == CMD_STATUS_FAILED ? -1 : 0; > +} > + > +int > +server_close_session(const char *dir_name, const char *file_name, unsigned num) > +{ > + int conn = srv_connect(dir_name, file_name); > + > + if (conn < 0) > + return -1; > + > + struct cmd hdr = { > + .type = CMD_CLOSE_SESSION, > + .datalen = sizeof(num), > + }; > + > + if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0) > + return -1; > + > + if (xsendmsg(conn, &num, sizeof(num)) < 0) > + return -1; > + > + cmd_status_t status; > + char *msg = NULL; > + > + if (recv_command_response(conn, &status, &msg) < 0) { > + free(msg); > + return -1; > + } > + > + close(conn); > + > + if (msg && *msg) { > + err("%s", msg); > + free(msg); > + } > + > + return status == CMD_STATUS_FAILED ? -1 : 0; > +} > + > +int > +server_task(int conn, task_t type) > +{ > + struct cmd hdr = { > + .type = CMD_TASK_BEGIN, > + .datalen = sizeof(type), > + }; > + > + if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0) > + return -1; > + > + if (xsendmsg(conn, &type, hdr.datalen) < 0) > + return -1; > + > + cmd_status_t status; > + char *msg = NULL; > + > + if (recv_command_response(conn, &status, &msg) < 0) { > + free(msg); > + return -1; > + } > + > + if (msg && *msg) { > + err("%s", msg); > + free(msg); > + } > + > + return status == CMD_STATUS_FAILED ? -1 : 0; > +} > + > +int > +server_task_fds(int conn) > +{ > + cmd_status_t status; > + char *msg = NULL; > + > + int myfds[3] = { > + STDIN_FILENO, > + STDOUT_FILENO, > + STDERR_FILENO, > + }; > + > + struct cmd hdr = { > + .type = CMD_TASK_FDS, > + .datalen = sizeof(myfds), > + }; > + > + if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0) > + return -1; > + > + if (fds_send(conn, myfds, 3) < 0) > + return -1; > + > + if (recv_command_response(conn, &status, &msg) < 0) { > + free(msg); > + return -1; > + } > + > + if (msg && *msg) { > + err("%s", msg); > + free(msg); > + } > + > + return status == CMD_STATUS_FAILED ? -1 : 0; > +} > diff --git a/hasher-priv/communication.h b/hasher-priv/communication.h > new file mode 100644 > index 0000000..e6f1530 > --- /dev/null > +++ b/hasher-priv/communication.h > @@ -0,0 +1,77 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + Functions for communication between the hasher-priv and the hasher-privd. Same as communication.c. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#ifndef _COMMUNICATION_H_ > +#define _COMMUNICATION_H_ > + > +#include > + > +typedef enum { > + CMD_NONE = 0, > + > + /* Master commands */ > + CMD_OPEN_SESSION, > + CMD_CLOSE_SESSION, > + > + /* Session commands */ > + CMD_TASK_BEGIN, > + CMD_TASK_FDS, > + CMD_TASK_ARGUMENTS, > + CMD_TASK_ENVIRON, > + CMD_TASK_RUN, > + > +} cmd_t; > + > +typedef enum { > + CMD_STATUS_DONE = 0, > + CMD_STATUS_FAILED, > +} cmd_status_t; > + > +struct cmd { > + cmd_t type; > + size_t datalen; > +}; > + > +typedef enum { > + TASK_NONE = 0, > + TASK_GETCONF, > + TASK_KILLUID, > + TASK_GETUGID1, > + TASK_CHROOTUID1, > + TASK_GETUGID2, > + TASK_CHROOTUID2 > +} task_t; > + > +struct task { > + int type; > + unsigned num; > + int stdin; > + int stdout; > + int stderr; > + char **argv; > + char **env; > +}; > + > +char *task2str(task_t type); > +task_t str2task(char *s) __attribute__((nonnull(1))); > + > +int recv_list(int conn, char ***argv, size_t datalen); > + > +long int send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...) __attribute__((format(printf, 3, 4))); > + > +int server_command(int conn, cmd_t cmd, const char **args); > +int server_open_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2))); > +int server_close_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2))); > + > +int server_task(int conn, task_t task); > +int server_task_fds(int conn); > + > +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) > + > +#endif /* _COMMUNICATION_H_ */ > diff --git a/hasher-priv/config.c b/hasher-priv/config.c > index e3fedcd..6b6bdb1 100644 > --- a/hasher-priv/config.c > +++ b/hasher-priv/config.c > @@ -1,6 +1,7 @@ > > /* > Copyright (C) 2003-2019 Dmitry V. Levin > + Copyright (C) 2019 Alexey Gladkov > > Configuration support module for the hasher-priv program. Priv or privd? > > @@ -19,13 +20,17 @@ > #include > #include > #include > +#include > > #include "priv.h" > #include "xmalloc.h" > +#include "logging.h" > > const char *const *chroot_prefix_list; > const char *chroot_prefix_path; > const char *change_user1, *change_user2; > +char *server_control_group = NULL; > +char *server_pidfile = NULL; > const char *term; > const char *x11_display, *x11_key; > str_list_t allowed_devices; > @@ -33,6 +38,8 @@ str_list_t allowed_mountpoints; > str_list_t requested_mountpoints; > uid_t change_uid1, change_uid2; > gid_t change_gid1, change_gid2; > +gid_t server_gid; > +unsigned long server_session_timeout = 0; > mode_t change_umask = 022; > int change_nice = 8; > int makedev_console; > @@ -42,6 +49,7 @@ int share_caller_network = 0; > int share_ipc = -1; > int share_network = -1; > int share_uts = -1; > +int server_log_priority = -1; > change_rlimit_t change_rlimit[] = { > > /* Per-process CPU limit, in seconds. */ > @@ -209,7 +217,7 @@ parse_rlim(const char *name, const char *value, const char *optname, > } > > static unsigned long > -str2wlim(const char *name, const char *value, const char *filename) > +str2ul(const char *name, const char *value, const char *filename) > { > char *p = 0; > unsigned long long n; > @@ -229,7 +237,7 @@ static void > modify_wlim(unsigned long *pval, const char *value, > const char *optname, const char *filename, int is_system) > { > - unsigned long val = str2wlim(optname, value, filename); > + unsigned long val = str2ul(optname, value, filename); > > if (is_system || *pval == 0 || (val > 0 && val < *pval)) > *pval = val; > @@ -633,3 +641,134 @@ parse_env(void) > if ((e = getenv("requested_mountpoints"))) > parse_str_list(e, &requested_mountpoints); > } > + > +static void > +check_server_control_group(void) > +{ > + struct group *gr; > + > + if (!server_control_group || !*server_control_group) > + error(EXIT_FAILURE, 0, "config: undefined: control_group"); > + > + gr = getgrnam(server_control_group); > + > + if (!gr || !gr->gr_name) > + error(EXIT_FAILURE, 0, "config: control_group: %s lookup failure", server_control_group); > + > + server_gid = gr->gr_gid; > +} > + > +static void > +set_server_config(const char *name, const char *value, const char *filename) > +{ > + if (!strcasecmp("priority", name)) { > + server_log_priority = logging_level(value); > + } else if (!strcasecmp("session_timeout", name)) { > + server_session_timeout = str2ul(name, value, filename); > + } else if (!strcasecmp("pidfile", name)) { > + free(server_pidfile); > + server_pidfile = xstrdup(value); > + } else if (!strcasecmp("control_group", name)) { > + free(server_control_group); > + server_control_group = xstrdup(value); > + } else { > + bad_option_name(name, filename); > + } > +} > + > +static void > +read_server_config(int fd, const char *name) > +{ > + FILE *fp = fdopen(fd, "r"); > + char buf[BUFSIZ]; > + unsigned line; > + > + if (!fp) > + error(EXIT_FAILURE, errno, "fdopen: %s", name); > + > + for (line = 1; fgets(buf, BUFSIZ, fp); ++line) { > + const char *start, *left; > + char *eq, *right, *end; > + > + for (start = buf; *start && isspace(*start); ++start) > + ; > + > + if (!*start || '#' == *start) > + continue; > + > + if (!(eq = strchr(start, '='))) > + error(EXIT_FAILURE, 0, "%s: syntax error at line %u", > + name, line); > + > + left = start; > + right = eq + 1; > + > + for (; eq > left; --eq) > + if (!isspace(eq[-1])) > + break; > + > + if (left == eq) > + error(EXIT_FAILURE, 0, "%s: syntax error at line %u", > + name, line); > + > + *eq = '\0'; > + end = right + strlen(right); > + > + for (; right < end; ++right) > + if (!isspace(*right)) > + break; > + > + for (; end > right; --end) > + if (!isspace(end[-1])) > + break; > + > + *end = '\0'; > + set_server_config(left, right, name); > + } > + > + if (ferror(fp)) > + error(EXIT_FAILURE, errno, "fgets: %s", name); > + > + if (fclose(fp)) > + error(EXIT_FAILURE, errno, "fclose: %s", name); > +} > + > +static void > +load_server_config(const char *name) > +{ > + struct stat st; > + int fd = open(name, O_RDONLY | O_NOFOLLOW | O_NOCTTY); > + > + if (fd < 0) > + error(EXIT_FAILURE, errno, "open: %s", name); > + > + if (fstat(fd, &st) < 0) > + error(EXIT_FAILURE, errno, "fstat: %s", name); > + > + stat_root_ok_validator(&st, name); > + > + if (!S_ISREG(st.st_mode)) > + error(EXIT_FAILURE, 0, "%s: not a regular file", name); > + > + if (st.st_size > MAX_CONFIG_SIZE) > + error(EXIT_FAILURE, 0, "%s: file too large: %lu", > + name, (unsigned long) st.st_size); > + > + read_server_config(fd, name); > +} > + > +void > +configure_server(void) > +{ > + safe_chdir("/", stat_root_ok_validator); > + safe_chdir("etc/hasher-priv", stat_root_ok_validator); > + load_server_config("server"); > + check_server_control_group(); > +} > + > +void > +free_server_configuration(void) > +{ > + free(server_pidfile); > + free(server_control_group); > +} > diff --git a/hasher-priv/epoll.c b/hasher-priv/epoll.c > new file mode 100644 > index 0000000..6eecd71 > --- /dev/null > +++ b/hasher-priv/epoll.c > @@ -0,0 +1,39 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + The helpers for epoll API for the hasher-privd program. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#include > + > +#include > +#include > + > +#include "epoll.h" > +#include "logging.h" > + > +int epollin_add(int fd_ep, int fd) > +{ > + struct epoll_event ev = { > + .events = EPOLLIN, > + .data.fd = fd, > + }; > + > + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ev) < 0) { > + err("epoll_ctl: %m"); > + return -1; > + } > + > + return 0; > +} > + > +void epollin_remove(int fd_ep, int fd) > +{ > + if (fd < 0) > + return; > + epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd, NULL); > + close(fd); > +} > diff --git a/hasher-priv/epoll.h b/hasher-priv/epoll.h > new file mode 100644 > index 0000000..dc1e567 > --- /dev/null > +++ b/hasher-priv/epoll.h > @@ -0,0 +1,18 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + The helpers for epoll API for the hasher-privd program. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#ifndef _EPOLL_H_ > +#define _EPOLL_H_ > + > +#include > + > +int epollin_add(int fd_ep, int fd); > +void epollin_remove(int fd_ep, int fd); > + > +#endif /* _EPOLL_H_ */ > diff --git a/hasher-priv/hasher-priv.c b/hasher-priv/hasher-priv.c > new file mode 100644 > index 0000000..9eb598c > --- /dev/null > +++ b/hasher-priv/hasher-priv.c > @@ -0,0 +1,78 @@ > + > +/* > + Copyright (C) 2003-2019 Dmitry V. Levin > + > + The entry function for the hasher-priv program. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +/* Code in this file may be executed with root privileges. */ > + > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "priv.h" > +#include "logging.h" > +#include "sockets.h" > +#include "communication.h" > + > +int log_fd = -1; > + > +static void > +my_error_print_progname(void) > +{ > + fprintf(stderr, "%s: ", program_invocation_short_name); > +} > + > +int > +main(int ac, const char *av[], const char *ev[]) > +{ > + int conn; > + task_t task; > + char socketname[UNIX_PATH_MAX]; > + > + error_print_progname = my_error_print_progname; > + > + /* First, check and sanitize file descriptors. */ > + sanitize_fds(); > + > + /* Second, parse command line arguments. */ > + task = parse_cmdline(ac, av); > + > + /* Connect to remote server and open session. */ > + if (server_open_session(SOCKETDIR, PROJECT, caller_num) < 0) > + return EXIT_FAILURE; > + > + /* Open user session */ > + snprintf(socketname, sizeof(socketname), "hasher-priv-%d-%u", geteuid(), caller_num); > + > + if ((conn = srv_connect(SOCKETDIR, socketname)) < 0) > + return EXIT_FAILURE; > + > + if (server_task(conn, task) < 0) > + return EXIT_FAILURE; > + > + if (server_task_fds(conn) < 0) > + return EXIT_FAILURE; > + > + if (server_command(conn, CMD_TASK_ARGUMENTS, task_args) < 0) > + return EXIT_FAILURE; > + > + if (server_command(conn, CMD_TASK_ENVIRON, ev) < 0) > + return EXIT_FAILURE; > + > + if (server_command(conn, CMD_TASK_RUN, NULL) < 0) > + return EXIT_FAILURE; > + > + /* Close session socket. */ > + close(conn); > + > + return EXIT_SUCCESS; > +} > diff --git a/hasher-priv/hasher-privd.c b/hasher-priv/hasher-privd.c > new file mode 100644 > index 0000000..5c56033 > --- /dev/null > +++ b/hasher-priv/hasher-privd.c > @@ -0,0 +1,375 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + The entry function for the hasher-privd program. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#include > + > +#include > +#include > +#include > +#include /* umask */ > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "epoll.h" > +#include "logging.h" > +#include "pidfile.h" > +#include "sockets.h" > +#include "communication.h" > +#include "priv.h" > + > +struct session { > + struct session *next; > + > + uid_t caller_uid; > + gid_t caller_gid; > + > + unsigned caller_num; > + > + pid_t server_pid; > +}; > + > +static struct session *pool = NULL; > +unsigned caller_num; > + > +static int > +start_session(int conn, unsigned num) > +{ > + uid_t uid; > + gid_t gid; > + pid_t server_pid; > + struct session **a = &pool; > + > + if (get_peercred(conn, NULL, &uid, &gid) < 0) > + return -1; > + > + while (a && *a) { > + if ((*a)->caller_uid == uid && (*a)->caller_num == num) { > + send_command_response(conn, CMD_STATUS_DONE, NULL); > + return 0; > + } > + a = &(*a)->next; > + } > + > + info("start session for %d:%u user", uid, num); > + > + if ((server_pid = fork_server(conn, uid, gid, num)) < 0) > + return -1; > + > + *a = calloc(1L, sizeof(struct session)); > + if (!*a) { > + err("calloc: %m"); > + return -1; > + } > + > + (*a)->caller_uid = uid; > + (*a)->caller_gid = gid; > + (*a)->caller_num = num; > + (*a)->server_pid = server_pid; > + > + return 0; > +} > + > +static int > +close_session(int conn, unsigned num) > +{ > + uid_t uid; > + gid_t gid; > + struct session *e = pool; > + > + if (get_peercred(conn, NULL, &uid, &gid) < 0) > + return -1; > + > + while (e) { > + if (e->caller_uid == uid && e->caller_num == num) { > + info("close session for %d:%u user by request", uid, num); > + if (kill(e->server_pid, SIGTERM) < 0) { > + err("kill: %m"); > + return -1; > + } > + break; > + } > + e = e->next; > + } > + > + return 0; > +} > + > +static int > +process_request(int conn) > +{ > + int rc; > + struct cmd hdr = { 0 }; > + unsigned num = 0; > + > + if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0) > + return -1; > + > + if (hdr.datalen != sizeof(num)) { > + err("bad command"); > + send_command_response(conn, CMD_STATUS_FAILED, "bad command"); > + return -1; > + } > + > + if (xrecvmsg(conn, &num, sizeof(num)) < 0) > + return -1; > + > + switch (hdr.type) { > + case CMD_OPEN_SESSION: > + rc = start_session(conn, num); > + break; > + case CMD_CLOSE_SESSION: > + rc = close_session(conn, num); > + (rc < 0) > + ? send_command_response(conn, CMD_STATUS_FAILED, "command failed") > + : send_command_response(conn, CMD_STATUS_DONE, NULL); > + break; > + default: > + err("unknown command"); > + send_command_response(conn, CMD_STATUS_FAILED, "unknown command"); > + rc = -1; > + } > + > + return rc; > +} > + > +static void > +finish_sessions(int sig) > +{ > + struct session *e = pool; > + > + while (e) { > + if (kill(e->server_pid, sig) < 0) > + err("kill: %m"); > + e = e->next; > + } > +} > + > +static void > +clean_session(pid_t pid) > +{ > + struct session *x, **a = &pool; > + > + while (a && *a) { > + if ((*a)->server_pid == pid) { > + x = *a; > + *a = (*a)->next; > + free(x); > + } > + a = &(*a)->next; > + } > +} > + > +int main(int argc, char **argv) > +{ > + int i; > + int loglevel = -1; > + > + const char *pidfile = NULL; > + int daemonize = 1; > + > + char socketpath[UNIX_PATH_MAX]; > + > + struct option long_options[] = { > + { "help", no_argument, 0, 'h' }, > + { "version", no_argument, 0, 'V' }, > + { "foreground", no_argument, 0, 'f' }, > + { "loglevel", required_argument, 0, 'l' }, > + { "pidfile", required_argument, 0, 'p' }, > + { 0, 0, 0, 0 } > + }; > + > + while ((i = getopt_long(argc, argv, "hVfl:p:", long_options, NULL)) != -1) { > + switch (i) { > + case 'p': > + pidfile = optarg; > + break; > + case 'l': > + loglevel = logging_level(optarg); > + break; > + case 'f': > + daemonize = 0; > + break; > + case 'V': > + printf("%s %s\n", program_invocation_short_name, PROJECT_VERSION); > + return EXIT_SUCCESS; > + default: > + case 'h': > + printf("Usage: %s [options]\n" > + " -p, --pidfile=FILE pid file location;\n" > + " -l, --loglevel=LVL set logging level;\n" > + " -f, --foreground stay in the foreground;\n" > + " -V, --version print program version and exit;\n" > + " -h, --help show this text and exit.\n" > + "\n", > + program_invocation_short_name); > + return EXIT_SUCCESS; > + } > + } > + > + configure_server(); > + > + if (!pidfile && server_pidfile && *server_pidfile) > + pidfile = server_pidfile; > + > + if (loglevel < 0) > + loglevel = (server_log_priority >= 0) > + ? server_log_priority > + : logging_level("info"); > + > + umask(022); > + > + if (pidfile && check_pid(pidfile)) > + error(EXIT_FAILURE, 0, "%s: already running", > + program_invocation_short_name); > + > + if (daemonize && daemon(0, 0) < 0) > + error(EXIT_FAILURE, errno, "daemon"); > + > + logging_init(loglevel, !daemonize); > + > + if (pidfile && write_pid(pidfile) == 0) > + return EXIT_FAILURE; > + > + sigset_t mask; > + > + sigfillset(&mask); > + sigprocmask(SIG_SETMASK, &mask, NULL); > + > + sigdelset(&mask, SIGABRT); > + sigdelset(&mask, SIGSEGV); > + > + int fd_ep = -1; > + > + if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0) > + fatal("epoll_create1: %m"); > + > + int fd_signal = -1; > + > + if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0) > + fatal("signalfd: %m"); > + > + mode_t m = umask(017); > + > + int fd_conn = -1; > + > + if ((fd_conn = srv_listen(SOCKETDIR, PROJECT)) < 0) > + return EXIT_FAILURE; > + > + umask(m); > + > + snprintf(socketpath, sizeof(socketpath), "%s/%s", SOCKETDIR, PROJECT); > + > + if (chown(socketpath, 0, server_gid)) > + fatal("fchown: %s: %m", socketpath); > + > + if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0) > + return EXIT_FAILURE; > + > + int ep_timeout = -1; > + int finish_server = 0; > + > + while (1) { > + struct epoll_event ev[42]; > + int fdcount; > + > + errno = 0; > + if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), ep_timeout)) < 0) { > + if (errno == EINTR) > + continue; > + err("epoll_wait: %m"); > + break; > + } > + > + for (i = 0; i < fdcount; i++) { > + if (!(ev[i].events & EPOLLIN)) { > + continue; > + > + } else if (ev[i].data.fd == fd_signal) { > + struct signalfd_siginfo fdsi; > + ssize_t size; > + > + size = read_retry(fd_signal, &fdsi, sizeof(fdsi)); > + > + if (size != sizeof(fdsi)) { > + err("unable to read signal info"); > + continue; > + } > + > + pid_t pid; > + int status; > + > + switch (fdsi.ssi_signo) { > + case SIGINT: > + case SIGTERM: > + finish_server = 1; > + break; > + case SIGCHLD: > + if ((pid = waitpid(-1, &status, 0)) < 0) > + err("waitpid: %m"); > + > + clean_session(pid); > + break; > + } > + > + } else if (ev[i].data.fd == fd_conn) { > + int conn; > + > + if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) { > + err("accept4: %m"); > + continue; > + } > + > + if (set_recv_timeout(conn, 3) < 0) { > + close(conn); > + continue; > + } > + > + process_request(conn); > + close(conn); > + } > + } > + > + if (finish_server) { > + if (!pool) > + break; > + > + if (fd_conn >= 0) { > + epollin_remove(fd_ep, fd_conn); > + fd_conn = -1; > + ep_timeout = 3000; > + } > + > + finish_sessions(SIGTERM); > + } > + } > + > + epollin_remove(fd_ep, fd_signal); > + epollin_remove(fd_ep, fd_conn); > + > + if (fd_ep >= 0) > + close(fd_ep); > + > + if (pidfile) > + remove_pid(pidfile); > + > + logging_close(); > + free_server_configuration(); > + > + return EXIT_SUCCESS; > +} > diff --git a/hasher-priv/io_log.c b/hasher-priv/io_log.c > index f5aea59..2689ff0 100644 > --- a/hasher-priv/io_log.c > +++ b/hasher-priv/io_log.c > @@ -2,7 +2,7 @@ > /* > Copyright (C) 2008-2019 Dmitry V. Levin > > - The chrootuid parent log I/O handler for the hasher-priv program. > + The chrootuid parent log I/O handler for the hasher-privd program. > > SPDX-License-Identifier: GPL-2.0-or-later > */ > diff --git a/hasher-priv/io_x11.c b/hasher-priv/io_x11.c > index 93e3add..f193c56 100644 > --- a/hasher-priv/io_x11.c > +++ b/hasher-priv/io_x11.c > @@ -2,7 +2,7 @@ > /* > Copyright (C) 2005-2019 Dmitry V. Levin > > - The chrootuid parent X11 I/O handler for the hasher-priv program. > + The chrootuid parent X11 I/O handler for the hasher-privd program. > > SPDX-License-Identifier: GPL-2.0-or-later > */ > diff --git a/hasher-priv/killuid.c b/hasher-priv/killuid.c > index f0bee84..71e52e1 100644 > --- a/hasher-priv/killuid.c > +++ b/hasher-priv/killuid.c > @@ -2,7 +2,7 @@ > /* > Copyright (C) 2003-2019 Dmitry V. Levin > > - The killuid actions for the hasher-priv program. > + The killuid actions for the hasher-privd program. > > SPDX-License-Identifier: GPL-2.0-or-later > */ > diff --git a/hasher-priv/logging.c b/hasher-priv/logging.c > new file mode 100644 > index 0000000..9adac47 > --- /dev/null > +++ b/hasher-priv/logging.c > @@ -0,0 +1,64 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + A logging functions for the hasher-privd program. "A functions"? Either an incorrect article or an unfitting plural. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#include > +#include > +#include > +#include > +#include > + > +#include "logging.h" > + > +int log_priority = -1; > + > +int logging_level(const char *name) > +{ > + if (!strcasecmp(name, "debug")) > + return LOG_DEBUG; > + > + if (!strcasecmp(name, "info")) > + return LOG_INFO; > + > + if (!strcasecmp(name, "warning")) > + return LOG_WARNING; > + > + if (!strcasecmp(name, "error")) > + return LOG_ERR; > + > + return 0; > +} > + > +void logging_init(int loglevel, int stderr) > +{ > + int options = LOG_PID; > + if (stderr) > + options |= LOG_PERROR; > + log_priority = loglevel; > + openlog(program_invocation_short_name, options, LOG_DAEMON); > +} > + > +void logging_close(void) > +{ > + closelog(); > +} > + > +void > +message(int priority, const char *fmt, ...) > +{ > + va_list ap; > + > + va_start(ap, fmt); > + if (priority <= log_priority) > + vsyslog(priority, fmt, ap); > + else if (log_priority < 0) { > + vfprintf(stderr, fmt, ap); > + fprintf(stderr, "\n"); > + } > + va_end(ap); > +} > diff --git a/hasher-priv/logging.h b/hasher-priv/logging.h > new file mode 100644 > index 0000000..9d28fc8 > --- /dev/null > +++ b/hasher-priv/logging.h > @@ -0,0 +1,55 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + A logging functions for the hasher-privd program. Same as above. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#ifndef _LOGGING_H_ > +#define _LOGGING_H_ > + > +#include > +#include > + > +void logging_init(int, int); > +void logging_close(void); > +int logging_level(const char *lvl) __attribute__((nonnull(1))); > + > +void message(int priority, const char *fmt, ...) __attribute__((format(printf, 2, 3))); > + > +#define fatal(format, arg...) \ > + do { \ > + message(LOG_CRIT, \ > + "%s(%d): %s: " format, \ > + __FILE__, __LINE__, __FUNCTION__, \ > + ##arg); \ > + exit(EXIT_FAILURE); \ > + } while (0) > + > +#define err(format, arg...) \ > + do { \ > + message(LOG_ERR, \ > + "%s(%d): %s: " format, \ > + __FILE__, __LINE__, __FUNCTION__, \ > + ##arg); \ > + } while (0) > + > +#define info(format, arg...) \ > + do { \ > + message(LOG_INFO, \ > + "%s(%d): %s: " format, \ > + __FILE__, __LINE__, __FUNCTION__, \ > + ##arg); \ > + } while (0) > + > +#define dbg(format, arg...) \ > + do { \ > + message(LOG_DEBUG, \ > + "%s(%d): %s: " format, \ > + __FILE__, __LINE__, __FUNCTION__, \ > + ##arg); \ > + } while (0) > + > +#endif /* _LOGGING_H_ */ > diff --git a/hasher-priv/main.c b/hasher-priv/main.c > deleted file mode 100644 > index 310fd5d..0000000 > --- a/hasher-priv/main.c > +++ /dev/null > @@ -1,75 +0,0 @@ > - > -/* > - Copyright (C) 2003-2019 Dmitry V. Levin > - > - The entry function for the hasher-priv program. > - > - SPDX-License-Identifier: GPL-2.0-or-later > -*/ > - > -/* Code in this file may be executed with root privileges. */ > - > -#include > -#include > -#include > -#include > - > -#include "priv.h" > - > -static void > -my_error_print_progname(void) > -{ > - fprintf(stderr, "%s: ", program_invocation_short_name); > -} > - > -int > -main(int ac, const char *av[]) > -{ > - task_t task; > - > - error_print_progname = my_error_print_progname; > - > - /* First, check and sanitize file descriptors. */ > - sanitize_fds(); > - > - /* Second, parse command line arguments. */ > - task = parse_cmdline(ac, av); > - > - if (chroot_path && *chroot_path != '/') > - error(EXIT_FAILURE, 0, "%s: invalid chroot path", > - chroot_path); > - > - /* Third, initialize data related to caller. */ > - init_caller_data(); > - > - /* 4th, parse environment for config options. */ > - parse_env(); > - > - /* We don't need environment variables any longer. */ > - if (clearenv() != 0) > - error(EXIT_FAILURE, errno, "clearenv"); > - > - /* Load config according to caller information. */ > - configure(); > - > - /* Finally, execute choosen task. */ > - switch (task) > - { > - case TASK_GETCONF: > - return do_getconf(); > - case TASK_KILLUID: > - return do_killuid(); > - case TASK_GETUGID1: > - return do_getugid1(); > - case TASK_CHROOTUID1: > - return do_chrootuid1(); > - case TASK_GETUGID2: > - return do_getugid2(); > - case TASK_CHROOTUID2: > - return do_chrootuid2(); > - default: > - error(EXIT_FAILURE, 0, "unknown task %d", task); > - } > - > - return EXIT_FAILURE; > -} > diff --git a/hasher-priv/pass.c b/hasher-priv/pass.c > index b52c87e..03a07ec 100644 > --- a/hasher-priv/pass.c > +++ b/hasher-priv/pass.c > @@ -17,6 +17,8 @@ > #include > #include > > +#include "logging.h" > +#include "sockets.h" > #include "priv.h" > > union cmsg_data_u > @@ -55,8 +57,7 @@ fd_send(int ctl, int pass, const char *data, size_t data_len) > > ssize_t rc; > > - if ((rc = TEMP_FAILURE_RETRY(sendmsg(ctl, &msg, 0))) != > - (ssize_t) data_len) > + if ((rc = sendmsg_retry(ctl, &msg, 0)) != (ssize_t) data_len) > { > if (rc < 0) > { > @@ -96,8 +97,7 @@ fd_recv(int ctl, char *data, size_t data_len) > > ssize_t rc; > > - if ((rc = TEMP_FAILURE_RETRY(recvmsg(ctl, &msg, 0))) != > - (ssize_t) data_len) > + if ((rc = recvmsg_retry(ctl, &msg, 0)) != (ssize_t) data_len) > { > if (rc < 0) > { > @@ -132,3 +132,112 @@ fd_recv(int ctl, char *data, size_t data_len) > cmsg_data_p.c = CMSG_DATA(cmsg); > return *cmsg_data_p.i; > } > + > +int > +fds_send(int conn, int *fds, size_t fds_len) > +{ > + ssize_t rc; > + size_t len = sizeof(int) * fds_len; > + > + union { > + char buf[CMSG_SPACE(len)]; > + struct cmsghdr align; > + } u; > + > + /* > + * We must send at least 1 byte of real data in > + * order to send ancillary data > + */ > + char dummy; > + > + struct iovec iov = { 0 }; > + > + iov.iov_base = &dummy; > + iov.iov_len = sizeof(dummy); > + > + struct msghdr msg = { 0 }; > + > + msg.msg_iov = &iov; > + msg.msg_iovlen = 1; > + msg.msg_control = u.buf; > + msg.msg_controllen = sizeof(u.buf); > + > + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); > + > + cmsg->cmsg_level = SOL_SOCKET; > + cmsg->cmsg_type = SCM_RIGHTS; > + cmsg->cmsg_len = CMSG_LEN(len); > + > + memcpy(CMSG_DATA(cmsg), fds, len); > + > + if ((rc = sendmsg_retry(conn, &msg, 0)) != sizeof(dummy)) { > + if (rc < 0) > + err("sendmsg: %m"); > + else if (rc) > + err("sendmsg: expected size %lu, got %lu", sizeof(dummy), rc); > + else > + err("sendmsg: unexpected EOF"); > + return -1; > + } > + > + return 0; > +} > + > +int > +fds_recv(int conn, int *fds, size_t n_fds) > +{ > + char dummy; > + size_t datalen = sizeof(int) * n_fds; > + > + union { > + struct cmsghdr cmh; > + char control[CMSG_SPACE(datalen)]; > + } u; > + > + u.cmh.cmsg_len = CMSG_LEN(datalen); > + u.cmh.cmsg_level = SOL_SOCKET; > + u.cmh.cmsg_type = SCM_RIGHTS; > + > + struct iovec iov = { 0 }; > + > + iov.iov_base = &dummy; > + iov.iov_len = sizeof(dummy); > + > + struct msghdr msg = { 0 }; > + > + msg.msg_iov = &iov; > + msg.msg_iovlen = 1; > + msg.msg_control = u.control; > + msg.msg_controllen = sizeof(u.control); > + > + ssize_t n; > + if ((n = recvmsg_retry(conn, &msg, 0)) != sizeof(dummy)) { > + if (n < 0) > + err("recvmsg: %m"); > + else if (n) > + err("recvmsg: expected size %lu, got %lu", sizeof(dummy), n); > + else > + err("recvmsg: unexpected EOF"); > + return -1; > + } > + > + if (!msg.msg_controllen) { > + err("ancillary data not specified"); > + return -1; > + } > + > + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { > + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) > + continue; > + > + if (cmsg->cmsg_len - CMSG_LEN(0) != datalen) { > + err("expected fd payload"); > + return -1; > + } > + > + memcpy(fds, CMSG_DATA(cmsg), datalen); > + break; > + } > + > + return 0; > +} > diff --git a/hasher-priv/pidfile.c b/hasher-priv/pidfile.c > new file mode 100644 > index 0000000..9e23e74 > --- /dev/null > +++ b/hasher-priv/pidfile.c > @@ -0,0 +1,128 @@ > + > +/* > + pidfile.c - interact with pidfiles > + Copyright (c) 1995 Martin Schulze > + > + This file is part of the sysklogd package, a kernel and system log daemon. Not anymore, that file is now (at patch acceptance) part of hasher-priv. An equivalent file is part of the sysklogd package, sure, but that's rather irrelevant. More like this file was pulled from and possibly originally written for the sysklogd package; I agree the message should definitely state that to preserve attribution. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +/* > + * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze > + * First version (v0.2) released > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "logging.h" > +#include "pidfile.h" > + > +/* read_pid > + * > + * Reads the specified pidfile and returns the read pid. > + * 0 is returned if either there's no pidfile, it's empty > + * or no pid can be read. > + */ > +int read_pid(const char *pidfile) > +{ > + FILE *f; > + int pid; > + > + if (!(f = fopen(pidfile, "r"))) > + return 0; > + if (fscanf(f, "%d", &pid) != 1) > + pid = 0; > + fclose(f); > + return pid; > +} > + > +/* check_pid > + * > + * Reads the pid using read_pid and looks up the pid in the process > + * table (using /proc) to determine if the process already exists. If > + * so 1 is returned, otherwise 0. > + */ > +int check_pid(const char *pidfile) > +{ > + int pid = read_pid(pidfile); > + > + /* Amazing ! _I_ am already holding the pid file... */ > + if ((!pid) || (pid == getpid())) > + return 0; > + > + /* > + * The 'standard' method of doing this is to try and do a 'fake' kill > + * of the process. If an ESRCH error is returned the process cannot > + * be found -- GW > + */ > + /* But... errno is usually changed only on error.. */ > + if (kill(pid, 0) && errno == ESRCH) > + return (0); > + > + return pid; > +} > + > +/* write_pid > + * > + * Writes the pid to the specified file. If that fails 0 is > + * returned, otherwise the pid. > + */ > +int write_pid(const char *pidfile) > +{ > + FILE *f; > + int fd; > + int pid; > + > + if (((fd = open(pidfile, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)) == -1) || > + ((f = fdopen(fd, "r+")) == NULL)) { > + err("Can't open or create %s", pidfile); > + return 0; > + } > + > + if (flock(fd, LOCK_EX | LOCK_NB) == -1) { > + if (fscanf(f, "%d", &pid) != 1) > + pid = 0; > + fclose(f); > + err("Can't lock, lock is held by pid %d", pid); > + return 0; > + } > + > + pid = getpid(); > + if (!fprintf(f, "%d\n", pid)) { > + err("Can't write pid: %m"); > + close(fd); > + return 0; > + } > + fflush(f); > + > + if (flock(fd, LOCK_UN) == -1) { > + err("flock: %s: %m", pidfile); > + close(fd); > + return 0; > + } > + close(fd); > + fclose(f); > + > + return pid; > +} > + > +/* remove_pid > + * > + * Remove the the specified file. The result from unlink(2) > + * is returned > + */ > +int remove_pid(const char *pidfile) > +{ > + if (unlink(pidfile) == -1) { > + err("unlink: %s: %m", pidfile); > + return -1; > + } > + return 0; > +} > diff --git a/hasher-priv/pidfile.h b/hasher-priv/pidfile.h > new file mode 100644 > index 0000000..e152538 > --- /dev/null > +++ b/hasher-priv/pidfile.h > @@ -0,0 +1,44 @@ > + > +/* > + pidfile.h - interact with pidfiles > + Copyright (c) 1995 Martin Schulze > + > + This file is part of the sysklogd package, a kernel and system log daemon. Same as above. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#ifndef _PIDFILE_H_ > +#define _PIDFILE_H_ > + > +/* read_pid > + * > + * Reads the specified pidfile and returns the read pid. > + * 0 is returned if either there's no pidfile, it's empty > + * or no pid can be read. > + */ > +int read_pid(const char *pidfile) __attribute__((nonnull(1))); > + > +/* check_pid > + * > + * Reads the pid using read_pid and looks up the pid in the process > + * table (using /proc) to determine if the process already exists. If > + * so 1 is returned, otherwise 0. > + */ > +int check_pid(const char *pidfile) __attribute__((nonnull(1))); > + > +/* write_pid > + * > + * Writes the pid to the specified file. If that fails 0 is > + * returned, otherwise the pid. > + */ > +int write_pid(const char *pidfile) __attribute__((nonnull(1))); > + > +/* remove_pid > + * > + * Remove the the specified file. The result from unlink(2) > + * is returned > + */ > +int remove_pid(const char *pidfile) __attribute__((nonnull(1))); > + > +#endif /* _PIDFILE_H_ */ > diff --git a/hasher-priv/priv.h b/hasher-priv/priv.h > index 87e72fb..f0eb9f9 100644 > --- a/hasher-priv/priv.h > +++ b/hasher-priv/priv.h > @@ -18,16 +18,7 @@ > #define MIN_CHANGE_GID 34 > #define MAX_CONFIG_SIZE 16384 > > -typedef enum > -{ > - TASK_NONE = 0, > - TASK_GETCONF, > - TASK_KILLUID, > - TASK_GETUGID1, > - TASK_CHROOTUID1, > - TASK_GETUGID2, > - TASK_CHROOTUID2, > -} task_t; > +#include "communication.h" > > typedef struct > { > @@ -68,9 +59,13 @@ void restore_tty(void); > int tty_copy_winsize(int master_fd, int slave_fd); > int open_pty(int *slave_fd, int chrooted, int verbose_error); > task_t parse_cmdline(int ac, const char *av[]); > -void init_caller_data(void); > +void parse_task_args(task_t task, const char *argv[]); > +int init_caller_data(uid_t uid, gid_t gid); > +void free_caller_data(void); > void parse_env(void); > void configure(void); > +void configure_server(void); > +void free_server_configuration(void); > void ch_uid(uid_t uid, uid_t *save); > void ch_gid(gid_t gid, gid_t *save); > void chdiruid(const char *path, VALIDATE_FPTR validator); > @@ -85,6 +80,9 @@ void stat_root_ok_validator(struct stat *st, const char *name); > void stat_any_ok_validator(struct stat *st, const char *name); > void fd_send(int ctl, int pass, const char *data, size_t len); > int fd_recv(int ctl, char *data, size_t data_len); > +int fds_send(int conn, int *fds, size_t fds_len) __attribute__((nonnull(2))); > +int fds_recv(int conn, int *fds, size_t datalen) __attribute__((nonnull(2))); > + > int unix_accept(int fd); > int log_listen(void); > void x11_drop_display(void); > @@ -120,9 +118,14 @@ int do_chrootuid1(void); > int do_getugid2(void); > int do_chrootuid2(void); > > +int process_caller_task(int, struct task *); > +pid_t fork_server(int, uid_t, gid_t, unsigned); > + > extern const char *chroot_path; > extern const char **chroot_argv; > > +extern const char **task_args; > + > extern str_list_t allowed_devices; > extern str_list_t allowed_mountpoints; > extern str_list_t requested_mountpoints; > @@ -143,7 +146,7 @@ extern int log_fd; > > extern const char *const *chroot_prefix_list; > extern const char *chroot_prefix_path; > -extern const char *caller_user, *caller_home; > +extern char *caller_user, *caller_home; > extern uid_t caller_uid; > extern gid_t caller_gid; > extern unsigned caller_num; > @@ -156,4 +159,10 @@ extern int change_nice; > extern change_rlimit_t change_rlimit[]; > extern work_limit_t wlimit; > > +extern int server_log_priority; > +extern unsigned long server_session_timeout; > +extern char *server_control_group; > +extern char *server_pidfile; > +extern gid_t server_gid; > + > #endif /* PKG_BUILD_PRIV_H */ > diff --git a/hasher-priv/server.conf b/hasher-priv/server.conf > new file mode 100644 > index 0000000..53ea5c3 > --- /dev/null > +++ b/hasher-priv/server.conf > @@ -0,0 +1,13 @@ > +# Server configuration > + > +# Set the default logging priority. (can override with command line arguments) > +priority=info > + > +# Write a pid file. (can override with command line arguments) > +pidfile=/var/run/hasher-privd.pid > + > +# Stop user's session server after {session_timeout} seconds of inactivity. > +session_timeout=3600 > + > +# Allow users of this group to interact with hasher-privd via the control socket. > +control_group=hashman > diff --git a/hasher-priv/sockets.c b/hasher-priv/sockets.c > new file mode 100644 > index 0000000..42618a7 > --- /dev/null > +++ b/hasher-priv/sockets.c > @@ -0,0 +1,183 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + A сollection of helpers to simplify work with sockets. This one's nice; however, "the use of" instead of "work with" looks even nicer to me. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#include > +#include > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > + > +#include "logging.h" > +#include "sockets.h" > + > +/* This function may be executed with caller or child privileges. */ > + > +int srv_listen(const char *dir_name, const char *file_name) > +{ > + struct sockaddr_un sun = { .sun_family = AF_UNIX }; > + > + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dir_name, file_name); > + > + if (unlink(sun.sun_path) && errno != ENOENT) { > + err("unlink: %s: %m", sun.sun_path); > + return -1; > + } > + > + if (mkdir(dir_name, 0700) && errno != EEXIST) { > + err("mkdir: %s: %m", dir_name); > + return -1; > + } > + > + int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > + > + if (fd < 0) { > + err("socket AF_UNIX: %m"); > + return -1; > + } > + > + if (bind(fd, (struct sockaddr *)&sun, (socklen_t) sizeof(sun))) { > + err("bind: %s: %m", sun.sun_path); > + (void)close(fd); > + return -1; > + } > + > + if (listen(fd, 16) < 0) { > + err("listen: %s: %m", sun.sun_path); > + (void)close(fd); > + return -1; > + } > + > + return fd; > +} > + > +int srv_connect(const char *dir_name, const char *file_name) > +{ > + int conn = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > + > + if (conn < 0) { > + err("socket AF_UNIX: %m"); > + return -1; > + } > + > + struct sockaddr_un sun = { .sun_family = AF_UNIX }; > + > + snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/%s", dir_name, file_name); > + > + if (connect(conn, (const struct sockaddr *)&sun, sizeof(sun)) < 0) { > + err("connect: %s: %m", sun.sun_path); > + return -1; > + } > + > + return conn; > +} > + > +int get_peercred(int fd, pid_t *pid, uid_t *uid, gid_t *gid) > +{ > + struct ucred uc; > + socklen_t len = sizeof(struct ucred); > + > + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) < 0) { > + err("getsockopt(SO_PEERCRED): %m"); > + return -1; > + } > + > + if (pid) > + *pid = uc.pid; > + > + if (uid) > + *uid = uc.uid; > + > + if (gid) > + *gid = uc.gid; > + > + return 0; > +} > + > +int > +set_recv_timeout(int fd, int secs) > +{ > + struct timeval tv = { .tv_sec = secs }; > + > + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { > + err("setsockopt(SO_RCVTIMEO): %m"); > + return -1; > + } > + return 0; > +} > + > +ssize_t > +sendmsg_retry(int sockfd, const struct msghdr *msg, int flags) > +{ > + return TEMP_FAILURE_RETRY(sendmsg(sockfd, msg, flags)); > +} > + > +ssize_t > +recvmsg_retry(int sockfd, struct msghdr *msg, int flags) > +{ > + return TEMP_FAILURE_RETRY(recvmsg(sockfd, msg, flags)); > +} > + > +int > +xsendmsg(int conn, void *data, size_t len) > +{ > + struct iovec iov = { 0 }; > + struct msghdr msg = { 0 }; > + > + iov.iov_base = data; > + iov.iov_len = len; > + > + msg.msg_iov = &iov; > + msg.msg_iovlen = 1; > + > + ssize_t n = sendmsg_retry(conn, &msg, 0); > + > + if (n != (ssize_t) len) { > + if (n < 0) > + err("recvmsg: %m"); > + else if (n) > + err("recvmsg: expected size %lu, got %lu", len, n); > + else > + err("recvmsg: unexpected EOF"); > + return -1; > + } > + > + return 0; > +} > + > +int > +xrecvmsg(int conn, void *data, size_t len) > +{ > + struct iovec iov = { 0 }; > + struct msghdr msg = { 0 }; > + > + iov.iov_base = data; > + iov.iov_len = len; > + > + msg.msg_iov = &iov; > + msg.msg_iovlen = 1; > + > + ssize_t n = recvmsg_retry(conn, &msg, MSG_WAITALL); > + > + if (n != (ssize_t) len) { > + if (n < 0) > + err("recvmsg: %m"); > + else if (n) > + err("recvmsg: expected size %lu, got %lu", len, n); > + else > + err("recvmsg: unexpected EOF"); > + return -1; > + } > + > + return 0; > +} > diff --git a/hasher-priv/sockets.h b/hasher-priv/sockets.h > new file mode 100644 > index 0000000..5acdb49 > --- /dev/null > +++ b/hasher-priv/sockets.h > @@ -0,0 +1,32 @@ > + > +/* > + Copyright (C) 2019 Alexey Gladkov > + > + A collection of helpers to simplify work with sockets. > + > + SPDX-License-Identifier: GPL-2.0-or-later > +*/ > + > +#ifndef _SOCKETS_H_ > +#define _SOCKETS_H_ > + > +#include > +#include > + > +ssize_t sendmsg_retry(int sockfd, const struct msghdr *msg, int flags) __attribute__((nonnull(2))); > +ssize_t recvmsg_retry(int sockfd, struct msghdr *msg, int flags) __attribute__((nonnull(2))); > + > +#include > + > +int srv_listen(const char *, const char *) __attribute__((nonnull(1, 2))); > +int srv_connect(const char *, const char *) __attribute__((nonnull(1, 2))); > + > +int get_peercred(int, pid_t *, uid_t *, gid_t *); > +int set_recv_timeout(int fd, int secs); > + > +#include > + > +int xsendmsg(int conn, void *data, size_t len) __attribute__((nonnull(2))); > +int xrecvmsg(int conn, void *data, size_t len) __attribute__((nonnull(2))); > + > +#endif /* _SOCKETS_H_ */ > diff --git a/hasher-priv/x11.c b/hasher-priv/x11.c > index 854c453..0a6b4fb 100644 > --- a/hasher-priv/x11.c > +++ b/hasher-priv/x11.c > @@ -22,6 +22,7 @@ > > #include "priv.h" > #include "xmalloc.h" > +#include "sockets.h" > > #define X11_UNIX_DIR "/tmp/.X11-unix" > > -- > 2.24.0 >