* [devel] [PATCH hasher-priv v2 0/6] hasher-privd
@ 2020-10-22 11:43 Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 1/6] Turn hasher-priv into a daemon Arseny Maslennikov
` (5 more replies)
0 siblings, 6 replies; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:43 UTC (permalink / raw)
To: devel, ldv; +Cc: Arseny Maslennikov
This series is essentially [1], but squashed.
Additional fixes not included in [1]:
* The listening unix-domain endpoints are now located at
drwx--x--- root hashman /run/hasher-priv.
* Minor code style adjustments.
Known issues:
On Thu, Sep 17, 2020 at 04:09:35PM +0300, Arseny Maslennikov wrote:
> There's an issue when hasher-privd tries to fulfill a chrootuid{1,2}
> request: the (eventually) unprivileged task executor process
> successfully invokes waitpid() or the likes on a child process,
> select()s on I/O descriptors, but gets CHLD later — and it looks like
> the inherited signal handler causes it to wait again. Ultimately the
> task executor stops forwarding standard I/O of the task to/from the
> caller and hangs.
See also: devel@ discussion of v1[2].
[1] http://git.altlinux.org/people/arseny/packages/?p=hasher-priv.git;a=shortlog;h=refs/heads/legion-daemon-v1-p
[2] https://lists.altlinux.org/pipermail/devel/2020-September/211871.html
Alexey Gladkov (3):
Turn hasher-priv into a daemon
Add systemd and sysvinit service files
Add preliminary cgroup support
Arseny Maslennikov (3):
Link with libsetproctitle by Dmitry V. Levin
daemon: set titles for subprocesses
Install hasher-priv without set ugids
hasher-priv/.gitignore | 1 +
hasher-priv/DESIGN | 281 +++++++++++++--------
hasher-priv/Makefile | 37 ++-
hasher-priv/caller.c | 81 +++---
hasher-priv/caller_server.c | 385 +++++++++++++++++++++++++++++
hasher-priv/caller_task.c | 226 +++++++++++++++++
hasher-priv/cgroup.c | 119 +++++++++
hasher-priv/cmdline.c | 27 +-
hasher-priv/communication.c | 394 ++++++++++++++++++++++++++++++
hasher-priv/communication.h | 79 ++++++
hasher-priv/config.c | 150 +++++++++++-
hasher-priv/epoll.c | 39 +++
hasher-priv/epoll.h | 18 ++
hasher-priv/hasher-priv.c | 78 ++++++
hasher-priv/hasher-priv.spec | 12 +-
hasher-priv/hasher-privd.c | 381 +++++++++++++++++++++++++++++
hasher-priv/hasher-privd.service | 14 ++
hasher-priv/hasher-privd.sysvinit | 103 ++++++++
hasher-priv/io_log.c | 2 +-
hasher-priv/io_x11.c | 2 +-
hasher-priv/killuid.c | 2 +-
hasher-priv/logging.c | 71 ++++++
hasher-priv/logging.h | 55 +++++
hasher-priv/main.c | 75 ------
hasher-priv/pass.c | 117 ++++++++-
hasher-priv/pidfile.c | 129 ++++++++++
hasher-priv/pidfile.h | 44 ++++
hasher-priv/priv.h | 35 ++-
hasher-priv/server.conf | 22 ++
hasher-priv/sockets.c | 183 ++++++++++++++
hasher-priv/sockets.h | 32 +++
hasher-priv/x11.c | 1 +
32 files changed, 2945 insertions(+), 250 deletions(-)
create mode 100644 hasher-priv/caller_server.c
create mode 100644 hasher-priv/caller_task.c
create mode 100644 hasher-priv/cgroup.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/hasher-privd.service
create mode 100755 hasher-priv/hasher-privd.sysvinit
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
--
2.25.4
^ permalink raw reply [flat|nested] 8+ messages in thread
* [devel] [PATCH v2 1/6] Turn hasher-priv into a daemon
2020-10-22 11:43 [devel] [PATCH hasher-priv v2 0/6] hasher-privd Arseny Maslennikov
@ 2020-10-22 11:43 ` Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 2/6] Link with libsetproctitle by Dmitry V. Levin Arseny Maslennikov
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:43 UTC (permalink / raw)
To: devel, ldv; +Cc: Arseny Maslennikov, Alexey Gladkov
From: Alexey Gladkov <legion@altlinux.org>
From: Alexey Gladkov <legion@altlinux.org>
All the privileged operations are now performed by a daemonised system
service. The daemon accepts IPC commands via a Unix domain socket,
fulfills the request and sends back a response.
The kernel provides a reliable way to obtain the sender's credentials,
which are then verified.
This allows to install hasher-privd without SUID bit to reduce attack
surface and facilitate NO_NEW_PRIVS environments.
When contacted by a client of user X, the daemon creates a separate
session process that handles requests only from user X. This session
process ends after a certain period of inactivity.
Signed-off-by: Alexey Gladkov <legion@altlinux.org>
Co-authored-by: Arseny Maslennikov <arseny@altlinux.org>
Signed-off-by: Arseny Maslennikov <arseny@altlinux.org>
---
hasher-priv/.gitignore | 1 +
hasher-priv/DESIGN | 281 ++++++++++++++++---------
hasher-priv/Makefile | 28 ++-
hasher-priv/caller.c | 81 ++++----
hasher-priv/caller_server.c | 382 ++++++++++++++++++++++++++++++++++
hasher-priv/caller_task.c | 217 ++++++++++++++++++++
hasher-priv/cmdline.c | 27 ++-
hasher-priv/communication.c | 394 ++++++++++++++++++++++++++++++++++++
hasher-priv/communication.h | 79 ++++++++
hasher-priv/config.c | 145 ++++++++++++-
hasher-priv/epoll.c | 39 ++++
hasher-priv/epoll.h | 18 ++
hasher-priv/hasher-priv.c | 78 +++++++
hasher-priv/hasher-privd.c | 381 ++++++++++++++++++++++++++++++++++
hasher-priv/io_log.c | 2 +-
hasher-priv/io_x11.c | 2 +-
hasher-priv/killuid.c | 2 +-
hasher-priv/logging.c | 71 +++++++
hasher-priv/logging.h | 55 +++++
hasher-priv/main.c | 75 -------
hasher-priv/pass.c | 117 ++++++++++-
hasher-priv/pidfile.c | 129 ++++++++++++
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, 2663 insertions(+), 247 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
index 1470ce7..8a57d2d 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 the control flow of hasher-priv (euid=user,egid=hashman,gid!=egid):
+ 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 <chroot path> <program> [program args]
- getugid2
- chrootuid2 <chroot path> <program> [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 the control flow of hasher-privd (euid=root,egid=hashman,gid==egid):
++ 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 incoming 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 <chroot path> <program> [program args]
+ getugid2
+ chrootuid2 <chroot path> <program> [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..ce55274 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 = /run/hasher-priv
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..abc2746
--- /dev/null
+++ b/hasher-priv/caller_server.c
@@ -0,0 +1,382 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ The per-calling-user server part of the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <linux/un.h>
+
+#include <sys/socket.h> /* SOCK_CLOEXEC */
+#include <sys/prctl.h>
+#include <sys/signalfd.h>
+#include <sys/wait.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <grp.h>
+
+#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->stdin > 0)
+ close(task->stdin);
+ if (task->stdout > 0)
+ close(task->stdout);
+ if (task->stderr > 0)
+ close(task->stderr);
+
+ 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 permission to send commands"
+ " 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() sends a response by itself. */
+ 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),
+ "%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/%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 the client that caller server is ready */
+ 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 from signalfd");
+ 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;
+ }
+
+ /* From now on the child sends its own failure responses. */
+ 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..8965196
--- /dev/null
+++ b/hasher-priv/caller_task.c
@@ -0,0 +1,217 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ The task handling part of the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#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;
+
+ pid = fork();
+ if (pid < 0) {
+ err("fork: %m");
+ return -1;
+ }
+ if (pid != 0) {
+ return pid;
+ }
+
+ if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
+ exit(rc);
+
+ /* clean up environment to avoid side effects. */
+ 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);
+
+ /* Third, ensure correctness of chroot path. */
+ if (chroot_path && *chroot_path != '/')
+ fatal("%s: invalid chroot path", chroot_path);
+
+ /* Fourth, parse environment for config options. */
+ parse_env();
+
+ /* We won't need environment variables any longer. */
+ if (clearenv() != 0)
+ fatal("clearenv: %m");
+
+ /* Finally, execute the requested task. */
+ 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 out all the user-space-buffered data */
+ fflush(stdout);
+ fflush(stderr);
+
+ exit(rc);
+}
+
+int
+process_caller_task(int conn, struct task *task)
+{
+ int rc = EXIT_FAILURE;
+ pid_t pid, cpid;
+
+ pid = fork();
+ if (pid < 0) {
+ err("fork: %m");
+ return -1;
+ }
+ if (pid != 0) {
+ return pid;
+ }
+
+ 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 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..2e3d442
--- /dev/null
+++ b/hasher-priv/communication.c
@@ -0,0 +1,394 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ This file offers some helper functions for client-server communication.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#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);
+
+const char* main_socket_base_name = "daemon";
+
+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..48a4e2d
--- /dev/null
+++ b/hasher-priv/communication.h
@@ -0,0 +1,79 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ This file offers some helper functions for client-server communication.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _COMMUNICATION_H_
+#define _COMMUNICATION_H_
+
+#include <stdint.h>
+
+const char* main_socket_base_name;
+
+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..ce83e15 100644
--- a/hasher-priv/config.c
+++ b/hasher-priv/config.c
@@ -1,8 +1,9 @@
/*
Copyright (C) 2003-2019 Dmitry V. Levin <ldv@altlinux.org>
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
- Configuration support module for the hasher-priv program.
+ Configuration support module for the hasher-privd.
SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -19,13 +20,17 @@
#include <unistd.h>
#include <limits.h>
#include <pwd.h>
+#include <grp.h>
#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_access_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_access_group(void)
+{
+ struct group *gr;
+
+ if (!server_access_group || !*server_access_group)
+ error(EXIT_FAILURE, 0, "config: undefined option: access_group");
+
+ gr = getgrnam(server_access_group);
+
+ if (!gr || !gr->gr_name)
+ error(EXIT_FAILURE, 0, "config: access_group: %s lookup failure", server_access_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("access_group", name)) {
+ free(server_access_group);
+ server_access_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_access_group();
+}
+
+void
+free_server_configuration(void)
+{
+ free(server_pidfile);
+ free(server_access_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 <legion@altlinux.org>
+
+ The helpers for epoll API for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <sys/epoll.h>
+
+#include <string.h>
+#include <unistd.h>
+
+#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 <legion@altlinux.org>
+
+ 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 <sys/epoll.h>
+
+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..f0c8fa8
--- /dev/null
+++ b/hasher-priv/hasher-priv.c
@@ -0,0 +1,78 @@
+
+/*
+ Copyright (C) 2003-2019 Dmitry V. Levin <ldv@altlinux.org>
+
+ 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 <linux/un.h>
+
+#include <errno.h>
+#include <error.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#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, main_socket_base_name, caller_num) < 0)
+ return EXIT_FAILURE;
+
+ /* Open user session */
+ snprintf(socketname, sizeof(socketname), "%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..270804e
--- /dev/null
+++ b/hasher-priv/hasher-privd.c
@@ -0,0 +1,381 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ The entry function for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <linux/un.h>
+
+#include <sys/types.h>
+#include <sys/epoll.h>
+#include <sys/signalfd.h>
+#include <sys/stat.h> /* umask */
+#include <sys/wait.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <error.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#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) {
+ /* Session exists and will be reused. */
+ 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);
+ /* The successful response is delayed until the child
+ * server is ready or an error happens.
+ */
+ if (rc < 0)
+ send_command_response(conn, CMD_STATUS_FAILED, "command failed");
+ 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 (daemonize && !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, main_socket_base_name)) < 0)
+ return EXIT_FAILURE;
+
+ umask(m);
+
+ snprintf(socketpath, sizeof(socketpath), "%s/%s", SOCKETDIR, main_socket_base_name);
+
+ 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 <ldv@altlinux.org>
- 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 <ldv@altlinux.org>
- 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 <ldv@altlinux.org>
- 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..cd693e5
--- /dev/null
+++ b/hasher-priv/logging.c
@@ -0,0 +1,71 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ Logging functions for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <strings.h>
+#include <errno.h>
+#include <syslog.h>
+
+#include "logging.h"
+
+int log_priority = -1;
+int log_to_stderr = 0;
+
+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 is_foreground)
+{
+ int options = LOG_PID;
+ log_priority = loglevel;
+ log_to_stderr = !!is_foreground;
+ if (!is_foreground)
+ openlog(program_invocation_short_name, options, LOG_DAEMON);
+}
+
+void logging_close(void)
+{
+ if (!log_to_stderr)
+ closelog();
+}
+
+void
+message(int priority, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (priority <= log_priority)
+ {
+ if (log_to_stderr)
+ {
+ fprintf(stderr, "<%d>", priority);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ }
+ else
+ vsyslog(priority, fmt, ap);
+ }
+ va_end(ap);
+}
diff --git a/hasher-priv/logging.h b/hasher-priv/logging.h
new file mode 100644
index 0000000..bc6d0da
--- /dev/null
+++ b/hasher-priv/logging.h
@@ -0,0 +1,55 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ Logging functions for the hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _LOGGING_H_
+#define _LOGGING_H_
+
+#include <syslog.h>
+#include <stdlib.h>
+
+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 <ldv@altlinux.org>
-
- 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 <errno.h>
-#include <error.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#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 <unistd.h>
#include <sys/socket.h>
+#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..3da63dd
--- /dev/null
+++ b/hasher-priv/pidfile.c
@@ -0,0 +1,129 @@
+
+/*
+ pidfile.c - interact with pidfiles
+ Copyright (c) 1995 Martin Schulze <Martin.Schulze@Linux.DE>
+
+ This file was originally part of the sysklogd package, a kernel and system
+ log daemon.
+
+ 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 <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 <Martin.Schulze@Linux.DE>
+
+ This file is part of the sysklogd package, a kernel and system log daemon.
+
+ 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..7d5a5a5 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_access_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..2d740a6
--- /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.
+access_group=hashman
diff --git a/hasher-priv/sockets.c b/hasher-priv/sockets.c
new file mode 100644
index 0000000..5ba3511
--- /dev/null
+++ b/hasher-priv/sockets.c
@@ -0,0 +1,183 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ A collection of helpers to simplify the use of sockets.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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..1ee37a2
--- /dev/null
+++ b/hasher-priv/sockets.h
@@ -0,0 +1,32 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ A collection of helpers to simplify the use of sockets.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _SOCKETS_H_
+#define _SOCKETS_H_
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+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 <unistd.h>
+
+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 <stdint.h>
+
+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.25.4
^ permalink raw reply [flat|nested] 8+ messages in thread
* [devel] [PATCH v2 2/6] Link with libsetproctitle by Dmitry V. Levin
2020-10-22 11:43 [devel] [PATCH hasher-priv v2 0/6] hasher-privd Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 1/6] Turn hasher-priv into a daemon Arseny Maslennikov
@ 2020-10-22 11:43 ` Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 3/6] daemon: set titles for subprocesses Arseny Maslennikov
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:43 UTC (permalink / raw)
To: devel, ldv; +Cc: Arseny Maslennikov
This being a shared library is an implementation detail: to overcome the
lack of such an interface on Linux, we need to run some preparation code
before main() and pass argv/envp memory buffers to it. A function
declared with __attribute__((constructor)) gets called with no
arguments, we can't pass the argv/envp buffers to it, so we put an
implementation of setproctitle(3) in a shared object and hijack its
_init().
Signed-off-by: Arseny Maslennikov <arseny@altlinux.org>
---
hasher-priv/Makefile | 3 ++-
hasher-priv/hasher-priv.spec | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
index ce55274..283249b 100644
--- a/hasher-priv/Makefile
+++ b/hasher-priv/Makefile
@@ -39,6 +39,7 @@ CPPFLAGS = -std=gnu99 -D_GNU_SOURCE $(CHDIRUID_FLAGS) \
CFLAGS = -pipe -O2
override CFLAGS += $(WARNINGS)
LDLIBS =
+server_LDLIBS = -lsetproctitle
SRC = hasher-priv.c cmdline.c fds.c sockets.c logging.c communication.c xmalloc.c pass.c
OBJ = $(SRC:.c=.o)
@@ -62,7 +63,7 @@ $(PROJECT): $(OBJ)
$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
hasher-privd: $(server_OBJ)
- $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
+ $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) $(server_LDLIBS) -o $@
install: all
$(MKDIR_P) -m710 $(DESTDIR)$(configdir)/user.d
diff --git a/hasher-priv/hasher-priv.spec b/hasher-priv/hasher-priv.spec
index 2da9ee4..fac25cd 100644
--- a/hasher-priv/hasher-priv.spec
+++ b/hasher-priv/hasher-priv.spec
@@ -20,6 +20,7 @@ Obsoletes: pkg-build-priv
Conflicts: hasher < 1.4.0
BuildPreReq: help2man, sisyphus_check >= 0:0.7.11
+BuildRequires: setproctitle-devel
%description
This package provides helpers for executing privileged operations
--
2.25.4
^ permalink raw reply [flat|nested] 8+ messages in thread
* [devel] [PATCH v2 3/6] daemon: set titles for subprocesses
2020-10-22 11:43 [devel] [PATCH hasher-priv v2 0/6] hasher-privd Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 1/6] Turn hasher-priv into a daemon Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 2/6] Link with libsetproctitle by Dmitry V. Levin Arseny Maslennikov
@ 2020-10-22 11:43 ` Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 4/6] Add systemd and sysvinit service files Arseny Maslennikov
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:43 UTC (permalink / raw)
To: devel, ldv; +Cc: Arseny Maslennikov
Signed-off-by: Arseny Maslennikov <arseny@altlinux.org>
---
hasher-priv/caller_server.c | 3 +++
hasher-priv/caller_task.c | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/hasher-priv/caller_server.c b/hasher-priv/caller_server.c
index abc2746..d472d9a 100644
--- a/hasher-priv/caller_server.c
+++ b/hasher-priv/caller_server.c
@@ -19,6 +19,8 @@
#include <unistd.h>
#include <grp.h>
+#include <setproctitle.h>
+
#include "priv.h"
#include "xmalloc.h"
#include "sockets.h"
@@ -364,6 +366,7 @@ fork_server(int cl_conn, uid_t uid, gid_t gid, unsigned num)
if (ret >= 0) {
info("%s(%d) num=%u: start session server", caller_user, caller_uid, caller_num);
+ setproctitle("%s %s/%u:%u", "privileged helper for", caller_user, caller_uid, caller_num);
ret = caller_server(cl_conn);
info("%s(%d): finish session server", caller_user, caller_uid);
}
diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c
index 8965196..46745a2 100644
--- a/hasher-priv/caller_task.c
+++ b/hasher-priv/caller_task.c
@@ -16,6 +16,8 @@
#include <stdio.h>
#include <stdint.h>
+#include <setproctitle.h>
+
#include "communication.h"
#include "xmalloc.h"
#include "logging.h"
@@ -96,6 +98,8 @@ caller_task(struct task *task)
return pid;
}
+ setproctitle("%s for [%u:%u]: %s", "task handler", caller_uid, caller_num, task2str(task->type));
+
if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
exit(rc);
@@ -175,6 +179,8 @@ process_caller_task(int conn, struct task *task)
return pid;
}
+ setproctitle("%s", __func__);
+
if ((cpid = caller_task(task)) > 0) {
while (1) {
pid_t w;
--
2.25.4
^ permalink raw reply [flat|nested] 8+ messages in thread
* [devel] [PATCH v2 4/6] Add systemd and sysvinit service files
2020-10-22 11:43 [devel] [PATCH hasher-priv v2 0/6] hasher-privd Arseny Maslennikov
` (2 preceding siblings ...)
2020-10-22 11:43 ` [devel] [PATCH v2 3/6] daemon: set titles for subprocesses Arseny Maslennikov
@ 2020-10-22 11:43 ` Arseny Maslennikov
2020-10-22 11:49 ` [devel] [PATCH v2 4/6] Add systemd and sysvinit service files: missing signoff Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 5/6] Install hasher-priv without set ugids Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 6/6] Add preliminary cgroup support Arseny Maslennikov
5 siblings, 1 reply; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:43 UTC (permalink / raw)
To: devel, ldv; +Cc: Alexey Gladkov
From: Alexey Gladkov <legion@altlinux.org>
Signed-off-by: Alexey Gladkov <legion@altlinux.org>
---
hasher-priv/Makefile | 6 ++
hasher-priv/hasher-priv.spec | 9 ++-
hasher-priv/hasher-privd.service | 14 ++++
hasher-priv/hasher-privd.sysvinit | 103 ++++++++++++++++++++++++++++++
4 files changed, 131 insertions(+), 1 deletion(-)
create mode 100644 hasher-priv/hasher-privd.service
create mode 100755 hasher-priv/hasher-privd.sysvinit
diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
index 283249b..6e6b1e5 100644
--- a/hasher-priv/Makefile
+++ b/hasher-priv/Makefile
@@ -14,6 +14,8 @@ MAN8PAGES = $(PROJECT).8 hasher-useradd.8
TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
sysconfdir = /etc
+initdir=$(sysconfdir)/rc.d/init.d
+systemd_unitdir=/lib/systemd/system
libexecdir = /usr/lib
sbindir = /usr/sbin
mandir = /usr/share/man
@@ -73,6 +75,10 @@ install: all
$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
+ $(MKDIR_P) -m755 $(DESTDIR)$(systemd_unitdir)
+ $(INSTALL) -p -m644 hasher-privd.service $(DESTDIR)$(systemd_unitdir)/
+ $(MKDIR_P) -m755 $(DESTDIR)$(initdir)
+ $(INSTALL) -p -m755 hasher-privd.sysvinit $(DESTDIR)$(initdir)/hasher-privd
$(MKDIR_P) -m755 $(DESTDIR)$(sbindir)
$(INSTALL) -p -m755 hasher-privd $(DESTDIR)$(sbindir)/
$(INSTALL) -p -m755 hasher-useradd $(DESTDIR)$(sbindir)/
diff --git a/hasher-priv/hasher-priv.spec b/hasher-priv/hasher-priv.spec
index fac25cd..c4f0e0e 100644
--- a/hasher-priv/hasher-priv.spec
+++ b/hasher-priv/hasher-priv.spec
@@ -33,7 +33,9 @@ required by hasher utilities.
%make_build CC="%__cc" CFLAGS="%optflags" libexecdir="%_libexecdir"
%install
-%makeinstall
+%makeinstall \
+ systemd_unitdir="%{?buildroot:%{buildroot}}%_unitdir" \
+ #
%pre
if getent group pkg-build > /dev/null; then
@@ -52,10 +54,15 @@ groupadd -r -f hashman
%attr(750,root,hashman) %dir %configdir/user.d
%attr(640,root,hashman) %config(noreplace) %configdir/fstab
%attr(640,root,hashman) %config(noreplace) %configdir/system
+%attr(640,root,hashman) %config(noreplace) %configdir/server
# helpers
%attr(750,root,hashman) %dir %helperdir
%attr(6710,root,hashman) %helperdir/%name
%attr(755,root,root) %helperdir/*.sh
+# daemon
+%_sbindir/hasher-privd
+%_unitdir/hasher-privd.service
+%_initdir/hasher-privd
%doc DESIGN
diff --git a/hasher-priv/hasher-privd.service b/hasher-priv/hasher-privd.service
new file mode 100644
index 0000000..f44faa0
--- /dev/null
+++ b/hasher-priv/hasher-privd.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=A privileged helper for the hasher project
+ConditionVirtualization=!container
+Documentation=man:hasher-priv(8)
+
+[Service]
+ExecStart=/usr/sbin/hasher-privd -f
+Group=hashman
+RuntimeDirectory=hasher-priv
+RuntimeDirectoryMode=0710
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff --git a/hasher-priv/hasher-privd.sysvinit b/hasher-priv/hasher-privd.sysvinit
new file mode 100755
index 0000000..263c9f7
--- /dev/null
+++ b/hasher-priv/hasher-privd.sysvinit
@@ -0,0 +1,103 @@
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Short-Description: A privileged helper for the hasher project
+# Description: A privileged helper for the hasher project
+# Provides: hasher-priv
+# Required-Start: $remote_fs
+# Required-Stop: $remote_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+### END INIT INFO
+
+WITHOUT_RC_COMPAT=1
+
+# Source function library.
+. /etc/init.d/functions
+
+NAME=hasher-privd
+PIDFILE="/var/run/$NAME.pid"
+LOCKFILE="/var/lock/subsys/$NAME"
+RUNTIMEDIR="/run/hasher-priv"
+RUNTIMEDIRMODE="0710"
+GROUP=hashman
+RETVAL=0
+
+ensure_runtime_directory()
+{
+ mkdir -p "$RUNTIMEDIR"
+ chmod 0710 "$RUNTIMEDIR"
+ chgrp "$GROUP" "$RUNTIMEDIR"
+}
+
+ensure_no_runtime_directory()
+{
+ rm -rf "$RUNTIMEDIR"
+}
+
+start()
+{
+ start_daemon --pidfile "$PIDFILE" --lockfile "$LOCKFILE" -- "$NAME"
+ RETVAL=$?
+ return $RETVAL
+}
+
+stop()
+{
+ stop_daemon --pidfile "$PIDFILE" --lockfile "$LOCKFILE" "$NAME"
+ RETVAL=$?
+ return $RETVAL
+}
+
+restart()
+{
+ stop
+ start
+}
+
+# See how we were called.
+case "$1" in
+ start)
+ ensure_runtime_directory
+ start
+ ;;
+ stop)
+ stop
+ ensure_no_runtime_directory
+ ;;
+ status)
+ status --pidfile "$PIDFILE" "$NAME"
+ RETVAL=$?
+ ;;
+ restart)
+ restart
+ ;;
+ reload)
+ restart
+ ;;
+ condstart)
+ if [ ! -e "$LOCKFILE" ]; then
+ start
+ fi
+ ;;
+ condstop)
+ if [ -e "$LOCKFILE" ]; then
+ stop
+ fi
+ ;;
+ condrestart)
+ if [ -e "$LOCKFILE" ]; then
+ restart
+ fi
+ ;;
+ condreload)
+ if [ -e "$LOCKFILE" ]; then
+ reload
+ fi
+ ;;
+ *)
+ msg_usage "${0##*/} {start|stop|status|restart|reload|condstart|condstop|condrestart|condreload}"
+ RETVAL=1
+esac
+
+exit $RETVAL
--
2.25.4
^ permalink raw reply [flat|nested] 8+ messages in thread
* [devel] [PATCH v2 5/6] Install hasher-priv without set ugids
2020-10-22 11:43 [devel] [PATCH hasher-priv v2 0/6] hasher-privd Arseny Maslennikov
` (3 preceding siblings ...)
2020-10-22 11:43 ` [devel] [PATCH v2 4/6] Add systemd and sysvinit service files Arseny Maslennikov
@ 2020-10-22 11:43 ` Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 6/6] Add preliminary cgroup support Arseny Maslennikov
5 siblings, 0 replies; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:43 UTC (permalink / raw)
To: devel, ldv; +Cc: Arseny Maslennikov
Signed-off-by: Arseny Maslennikov <arseny@altlinux.org>
---
hasher-priv/hasher-priv.spec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hasher-priv/hasher-priv.spec b/hasher-priv/hasher-priv.spec
index c4f0e0e..a37d20c 100644
--- a/hasher-priv/hasher-priv.spec
+++ b/hasher-priv/hasher-priv.spec
@@ -57,7 +57,7 @@ groupadd -r -f hashman
%attr(640,root,hashman) %config(noreplace) %configdir/server
# helpers
%attr(750,root,hashman) %dir %helperdir
-%attr(6710,root,hashman) %helperdir/%name
+%attr(710,root,hashman) %helperdir/%name
%attr(755,root,root) %helperdir/*.sh
# daemon
%_sbindir/hasher-privd
--
2.25.4
^ permalink raw reply [flat|nested] 8+ messages in thread
* [devel] [PATCH v2 6/6] Add preliminary cgroup support
2020-10-22 11:43 [devel] [PATCH hasher-priv v2 0/6] hasher-privd Arseny Maslennikov
` (4 preceding siblings ...)
2020-10-22 11:43 ` [devel] [PATCH v2 5/6] Install hasher-priv without set ugids Arseny Maslennikov
@ 2020-10-22 11:43 ` Arseny Maslennikov
5 siblings, 0 replies; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:43 UTC (permalink / raw)
To: devel, ldv; +Cc: Arseny Maslennikov, Alexey Gladkov
From: Alexey Gladkov <legion@altlinux.org>
A new configuration option allows to put all request handlers to a
particular cgroup path, which is built according to a specified template.
Note that cgroup1 API is not supported in favour of cgroup2.
Signed-off-by: Alexey Gladkov <legion@altlinux.org>
Signed-off-by: Arseny Maslennikov <arseny@altlinux.org>
---
hasher-priv/Makefile | 2 +-
hasher-priv/caller_task.c | 3 +
hasher-priv/cgroup.c | 119 ++++++++++++++++++++++++++++++++++++++
hasher-priv/config.c | 5 ++
hasher-priv/priv.h | 2 +
hasher-priv/server.conf | 9 +++
6 files changed, 139 insertions(+), 1 deletion(-)
create mode 100644 hasher-priv/cgroup.c
diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
index 6e6b1e5..be25fb7 100644
--- a/hasher-priv/Makefile
+++ b/hasher-priv/Makefile
@@ -52,7 +52,7 @@ server_SRC = hasher-privd.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 \
makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \
- unshare.c xmalloc.c x11.c
+ unshare.c xmalloc.c x11.c cgroup.c
server_OBJ = $(server_SRC:.c=.o)
DEP = $(SRC:.c=.d) $(server_SRC:.c=.d)
diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c
index 46745a2..48d8fbd 100644
--- a/hasher-priv/caller_task.c
+++ b/hasher-priv/caller_task.c
@@ -100,6 +100,9 @@ caller_task(struct task *task)
setproctitle("%s for [%u:%u]: %s", "task handler", caller_uid, caller_num, task2str(task->type));
+ if (join_cgroup() < 0)
+ exit(rc);
+
if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
exit(rc);
diff --git a/hasher-priv/cgroup.c b/hasher-priv/cgroup.c
new file mode 100644
index 0000000..ac14938
--- /dev/null
+++ b/hasher-priv/cgroup.c
@@ -0,0 +1,119 @@
+
+/*
+ Copyright (C) 2019 Alexey Gladkov <legion@altlinux.org>
+
+ The cgroup helper for hasher-privd program.
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "logging.h"
+#include "priv.h"
+
+int
+join_cgroup(void)
+{
+ int ret = 0;
+
+ if (!server_cgroup_template)
+ return ret;
+
+ char cgroup_path[MAXPATHLEN];
+
+ size_t i, j, escape;
+ size_t len = strlen(server_cgroup_template);
+ int fd = -1;
+
+ i = j = escape = 0;
+
+ for (; i < len; i++) {
+ if (j > sizeof(cgroup_path)) {
+ err("path too long");
+ ret = -1;
+ goto fail;
+ }
+
+ if (escape) {
+ ssize_t n = 0;
+ char *p = cgroup_path + j;
+ size_t sz = (size_t) (p - cgroup_path);
+
+ switch (server_cgroup_template[i]) {
+ case 'u':
+ n = snprintf(p, sz, "%s", caller_user);
+ break;
+ case 'U':
+ n = snprintf(p, sz, "%u", caller_uid);
+ break;
+ case 'G':
+ n = snprintf(p, sz, "%u", caller_gid);
+ break;
+ case 'N':
+ n = snprintf(p, sz, "%u", caller_num);
+ break;
+ case '%':
+ n = snprintf(p, sz, "%%");
+ break;
+ }
+
+ if (n <= 0) {
+ err("unable to expand escape sequence: %%%c",
+ server_cgroup_template[i]);
+ ret = -1;
+ goto fail;
+ }
+
+ j += (size_t) n;
+
+ escape = 0;
+ continue;
+
+ } else if (server_cgroup_template[i] == '%') {
+ escape = 1;
+ continue;
+
+ } else if (server_cgroup_template[i] == '/' && j > 0) {
+ cgroup_path[j] = '\0';
+
+ errno = 0;
+ if (mkdir(cgroup_path, 0755) < 0 && errno != EEXIST) {
+ err("mkdir: %s: errno=%d: %m", cgroup_path, errno);
+ ret = -1;
+ goto fail;
+ }
+ }
+
+ cgroup_path[j++] = server_cgroup_template[i];
+ }
+
+ cgroup_path[j] = '\0';
+
+ if ((fd = open(cgroup_path, O_CREAT | O_WRONLY | O_CLOEXEC, 0644)) < 0) {
+ err("open: %s: %m", cgroup_path);
+ ret = -1;
+ goto fail;
+ }
+
+ if (dprintf(fd, "%d\n", getpid()) < 0) {
+ err("dprintf: %s: unable to write pid", cgroup_path);
+ ret = -1;
+ }
+fail:
+ if (fd >= 0 && close(fd) < 0) {
+ err("close: %s: %m", cgroup_path);
+ ret = -1;
+ }
+
+ return ret;
+}
diff --git a/hasher-priv/config.c b/hasher-priv/config.c
index ce83e15..9729982 100644
--- a/hasher-priv/config.c
+++ b/hasher-priv/config.c
@@ -30,6 +30,7 @@ const char *const *chroot_prefix_list;
const char *chroot_prefix_path;
const char *change_user1, *change_user2;
char *server_access_group = NULL;
+char *server_cgroup_template = NULL;
char *server_pidfile = NULL;
const char *term;
const char *x11_display, *x11_key;
@@ -671,6 +672,9 @@ set_server_config(const char *name, const char *value, const char *filename)
} else if (!strcasecmp("access_group", name)) {
free(server_access_group);
server_access_group = xstrdup(value);
+ } else if (!strcasecmp("cgroup_template", name)) {
+ free(server_cgroup_template);
+ server_cgroup_template = xstrdup(value);
} else {
bad_option_name(name, filename);
}
@@ -771,4 +775,5 @@ free_server_configuration(void)
{
free(server_pidfile);
free(server_access_group);
+ free(server_cgroup_template);
}
diff --git a/hasher-priv/priv.h b/hasher-priv/priv.h
index 7d5a5a5..6e8a032 100644
--- a/hasher-priv/priv.h
+++ b/hasher-priv/priv.h
@@ -120,6 +120,7 @@ int do_chrootuid2(void);
int process_caller_task(int, struct task *);
pid_t fork_server(int, uid_t, gid_t, unsigned);
+int join_cgroup(void);
extern const char *chroot_path;
extern const char **chroot_argv;
@@ -162,6 +163,7 @@ extern work_limit_t wlimit;
extern int server_log_priority;
extern unsigned long server_session_timeout;
extern char *server_access_group;
+extern char *server_cgroup_template;
extern char *server_pidfile;
extern gid_t server_gid;
diff --git a/hasher-priv/server.conf b/hasher-priv/server.conf
index 2d740a6..57744f9 100644
--- a/hasher-priv/server.conf
+++ b/hasher-priv/server.conf
@@ -11,3 +11,12 @@ session_timeout=3600
# Allow users of this group to interact with hasher-privd via the control socket.
access_group=hashman
+
+# Template for cgroup path to which task handler should be added.
+#
+# %u -- Session's user name.
+# %U -- Session's user numeric ID.
+# %G -- Session's group numeric ID.
+# %N -- Session's user number.
+#
+#cgroup_template=/sys/fs/cgroup2/hasher-priv/%u/cgroup.procs
--
2.25.4
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [devel] [PATCH v2 4/6] Add systemd and sysvinit service files: missing signoff
2020-10-22 11:43 ` [devel] [PATCH v2 4/6] Add systemd and sysvinit service files Arseny Maslennikov
@ 2020-10-22 11:49 ` Arseny Maslennikov
0 siblings, 0 replies; 8+ messages in thread
From: Arseny Maslennikov @ 2020-10-22 11:49 UTC (permalink / raw)
To: ALT Linux Team development discussions; +Cc: Alexey Gladkov, ldv
[-- Attachment #1: Type: text/plain, Size: 5791 bytes --]
On Thu, Oct 22, 2020 at 02:43:41PM +0300, Arseny Maslennikov wrote:
> From: Alexey Gladkov <legion@altlinux.org>
>
> Signed-off-by: Alexey Gladkov <legion@altlinux.org>
Signed-off-by: Arseny Maslennikov <arseny@altlinux.org>
> ---
> hasher-priv/Makefile | 6 ++
> hasher-priv/hasher-priv.spec | 9 ++-
> hasher-priv/hasher-privd.service | 14 ++++
> hasher-priv/hasher-privd.sysvinit | 103 ++++++++++++++++++++++++++++++
> 4 files changed, 131 insertions(+), 1 deletion(-)
> create mode 100644 hasher-priv/hasher-privd.service
> create mode 100755 hasher-priv/hasher-privd.sysvinit
>
> diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
> index 283249b..6e6b1e5 100644
> --- a/hasher-priv/Makefile
> +++ b/hasher-priv/Makefile
> @@ -14,6 +14,8 @@ MAN8PAGES = $(PROJECT).8 hasher-useradd.8
> TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
>
> sysconfdir = /etc
> +initdir=$(sysconfdir)/rc.d/init.d
> +systemd_unitdir=/lib/systemd/system
> libexecdir = /usr/lib
> sbindir = /usr/sbin
> mandir = /usr/share/man
> @@ -73,6 +75,10 @@ install: all
> $(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
> $(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
> $(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
> + $(MKDIR_P) -m755 $(DESTDIR)$(systemd_unitdir)
> + $(INSTALL) -p -m644 hasher-privd.service $(DESTDIR)$(systemd_unitdir)/
> + $(MKDIR_P) -m755 $(DESTDIR)$(initdir)
> + $(INSTALL) -p -m755 hasher-privd.sysvinit $(DESTDIR)$(initdir)/hasher-privd
> $(MKDIR_P) -m755 $(DESTDIR)$(sbindir)
> $(INSTALL) -p -m755 hasher-privd $(DESTDIR)$(sbindir)/
> $(INSTALL) -p -m755 hasher-useradd $(DESTDIR)$(sbindir)/
> diff --git a/hasher-priv/hasher-priv.spec b/hasher-priv/hasher-priv.spec
> index fac25cd..c4f0e0e 100644
> --- a/hasher-priv/hasher-priv.spec
> +++ b/hasher-priv/hasher-priv.spec
> @@ -33,7 +33,9 @@ required by hasher utilities.
> %make_build CC="%__cc" CFLAGS="%optflags" libexecdir="%_libexecdir"
>
> %install
> -%makeinstall
> +%makeinstall \
> + systemd_unitdir="%{?buildroot:%{buildroot}}%_unitdir" \
> + #
>
> %pre
> if getent group pkg-build > /dev/null; then
> @@ -52,10 +54,15 @@ groupadd -r -f hashman
> %attr(750,root,hashman) %dir %configdir/user.d
> %attr(640,root,hashman) %config(noreplace) %configdir/fstab
> %attr(640,root,hashman) %config(noreplace) %configdir/system
> +%attr(640,root,hashman) %config(noreplace) %configdir/server
> # helpers
> %attr(750,root,hashman) %dir %helperdir
> %attr(6710,root,hashman) %helperdir/%name
> %attr(755,root,root) %helperdir/*.sh
> +# daemon
> +%_sbindir/hasher-privd
> +%_unitdir/hasher-privd.service
> +%_initdir/hasher-privd
>
> %doc DESIGN
>
> diff --git a/hasher-priv/hasher-privd.service b/hasher-priv/hasher-privd.service
> new file mode 100644
> index 0000000..f44faa0
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.service
> @@ -0,0 +1,14 @@
> +[Unit]
> +Description=A privileged helper for the hasher project
> +ConditionVirtualization=!container
> +Documentation=man:hasher-priv(8)
> +
> +[Service]
> +ExecStart=/usr/sbin/hasher-privd -f
> +Group=hashman
> +RuntimeDirectory=hasher-priv
> +RuntimeDirectoryMode=0710
> +Restart=on-failure
> +
> +[Install]
> +WantedBy=multi-user.target
> diff --git a/hasher-priv/hasher-privd.sysvinit b/hasher-priv/hasher-privd.sysvinit
> new file mode 100755
> index 0000000..263c9f7
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.sysvinit
> @@ -0,0 +1,103 @@
> +#! /bin/sh
> +
> +### BEGIN INIT INFO
> +# Short-Description: A privileged helper for the hasher project
> +# Description: A privileged helper for the hasher project
> +# Provides: hasher-priv
> +# Required-Start: $remote_fs
> +# Required-Stop: $remote_fs
> +# Default-Start: 2 3 4 5
> +# Default-Stop: 0 1 6
> +### END INIT INFO
> +
> +WITHOUT_RC_COMPAT=1
> +
> +# Source function library.
> +. /etc/init.d/functions
> +
> +NAME=hasher-privd
> +PIDFILE="/var/run/$NAME.pid"
> +LOCKFILE="/var/lock/subsys/$NAME"
> +RUNTIMEDIR="/run/hasher-priv"
> +RUNTIMEDIRMODE="0710"
> +GROUP=hashman
> +RETVAL=0
> +
> +ensure_runtime_directory()
> +{
> + mkdir -p "$RUNTIMEDIR"
> + chmod 0710 "$RUNTIMEDIR"
> + chgrp "$GROUP" "$RUNTIMEDIR"
> +}
> +
> +ensure_no_runtime_directory()
> +{
> + rm -rf "$RUNTIMEDIR"
> +}
> +
> +start()
> +{
> + start_daemon --pidfile "$PIDFILE" --lockfile "$LOCKFILE" -- "$NAME"
> + RETVAL=$?
> + return $RETVAL
> +}
> +
> +stop()
> +{
> + stop_daemon --pidfile "$PIDFILE" --lockfile "$LOCKFILE" "$NAME"
> + RETVAL=$?
> + return $RETVAL
> +}
> +
> +restart()
> +{
> + stop
> + start
> +}
> +
> +# See how we were called.
> +case "$1" in
> + start)
> + ensure_runtime_directory
> + start
> + ;;
> + stop)
> + stop
> + ensure_no_runtime_directory
> + ;;
> + status)
> + status --pidfile "$PIDFILE" "$NAME"
> + RETVAL=$?
> + ;;
> + restart)
> + restart
> + ;;
> + reload)
> + restart
> + ;;
> + condstart)
> + if [ ! -e "$LOCKFILE" ]; then
> + start
> + fi
> + ;;
> + condstop)
> + if [ -e "$LOCKFILE" ]; then
> + stop
> + fi
> + ;;
> + condrestart)
> + if [ -e "$LOCKFILE" ]; then
> + restart
> + fi
> + ;;
> + condreload)
> + if [ -e "$LOCKFILE" ]; then
> + reload
> + fi
> + ;;
> + *)
> + msg_usage "${0##*/} {start|stop|status|restart|reload|condstart|condstop|condrestart|condreload}"
> + RETVAL=1
> +esac
> +
> +exit $RETVAL
> --
> 2.25.4
>
> _______________________________________________
> Devel mailing list
> Devel@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/devel
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2020-10-22 11:49 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-22 11:43 [devel] [PATCH hasher-priv v2 0/6] hasher-privd Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 1/6] Turn hasher-priv into a daemon Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 2/6] Link with libsetproctitle by Dmitry V. Levin Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 3/6] daemon: set titles for subprocesses Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 4/6] Add systemd and sysvinit service files Arseny Maslennikov
2020-10-22 11:49 ` [devel] [PATCH v2 4/6] Add systemd and sysvinit service files: missing signoff Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 5/6] Install hasher-priv without set ugids Arseny Maslennikov
2020-10-22 11:43 ` [devel] [PATCH v2 6/6] Add preliminary cgroup support Arseny Maslennikov
ALT Linux Team development discussions
This inbox may be cloned and mirrored by anyone:
git clone --mirror http://lore.altlinux.org/devel/0 devel/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 devel devel/ http://lore.altlinux.org/devel \
devel@altlinux.org devel@altlinux.ru devel@lists.altlinux.org devel@lists.altlinux.ru devel@linux.iplabs.ru mandrake-russian@linuxteam.iplabs.ru sisyphus@linuxteam.iplabs.ru
public-inbox-index devel
Example config snippet for mirrors.
Newsgroup available over NNTP:
nntp://lore.altlinux.org/org.altlinux.lists.devel
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git