ALT Linux Team development discussions
 help / color / mirror / Atom feed
* [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
@ 2019-12-13 11:42 Alex Gladkov
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
                   ` (6 more replies)
  0 siblings, 7 replies; 52+ messages in thread
From: Alex Gladkov @ 2019-12-13 11:42 UTC (permalink / raw)
  To: ldv; +Cc: devel

From: Alexey Gladkov <legion@altlinux.org>

The hasher-priv is a SUID utility. This is not good. Separation of the
server and client parts will allow us to remove SUID flag.

The separation of server and client is not intended to give clients
access over the network. This separation is only necessary to distinguish
privileges. Only UNIX domain socket is used.

A separate session process is created for each connected user. Each such
process ends after a certain period of inactivity.

Alexey Gladkov (3):
  Make a daemon from the hasher-priv
  Add systemd and sysvinit service files
  Add cgroup support

 hasher-priv/.gitignore            |   1 +
 hasher-priv/DESIGN                | 281 +++++++++++++--------
 hasher-priv/Makefile              |  34 ++-
 hasher-priv/caller.c              |  81 +++---
 hasher-priv/caller_server.c       | 373 ++++++++++++++++++++++++++++
 hasher-priv/caller_task.c         | 217 +++++++++++++++++
 hasher-priv/cgroup.c              | 119 +++++++++
 hasher-priv/cmdline.c             |  27 +-
 hasher-priv/communication.c       | 392 ++++++++++++++++++++++++++++++
 hasher-priv/communication.h       |  77 ++++++
 hasher-priv/config.c              | 148 ++++++++++-
 hasher-priv/epoll.c               |  39 +++
 hasher-priv/epoll.h               |  18 ++
 hasher-priv/hasher-priv.c         |  78 ++++++
 hasher-priv/hasher-privd.c        | 375 ++++++++++++++++++++++++++++
 hasher-priv/hasher-privd.service  |  11 +
 hasher-priv/hasher-privd.sysvinit |  86 +++++++
 hasher-priv/io_log.c              |   2 +-
 hasher-priv/io_x11.c              |   2 +-
 hasher-priv/killuid.c             |   2 +-
 hasher-priv/logging.c             |  64 +++++
 hasher-priv/logging.h             |  55 +++++
 hasher-priv/main.c                |  75 ------
 hasher-priv/pass.c                | 117 ++++++++-
 hasher-priv/pidfile.c             | 128 ++++++++++
 hasher-priv/pidfile.h             |  44 ++++
 hasher-priv/priv.h                |  35 ++-
 hasher-priv/server.conf           |  22 ++
 hasher-priv/sockets.c             | 183 ++++++++++++++
 hasher-priv/sockets.h             |  32 +++
 hasher-priv/x11.c                 |   1 +
 31 files changed, 2872 insertions(+), 247 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.24.0



^ permalink raw reply	[flat|nested] 52+ messages in thread

* [devel] [PATCH hasher-priv v1 1/3] Make a daemon from the hasher-priv
  2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
@ 2019-12-13 11:42 ` Alex Gladkov
  2020-09-17 13:10   ` Arseny Maslennikov
                     ` (8 more replies)
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files Alex Gladkov
                   ` (5 subsequent siblings)
  6 siblings, 9 replies; 52+ messages in thread
From: Alex Gladkov @ 2019-12-13 11:42 UTC (permalink / raw)
  To: ldv; +Cc: devel

From: Alexey Gladkov <legion@altlinux.org>

All privileged operations moved to the daemon. Commands to the server
are sent through the unix domain socket. The credentials which the sender
specifies are checked by the kernel. The hasher-priv no longer SUID.

For each user server creates a separate session process that executes
commands only from the user who created it. The session process ends
after a certain period of inactivity.

Signed-off-by: Alexey Gladkov <legion@altlinux.org>
---
 hasher-priv/.gitignore      |   1 +
 hasher-priv/DESIGN          | 281 ++++++++++++++++----------
 hasher-priv/Makefile        |  28 ++-
 hasher-priv/caller.c        |  81 ++++----
 hasher-priv/caller_server.c | 373 ++++++++++++++++++++++++++++++++++
 hasher-priv/caller_task.c   | 214 ++++++++++++++++++++
 hasher-priv/cmdline.c       |  27 ++-
 hasher-priv/communication.c | 392 ++++++++++++++++++++++++++++++++++++
 hasher-priv/communication.h |  77 +++++++
 hasher-priv/config.c        | 143 ++++++++++++-
 hasher-priv/epoll.c         |  39 ++++
 hasher-priv/epoll.h         |  18 ++
 hasher-priv/hasher-priv.c   |  78 +++++++
 hasher-priv/hasher-privd.c  | 375 ++++++++++++++++++++++++++++++++++
 hasher-priv/io_log.c        |   2 +-
 hasher-priv/io_x11.c        |   2 +-
 hasher-priv/killuid.c       |   2 +-
 hasher-priv/logging.c       |  64 ++++++
 hasher-priv/logging.h       |  55 +++++
 hasher-priv/main.c          |  75 -------
 hasher-priv/pass.c          | 117 ++++++++++-
 hasher-priv/pidfile.c       | 128 ++++++++++++
 hasher-priv/pidfile.h       |  44 ++++
 hasher-priv/priv.h          |  33 +--
 hasher-priv/server.conf     |  13 ++
 hasher-priv/sockets.c       | 183 +++++++++++++++++
 hasher-priv/sockets.h       |  32 +++
 hasher-priv/x11.c           |   1 +
 28 files changed, 2632 insertions(+), 246 deletions(-)
 create mode 100644 hasher-priv/caller_server.c
 create mode 100644 hasher-priv/caller_task.c
 create mode 100644 hasher-priv/communication.c
 create mode 100644 hasher-priv/communication.h
 create mode 100644 hasher-priv/epoll.c
 create mode 100644 hasher-priv/epoll.h
 create mode 100644 hasher-priv/hasher-priv.c
 create mode 100644 hasher-priv/hasher-privd.c
 create mode 100644 hasher-priv/logging.c
 create mode 100644 hasher-priv/logging.h
 delete mode 100644 hasher-priv/main.c
 create mode 100644 hasher-priv/pidfile.c
 create mode 100644 hasher-priv/pidfile.h
 create mode 100644 hasher-priv/server.conf
 create mode 100644 hasher-priv/sockets.c
 create mode 100644 hasher-priv/sockets.h

diff --git a/hasher-priv/.gitignore b/hasher-priv/.gitignore
index 5ca24b0..8f0f658 100644
--- a/hasher-priv/.gitignore
+++ b/hasher-priv/.gitignore
@@ -4,6 +4,7 @@ getconf.sh
 getugid1.sh
 getugid2.sh
 hasher-priv
+hasher-privd
 hasher-priv.8
 hasher-priv.conf.5
 hasher-useradd
diff --git a/hasher-priv/DESIGN b/hasher-priv/DESIGN
index 1470ce7..310667d 100644
--- a/hasher-priv/DESIGN
+++ b/hasher-priv/DESIGN
@@ -1,35 +1,63 @@
 
-Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
+Here is a hasher-priv (euid=user,egid=hashman,gid!=egid) control flow:
 + 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 a hasher-privd (euid=root,egid=hashman,gid==egid) control flow:
++ parse command line arguments
+  + parse -h and --help options
++ set safe umask
++ check pidfile, abort if server already running
++ daemonize
+  + change current working directory to "/"
+  + close all file descriptors
++ initialize logger
++ create pidfile
++ examine and change blocked signals
++ I/O event notification and add file descriptors
+  + create a file descriptor for accepting signals
+  + create and listen server socket
++ wait for incomming caller connections
+  + handle signal if signal is received
+    + close caller's session
+  + handle connection if the caller opened a new connection
+    + get connection credentials
+    + fork new process for caller if don't have any
+    + notify the client if the session server already running
+    + close caller connection
++ close all descriptors in notification poll
++ close and remove pidfile
+
+Here is control flow for session server:
 + initialize data related to caller
-  + caller_uid initialized here from getuid()
+  + caller_uid initialized here by uid received via the socket
     + caller_uid must be valid uid
-  + caller_gid initialized here from getgid()
+  + caller_gid initialized here by uid received via the socket
     + caller_gid must be valid gid
-  + caller_user initialized here from getpwnam(LOGNAME)->pw_name or
-    getpwuid(caller_uid)->pw_name
+  + caller_user initialized here from getpwuid(caller_uid)->pw_name
     + must be true: caller_uid == pw->pw_uid
     + must be true: caller_gid == pw->pw_gid
   + caller_home initialized here
     + caller_user's home directory must exist
-+ read work limit hints from environment variables
-+ drop all environment variables
++ set safe umask
++ open a caller-specific socket
 + load configuration
   + safe chdir to /etc/hasher-priv
   + safe load "system" file
@@ -40,7 +68,7 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
       prefix
       umask
       nice
-      allowed_devices
+      allow_ttydev
       allowed_mountpoints
       rlimit_(hard|soft)_*
       wlimit_(time_elapsed|time_idle|bytes_written)
@@ -53,85 +81,136 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
       + change_user1 and change_user2 should be initialized here
   + change_uid1 and change_gid1 initialized from change_user1
   + change_uid2 and change_gid2 initialized from change_user2
-+ execute choosen task
-  + getconf: print config file /etc/hasher-priv/user.d/caller_user[:caller_num]
-  + getugid1: print change_uid1:change_gid1 pair
-  + getugid2: print change_uid2:change_gid2 pair
-  + killuid
-    + check for valid uids specified
-    + drop dumpable flag (just in case - not required)
-    + setuid to specified uid pair
-    + kill (-1, SIGKILL)
-    + purge all SYSV IPC objects belonging to specified uid pair
-  + chrootuid1/chrootuid2
-    + check for valid uid specified
-    + setup mounts and devices
-      + unshare mount namespace
-      + safe chdir to chroot_path
-      + safe chdir to dev
-      + mount dev
-      + create devices available to all users: null, zero, full, random, urandom
-      + if makedev_console environment variable is true,
-        create devices available to root only: console, tty0, fb0
-      + mount /dev/shm
-      + mount all mountpoints specified by requested_mountpoints environment variable
-      + if /dev/pts was mounted, create devices: tty, ptmx
-    + safe chdir to chroot_path
-    + sanitize file descriptors again
-    + if use_pty is disabled, create pipe to handle child's stdout and stderr
-    + if X11 forwarding is requested, create socketpair and
-      open /tmp/.X11-unix directory readonly for later use with fchdir()
-    + unless share_ipc is enabled, isolate System V IPC namespace
-    + unless share_uts is enabled, unshare UTS namespace
-    + if X11 forwarding to a tcp address was not requested,
-      unless share_network is enabled, unshare network
-    + clear supplementary group access list
-    + create pty:
-      + temporarily switch to called_uid:caller_gid
-      + open /dev/ptmx
-      + fetch pts number
-      + unlock pts pair
-      + open pts slave
-      + switch uid:gid back
-    + chroot to "."
-    + create another pty if possible:
-      + temporarily switch to called_uid:caller_gid
-      + safely chdir to "dev"
-      + if "pts/ptmx" is available with right permissions:
-        + replace "ptmx" with a symlink to "pts/ptmx"
-        + open "ptmx"
-        + fetch pts number
-        + unlock pts pair
-        + open pts slave
-      + chdir back to "/"
-      + switch uid:gid back
-      + if another pty was crated, close pts pair that was opened earlier
-    + set rlimits
-    + set close-on-exec flag on all non-standard descriptors
-    + fork
-      + in parent:
-        + setgid/setuid to caller user
-        + install CHLD signal handler
-        + unblock master pty and pipe descriptors
-        + if use_pty is enabled, initialize tty and install WINCH signal handler
-        + listen to "/dev/log"
-        + while work limits are not exceeded, handle child input/output
-        + close master pty descriptor, thus sending HUP to child session
-        + wait for child process termination
-        + remove CHLD signal handler
-        + return child proccess exit code
-      + in child:
-        + if X11 forwarding to a tcp address was requested,
++ I/O event notification and add file descriptors
+  + create a file descriptor for accepting signals
+  + create and listen the session socket
++ notify the client that the session server is ready
++ wait for incomming caller connections
+  + handle signal if signal is received
+  + handle connection if the caller opened a new connection
+    + task handler
+    + reset timeout timer
+  + finish the server if the task doesn't arrive from the caller
+    for more than a minute.
+
+Here is control flow for the task handler:
++ receive task header
+  + receive client's stdin, stdout and stderr
+  + check number of arguments
++ receive task arguments if we expect them
++ receive environment variables if client want to send them
++ fork process to handle task
+  + in parent:
+    + wait exit status
+  + in child:
+    + replace stdin, stdout and stderr with those that were received
+    + drop all environment variables
+    + apply the client's environment variables
+    + sanitize file descriptors
+      + set safe umask
+      + ensure 0,1,2 are valid file descriptors
+      + close all the rest
+    + parse task arguments
+    + check for non-zero argument list
+    + parse -h, --help and -number options
+      + caller_num initialized here
+    + parse arguments, abort if wrong
+      + chroot_path and chroot_argv are initialized here
+      + valid arguments are:
+        getconf
+        killuid
+        getugid1
+        chrootuid1 <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..82aa385 100644
--- a/hasher-priv/Makefile
+++ b/hasher-priv/Makefile
@@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec)
 HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh
 MAN5PAGES = $(PROJECT).conf.5
 MAN8PAGES = $(PROJECT).8 hasher-useradd.8
-TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
+TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
 
 sysconfdir = /etc
 libexecdir = /usr/lib
@@ -21,6 +21,7 @@ man5dir = $(mandir)/man5
 man8dir = $(mandir)/man8
 configdir = $(sysconfdir)/$(PROJECT)
 helperdir = $(libexecdir)/$(PROJECT)
+socketdir = /var/run
 DESTDIR =
 
 MKDIR_P = mkdir -p
@@ -33,17 +34,25 @@ WARNINGS = -Wall -W -Wshadow -Wpointer-arith -Wwrite-strings \
 	-Wmissing-prototypes -Wmissing-declarations -Wmissing-noreturn \
 	-Wmissing-format-attribute -Wredundant-decls -Wdisabled-optimization
 CPPFLAGS = -std=gnu99 -D_GNU_SOURCE $(CHDIRUID_FLAGS) \
-	$(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\"
+	$(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\" \
+	-DSOCKETDIR=\"$(socketdir)\" -DPROJECT=\"$(PROJECT)\"
 CFLAGS = -pipe -O2
 override CFLAGS += $(WARNINGS)
 LDLIBS =
 
-SRC = caller.c chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \
+SRC = hasher-priv.c cmdline.c fds.c sockets.c logging.c communication.c xmalloc.c pass.c
+OBJ = $(SRC:.c=.o)
+
+server_SRC = hasher-privd.c \
+	communication.c epoll.c pidfile.c logging.c sockets.c \
+	caller.c caller_server.c caller_task.c \
+	chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \
 	config.c fds.c getconf.c getugid.c ipc.c killuid.c io_log.c io_x11.c \
-	main.c makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \
+	makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \
 	unshare.c xmalloc.c x11.c
-OBJ = $(SRC:.c=.o)
-DEP = $(SRC:.c=.d)
+server_OBJ = $(server_SRC:.c=.o)
+
+DEP = $(SRC:.c=.d) $(server_SRC:.c=.d)
 
 .PHONY:	all install clean indent
 
@@ -52,14 +61,19 @@ all: $(TARGETS)
 $(PROJECT): $(OBJ)
 	$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
 
+hasher-privd: $(server_OBJ)
+	$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
+
 install: all
 	$(MKDIR_P) -m710 $(DESTDIR)$(configdir)/user.d
 	$(INSTALL) -p -m640 fstab $(DESTDIR)$(configdir)/fstab
 	$(INSTALL) -p -m640 system.conf $(DESTDIR)$(configdir)/system
+	$(INSTALL) -p -m640 server.conf $(DESTDIR)$(configdir)/server
 	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
 	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
 	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
 	$(MKDIR_P) -m755 $(DESTDIR)$(sbindir)
+	$(INSTALL) -p -m755 hasher-privd $(DESTDIR)$(sbindir)/
 	$(INSTALL) -p -m755 hasher-useradd $(DESTDIR)$(sbindir)/
 	$(MKDIR_P) -m755 $(DESTDIR)$(man5dir)
 	$(INSTALL) -p -m644 $(MAN5PAGES) $(DESTDIR)$(man5dir)/
@@ -67,7 +81,7 @@ install: all
 	$(INSTALL) -p -m644 $(MAN8PAGES) $(DESTDIR)$(man8dir)/
 
 clean:
-	$(RM) $(TARGETS) $(DEP) $(OBJ) core *~
+	$(RM) $(TARGETS) $(DEP) $(OBJ) $(server_OBJ) core *~
 
 indent:
 	indent *.h *.c
diff --git a/hasher-priv/caller.c b/hasher-priv/caller.c
index e83084a..031ddef 100644
--- a/hasher-priv/caller.c
+++ b/hasher-priv/caller.c
@@ -19,62 +19,67 @@
 
 #include "priv.h"
 #include "xmalloc.h"
+#include "logging.h"
 
-const char *caller_user, *caller_home;
-uid_t   caller_uid;
-gid_t   caller_gid;
+char *caller_user = NULL;
+char *caller_home = NULL;
+uid_t caller_uid;
+gid_t caller_gid;
 
 /*
  * Initialize caller_user, caller_uid, caller_gid and caller_home.
  */
-void
-init_caller_data(void)
+int
+init_caller_data(uid_t uid, gid_t gid)
 {
-	const char *logname;
 	struct passwd *pw = 0;
 
-	caller_uid = getuid();
-	if (caller_uid < MIN_CHANGE_UID)
-		error(EXIT_FAILURE, 0, "caller has invalid uid: %u",
-		      caller_uid);
-
-	caller_gid = getgid();
-	if (caller_gid < MIN_CHANGE_GID)
-		error(EXIT_FAILURE, 0, "caller has invalid gid: %u",
-		      caller_gid);
-
-	if ((logname = getenv("LOGNAME")))
-		if (!*logname || strchr(logname, ':'))
-			logname = 0;
-
-	if (logname)
-	{
-		pw = getpwnam(logname);
-		if (caller_uid != pw->pw_uid || caller_gid != pw->pw_gid)
-			pw = 0;
+	caller_uid = uid;
+	if (caller_uid < MIN_CHANGE_UID) {
+		err("caller has invalid uid: %u", caller_uid);
+		return -1;
+	}
+
+	caller_gid = gid;
+	if (caller_gid < MIN_CHANGE_GID) {
+		err("caller has invalid gid: %u", caller_gid);
+		return -1;
 	}
 
-	if (!pw)
-		pw = getpwuid(caller_uid);
+	pw = getpwuid(caller_uid);
 
-	if (!pw || !pw->pw_name)
-		error(EXIT_FAILURE, 0, "caller lookup failure");
+	if (!pw || !pw->pw_name) {
+		err("caller lookup failure");
+		return -1;
+	}
 
 	caller_user = xstrdup(pw->pw_name);
 
-	if (caller_uid != pw->pw_uid)
-		error(EXIT_FAILURE, 0, "caller %s: uid mismatch",
-		      caller_user);
+	if (caller_uid != pw->pw_uid) {
+		err("caller %s: uid mismatch", caller_user);
+		return -1;
+	}
 
-	if (caller_gid != pw->pw_gid)
-		error(EXIT_FAILURE, 0, "caller %s: gid mismatch",
-		      caller_user);
+	if (caller_gid != pw->pw_gid) {
+		err("caller %s: gid mismatch", caller_user);
+		return -1;
+	}
 
 	errno = 0;
 	if (pw->pw_dir && *pw->pw_dir)
 		caller_home = canonicalize_file_name(pw->pw_dir);
 
-	if (!caller_home || !*caller_home)
-		error(EXIT_FAILURE, errno, "caller %s: invalid home",
-		      caller_user);
+	if (!caller_home || !*caller_home) {
+		err("caller %s: invalid home: %m", caller_user);
+		return -1;
+	}
+
+	return 0;
+}
+
+void
+free_caller_data(void)
+{
+	free(caller_user);
+	free(caller_home);
 }
diff --git a/hasher-priv/caller_server.c b/hasher-priv/caller_server.c
new file mode 100644
index 0000000..8182c69
--- /dev/null
+++ b/hasher-priv/caller_server.c
@@ -0,0 +1,373 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
+
+  The 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->env) {
+		free(task->env[0]);
+		free(task->env);
+	}
+
+	if (task->argv) {
+		free(task->argv[0]);
+		free(task->argv);
+	}
+}
+
+static int
+cancel_task(int conn, struct task *task)
+{
+	free_task(task);
+	send_command_response(conn, CMD_STATUS_FAILED, "command failed");
+	return 0;
+}
+
+static int
+process_task(int conn)
+{
+	uid_t uid;
+	gid_t gid;
+
+	if (get_peercred(conn, NULL, &uid, &gid) < 0)
+		return -1;
+
+	if (caller_uid != uid || caller_gid != gid) {
+		err("user (uid=%d) has no permissions to send commands"
+		    " to the session of another user (uid=%d)",
+		    uid, caller_uid);
+		return -1;
+	}
+
+	struct task task = { 0 };
+	int fds[3];
+
+	while (1) {
+		task_t type = TASK_NONE;
+		struct cmd hdr = { 0 };
+
+		if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
+			return cancel_task(conn, &task);
+
+		switch (hdr.type) {
+			case CMD_TASK_BEGIN:
+				if (hdr.datalen != sizeof(type))
+					return cancel_task(conn, &task);
+
+				if (xrecvmsg(conn, &type, hdr.datalen) < 0)
+					return cancel_task(conn, &task);
+
+				task.type = type;
+
+				break;
+
+			case CMD_TASK_FDS:
+				if (hdr.datalen != sizeof(int) * 3)
+					return cancel_task(conn, &task);
+
+				if (task.stdin)
+					close(task.stdin);
+
+				if (task.stdout)
+					close(task.stdout);
+
+				if (task.stderr)
+					close(task.stderr);
+
+				if (fds_recv(conn, fds, 3) < 0)
+					return cancel_task(conn, &task);
+
+				task.stdin  = fds[0];
+				task.stdout = fds[1];
+				task.stderr = fds[2];
+
+				break;
+
+			case CMD_TASK_ARGUMENTS:
+				if (task.argv) {
+					free(task.argv[0]);
+					free(task.argv);
+				}
+
+				if (recv_list(conn, &task.argv, hdr.datalen) < 0)
+					return cancel_task(conn, &task);
+
+				if (validate_arguments(task.type, task.argv) < 0)
+					return cancel_task(conn, &task);
+
+				break;
+
+			case CMD_TASK_ENVIRON:
+				if (task.env) {
+					free(task.env[0]);
+					free(task.env);
+				}
+
+				if (recv_list(conn, &task.env, hdr.datalen) < 0)
+					return cancel_task(conn, &task);
+
+				break;
+
+			case CMD_TASK_RUN:
+				process_caller_task(conn, &task);
+				free_task(&task);
+				return 0;
+
+			default:
+				err("unsupported command: %d", hdr.type);
+		}
+
+		send_command_response(conn, CMD_STATUS_DONE, NULL);
+	}
+
+	return 0;
+}
+
+static int
+caller_server(int cl_conn)
+{
+	int ret = 0;
+
+	umask(077);
+
+	snprintf(socketpath, sizeof(socketpath),
+		"hasher-priv-%d-%u",
+		caller_uid, caller_num);
+
+	int fd_conn   = -1;
+	int fd_signal = -1;
+	int fd_ep     = -1;
+
+	if ((fd_conn = srv_listen(SOCKETDIR, socketpath)) < 0)
+		return -1;
+
+	snprintf(socketpath, sizeof(socketpath),
+		"%s/hasher-priv-%d-%u",
+		SOCKETDIR, caller_uid, caller_num);
+
+	if (chown(socketpath, caller_uid, caller_gid)) {
+		err("fchown: %s: %m", socketpath);
+		ret = -1;
+		goto fail;
+	}
+
+	/* Load config according to caller information. */
+	configure();
+
+	sigset_t mask;
+
+	sigfillset(&mask);
+	sigprocmask(SIG_SETMASK, &mask, NULL);
+
+	if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0) {
+		err("signalfd: %m");
+		ret = -1;
+		goto fail;
+	}
+
+	if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
+		err("epoll_create1: %m");
+		ret = -1;
+		goto fail;
+	}
+
+	if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0) {
+		err("epollin_add: failed");
+		ret = -1;
+		goto fail;
+	}
+
+	/* Tell client that caller server is ready */
+	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");
+					continue;
+				}
+
+				int status;
+
+				switch (fdsi.ssi_signo) {
+					case SIGINT:
+					case SIGTERM:
+						finish_server = 1;
+						break;
+					case SIGCHLD:
+						if (waitpid(-1, &status, 0) < 0) 
+							err("waitpid: %m");
+						break;
+				}
+
+			} else if (ev[i].data.fd == fd_conn) {
+				int conn;
+
+				if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
+					err("accept4: %m");
+					continue;
+				}
+
+				if (set_recv_timeout(conn, 3) < 0) {
+					close(conn);
+					continue;
+				}
+
+				if (!process_task(conn)) {
+					/* reset timer */
+					nsec = 0;
+				}
+
+				close(conn);
+			}
+		}
+	}
+fail:
+	epollin_remove(fd_ep, fd_signal);
+	epollin_remove(fd_ep, fd_conn);
+
+	if (fd_ep >= 0)
+		close(fd_ep);
+
+	unlink(socketpath);
+
+	return ret;
+}
+
+pid_t
+fork_server(int cl_conn, uid_t uid, gid_t gid, unsigned num)
+{
+	pid_t pid = fork();
+
+	if (pid != 0) {
+		if (pid < 0) {
+			err("fork: %m");
+			return -1;
+		}
+		return pid;
+	}
+
+	caller_num = num;
+
+	int ret = init_caller_data(uid, gid);
+
+	if (ret < 0) {
+		info("%s(%d) num=%u: start session server", caller_user, caller_uid, caller_num);
+		ret = caller_server(cl_conn);
+		info("%s(%d): finish session server", caller_user, caller_uid);
+	}
+
+	if (ret < 0) {
+		send_command_response(cl_conn, CMD_STATUS_FAILED, NULL);
+		ret = EXIT_FAILURE;
+	} else {
+		ret = EXIT_SUCCESS;
+	}
+
+	free_caller_data();
+	close(cl_conn);
+
+	exit(ret);
+}
diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c
new file mode 100644
index 0000000..d8f2dd5
--- /dev/null
+++ b/hasher-priv/caller_task.c
@@ -0,0 +1,214 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
+
+  The 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;
+
+	if ((pid = fork()) != 0) {
+		if (pid < 0) {
+			err("fork: %m");
+			return -1;
+		}
+		return pid;
+	}
+
+	if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
+		exit(rc);
+
+	/* cleanup environment to avoid side effects. */
+	if (clearenv() != 0)
+		fatal("clearenv: %m");
+
+	while (task->env && task->env[i]) {
+		if (putenv(task->env[i++]) != 0)
+			fatal("putenv: %m");
+	}
+
+	/* First, check and sanitize file descriptors. */
+	sanitize_fds();
+
+	/* Second, parse task arguments. */
+	parse_task_args(task->type, (const char **) task->argv);
+
+	if (chroot_path && *chroot_path != '/')
+		fatal("%s: invalid chroot path", chroot_path);
+
+	/* Fourth, parse environment for config options. */
+	parse_env();
+
+	/* We don't need environment variables any longer. */
+	if (clearenv() != 0)
+		fatal("clearenv: %m");
+
+	/* Finally, execute choosen 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 of all 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;
+
+	if ((pid = fork()) != 0) {
+		if (pid < 0) {
+			err("fork: %m");
+			return -1;
+		}
+		return 0;
+	}
+
+	if ((cpid = caller_task(task)) > 0) {
+		while (1) {
+			pid_t w;
+			int wstatus;
+
+			if ((w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED)) < 0) {
+				err("waitpid: %m");
+				break;
+			}
+
+			if (WIFEXITED(wstatus)) {
+				rc = WEXITSTATUS(wstatus);
+				info("%s: process %d exited, status=%d", task2str(task->type), cpid, WEXITSTATUS(wstatus));
+				break;
+			}
+
+			if (WIFSIGNALED(wstatus)) {
+				info("%s: process %d killed by signal %d", task2str(task->type), cpid, WTERMSIG(wstatus));
+				break;
+			}
+		}
+	}
+
+	if (task->env) {
+		free(task->env[0]);
+		free(task->env);
+	}
+
+	if (task->argv) {
+		free(task->argv[0]);
+		free(task->argv);
+	}
+
+	/* Notify client about result */
+	(rc == EXIT_FAILURE)
+		? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
+		: send_command_response(conn, CMD_STATUS_DONE, NULL);
+
+	exit(rc);
+}
diff --git a/hasher-priv/cmdline.c b/hasher-priv/cmdline.c
index 1cdfe23..aba802f 100644
--- a/hasher-priv/cmdline.c
+++ b/hasher-priv/cmdline.c
@@ -80,6 +80,8 @@ const char *chroot_path;
 const char **chroot_argv;
 unsigned caller_num;
 
+const char **task_args;
+
 static unsigned
 get_caller_num(const char *str)
 {
@@ -126,40 +128,57 @@ parse_cmdline(int argc, const char *argv[])
 	if (ac < 1)
 		show_usage("insufficient arguments");
 
+	task_args = NULL;
+
 	if (!strcmp("getconf", av[0]))
 	{
 		if (ac != 1)
 			show_usage("%s: invalid usage", av[0]);
+		task_args = av + 1;
 		return TASK_GETCONF;
 	} else if (!strcmp("killuid", av[0]))
 	{
 		if (ac != 1)
 			show_usage("%s: invalid usage", av[0]);
+		task_args = av + 1;
 		return TASK_KILLUID;
 	} else if (!strcmp("getugid1", av[0]))
 	{
 		if (ac != 1)
 			show_usage("%s: invalid usage", av[0]);
+		task_args = av + 1;
 		return TASK_GETUGID1;
 	} else if (!strcmp("chrootuid1", av[0]))
 	{
 		if (ac < 3)
 			show_usage("%s: invalid usage", av[0]);
-		chroot_path = av[1];
-		chroot_argv = av + 2;
+		task_args = av + 1;
 		return TASK_CHROOTUID1;
 	} else if (!strcmp("getugid2", av[0]))
 	{
 		if (ac != 1)
 			show_usage("%s: invalid usage", av[0]);
+		task_args = av + 1;
 		return TASK_GETUGID2;
 	} else if (!strcmp("chrootuid2", av[0]))
 	{
 		if (ac < 3)
 			show_usage("%s: invalid usage", av[0]);
-		chroot_path = av[1];
-		chroot_argv = av + 2;
+		task_args = av + 1;
 		return TASK_CHROOTUID2;
 	} else
 		show_usage("%s: invalid argument", av[0]);
 }
+
+void
+parse_task_args(task_t task, const char *argv[])
+{
+	switch (task) {
+		case TASK_CHROOTUID1:
+		case TASK_CHROOTUID2:
+			chroot_path = argv[0];
+			chroot_argv = (const char **)argv + 1;
+		default:
+			break;
+	}
+}
diff --git a/hasher-priv/communication.c b/hasher-priv/communication.c
new file mode 100644
index 0000000..77f93e1
--- /dev/null
+++ b/hasher-priv/communication.c
@@ -0,0 +1,392 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
+
+  Functions for communication between the hasher-priv and the hasher-privd.
+
+  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);
+
+char *
+task2str(task_t type)
+{
+	size_t i;
+
+	for (i = 0; i < taskmap_size; i++) {
+		if (taskmap[i].value == type)
+			return strncpy(taskbuf, taskmap[i].name, taskbuflen - 1);
+	}
+	return NULL;
+}
+
+task_t
+str2task(char *s)
+{
+	size_t i;
+
+	for (i = 0; i < taskmap_size; i++) {
+		if (!strcmp(taskmap[i].name, s))
+			return taskmap[i].value;
+	}
+	return TASK_NONE;
+}
+
+static int
+send_list(int conn, cmd_t cmd, const char **argv)
+{
+	int i = 0;
+	struct cmd hdr = { .type = cmd };
+
+	while (argv && argv[i])
+		hdr.datalen += strlen(argv[i++]) + 1;
+
+	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+		return -1;
+
+	i = 0;
+	while (argv && argv[i]) {
+		if (xsendmsg(conn, (char *) argv[i], strlen(argv[i]) + 1) < 0)
+			return -1;
+		i++;
+	}
+
+	return 0;
+}
+
+int
+recv_list(int conn, char ***argv, size_t datalen)
+{
+	char *args = calloc(1UL, datalen);
+	if (!args) {
+		err("calloc: %m");
+		return -1;
+	}
+
+	if (datalen > 0 && xrecvmsg(conn, args, datalen) < 0) {
+		free(args);
+		return -1;
+	}
+
+	if (!argv) {
+		free(args);
+		return 0;
+	}
+
+	size_t i = 0, n = 0;
+
+	while (args && n < datalen) {
+		i++;
+		n += strnlen(args + n, datalen - n) + 1;
+	}
+
+	char **av = malloc(sizeof(char *) * (i + 1));
+	if (!av) {
+		err("malloc: %m");
+		return -1;
+	}
+	av[i] = NULL;
+
+	i = n = 0;
+
+	while (args && n < datalen) {
+		av[i++] = args + n;
+		n += strnlen(args + n, datalen - n) + 1;
+	}
+
+	*argv = av;
+
+	return 0;
+}
+
+long int
+send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...)
+{
+	va_list ap;
+
+	struct cmd_resp rs = { 0 };
+	struct iovec iov = { 0 };
+	struct msghdr msg = { 0 };
+
+	rs.status = retcode;
+	rs.msglen = 0;
+
+	if (fmt && *fmt) {
+		int len;
+
+		va_start(ap, fmt);
+		len = vsnprintf(NULL, 0, fmt, ap);
+		va_end(ap);
+
+		if (len < 0) {
+			err("unable to calculate message size");
+			return -1;
+		}
+
+		rs.msglen = (size_t) len + 1;
+	}
+
+	iov.iov_base = &rs;
+	iov.iov_len  = sizeof(rs);
+
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+
+	errno = 0;
+	if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0) {
+		/* The client left without waiting for an answer */
+		if (errno == EPIPE)
+			return 0;
+
+		err("sendmsg: %m");
+		return -1;
+	}
+
+	if (!rs.msglen)
+		return 0;
+
+	msg.msg_iov[0].iov_base = malloc(rs.msglen);
+
+	if (!msg.msg_iov[0].iov_base) {
+		err("malloc: %m");
+		return -1;
+	}
+
+	msg.msg_iov[0].iov_len  = rs.msglen;
+
+	va_start(ap, fmt);
+	vsnprintf(msg.msg_iov[0].iov_base, msg.msg_iov[0].iov_len, fmt, ap);
+	va_end(ap);
+
+	long int rc = 0;
+	errno = 0;
+	if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0 && errno != EPIPE) {
+		err("sendmsg: %m");
+		rc = -1;
+	}
+
+	free(msg.msg_iov[0].iov_base);
+
+	return rc;
+}
+
+static int
+recv_command_response(int conn, cmd_status_t *retcode, char **m)
+{
+	struct cmd_resp rs = { 0 };
+
+	if (xrecvmsg(conn, &rs, sizeof(rs)) < 0)
+		return -1;
+
+	if (retcode)
+		*retcode = rs.status;
+
+	if (!m || !rs.msglen)
+		return 0;
+
+	char *x = xcalloc(1UL, rs.msglen);
+	if (xrecvmsg(conn, x, rs.msglen) < 0)
+		return -1;
+
+	*m = x;
+	return 0;
+}
+
+int
+server_command(int conn, cmd_t cmd, const char **args)
+{
+	cmd_status_t status;
+	char *msg = NULL;
+
+	if (send_list(conn, cmd, args) < 0)
+		return -1;
+
+	if (recv_command_response(conn, &status, &msg) < 0) {
+		free(msg);
+		return -1;
+	}
+
+	if (msg && *msg) {
+		err("%s", msg);
+		free(msg);
+	}
+
+	return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_open_session(const char *dir_name, const char *file_name, unsigned num)
+{
+	int conn = srv_connect(dir_name, file_name);
+
+	if (conn < 0)
+		return -1;
+
+	struct cmd hdr = {
+		.type    = CMD_OPEN_SESSION,
+		.datalen = sizeof(num),
+	};
+
+	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+		return -1;
+
+	if (xsendmsg(conn, &num, sizeof(num)) < 0)
+		return -1;
+
+	cmd_status_t status;
+	char *msg = NULL;
+
+	if (recv_command_response(conn, &status, &msg) < 0) {
+		free(msg);
+		return -1;
+	}
+
+	close(conn);
+
+	if (msg && *msg) {
+		err("%s", msg);
+		free(msg);
+	}
+
+	return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_close_session(const char *dir_name, const char *file_name, unsigned num)
+{
+	int conn = srv_connect(dir_name, file_name);
+
+	if (conn < 0)
+		return -1;
+
+	struct cmd hdr = {
+		.type    = CMD_CLOSE_SESSION,
+		.datalen = sizeof(num),
+	};
+
+	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+		return -1;
+
+	if (xsendmsg(conn, &num, sizeof(num)) < 0)
+		return -1;
+
+	cmd_status_t status;
+	char *msg = NULL;
+
+	if (recv_command_response(conn, &status, &msg) < 0) {
+		free(msg);
+		return -1;
+	}
+
+	close(conn);
+
+	if (msg && *msg) {
+		err("%s", msg);
+		free(msg);
+	}
+
+	return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_task(int conn, task_t type)
+{
+	struct cmd hdr = {
+		.type    = CMD_TASK_BEGIN,
+		.datalen = sizeof(type),
+	};
+
+	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+		return -1;
+
+	if (xsendmsg(conn, &type, hdr.datalen) < 0)
+		return -1;
+
+	cmd_status_t status;
+	char *msg = NULL;
+
+	if (recv_command_response(conn, &status, &msg) < 0) {
+		free(msg);
+		return -1;
+	}
+
+	if (msg && *msg) {
+		err("%s", msg);
+		free(msg);
+	}
+
+	return status == CMD_STATUS_FAILED ? -1 : 0;
+}
+
+int
+server_task_fds(int conn)
+{
+	cmd_status_t status;
+	char *msg = NULL;
+
+	int myfds[3] = {
+		STDIN_FILENO,
+		STDOUT_FILENO,
+		STDERR_FILENO,
+	};
+
+	struct cmd hdr = {
+		.type    = CMD_TASK_FDS,
+		.datalen = sizeof(myfds),
+	};
+
+	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
+		return -1;
+
+	if (fds_send(conn, myfds, 3) < 0)
+		return -1;
+
+	if (recv_command_response(conn, &status, &msg) < 0) {
+		free(msg);
+		return -1;
+	}
+
+	if (msg && *msg) {
+		err("%s", msg);
+		free(msg);
+	}
+
+	return status == CMD_STATUS_FAILED ? -1 : 0;
+}
diff --git a/hasher-priv/communication.h b/hasher-priv/communication.h
new file mode 100644
index 0000000..e6f1530
--- /dev/null
+++ b/hasher-priv/communication.h
@@ -0,0 +1,77 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
+
+  Functions for communication between the hasher-priv and the hasher-privd.
+
+  SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef _COMMUNICATION_H_
+#define _COMMUNICATION_H_
+
+#include <stdint.h>
+
+typedef enum {
+	CMD_NONE = 0,
+
+	/* Master commands */
+	CMD_OPEN_SESSION,
+	CMD_CLOSE_SESSION,
+
+	/* Session commands */
+	CMD_TASK_BEGIN,
+	CMD_TASK_FDS,
+	CMD_TASK_ARGUMENTS,
+	CMD_TASK_ENVIRON,
+	CMD_TASK_RUN,
+
+} cmd_t;
+
+typedef enum {
+	CMD_STATUS_DONE = 0,
+	CMD_STATUS_FAILED,
+} cmd_status_t;
+
+struct cmd {
+	cmd_t  type;
+	size_t datalen;
+};
+
+typedef enum {
+	TASK_NONE = 0,
+	TASK_GETCONF,
+	TASK_KILLUID,
+	TASK_GETUGID1,
+	TASK_CHROOTUID1,
+	TASK_GETUGID2,
+	TASK_CHROOTUID2
+} task_t;
+
+struct task {
+	int type;
+	unsigned num;
+	int stdin;
+	int stdout;
+	int stderr;
+	char **argv;
+	char **env;
+};
+
+char *task2str(task_t type);
+task_t str2task(char *s) __attribute__((nonnull(1)));
+
+int recv_list(int conn, char ***argv, size_t datalen);
+
+long int send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
+
+int server_command(int conn, cmd_t cmd, const char **args);
+int server_open_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2)));
+int server_close_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2)));
+
+int server_task(int conn, task_t task);
+int server_task_fds(int conn);
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#endif /* _COMMUNICATION_H_ */
diff --git a/hasher-priv/config.c b/hasher-priv/config.c
index e3fedcd..6b6bdb1 100644
--- a/hasher-priv/config.c
+++ b/hasher-priv/config.c
@@ -1,6 +1,7 @@
 
 /*
   Copyright (C) 2003-2019  Dmitry V. Levin <ldv@altlinux.org>
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
 
   Configuration support module for the hasher-priv program.
 
@@ -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_control_group = NULL;
+char *server_pidfile = NULL;
 const char *term;
 const char *x11_display, *x11_key;
 str_list_t allowed_devices;
@@ -33,6 +38,8 @@ str_list_t allowed_mountpoints;
 str_list_t requested_mountpoints;
 uid_t   change_uid1, change_uid2;
 gid_t   change_gid1, change_gid2;
+gid_t   server_gid;
+unsigned long server_session_timeout = 0;
 mode_t  change_umask = 022;
 int change_nice = 8;
 int     makedev_console;
@@ -42,6 +49,7 @@ int share_caller_network = 0;
 int share_ipc = -1;
 int share_network = -1;
 int share_uts = -1;
+int server_log_priority = -1;
 change_rlimit_t change_rlimit[] = {
 
 /* Per-process CPU limit, in seconds.  */
@@ -209,7 +217,7 @@ parse_rlim(const char *name, const char *value, const char *optname,
 }
 
 static unsigned long
-str2wlim(const char *name, const char *value, const char *filename)
+str2ul(const char *name, const char *value, const char *filename)
 {
 	char   *p = 0;
 	unsigned long long n;
@@ -229,7 +237,7 @@ static void
 modify_wlim(unsigned long *pval, const char *value,
 	    const char *optname, const char *filename, int is_system)
 {
-	unsigned long val = str2wlim(optname, value, filename);
+	unsigned long val = str2ul(optname, value, filename);
 
 	if (is_system || *pval == 0 || (val > 0 && val < *pval))
 		*pval = val;
@@ -633,3 +641,134 @@ parse_env(void)
 	if ((e = getenv("requested_mountpoints")))
 		parse_str_list(e, &requested_mountpoints);
 }
+
+static void
+check_server_control_group(void)
+{
+	struct group *gr;
+
+	if (!server_control_group || !*server_control_group)
+		error(EXIT_FAILURE, 0, "config: undefined: control_group");
+
+	gr = getgrnam(server_control_group);
+
+	if (!gr || !gr->gr_name)
+		error(EXIT_FAILURE, 0, "config: control_group: %s lookup failure", server_control_group);
+
+	server_gid = gr->gr_gid;
+}
+
+static void
+set_server_config(const char *name, const char *value, const char *filename)
+{
+	if (!strcasecmp("priority", name)) {
+		server_log_priority = logging_level(value);
+	} else if (!strcasecmp("session_timeout", name)) {
+		server_session_timeout = str2ul(name, value, filename);
+	} else if (!strcasecmp("pidfile", name)) {
+		free(server_pidfile);
+		server_pidfile = xstrdup(value);
+	} else if (!strcasecmp("control_group", name)) {
+		free(server_control_group);
+		server_control_group = xstrdup(value);
+	} else {
+		bad_option_name(name, filename);
+	}
+}
+
+static void
+read_server_config(int fd, const char *name)
+{
+	FILE *fp = fdopen(fd, "r");
+	char buf[BUFSIZ];
+	unsigned line;
+
+	if (!fp)
+		error(EXIT_FAILURE, errno, "fdopen: %s", name);
+
+	for (line = 1; fgets(buf, BUFSIZ, fp); ++line) {
+		const char *start, *left;
+		char   *eq, *right, *end;
+
+		for (start = buf; *start && isspace(*start); ++start)
+			;
+
+		if (!*start || '#' == *start)
+			continue;
+
+		if (!(eq = strchr(start, '=')))
+			error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
+			      name, line);
+
+		left = start;
+		right = eq + 1;
+
+		for (; eq > left; --eq)
+			if (!isspace(eq[-1]))
+				break;
+
+		if (left == eq)
+			error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
+			      name, line);
+
+		*eq = '\0';
+		end = right + strlen(right);
+
+		for (; right < end; ++right)
+			if (!isspace(*right))
+				break;
+
+		for (; end > right; --end)
+			if (!isspace(end[-1]))
+				break;
+
+		*end = '\0';
+		set_server_config(left, right, name);
+	}
+
+	if (ferror(fp))
+		error(EXIT_FAILURE, errno, "fgets: %s", name);
+
+	if (fclose(fp))
+		error(EXIT_FAILURE, errno, "fclose: %s", name);
+}
+
+static void
+load_server_config(const char *name)
+{
+	struct stat st;
+	int fd = open(name, O_RDONLY | O_NOFOLLOW | O_NOCTTY);
+
+	if (fd < 0)
+		error(EXIT_FAILURE, errno, "open: %s", name);
+
+	if (fstat(fd, &st) < 0)
+		error(EXIT_FAILURE, errno, "fstat: %s", name);
+
+	stat_root_ok_validator(&st, name);
+
+	if (!S_ISREG(st.st_mode))
+		error(EXIT_FAILURE, 0, "%s: not a regular file", name);
+
+	if (st.st_size > MAX_CONFIG_SIZE)
+		error(EXIT_FAILURE, 0, "%s: file too large: %lu",
+		      name, (unsigned long) st.st_size);
+
+	read_server_config(fd, name);
+}
+
+void
+configure_server(void)
+{
+	safe_chdir("/", stat_root_ok_validator);
+	safe_chdir("etc/hasher-priv", stat_root_ok_validator);
+	load_server_config("server");
+	check_server_control_group();
+}
+
+void
+free_server_configuration(void)
+{
+	free(server_pidfile);
+	free(server_control_group);
+}
diff --git a/hasher-priv/epoll.c b/hasher-priv/epoll.c
new file mode 100644
index 0000000..6eecd71
--- /dev/null
+++ b/hasher-priv/epoll.c
@@ -0,0 +1,39 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <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..9eb598c
--- /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, PROJECT, caller_num) < 0)
+		return EXIT_FAILURE;
+
+	/* Open user session */
+	snprintf(socketname, sizeof(socketname), "hasher-priv-%d-%u", geteuid(), caller_num);
+
+	if ((conn = srv_connect(SOCKETDIR, socketname)) < 0)
+		return EXIT_FAILURE;
+
+	if (server_task(conn, task) < 0)
+		return EXIT_FAILURE;
+
+	if (server_task_fds(conn) < 0)
+		return EXIT_FAILURE;
+
+	if (server_command(conn, CMD_TASK_ARGUMENTS, task_args) < 0)
+		return EXIT_FAILURE;
+
+	if (server_command(conn, CMD_TASK_ENVIRON, ev) < 0)
+		return EXIT_FAILURE;
+
+	if (server_command(conn, CMD_TASK_RUN, NULL) < 0)
+		return EXIT_FAILURE;
+
+	/* Close session socket. */
+	close(conn);
+
+	return EXIT_SUCCESS;
+}
diff --git a/hasher-priv/hasher-privd.c b/hasher-priv/hasher-privd.c
new file mode 100644
index 0000000..5c56033
--- /dev/null
+++ b/hasher-priv/hasher-privd.c
@@ -0,0 +1,375 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <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) {
+			send_command_response(conn, CMD_STATUS_DONE, NULL);
+			return 0;
+		}
+		a = &(*a)->next;
+	}
+
+	info("start session for %d:%u user", uid, num);
+
+	if ((server_pid = fork_server(conn, uid, gid, num)) < 0)
+		return -1;
+
+	*a = calloc(1L, sizeof(struct session));
+	if (!*a) {
+		err("calloc: %m");
+		return -1;
+	}
+
+	(*a)->caller_uid = uid;
+	(*a)->caller_gid = gid;
+	(*a)->caller_num = num;
+	(*a)->server_pid = server_pid;
+
+	return 0;
+}
+
+static int
+close_session(int conn, unsigned num)
+{
+	uid_t uid;
+	gid_t gid;
+	struct session *e = pool;
+
+	if (get_peercred(conn, NULL, &uid, &gid) < 0)
+		return -1;
+
+	while (e) {
+		if (e->caller_uid == uid && e->caller_num == num) {
+			info("close session for %d:%u user by request", uid, num);
+			if (kill(e->server_pid, SIGTERM) < 0) {
+				err("kill: %m");
+				return -1;
+			}
+			break;
+		}
+		e = e->next;
+	}
+
+	return 0;
+}
+
+static int
+process_request(int conn)
+{
+	int rc;
+	struct cmd hdr = { 0 };
+	unsigned num = 0;
+
+	if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
+		return -1;
+
+	if (hdr.datalen != sizeof(num)) {
+		err("bad command");
+		send_command_response(conn, CMD_STATUS_FAILED, "bad command");
+		return -1;
+	}
+
+	if (xrecvmsg(conn, &num, sizeof(num)) < 0)
+		return -1;
+
+	switch (hdr.type) {
+		case CMD_OPEN_SESSION:
+			rc = start_session(conn, num);
+			break;
+		case CMD_CLOSE_SESSION:
+			rc = close_session(conn, num);
+			(rc < 0)
+				? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
+				: send_command_response(conn, CMD_STATUS_DONE, NULL);
+			break;
+		default:
+			err("unknown command");
+			send_command_response(conn, CMD_STATUS_FAILED, "unknown command");
+			rc = -1;
+	}
+
+	return rc;
+}
+
+static void
+finish_sessions(int sig)
+{
+	struct session *e = pool;
+
+	while (e) {
+		if (kill(e->server_pid, sig) < 0)
+			err("kill: %m");
+		e = e->next;
+	}
+}
+
+static void
+clean_session(pid_t pid)
+{
+	struct session *x, **a = &pool;
+
+	while (a && *a) {
+		if ((*a)->server_pid == pid) {
+			x = *a;
+			*a = (*a)->next;
+			free(x);
+		}
+		a = &(*a)->next;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	int i;
+	int loglevel = -1;
+
+	const char *pidfile = NULL;
+	int daemonize = 1;
+
+	char socketpath[UNIX_PATH_MAX];
+
+	struct option long_options[] = {
+		{ "help", no_argument, 0, 'h' },
+		{ "version", no_argument, 0, 'V' },
+		{ "foreground", no_argument, 0, 'f' },
+		{ "loglevel", required_argument, 0, 'l' },
+		{ "pidfile", required_argument, 0, 'p' },
+		{ 0, 0, 0, 0 }
+	};
+
+	while ((i = getopt_long(argc, argv, "hVfl:p:", long_options, NULL)) != -1) {
+		switch (i) {
+			case 'p':
+				pidfile = optarg;
+				break;
+			case 'l':
+				loglevel = logging_level(optarg);
+				break;
+			case 'f':
+				daemonize = 0;
+				break;
+			case 'V':
+				printf("%s %s\n", program_invocation_short_name, PROJECT_VERSION);
+				return EXIT_SUCCESS;
+			default:
+			case 'h':
+				printf("Usage: %s [options]\n"
+				       " -p, --pidfile=FILE   pid file location;\n"
+				       " -l, --loglevel=LVL   set logging level;\n"
+				       " -f, --foreground     stay in the foreground;\n"
+				       " -V, --version        print program version and exit;\n"
+				       " -h, --help           show this text and exit.\n"
+				       "\n",
+				       program_invocation_short_name);
+				return EXIT_SUCCESS;
+		}
+	}
+
+	configure_server();
+
+	if (!pidfile && server_pidfile && *server_pidfile)
+		pidfile = server_pidfile;
+
+	if (loglevel < 0)
+		loglevel = (server_log_priority >= 0)
+			? server_log_priority
+			: logging_level("info");
+
+	umask(022);
+
+	if (pidfile && check_pid(pidfile))
+		error(EXIT_FAILURE, 0, "%s: already running",
+		      program_invocation_short_name);
+
+	if (daemonize && daemon(0, 0) < 0)
+		error(EXIT_FAILURE, errno, "daemon");
+
+	logging_init(loglevel, !daemonize);
+
+	if (pidfile && write_pid(pidfile) == 0)
+		return EXIT_FAILURE;
+
+	sigset_t mask;
+
+	sigfillset(&mask);
+	sigprocmask(SIG_SETMASK, &mask, NULL);
+
+	sigdelset(&mask, SIGABRT);
+	sigdelset(&mask, SIGSEGV);
+
+	int fd_ep = -1;
+
+	if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0)
+		fatal("epoll_create1: %m");
+
+	int fd_signal = -1;
+
+	if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0)
+		fatal("signalfd: %m");
+
+	mode_t m = umask(017);
+
+	int fd_conn = -1;
+
+	if ((fd_conn = srv_listen(SOCKETDIR, PROJECT)) < 0)
+		return EXIT_FAILURE;
+
+	umask(m);
+
+	snprintf(socketpath, sizeof(socketpath), "%s/%s", SOCKETDIR, PROJECT);
+
+	if (chown(socketpath, 0, server_gid))
+		fatal("fchown: %s: %m", socketpath);
+
+	if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0)
+		return EXIT_FAILURE;
+
+	int ep_timeout = -1;
+	int finish_server = 0;
+
+	while (1) {
+		struct epoll_event ev[42];
+		int fdcount;
+
+		errno = 0;
+		if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), ep_timeout)) < 0) {
+			if (errno == EINTR)
+				continue;
+			err("epoll_wait: %m");
+			break;
+		}
+
+		for (i = 0; i < fdcount; i++) {
+			if (!(ev[i].events & EPOLLIN)) {
+				continue;
+
+			} else if (ev[i].data.fd == fd_signal) {
+				struct signalfd_siginfo fdsi;
+				ssize_t size;
+
+				size = read_retry(fd_signal, &fdsi, sizeof(fdsi));
+
+				if (size != sizeof(fdsi)) {
+					err("unable to read signal info");
+					continue;
+				}
+
+				pid_t pid;
+				int status;
+
+				switch (fdsi.ssi_signo) {
+					case SIGINT:
+					case SIGTERM:
+						finish_server = 1;
+						break;
+					case SIGCHLD:
+						if ((pid = waitpid(-1, &status, 0)) < 0)
+							err("waitpid: %m");
+
+						clean_session(pid);
+						break;
+				}
+
+			} else if (ev[i].data.fd == fd_conn) {
+				int conn;
+
+				if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
+					err("accept4: %m");
+					continue;
+				}
+
+				if (set_recv_timeout(conn, 3) < 0) {
+					close(conn);
+					continue;
+				}
+
+				process_request(conn);
+				close(conn);
+			}
+		}
+
+		if (finish_server) {
+			if (!pool)
+				break;
+
+			if (fd_conn >= 0) {
+				epollin_remove(fd_ep, fd_conn);
+				fd_conn = -1;
+				ep_timeout = 3000;
+			}
+
+			finish_sessions(SIGTERM);
+		}
+	}
+
+	epollin_remove(fd_ep, fd_signal);
+	epollin_remove(fd_ep, fd_conn);
+
+	if (fd_ep >= 0)
+		close(fd_ep);
+
+	if (pidfile)
+		remove_pid(pidfile);
+
+	logging_close();
+	free_server_configuration();
+
+	return EXIT_SUCCESS;
+}
diff --git a/hasher-priv/io_log.c b/hasher-priv/io_log.c
index f5aea59..2689ff0 100644
--- a/hasher-priv/io_log.c
+++ b/hasher-priv/io_log.c
@@ -2,7 +2,7 @@
 /*
   Copyright (C) 2008-2019  Dmitry V. Levin <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..9adac47
--- /dev/null
+++ b/hasher-priv/logging.c
@@ -0,0 +1,64 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
+
+  A 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 logging_level(const char *name)
+{
+	if (!strcasecmp(name, "debug"))
+		return LOG_DEBUG;
+
+	if (!strcasecmp(name, "info"))
+		return LOG_INFO;
+
+	if (!strcasecmp(name, "warning"))
+		return LOG_WARNING;
+
+	if (!strcasecmp(name, "error"))
+		return LOG_ERR;
+
+	return 0;
+}
+
+void logging_init(int loglevel, int stderr)
+{
+	int options = LOG_PID;
+	if (stderr)
+		options |= LOG_PERROR;
+	log_priority = loglevel;
+	openlog(program_invocation_short_name, options, LOG_DAEMON);
+}
+
+void logging_close(void)
+{
+	closelog();
+}
+
+void
+message(int priority, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	if (priority <= log_priority)
+		vsyslog(priority, fmt, ap);
+	else if (log_priority < 0) {
+		vfprintf(stderr, fmt, ap);
+		fprintf(stderr, "\n");
+	}
+	va_end(ap);
+}
diff --git a/hasher-priv/logging.h b/hasher-priv/logging.h
new file mode 100644
index 0000000..9d28fc8
--- /dev/null
+++ b/hasher-priv/logging.h
@@ -0,0 +1,55 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
+
+  A 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..9e23e74
--- /dev/null
+++ b/hasher-priv/pidfile.c
@@ -0,0 +1,128 @@
+
+/*
+    pidfile.c - 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
+*/
+
+/*
+ * 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..f0eb9f9 100644
--- a/hasher-priv/priv.h
+++ b/hasher-priv/priv.h
@@ -18,16 +18,7 @@
 #define	MIN_CHANGE_GID	34
 #define	MAX_CONFIG_SIZE	16384
 
-typedef enum
-{
-	TASK_NONE = 0,
-	TASK_GETCONF,
-	TASK_KILLUID,
-	TASK_GETUGID1,
-	TASK_CHROOTUID1,
-	TASK_GETUGID2,
-	TASK_CHROOTUID2,
-} task_t;
+#include "communication.h"
 
 typedef struct
 {
@@ -68,9 +59,13 @@ void    restore_tty(void);
 int     tty_copy_winsize(int master_fd, int slave_fd);
 int     open_pty(int *slave_fd, int chrooted, int verbose_error);
 task_t  parse_cmdline(int ac, const char *av[]);
-void    init_caller_data(void);
+void    parse_task_args(task_t task, const char *argv[]);
+int     init_caller_data(uid_t uid, gid_t gid);
+void    free_caller_data(void);
 void    parse_env(void);
 void    configure(void);
+void    configure_server(void);
+void    free_server_configuration(void);
 void    ch_uid(uid_t uid, uid_t *save);
 void    ch_gid(gid_t gid, gid_t *save);
 void    chdiruid(const char *path, VALIDATE_FPTR validator);
@@ -85,6 +80,9 @@ void    stat_root_ok_validator(struct stat *st, const char *name);
 void    stat_any_ok_validator(struct stat *st, const char *name);
 void    fd_send(int ctl, int pass, const char *data, size_t len);
 int     fd_recv(int ctl, char *data, size_t data_len);
+int     fds_send(int conn, int *fds, size_t fds_len) __attribute__((nonnull(2)));
+int     fds_recv(int conn, int *fds, size_t datalen) __attribute__((nonnull(2)));
+
 int     unix_accept(int fd);
 int     log_listen(void);
 void    x11_drop_display(void);
@@ -120,9 +118,14 @@ int     do_chrootuid1(void);
 int     do_getugid2(void);
 int     do_chrootuid2(void);
 
+int process_caller_task(int, struct task *);
+pid_t fork_server(int, uid_t, gid_t, unsigned);
+
 extern const char *chroot_path;
 extern const char **chroot_argv;
 
+extern const char **task_args;
+
 extern str_list_t allowed_devices;
 extern str_list_t allowed_mountpoints;
 extern str_list_t requested_mountpoints;
@@ -143,7 +146,7 @@ extern int log_fd;
 
 extern const char *const *chroot_prefix_list;
 extern const char *chroot_prefix_path;
-extern const char *caller_user, *caller_home;
+extern char *caller_user, *caller_home;
 extern uid_t caller_uid;
 extern gid_t caller_gid;
 extern unsigned caller_num;
@@ -156,4 +159,10 @@ extern int change_nice;
 extern change_rlimit_t change_rlimit[];
 extern work_limit_t wlimit;
 
+extern int server_log_priority;
+extern unsigned long server_session_timeout;
+extern char *server_control_group;
+extern char *server_pidfile;
+extern gid_t server_gid;
+
 #endif /* PKG_BUILD_PRIV_H */
diff --git a/hasher-priv/server.conf b/hasher-priv/server.conf
new file mode 100644
index 0000000..53ea5c3
--- /dev/null
+++ b/hasher-priv/server.conf
@@ -0,0 +1,13 @@
+# Server configuration
+
+# Set the default logging priority. (can override with command line arguments)
+priority=info
+
+# Write a pid file. (can override with command line arguments)
+pidfile=/var/run/hasher-privd.pid
+
+# Stop user's session server after {session_timeout} seconds of inactivity.
+session_timeout=3600
+
+# Allow users of this group to interact with hasher-privd via the control socket.
+control_group=hashman
diff --git a/hasher-priv/sockets.c b/hasher-priv/sockets.c
new file mode 100644
index 0000000..42618a7
--- /dev/null
+++ b/hasher-priv/sockets.c
@@ -0,0 +1,183 @@
+
+/*
+  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
+
+  A сollection of helpers to simplify work with 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..5acdb49
--- /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 work with 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.24.0



^ permalink raw reply	[flat|nested] 52+ messages in thread

* [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
@ 2019-12-13 11:42 ` Alex Gladkov
  2020-06-17 22:31   ` Mikhail Novosyolov
  2020-09-17 13:10   ` Arseny Maslennikov
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 3/3] Add cgroup support Alex Gladkov
                   ` (4 subsequent siblings)
  6 siblings, 2 replies; 52+ messages in thread
From: Alex Gladkov @ 2019-12-13 11:42 UTC (permalink / raw)
  To: ldv; +Cc: devel

From: Alexey Gladkov <legion@altlinux.org>

Signed-off-by: Alexey Gladkov <legion@altlinux.org>
---
 hasher-priv/Makefile              |  4 ++
 hasher-priv/hasher-privd.service  | 11 ++++
 hasher-priv/hasher-privd.sysvinit | 86 +++++++++++++++++++++++++++++++
 3 files changed, 101 insertions(+)
 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 82aa385..c73216f 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
@@ -72,6 +74,8 @@ install: all
 	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
 	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
 	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
+	$(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-privd.service b/hasher-priv/hasher-privd.service
new file mode 100644
index 0000000..e5ed9ac
--- /dev/null
+++ b/hasher-priv/hasher-privd.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=A privileged helper for the hasher project
+ConditionVirtualization=!container
+Documentation=man:hasher-priv(8)
+
+[Service]
+ExecStart=/usr/sbin/hasher-privd
+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..914fb53
--- /dev/null
+++ b/hasher-priv/hasher-privd.sysvinit
@@ -0,0 +1,86 @@
+#! /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"
+RETVAL=0
+
+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)
+		start
+		;;
+	stop)
+		stop
+		;;
+	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.24.0



^ permalink raw reply	[flat|nested] 52+ messages in thread

* [devel] [PATCH hasher-priv v1 3/3] Add cgroup support
  2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files Alex Gladkov
@ 2019-12-13 11:42 ` Alex Gladkov
  2020-09-17 13:11   ` Arseny Maslennikov
  2019-12-15  8:50 ` [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alexey Tourbin
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 52+ messages in thread
From: Alex Gladkov @ 2019-12-13 11:42 UTC (permalink / raw)
  To: ldv; +Cc: devel

From: Alexey Gladkov <legion@altlinux.org>

Signed-off-by: Alexey Gladkov <legion@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 c73216f..e999972 100644
--- a/hasher-priv/Makefile
+++ b/hasher-priv/Makefile
@@ -51,7 +51,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 d8f2dd5..722e0a6 100644
--- a/hasher-priv/caller_task.c
+++ b/hasher-priv/caller_task.c
@@ -95,6 +95,9 @@ caller_task(struct task *task)
 		return pid;
 	}
 
+	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 6b6bdb1..3faf936 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_control_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("control_group", name)) {
 		free(server_control_group);
 		server_control_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_control_group);
+	free(server_cgroup_template);
 }
diff --git a/hasher-priv/priv.h b/hasher-priv/priv.h
index f0eb9f9..f29603a 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_control_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 53ea5c3..9e70487 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.
 control_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.24.0



^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
                   ` (2 preceding siblings ...)
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 3/3] Add cgroup support Alex Gladkov
@ 2019-12-15  8:50 ` Alexey Tourbin
  2019-12-15 23:33   ` Andrey Savchenko
  2019-12-16  9:35   ` Dmitry V. Levin
  2020-03-16 10:34 ` Alexey Gladkov
                   ` (2 subsequent siblings)
  6 siblings, 2 replies; 52+ messages in thread
From: Alexey Tourbin @ 2019-12-15  8:50 UTC (permalink / raw)
  To: ALT Linux Team development discussions

On Fri, Dec 13, 2019 at 2:42 PM Alex Gladkov <legion@altlinux.ru> wrote:
> The hasher-priv is a SUID utility. This is not good. Separation of the
> server and client parts will allow us to remove SUID flag.

Removing the SUID flag shouldn't be an end in itself. You're still
running a process with root privileges which serves user requests.
It's the same, except that instead of the SUID flag, the process just
starts as root.  So you are not improving privilege separation or
something, you are only limiting the ability of the user to tamper
with the SUID binary. And tampering with the binary should be
pointless anyway (unless glibc is faulty and permits arbitrary code
injection, etc.).


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2019-12-15  8:50 ` [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alexey Tourbin
@ 2019-12-15 23:33   ` Andrey Savchenko
  2019-12-16  9:35   ` Dmitry V. Levin
  1 sibling, 0 replies; 52+ messages in thread
From: Andrey Savchenko @ 2019-12-15 23:33 UTC (permalink / raw)
  To: ALT Linux Team development discussions

[-- Attachment #1: Type: text/plain, Size: 1236 bytes --]

On Sun, 15 Dec 2019 11:50:13 +0300 Alexey Tourbin wrote:
> On Fri, Dec 13, 2019 at 2:42 PM Alex Gladkov <legion@altlinux.ru> wrote:
> > The hasher-priv is a SUID utility. This is not good. Separation of the
> > server and client parts will allow us to remove SUID flag.
> 
> Removing the SUID flag shouldn't be an end in itself. You're still
> running a process with root privileges which serves user requests.
> It's the same, except that instead of the SUID flag, the process just
> starts as root.  So you are not improving privilege separation or
> something, you are only limiting the ability of the user to tamper
> with the SUID binary. And tampering with the binary should be
> pointless anyway (unless glibc is faulty and permits arbitrary code
> injection, etc.).

The code separation for the privileged and the unprivileged
processes allows to reduce the attack surface when implemented
properly. Furthermore it should be possible to replace the SUID by
the Linux capabilities in future — so the code/process separation
makes even more sense here as it will lead to a smaller number of
capabilities required.

I have not reviewed this code yet, but I like the idea.

Best regards,
Andrew Savchenko

[-- Attachment #2: Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2019-12-15  8:50 ` [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alexey Tourbin
  2019-12-15 23:33   ` Andrey Savchenko
@ 2019-12-16  9:35   ` Dmitry V. Levin
  2019-12-29 11:03     ` Alexey Tourbin
  1 sibling, 1 reply; 52+ messages in thread
From: Dmitry V. Levin @ 2019-12-16  9:35 UTC (permalink / raw)
  To: ALT Devel discussion list

[-- Attachment #1: Type: text/plain, Size: 1372 bytes --]

On Sun, Dec 15, 2019 at 11:50:13AM +0300, Alexey Tourbin wrote:
> On Fri, Dec 13, 2019 at 2:42 PM Alex Gladkov <legion@altlinux.ru> wrote:
> > The hasher-priv is a SUID utility. This is not good. Separation of the
> > server and client parts will allow us to remove SUID flag.
> 
> Removing the SUID flag shouldn't be an end in itself. You're still
> running a process with root privileges which serves user requests.
> It's the same, except that instead of the SUID flag, the process just
> starts as root.  So you are not improving privilege separation or
> something, you are only limiting the ability of the user to tamper
> with the SUID binary. And tampering with the binary should be
> pointless anyway (unless glibc is faulty and permits arbitrary code
> injection, etc.).

While turning a suid root executable into a daemon doesn't automagically
make everything more secure, it's an important move in the right direction.

Firstly, the attack surface of a suid root executable is larger than
of the equivalent root daemon on the other side of a unix domain socket,
so this change narrows the attack surface.

Secondly, this change opens the way for more elaborate privilege separation.

Thirdly, it makes hasher available for PR_SET_NO_NEW_PRIVS'ed
processes (e.g. self-seccomp'ed) that cannot make use of suid executables.


-- 
ldv

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2019-12-16  9:35   ` Dmitry V. Levin
@ 2019-12-29 11:03     ` Alexey Tourbin
  0 siblings, 0 replies; 52+ messages in thread
From: Alexey Tourbin @ 2019-12-29 11:03 UTC (permalink / raw)
  To: ALT Linux Team development discussions

On Mon, Dec 16, 2019 at 12:35 PM Dmitry V. Levin <ldv@altlinux.org> wrote:
> On Sun, Dec 15, 2019 at 11:50:13AM +0300, Alexey Tourbin wrote:
> > On Fri, Dec 13, 2019 at 2:42 PM Alex Gladkov <legion@altlinux.ru> wrote:
> > > The hasher-priv is a SUID utility. This is not good. Separation of the
> > > server and client parts will allow us to remove SUID flag.
> >
> > Removing the SUID flag shouldn't be an end in itself. You're still
> > running a process with root privileges which serves user requests.
> > It's the same, except that instead of the SUID flag, the process just
> > starts as root.  So you are not improving privilege separation or
> > something, you are only limiting the ability of the user to tamper
> > with the SUID binary. And tampering with the binary should be
> > pointless anyway (unless glibc is faulty and permits arbitrary code
> > injection, etc.).
>
> While turning a suid root executable into a daemon doesn't automagically
> make everything more secure, it's an important move in the right direction.

Not necessarily. Conversion into a daemon takes more code, which can
have its own faults. Instead of relying on the set-uid mechanism,
you're very likely to up end up with a more complex DIY construction.

> Firstly, the attack surface of a suid root executable is larger than
> of the equivalent root daemon on the other side of a unix domain socket,
> so this change narrows the attack surface.

You are casting doubt on the venerable set-uid mechanism. What if it's
faulty? What if the user can tamper with the binary and somehow inject
arbitrary code? Well, you can do nothing about it, and moreover it's
not your problem. (Likewise, if the kernel is faulty and permits
privilege escalation, you can do nothing about it, and the only way
round is to fix the kernel.) Your basic mechanisms must be secure, and
it's doable. The "attack surface" is just a highbrow way of saying
that the dynamic loader should be insensitive to LD_PRELOAD. :)

> Secondly, this change opens the way for more elaborate privilege separation.
>
> Thirdly, it makes hasher available for PR_SET_NO_NEW_PRIVS'ed
> processes (e.g. self-seccomp'ed) that cannot make use of suid executables.

These might be valid arguments. Still, I find it hard to believe it's
really about security. hasher-priv is minimalistic, and its use is
limited to those few machines that need it, some of them booted over
the network. There is no good reason to believe that we might face any
security risks.


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
                   ` (3 preceding siblings ...)
  2019-12-15  8:50 ` [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alexey Tourbin
@ 2020-03-16 10:34 ` Alexey Gladkov
  2020-06-17 22:01 ` Alexey Gladkov
  2020-09-17 13:09 ` Arseny Maslennikov
  6 siblings, 0 replies; 52+ messages in thread
From: Alexey Gladkov @ 2020-03-16 10:34 UTC (permalink / raw)
  To: ldv; +Cc: devel

On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion@altlinux.org>
> 
> The hasher-priv is a SUID utility. This is not good. Separation of the
> server and client parts will allow us to remove SUID flag.
> 
> The separation of server and client is not intended to give clients
> access over the network. This separation is only necessary to distinguish
> privileges. Only UNIX domain socket is used.
> 
> A separate session process is created for each connected user. Each such
> process ends after a certain period of inactivity.

Gently remind about patches.

> Alexey Gladkov (3):
>   Make a daemon from the hasher-priv
>   Add systemd and sysvinit service files
>   Add cgroup support
> 
>  hasher-priv/.gitignore            |   1 +
>  hasher-priv/DESIGN                | 281 +++++++++++++--------
>  hasher-priv/Makefile              |  34 ++-
>  hasher-priv/caller.c              |  81 +++---
>  hasher-priv/caller_server.c       | 373 ++++++++++++++++++++++++++++
>  hasher-priv/caller_task.c         | 217 +++++++++++++++++
>  hasher-priv/cgroup.c              | 119 +++++++++
>  hasher-priv/cmdline.c             |  27 +-
>  hasher-priv/communication.c       | 392 ++++++++++++++++++++++++++++++
>  hasher-priv/communication.h       |  77 ++++++
>  hasher-priv/config.c              | 148 ++++++++++-
>  hasher-priv/epoll.c               |  39 +++
>  hasher-priv/epoll.h               |  18 ++
>  hasher-priv/hasher-priv.c         |  78 ++++++
>  hasher-priv/hasher-privd.c        | 375 ++++++++++++++++++++++++++++
>  hasher-priv/hasher-privd.service  |  11 +
>  hasher-priv/hasher-privd.sysvinit |  86 +++++++
>  hasher-priv/io_log.c              |   2 +-
>  hasher-priv/io_x11.c              |   2 +-
>  hasher-priv/killuid.c             |   2 +-
>  hasher-priv/logging.c             |  64 +++++
>  hasher-priv/logging.h             |  55 +++++
>  hasher-priv/main.c                |  75 ------
>  hasher-priv/pass.c                | 117 ++++++++-
>  hasher-priv/pidfile.c             | 128 ++++++++++
>  hasher-priv/pidfile.h             |  44 ++++
>  hasher-priv/priv.h                |  35 ++-
>  hasher-priv/server.conf           |  22 ++
>  hasher-priv/sockets.c             | 183 ++++++++++++++
>  hasher-priv/sockets.h             |  32 +++
>  hasher-priv/x11.c                 |   1 +
>  31 files changed, 2872 insertions(+), 247 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.24.0

-- 
Rgrds, legion



^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
                   ` (4 preceding siblings ...)
  2020-03-16 10:34 ` Alexey Gladkov
@ 2020-06-17 22:01 ` Alexey Gladkov
  2020-09-17 13:09 ` Arseny Maslennikov
  6 siblings, 0 replies; 52+ messages in thread
From: Alexey Gladkov @ 2020-06-17 22:01 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion@altlinux.org>

ping

> The hasher-priv is a SUID utility. This is not good. Separation of the
> server and client parts will allow us to remove SUID flag.
> 
> The separation of server and client is not intended to give clients
> access over the network. This separation is only necessary to distinguish
> privileges. Only UNIX domain socket is used.
> 
> A separate session process is created for each connected user. Each such
> process ends after a certain period of inactivity.
> 
> Alexey Gladkov (3):
>   Make a daemon from the hasher-priv
>   Add systemd and sysvinit service files
>   Add cgroup support
> 
>  hasher-priv/.gitignore            |   1 +
>  hasher-priv/DESIGN                | 281 +++++++++++++--------
>  hasher-priv/Makefile              |  34 ++-
>  hasher-priv/caller.c              |  81 +++---
>  hasher-priv/caller_server.c       | 373 ++++++++++++++++++++++++++++
>  hasher-priv/caller_task.c         | 217 +++++++++++++++++
>  hasher-priv/cgroup.c              | 119 +++++++++
>  hasher-priv/cmdline.c             |  27 +-
>  hasher-priv/communication.c       | 392 ++++++++++++++++++++++++++++++
>  hasher-priv/communication.h       |  77 ++++++
>  hasher-priv/config.c              | 148 ++++++++++-
>  hasher-priv/epoll.c               |  39 +++
>  hasher-priv/epoll.h               |  18 ++
>  hasher-priv/hasher-priv.c         |  78 ++++++
>  hasher-priv/hasher-privd.c        | 375 ++++++++++++++++++++++++++++
>  hasher-priv/hasher-privd.service  |  11 +
>  hasher-priv/hasher-privd.sysvinit |  86 +++++++
>  hasher-priv/io_log.c              |   2 +-
>  hasher-priv/io_x11.c              |   2 +-
>  hasher-priv/killuid.c             |   2 +-
>  hasher-priv/logging.c             |  64 +++++
>  hasher-priv/logging.h             |  55 +++++
>  hasher-priv/main.c                |  75 ------
>  hasher-priv/pass.c                | 117 ++++++++-
>  hasher-priv/pidfile.c             | 128 ++++++++++
>  hasher-priv/pidfile.h             |  44 ++++
>  hasher-priv/priv.h                |  35 ++-
>  hasher-priv/server.conf           |  22 ++
>  hasher-priv/sockets.c             | 183 ++++++++++++++
>  hasher-priv/sockets.h             |  32 +++
>  hasher-priv/x11.c                 |   1 +
>  31 files changed, 2872 insertions(+), 247 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.24.0
> 
> _______________________________________________
> Devel mailing list
> Devel@lists.altlinux.org
> https://lists.altlinux.org/mailman/listinfo/devel

-- 
Rgrds, legion



^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files Alex Gladkov
@ 2020-06-17 22:31   ` Mikhail Novosyolov
  2020-06-17 22:38     ` Mikhail Novosyolov
  2020-06-17 22:43     ` Alexey Gladkov
  2020-09-17 13:10   ` Arseny Maslennikov
  1 sibling, 2 replies; 52+ messages in thread
From: Mikhail Novosyolov @ 2020-06-17 22:31 UTC (permalink / raw)
  To: devel; +Cc: Alex Gladkov

13.12.2019 14:42, Alex Gladkov пишет:
> From: Alexey Gladkov <legion@altlinux.org>
>
> Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> ---
>  hasher-priv/Makefile              |  4 ++
>  hasher-priv/hasher-privd.service  | 11 ++++
>  hasher-priv/hasher-privd.sysvinit | 86 +++++++++++++++++++++++++++++++
>  3 files changed, 101 insertions(+)
>  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 82aa385..c73216f 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
> @@ -72,6 +74,8 @@ install: all
>  	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
>  	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
>  	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
> +	$(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-privd.service b/hasher-priv/hasher-privd.service
> new file mode 100644
> index 0000000..e5ed9ac
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.service
> @@ -0,0 +1,11 @@
> +[Unit]
> +Description=A privileged helper for the hasher project
> +ConditionVirtualization=!container
А если контейнеру выданы нужные привелегии/capabilities, то почему нельзя? Может, лучше ConditionCapability=?
> +Documentation=man:hasher-priv(8)
> +
> +[Service]
> +ExecStart=/usr/sbin/hasher-privd
> +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..914fb53
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.sysvinit
> @@ -0,0 +1,86 @@
> +#! /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"
> +RETVAL=0
> +
> +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)
> +		start
> +		;;
> +	stop)
> +		stop
> +		;;
> +	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


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2020-06-17 22:31   ` Mikhail Novosyolov
@ 2020-06-17 22:38     ` Mikhail Novosyolov
  2020-06-17 22:50       ` Alexey Gladkov
  2020-06-17 22:43     ` Alexey Gladkov
  1 sibling, 1 reply; 52+ messages in thread
From: Mikhail Novosyolov @ 2020-06-17 22:38 UTC (permalink / raw)
  To: devel; +Cc: Alex Gladkov

18.06.2020 01:31, Mikhail Novosyolov пишет:
> 13.12.2019 14:42, Alex Gladkov пишет:
>> diff --git a/hasher-priv/hasher-privd.service b/hasher-priv/hasher-privd.service
>> new file mode 100644
>> index 0000000..e5ed9ac
>> --- /dev/null
>> +++ b/hasher-priv/hasher-privd.service
>> @@ -0,0 +1,11 @@
>> +[Unit]
>> +Description=A privileged helper for the hasher project
>> +ConditionVirtualization=!container
> А если контейнеру выданы нужные привелегии/capabilities, то почему нельзя? Может, лучше ConditionCapability=?

...или вообще без таких проверок условий, не смог запуститься - написал ошибку в явном виде

P.S. Какие capabilities нужны, чтобы hsh работал в systemd-nspawn?




^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2020-06-17 22:31   ` Mikhail Novosyolov
  2020-06-17 22:38     ` Mikhail Novosyolov
@ 2020-06-17 22:43     ` Alexey Gladkov
  2020-06-17 22:53       ` Mikhail Novosyolov
  1 sibling, 1 reply; 52+ messages in thread
From: Alexey Gladkov @ 2020-06-17 22:43 UTC (permalink / raw)
  To: Mikhail Novosyolov; +Cc: devel

On Thu, Jun 18, 2020 at 01:31:38AM +0300, Mikhail Novosyolov wrote:
> 13.12.2019 14:42, Alex Gladkov пишет:
> > From: Alexey Gladkov <legion@altlinux.org>
> >
> > Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> > ---
> >  hasher-priv/Makefile              |  4 ++
> >  hasher-priv/hasher-privd.service  | 11 ++++
> >  hasher-priv/hasher-privd.sysvinit | 86 +++++++++++++++++++++++++++++++
> >  3 files changed, 101 insertions(+)
> >  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 82aa385..c73216f 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
> > @@ -72,6 +74,8 @@ install: all
> >  	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
> >  	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
> >  	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
> > +	$(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-privd.service b/hasher-priv/hasher-privd.service
> > new file mode 100644
> > index 0000000..e5ed9ac
> > --- /dev/null
> > +++ b/hasher-priv/hasher-privd.service
> > @@ -0,0 +1,11 @@
> > +[Unit]
> > +Description=A privileged helper for the hasher project
> > +ConditionVirtualization=!container
> А если контейнеру выданы нужные привелегии/capabilities, то почему нельзя? Может, лучше ConditionCapability=?

Я не специалист в systemd. Я не смогу написать ConditionCapability
правильно. Я брал за основу какой-то другой сервис. Если вы считаете, что
это критично, то могу убрать этот сервис вообще.

> > +Documentation=man:hasher-priv(8)
> > +
> > +[Service]
> > +ExecStart=/usr/sbin/hasher-privd
> > +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..914fb53
> > --- /dev/null
> > +++ b/hasher-priv/hasher-privd.sysvinit
> > @@ -0,0 +1,86 @@
> > +#! /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"
> > +RETVAL=0
> > +
> > +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)
> > +		start
> > +		;;
> > +	stop)
> > +		stop
> > +		;;
> > +	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
> 

-- 
Rgrds, legion



^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2020-06-17 22:38     ` Mikhail Novosyolov
@ 2020-06-17 22:50       ` Alexey Gladkov
  0 siblings, 0 replies; 52+ messages in thread
From: Alexey Gladkov @ 2020-06-17 22:50 UTC (permalink / raw)
  To: Mikhail Novosyolov; +Cc: devel

On Thu, Jun 18, 2020 at 01:38:36AM +0300, Mikhail Novosyolov wrote:
> 18.06.2020 01:31, Mikhail Novosyolov пишет:
> > 13.12.2019 14:42, Alex Gladkov пишет:
> >> diff --git a/hasher-priv/hasher-privd.service b/hasher-priv/hasher-privd.service
> >> new file mode 100644
> >> index 0000000..e5ed9ac
> >> --- /dev/null
> >> +++ b/hasher-priv/hasher-privd.service
> >> @@ -0,0 +1,11 @@
> >> +[Unit]
> >> +Description=A privileged helper for the hasher project
> >> +ConditionVirtualization=!container
> > А если контейнеру выданы нужные привелегии/capabilities, то почему нельзя? Может, лучше ConditionCapability=?
> 
> ...или вообще без таких проверок условий, не смог запуститься - написал ошибку в явном виде
> 
> P.S. Какие capabilities нужны, чтобы hsh работал в systemd-nspawn?

Так как hasher может создавать namespace, то нужен CAP_SYS_ADMIN.

-- 
Rgrds, legion



^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2020-06-17 22:43     ` Alexey Gladkov
@ 2020-06-17 22:53       ` Mikhail Novosyolov
  0 siblings, 0 replies; 52+ messages in thread
From: Mikhail Novosyolov @ 2020-06-17 22:53 UTC (permalink / raw)
  To: Alexey Gladkov; +Cc: devel

18.06.2020 01:43, Alexey Gladkov пишет:
> On Thu, Jun 18, 2020 at 01:31:38AM +0300, Mikhail Novosyolov wrote:
>> 13.12.2019 14:42, Alex Gladkov пишет:
>>> From: Alexey Gladkov <legion@altlinux.org>
>>>
>>> Signed-off-by: Alexey Gladkov <legion@altlinux.org>
>>> ---
>>>  hasher-priv/Makefile              |  4 ++
>>>  hasher-priv/hasher-privd.service  | 11 ++++
>>>  hasher-priv/hasher-privd.sysvinit | 86 +++++++++++++++++++++++++++++++
>>>  3 files changed, 101 insertions(+)
>>>  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 82aa385..c73216f 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
>>> @@ -72,6 +74,8 @@ install: all
>>>  	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
>>>  	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
>>>  	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
>>> +	$(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-privd.service b/hasher-priv/hasher-privd.service
>>> new file mode 100644
>>> index 0000000..e5ed9ac
>>> --- /dev/null
>>> +++ b/hasher-priv/hasher-privd.service
>>> @@ -0,0 +1,11 @@
>>> +[Unit]
>>> +Description=A privileged helper for the hasher project
>>> +ConditionVirtualization=!container
>> А если контейнеру выданы нужные привелегии/capabilities, то почему нельзя? Может, лучше ConditionCapability=?
> Я не специалист в systemd. Я не смогу написать ConditionCapability
> правильно. Я брал за основу какой-то другой сервис. Если вы считаете, что
> это критично, то могу убрать этот сервис вообще.
Критичным не считаю, я вообще не очень в курсе, как работает хешер, мимо проходил, но эту строку бы убрал из сервиса systemd.



^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
                   ` (5 preceding siblings ...)
  2020-06-17 22:01 ` Alexey Gladkov
@ 2020-09-17 13:09 ` Arseny Maslennikov
  2020-10-01 17:21   ` Alexey Gladkov
  6 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:09 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 4527 bytes --]

On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion@altlinux.org>
> 
> The hasher-priv is a SUID utility. This is not good. Separation of the
> server and client parts will allow us to remove SUID flag.
> 
> The separation of server and client is not intended to give clients
> access over the network. This separation is only necessary to distinguish
> privileges. Only UNIX domain socket is used.
> 
> A separate session process is created for each connected user. Each such
> process ends after a certain period of inactivity.

Thank you for trying this idea out; despite the trolling attempts, this
effort is long welcome.

There are some issues with the patchset, which I intend to cover in
subsequent emails. I have published[1] some fix-up commits on top of
these patches in an attempt to ensure that, barring the issues with a
known fix, this works; however, some bugs are definitely still unsolved
by now, so I decided to discuss the more apparent points first.

[1] http://git.altlinux.org/people/arseny/packages/hasher-priv.git?a=summary

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.
I've not yet found a decent reproducer — the following command:
`hsh-shell $workdir'
reproduces the issue reliably for me, but hsh-mkchroot, hsh-rmchroot,
hsh-install are all OK. The root cause nevertheless is not yet
established. It looks like this has to be patched somewhere in
chrootuid(), but I might be wrong on this one.

> 
> Alexey Gladkov (3):
>   Make a daemon from the hasher-priv
>   Add systemd and sysvinit service files
>   Add cgroup support
> 
>  hasher-priv/.gitignore            |   1 +
>  hasher-priv/DESIGN                | 281 +++++++++++++--------
>  hasher-priv/Makefile              |  34 ++-
>  hasher-priv/caller.c              |  81 +++---
>  hasher-priv/caller_server.c       | 373 ++++++++++++++++++++++++++++
>  hasher-priv/caller_task.c         | 217 +++++++++++++++++
>  hasher-priv/cgroup.c              | 119 +++++++++
>  hasher-priv/cmdline.c             |  27 +-
>  hasher-priv/communication.c       | 392 ++++++++++++++++++++++++++++++
>  hasher-priv/communication.h       |  77 ++++++
>  hasher-priv/config.c              | 148 ++++++++++-
>  hasher-priv/epoll.c               |  39 +++
>  hasher-priv/epoll.h               |  18 ++
>  hasher-priv/hasher-priv.c         |  78 ++++++
>  hasher-priv/hasher-privd.c        | 375 ++++++++++++++++++++++++++++
>  hasher-priv/hasher-privd.service  |  11 +
>  hasher-priv/hasher-privd.sysvinit |  86 +++++++
>  hasher-priv/io_log.c              |   2 +-
>  hasher-priv/io_x11.c              |   2 +-
>  hasher-priv/killuid.c             |   2 +-
>  hasher-priv/logging.c             |  64 +++++
>  hasher-priv/logging.h             |  55 +++++
>  hasher-priv/main.c                |  75 ------
>  hasher-priv/pass.c                | 117 ++++++++-
>  hasher-priv/pidfile.c             | 128 ++++++++++
>  hasher-priv/pidfile.h             |  44 ++++
>  hasher-priv/priv.h                |  35 ++-
>  hasher-priv/server.conf           |  22 ++
>  hasher-priv/sockets.c             | 183 ++++++++++++++
>  hasher-priv/sockets.h             |  32 +++
>  hasher-priv/x11.c                 |   1 +
>  31 files changed, 2872 insertions(+), 247 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
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Make a daemon from the hasher-priv
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
@ 2020-09-17 13:10   ` Arseny Maslennikov
  2020-10-01 19:43     ` Alexey Gladkov
  2020-09-17 13:10   ` [devel] [PATCH hasher-priv v1 1/3] *literacy* Arseny Maslennikov
                     ` (7 subsequent siblings)
  8 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:10 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 4487 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion@altlinux.org>
> 
> All privileged operations moved to the daemon. Commands to the server
> are sent through the unix domain socket. The credentials which the sender
> specifies are checked by the kernel. The hasher-priv no longer SUID.

I'm going to suggest some English literacy/typo improvements in a separate
email.

> 
> For each user server creates a separate session process that executes
> commands only from the user who created it. The session process ends

Since that new process will still be privileged, why the additional fork
and the new listening socket inode? Is the strive for less complex
daemon source code the only driver for that, albeit a perfectly viable
one?

> after a certain period of inactivity.

No problem with that, but IMO we still should be careful
about resource leaks.

> 



> Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> ---
>  hasher-priv/.gitignore      |   1 +
>  hasher-priv/DESIGN          | 281 ++++++++++++++++----------

I'd like to see a formal description of the client-server protocol in e.
g. hasher-priv/IPC, that shows the intended message exchanges and
meanings. The message contents can be rather easily inferred from the C
headers, but the semantics cannot. This ends up as a source of
programming errors, see the other emails.

The relevant information is probably already present in the DESIGN file,
but, as this is documentation for humans, we should wrap it into a form
that's easier to comprehend without wasting much time.

I'm also worried about the message format using plain C ABI structs.
We're not going to use it over the network on machines with different
architectures, and we're never going to support a client and server from
different package builds, sure. But is this enough of a justification?

>  hasher-priv/Makefile        |  28 ++-
>  hasher-priv/caller.c        |  81 ++++----

After reading the following 2 new files...

>  hasher-priv/caller_server.c | 373 ++++++++++++++++++++++++++++++++++
>  hasher-priv/caller_task.c   | 214 ++++++++++++++++++++

..., I'm not sure why do they have to be separate.
They can even be merged with caller.c, perhaps.

>  hasher-priv/cmdline.c       |  27 ++-
>  hasher-priv/communication.c | 392 ++++++++++++++++++++++++++++++++++++
>  hasher-priv/communication.h |  77 +++++++
>  hasher-priv/config.c        | 143 ++++++++++++-
>  hasher-priv/epoll.c         |  39 ++++
>  hasher-priv/epoll.h         |  18 ++
>  hasher-priv/hasher-priv.c   |  78 +++++++
>  hasher-priv/hasher-privd.c  | 375 ++++++++++++++++++++++++++++++++++
>  hasher-priv/io_log.c        |   2 +-
>  hasher-priv/io_x11.c        |   2 +-
>  hasher-priv/killuid.c       |   2 +-
>  hasher-priv/logging.c       |  64 ++++++
>  hasher-priv/logging.h       |  55 +++++
>  hasher-priv/main.c          |  75 -------
>  hasher-priv/pass.c          | 117 ++++++++++-
>  hasher-priv/pidfile.c       | 128 ++++++++++++
>  hasher-priv/pidfile.h       |  44 ++++
>  hasher-priv/priv.h          |  33 +--
>  hasher-priv/server.conf     |  13 ++
>  hasher-priv/sockets.c       | 183 +++++++++++++++++
>  hasher-priv/sockets.h       |  32 +++
>  hasher-priv/x11.c           |   1 +
>  28 files changed, 2632 insertions(+), 246 deletions(-)
>  create mode 100644 hasher-priv/caller_server.c
>  create mode 100644 hasher-priv/caller_task.c
>  create mode 100644 hasher-priv/communication.c
>  create mode 100644 hasher-priv/communication.h
>  create mode 100644 hasher-priv/epoll.c
>  create mode 100644 hasher-priv/epoll.h
>  create mode 100644 hasher-priv/hasher-priv.c
>  create mode 100644 hasher-priv/hasher-privd.c
>  create mode 100644 hasher-priv/logging.c
>  create mode 100644 hasher-priv/logging.h
>  delete mode 100644 hasher-priv/main.c
>  create mode 100644 hasher-priv/pidfile.c
>  create mode 100644 hasher-priv/pidfile.h
>  create mode 100644 hasher-priv/server.conf
>  create mode 100644 hasher-priv/sockets.c
>  create mode 100644 hasher-priv/sockets.h

Wow, this patch is rather big. That would be easier to review if there
was a bunch of logically self-contained commits, which could later be
squashed by the merging maintainer, perhaps, if there's a requirement
that every commit has to build successfully and run well.

I'm going to post reviews in separate messages, one answer per file.


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] *literacy*
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
  2020-09-17 13:10   ` Arseny Maslennikov
@ 2020-09-17 13:10   ` Arseny Maslennikov
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller.c Arseny Maslennikov
                     ` (6 subsequent siblings)
  8 siblings, 0 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:10 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 94118 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion@altlinux.org>
> 
> All privileged operations moved to the daemon. Commands to the server

Suggested replacement:
"All privileged operations are moved to the daemon. Commands to the server"

> are sent through the unix domain socket. The credentials which the sender
> specifies are checked by the kernel. The hasher-priv no longer SUID.

Suggested replacement for the last sentence:
"Neither hasher-priv nor its daemon are installed with SUID on"

> 
> For each user server creates a separate session process that executes

Suggested replacement:
s/server/the server/

> commands only from the user who created it. The session process ends
> after a certain period of inactivity.
> 
> Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> ---
>  hasher-priv/.gitignore      |   1 +
>  hasher-priv/DESIGN          | 281 ++++++++++++++++----------
>  hasher-priv/Makefile        |  28 ++-
>  hasher-priv/caller.c        |  81 ++++----
>  hasher-priv/caller_server.c | 373 ++++++++++++++++++++++++++++++++++
>  hasher-priv/caller_task.c   | 214 ++++++++++++++++++++
>  hasher-priv/cmdline.c       |  27 ++-
>  hasher-priv/communication.c | 392 ++++++++++++++++++++++++++++++++++++
>  hasher-priv/communication.h |  77 +++++++
>  hasher-priv/config.c        | 143 ++++++++++++-
>  hasher-priv/epoll.c         |  39 ++++
>  hasher-priv/epoll.h         |  18 ++
>  hasher-priv/hasher-priv.c   |  78 +++++++
>  hasher-priv/hasher-privd.c  | 375 ++++++++++++++++++++++++++++++++++
>  hasher-priv/io_log.c        |   2 +-
>  hasher-priv/io_x11.c        |   2 +-
>  hasher-priv/killuid.c       |   2 +-
>  hasher-priv/logging.c       |  64 ++++++
>  hasher-priv/logging.h       |  55 +++++
>  hasher-priv/main.c          |  75 -------
>  hasher-priv/pass.c          | 117 ++++++++++-
>  hasher-priv/pidfile.c       | 128 ++++++++++++
>  hasher-priv/pidfile.h       |  44 ++++
>  hasher-priv/priv.h          |  33 +--
>  hasher-priv/server.conf     |  13 ++
>  hasher-priv/sockets.c       | 183 +++++++++++++++++
>  hasher-priv/sockets.h       |  32 +++
>  hasher-priv/x11.c           |   1 +
>  28 files changed, 2632 insertions(+), 246 deletions(-)
>  create mode 100644 hasher-priv/caller_server.c
>  create mode 100644 hasher-priv/caller_task.c
>  create mode 100644 hasher-priv/communication.c
>  create mode 100644 hasher-priv/communication.h
>  create mode 100644 hasher-priv/epoll.c
>  create mode 100644 hasher-priv/epoll.h
>  create mode 100644 hasher-priv/hasher-priv.c
>  create mode 100644 hasher-priv/hasher-privd.c
>  create mode 100644 hasher-priv/logging.c
>  create mode 100644 hasher-priv/logging.h
>  delete mode 100644 hasher-priv/main.c
>  create mode 100644 hasher-priv/pidfile.c
>  create mode 100644 hasher-priv/pidfile.h
>  create mode 100644 hasher-priv/server.conf
>  create mode 100644 hasher-priv/sockets.c
>  create mode 100644 hasher-priv/sockets.h
> 
> diff --git a/hasher-priv/.gitignore b/hasher-priv/.gitignore
> index 5ca24b0..8f0f658 100644
> --- a/hasher-priv/.gitignore
> +++ b/hasher-priv/.gitignore
> @@ -4,6 +4,7 @@ getconf.sh
>  getugid1.sh
>  getugid2.sh
>  hasher-priv
> +hasher-privd
>  hasher-priv.8
>  hasher-priv.conf.5
>  hasher-useradd
> diff --git a/hasher-priv/DESIGN b/hasher-priv/DESIGN

This file desperately needs lots of fixes throughout, to be fair.

> index 1470ce7..310667d 100644
> --- a/hasher-priv/DESIGN
> +++ b/hasher-priv/DESIGN
> @@ -1,35 +1,63 @@
>  
> -Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
> +Here is a hasher-priv (euid=user,egid=hashman,gid!=egid) control flow:

Suggested replacement:
s/a hasher-priv/the client's/

A reword of all the header phrases would also suffice.

>  + sanitize file descriptors
>    + set safe umask
>    + ensure 0,1,2 are valid file descriptors
>    + close all the rest
>  + parse command line arguments
> -  + check for non-zero argument list
> -  + parse -h, --help and -number options
> -    + caller_num initialized here
> -  + parse arguments, abort if wrong
> -    + chroot_path and chroot_argv are initialized here
> -    + valid arguments are:
> -      getconf
> -      killuid
> -      getugid1
> -      chrootuid1 <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 a hasher-privd (euid=root,egid=hashman,gid==egid) control flow:

Same as above.

> ++ parse command line arguments
> +  + parse -h and --help options
> ++ set safe umask
> ++ check pidfile, abort if server already running
> ++ daemonize
> +  + change current working directory to "/"
> +  + close all file descriptors
> ++ initialize logger
> ++ create pidfile
> ++ examine and change blocked signals
> ++ I/O event notification and add file descriptors
> +  + create a file descriptor for accepting signals
> +  + create and listen server socket
> ++ wait for incomming caller connections
> +  + handle signal if signal is received
> +    + close caller's session
> +  + handle connection if the caller opened a new connection
> +    + get connection credentials
> +    + fork new process for caller if don't have any
> +    + notify the client if the session server already running
> +    + close caller connection
> ++ close all descriptors in notification poll
> ++ close and remove pidfile
> +
> +Here is control flow for session server:
>  + initialize data related to caller
> -  + caller_uid initialized here from getuid()
> +  + caller_uid initialized here by uid received via the socket
>      + caller_uid must be valid uid
> -  + caller_gid initialized here from getgid()
> +  + caller_gid initialized here by uid received via the socket
>      + caller_gid must be valid gid
> -  + caller_user initialized here from getpwnam(LOGNAME)->pw_name or
> -    getpwuid(caller_uid)->pw_name
> +  + caller_user initialized here from getpwuid(caller_uid)->pw_name
>      + must be true: caller_uid == pw->pw_uid
>      + must be true: caller_gid == pw->pw_gid
>    + caller_home initialized here
>      + caller_user's home directory must exist
> -+ read work limit hints from environment variables
> -+ drop all environment variables
> ++ set safe umask
> ++ open a caller-specific socket
>  + load configuration
>    + safe chdir to /etc/hasher-priv
>    + safe load "system" file
> @@ -40,7 +68,7 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
>        prefix
>        umask
>        nice
> -      allowed_devices
> +      allow_ttydev
>        allowed_mountpoints
>        rlimit_(hard|soft)_*
>        wlimit_(time_elapsed|time_idle|bytes_written)
> @@ -53,85 +81,136 @@ Here is a hasher-priv (euid=root,egid=hashman,gid!=egid) control flow:
>        + change_user1 and change_user2 should be initialized here
>    + change_uid1 and change_gid1 initialized from change_user1
>    + change_uid2 and change_gid2 initialized from change_user2
> -+ execute choosen task
> -  + getconf: print config file /etc/hasher-priv/user.d/caller_user[:caller_num]
> -  + getugid1: print change_uid1:change_gid1 pair
> -  + getugid2: print change_uid2:change_gid2 pair
> -  + killuid
> -    + check for valid uids specified
> -    + drop dumpable flag (just in case - not required)
> -    + setuid to specified uid pair
> -    + kill (-1, SIGKILL)
> -    + purge all SYSV IPC objects belonging to specified uid pair
> -  + chrootuid1/chrootuid2
> -    + check for valid uid specified
> -    + setup mounts and devices
> -      + unshare mount namespace
> -      + safe chdir to chroot_path
> -      + safe chdir to dev
> -      + mount dev
> -      + create devices available to all users: null, zero, full, random, urandom
> -      + if makedev_console environment variable is true,
> -        create devices available to root only: console, tty0, fb0
> -      + mount /dev/shm
> -      + mount all mountpoints specified by requested_mountpoints environment variable
> -      + if /dev/pts was mounted, create devices: tty, ptmx
> -    + safe chdir to chroot_path
> -    + sanitize file descriptors again
> -    + if use_pty is disabled, create pipe to handle child's stdout and stderr
> -    + if X11 forwarding is requested, create socketpair and
> -      open /tmp/.X11-unix directory readonly for later use with fchdir()
> -    + unless share_ipc is enabled, isolate System V IPC namespace
> -    + unless share_uts is enabled, unshare UTS namespace
> -    + if X11 forwarding to a tcp address was not requested,
> -      unless share_network is enabled, unshare network
> -    + clear supplementary group access list
> -    + create pty:
> -      + temporarily switch to called_uid:caller_gid
> -      + open /dev/ptmx
> -      + fetch pts number
> -      + unlock pts pair
> -      + open pts slave
> -      + switch uid:gid back
> -    + chroot to "."
> -    + create another pty if possible:
> -      + temporarily switch to called_uid:caller_gid
> -      + safely chdir to "dev"
> -      + if "pts/ptmx" is available with right permissions:
> -        + replace "ptmx" with a symlink to "pts/ptmx"
> -        + open "ptmx"
> -        + fetch pts number
> -        + unlock pts pair
> -        + open pts slave
> -      + chdir back to "/"
> -      + switch uid:gid back
> -      + if another pty was crated, close pts pair that was opened earlier
> -    + set rlimits
> -    + set close-on-exec flag on all non-standard descriptors
> -    + fork
> -      + in parent:
> -        + setgid/setuid to caller user
> -        + install CHLD signal handler
> -        + unblock master pty and pipe descriptors
> -        + if use_pty is enabled, initialize tty and install WINCH signal handler
> -        + listen to "/dev/log"
> -        + while work limits are not exceeded, handle child input/output
> -        + close master pty descriptor, thus sending HUP to child session
> -        + wait for child process termination
> -        + remove CHLD signal handler
> -        + return child proccess exit code
> -      + in child:
> -        + if X11 forwarding to a tcp address was requested,
> ++ I/O event notification and add file descriptors
> +  + create a file descriptor for accepting signals
> +  + create and listen the session socket
> ++ notify the client that the session server is ready
> ++ wait for incomming caller connections
> +  + handle signal if signal is received
> +  + handle connection if the caller opened a new connection
> +    + task handler
> +    + reset timeout timer
> +  + finish the server if the task doesn't arrive from the caller
> +    for more than a minute.
> +
> +Here is control flow for the task handler:
> ++ receive task header
> +  + receive client's stdin, stdout and stderr
> +  + check number of arguments
> ++ receive task arguments if we expect them
> ++ receive environment variables if client want to send them
> ++ fork process to handle task
> +  + in parent:
> +    + wait exit status
> +  + in child:
> +    + replace stdin, stdout and stderr with those that were received
> +    + drop all environment variables
> +    + apply the client's environment variables
> +    + sanitize file descriptors
> +      + set safe umask
> +      + ensure 0,1,2 are valid file descriptors
> +      + close all the rest
> +    + parse task arguments
> +    + check for non-zero argument list
> +    + parse -h, --help and -number options
> +      + caller_num initialized here
> +    + parse arguments, abort if wrong
> +      + chroot_path and chroot_argv are initialized here
> +      + valid arguments are:
> +        getconf
> +        killuid
> +        getugid1
> +        chrootuid1 <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..82aa385 100644
> --- a/hasher-priv/Makefile
> +++ b/hasher-priv/Makefile
> @@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec)
>  HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh
>  MAN5PAGES = $(PROJECT).conf.5
>  MAN8PAGES = $(PROJECT).8 hasher-useradd.8
> -TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> +TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
>  
>  sysconfdir = /etc
>  libexecdir = /usr/lib
> @@ -21,6 +21,7 @@ man5dir = $(mandir)/man5
>  man8dir = $(mandir)/man8
>  configdir = $(sysconfdir)/$(PROJECT)
>  helperdir = $(libexecdir)/$(PROJECT)
> +socketdir = /var/run
>  DESTDIR =
>  
>  MKDIR_P = mkdir -p
> @@ -33,17 +34,25 @@ WARNINGS = -Wall -W -Wshadow -Wpointer-arith -Wwrite-strings \
>  	-Wmissing-prototypes -Wmissing-declarations -Wmissing-noreturn \
>  	-Wmissing-format-attribute -Wredundant-decls -Wdisabled-optimization
>  CPPFLAGS = -std=gnu99 -D_GNU_SOURCE $(CHDIRUID_FLAGS) \
> -	$(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\"
> +	$(LFS_CFLAGS) -DPROJECT_VERSION=\"$(VERSION)\" \
> +	-DSOCKETDIR=\"$(socketdir)\" -DPROJECT=\"$(PROJECT)\"
>  CFLAGS = -pipe -O2
>  override CFLAGS += $(WARNINGS)
>  LDLIBS =
>  
> -SRC = caller.c chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \
> +SRC = hasher-priv.c cmdline.c fds.c sockets.c logging.c communication.c xmalloc.c pass.c
> +OBJ = $(SRC:.c=.o)
> +
> +server_SRC = hasher-privd.c \
> +	communication.c epoll.c pidfile.c logging.c sockets.c \
> +	caller.c caller_server.c caller_task.c \
> +	chdir.c chdiruid.c chid.c child.c chrootuid.c cmdline.c \
>  	config.c fds.c getconf.c getugid.c ipc.c killuid.c io_log.c io_x11.c \
> -	main.c makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \
> +	makedev.c mount.c net.c parent.c pass.c pty.c signal.c tty.c \
>  	unshare.c xmalloc.c x11.c
> -OBJ = $(SRC:.c=.o)
> -DEP = $(SRC:.c=.d)
> +server_OBJ = $(server_SRC:.c=.o)
> +
> +DEP = $(SRC:.c=.d) $(server_SRC:.c=.d)
>  
>  .PHONY:	all install clean indent
>  
> @@ -52,14 +61,19 @@ all: $(TARGETS)
>  $(PROJECT): $(OBJ)
>  	$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
>  
> +hasher-privd: $(server_OBJ)
> +	$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@
> +
>  install: all
>  	$(MKDIR_P) -m710 $(DESTDIR)$(configdir)/user.d
>  	$(INSTALL) -p -m640 fstab $(DESTDIR)$(configdir)/fstab
>  	$(INSTALL) -p -m640 system.conf $(DESTDIR)$(configdir)/system
> +	$(INSTALL) -p -m640 server.conf $(DESTDIR)$(configdir)/server
>  	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
>  	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
>  	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
>  	$(MKDIR_P) -m755 $(DESTDIR)$(sbindir)
> +	$(INSTALL) -p -m755 hasher-privd $(DESTDIR)$(sbindir)/
>  	$(INSTALL) -p -m755 hasher-useradd $(DESTDIR)$(sbindir)/
>  	$(MKDIR_P) -m755 $(DESTDIR)$(man5dir)
>  	$(INSTALL) -p -m644 $(MAN5PAGES) $(DESTDIR)$(man5dir)/
> @@ -67,7 +81,7 @@ install: all
>  	$(INSTALL) -p -m644 $(MAN8PAGES) $(DESTDIR)$(man8dir)/
>  
>  clean:
> -	$(RM) $(TARGETS) $(DEP) $(OBJ) core *~
> +	$(RM) $(TARGETS) $(DEP) $(OBJ) $(server_OBJ) core *~
>  
>  indent:
>  	indent *.h *.c
> diff --git a/hasher-priv/caller.c b/hasher-priv/caller.c
> index e83084a..031ddef 100644
> --- a/hasher-priv/caller.c
> +++ b/hasher-priv/caller.c
> @@ -19,62 +19,67 @@
>  
>  #include "priv.h"
>  #include "xmalloc.h"
> +#include "logging.h"
>  
> -const char *caller_user, *caller_home;
> -uid_t   caller_uid;
> -gid_t   caller_gid;
> +char *caller_user = NULL;
> +char *caller_home = NULL;
> +uid_t caller_uid;
> +gid_t caller_gid;
>  
>  /*
>   * Initialize caller_user, caller_uid, caller_gid and caller_home.
>   */
> -void
> -init_caller_data(void)
> +int
> +init_caller_data(uid_t uid, gid_t gid)
>  {
> -	const char *logname;
>  	struct passwd *pw = 0;
>  
> -	caller_uid = getuid();
> -	if (caller_uid < MIN_CHANGE_UID)
> -		error(EXIT_FAILURE, 0, "caller has invalid uid: %u",
> -		      caller_uid);
> -
> -	caller_gid = getgid();
> -	if (caller_gid < MIN_CHANGE_GID)
> -		error(EXIT_FAILURE, 0, "caller has invalid gid: %u",
> -		      caller_gid);
> -
> -	if ((logname = getenv("LOGNAME")))
> -		if (!*logname || strchr(logname, ':'))
> -			logname = 0;
> -
> -	if (logname)
> -	{
> -		pw = getpwnam(logname);
> -		if (caller_uid != pw->pw_uid || caller_gid != pw->pw_gid)
> -			pw = 0;
> +	caller_uid = uid;
> +	if (caller_uid < MIN_CHANGE_UID) {
> +		err("caller has invalid uid: %u", caller_uid);
> +		return -1;
> +	}
> +
> +	caller_gid = gid;
> +	if (caller_gid < MIN_CHANGE_GID) {
> +		err("caller has invalid gid: %u", caller_gid);
> +		return -1;
>  	}
>  
> -	if (!pw)
> -		pw = getpwuid(caller_uid);
> +	pw = getpwuid(caller_uid);
>  
> -	if (!pw || !pw->pw_name)
> -		error(EXIT_FAILURE, 0, "caller lookup failure");
> +	if (!pw || !pw->pw_name) {
> +		err("caller lookup failure");
> +		return -1;
> +	}
>  
>  	caller_user = xstrdup(pw->pw_name);
>  
> -	if (caller_uid != pw->pw_uid)
> -		error(EXIT_FAILURE, 0, "caller %s: uid mismatch",
> -		      caller_user);
> +	if (caller_uid != pw->pw_uid) {
> +		err("caller %s: uid mismatch", caller_user);
> +		return -1;
> +	}
>  
> -	if (caller_gid != pw->pw_gid)
> -		error(EXIT_FAILURE, 0, "caller %s: gid mismatch",
> -		      caller_user);
> +	if (caller_gid != pw->pw_gid) {
> +		err("caller %s: gid mismatch", caller_user);
> +		return -1;
> +	}
>  
>  	errno = 0;
>  	if (pw->pw_dir && *pw->pw_dir)
>  		caller_home = canonicalize_file_name(pw->pw_dir);
>  
> -	if (!caller_home || !*caller_home)
> -		error(EXIT_FAILURE, errno, "caller %s: invalid home",
> -		      caller_user);
> +	if (!caller_home || !*caller_home) {
> +		err("caller %s: invalid home: %m", caller_user);
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +void
> +free_caller_data(void)
> +{
> +	free(caller_user);
> +	free(caller_home);
>  }
> diff --git a/hasher-priv/caller_server.c b/hasher-priv/caller_server.c
> new file mode 100644
> index 0000000..8182c69
> --- /dev/null
> +++ b/hasher-priv/caller_server.c
> @@ -0,0 +1,373 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  The part of the hasher-privd program.

Which part?

> +
> +  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->env) {
> +		free(task->env[0]);
> +		free(task->env);
> +	}
> +
> +	if (task->argv) {
> +		free(task->argv[0]);
> +		free(task->argv);
> +	}
> +}
> +
> +static int
> +cancel_task(int conn, struct task *task)
> +{
> +	free_task(task);
> +	send_command_response(conn, CMD_STATUS_FAILED, "command failed");
> +	return 0;
> +}
> +
> +static int
> +process_task(int conn)
> +{
> +	uid_t uid;
> +	gid_t gid;
> +
> +	if (get_peercred(conn, NULL, &uid, &gid) < 0)
> +		return -1;
> +
> +	if (caller_uid != uid || caller_gid != gid) {
> +		err("user (uid=%d) has no permissions to send commands"

Suggested replacement:
"has no permissions" -> "has no permission"

> +		    " to the session of another user (uid=%d)",
> +		    uid, caller_uid);
> +		return -1;
> +	}
> +
> +	struct task task = { 0 };
> +	int fds[3];
> +
> +	while (1) {
> +		task_t type = TASK_NONE;
> +		struct cmd hdr = { 0 };
> +
> +		if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
> +			return cancel_task(conn, &task);
> +
> +		switch (hdr.type) {
> +			case CMD_TASK_BEGIN:
> +				if (hdr.datalen != sizeof(type))
> +					return cancel_task(conn, &task);
> +
> +				if (xrecvmsg(conn, &type, hdr.datalen) < 0)
> +					return cancel_task(conn, &task);
> +
> +				task.type = type;
> +
> +				break;
> +
> +			case CMD_TASK_FDS:
> +				if (hdr.datalen != sizeof(int) * 3)
> +					return cancel_task(conn, &task);
> +
> +				if (task.stdin)
> +					close(task.stdin);
> +
> +				if (task.stdout)
> +					close(task.stdout);
> +
> +				if (task.stderr)
> +					close(task.stderr);
> +
> +				if (fds_recv(conn, fds, 3) < 0)
> +					return cancel_task(conn, &task);
> +
> +				task.stdin  = fds[0];
> +				task.stdout = fds[1];
> +				task.stderr = fds[2];
> +
> +				break;
> +
> +			case CMD_TASK_ARGUMENTS:
> +				if (task.argv) {
> +					free(task.argv[0]);
> +					free(task.argv);
> +				}
> +
> +				if (recv_list(conn, &task.argv, hdr.datalen) < 0)
> +					return cancel_task(conn, &task);
> +
> +				if (validate_arguments(task.type, task.argv) < 0)
> +					return cancel_task(conn, &task);
> +
> +				break;
> +
> +			case CMD_TASK_ENVIRON:
> +				if (task.env) {
> +					free(task.env[0]);
> +					free(task.env);
> +				}
> +
> +				if (recv_list(conn, &task.env, hdr.datalen) < 0)
> +					return cancel_task(conn, &task);
> +
> +				break;
> +
> +			case CMD_TASK_RUN:
> +				process_caller_task(conn, &task);
> +				free_task(&task);
> +				return 0;
> +
> +			default:
> +				err("unsupported command: %d", hdr.type);
> +		}
> +
> +		send_command_response(conn, CMD_STATUS_DONE, NULL);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +caller_server(int cl_conn)
> +{
> +	int ret = 0;
> +
> +	umask(077);
> +
> +	snprintf(socketpath, sizeof(socketpath),
> +		"hasher-priv-%d-%u",
> +		caller_uid, caller_num);
> +
> +	int fd_conn   = -1;
> +	int fd_signal = -1;
> +	int fd_ep     = -1;
> +
> +	if ((fd_conn = srv_listen(SOCKETDIR, socketpath)) < 0)
> +		return -1;
> +
> +	snprintf(socketpath, sizeof(socketpath),
> +		"%s/hasher-priv-%d-%u",
> +		SOCKETDIR, caller_uid, caller_num);
> +
> +	if (chown(socketpath, caller_uid, caller_gid)) {
> +		err("fchown: %s: %m", socketpath);
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	/* Load config according to caller information. */
> +	configure();
> +
> +	sigset_t mask;
> +
> +	sigfillset(&mask);
> +	sigprocmask(SIG_SETMASK, &mask, NULL);
> +
> +	if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0) {
> +		err("signalfd: %m");
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
> +		err("epoll_create1: %m");
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0) {
> +		err("epollin_add: failed");
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	/* Tell client that caller server is ready */

Suggested replacement:
"client" -> "the client"

> +	send_command_response(cl_conn, CMD_STATUS_DONE, NULL);
> +	close(cl_conn);
> +
> +	unsigned long nsec = 0;
> +	int finish_server = 0;
> +
> +	while (!finish_server) {
> +		struct epoll_event ev[42];
> +		int fdcount;
> +
> +		errno = 0;
> +		if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), 1000)) < 0) {
> +			if (errno == EINTR)
> +				continue;
> +			err("epoll_wait: %m");
> +			break;
> +		}
> +
> +		if (fdcount == 0) {
> +			nsec++;
> +
> +			if (nsec >= server_session_timeout)
> +				break;
> +
> +		} else for (int i = 0; i < fdcount; i++) {
> +			if (!(ev[i].events & EPOLLIN)) {
> +				continue;
> +
> +			} else if (ev[i].data.fd == fd_signal) {
> +				struct signalfd_siginfo fdsi;
> +				ssize_t size;
> +
> +				size = read_retry(fd_signal, &fdsi, sizeof(fdsi));
> +
> +				if (size != sizeof(fdsi)) {
> +					err("unable to read signal info");

Suggested error message:
"unable to read signal info from signalfd"
The form present in the patch is only obvious to a developer.

> +					continue;
> +				}
> +
> +				int status;
> +
> +				switch (fdsi.ssi_signo) {
> +					case SIGINT:
> +					case SIGTERM:
> +						finish_server = 1;
> +						break;
> +					case SIGCHLD:
> +						if (waitpid(-1, &status, 0) < 0) 
> +							err("waitpid: %m");
> +						break;
> +				}
> +
> +			} else if (ev[i].data.fd == fd_conn) {
> +				int conn;
> +
> +				if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
> +					err("accept4: %m");
> +					continue;
> +				}
> +
> +				if (set_recv_timeout(conn, 3) < 0) {
> +					close(conn);
> +					continue;
> +				}
> +
> +				if (!process_task(conn)) {
> +					/* reset timer */
> +					nsec = 0;
> +				}
> +
> +				close(conn);
> +			}
> +		}
> +	}
> +fail:
> +	epollin_remove(fd_ep, fd_signal);
> +	epollin_remove(fd_ep, fd_conn);
> +
> +	if (fd_ep >= 0)
> +		close(fd_ep);
> +
> +	unlink(socketpath);
> +
> +	return ret;
> +}
> +
> +pid_t
> +fork_server(int cl_conn, uid_t uid, gid_t gid, unsigned num)
> +{
> +	pid_t pid = fork();
> +
> +	if (pid != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}
> +		return pid;
> +	}
> +
> +	caller_num = num;
> +
> +	int ret = init_caller_data(uid, gid);
> +
> +	if (ret < 0) {
> +		info("%s(%d) num=%u: start session server", caller_user, caller_uid, caller_num);
> +		ret = caller_server(cl_conn);
> +		info("%s(%d): finish session server", caller_user, caller_uid);
> +	}
> +
> +	if (ret < 0) {
> +		send_command_response(cl_conn, CMD_STATUS_FAILED, NULL);
> +		ret = EXIT_FAILURE;
> +	} else {
> +		ret = EXIT_SUCCESS;
> +	}
> +
> +	free_caller_data();
> +	close(cl_conn);
> +
> +	exit(ret);
> +}
> diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c
> new file mode 100644
> index 0000000..d8f2dd5
> --- /dev/null
> +++ b/hasher-priv/caller_task.c
> @@ -0,0 +1,214 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  The part of the hasher-privd program.

Which part?
The same anonymous part as caller_server.c? =)

> +
> +  SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +#include <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;
> +
> +	if ((pid = fork()) != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}
> +		return pid;
> +	}
> +
> +	if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
> +		exit(rc);
> +
> +	/* cleanup environment to avoid side effects. */

s/cleanup/clean up/

> +	if (clearenv() != 0)
> +		fatal("clearenv: %m");
> +
> +	while (task->env && task->env[i]) {
> +		if (putenv(task->env[i++]) != 0)
> +			fatal("putenv: %m");
> +	}
> +
> +	/* First, check and sanitize file descriptors. */
> +	sanitize_fds();
> +
> +	/* Second, parse task arguments. */
> +	parse_task_args(task->type, (const char **) task->argv);
> +
> +	if (chroot_path && *chroot_path != '/')
> +		fatal("%s: invalid chroot path", chroot_path);
> +
> +	/* Fourth, parse environment for config options. */

This reminds me of Michael Zadornov and his numbered pigs.

> +	parse_env();
> +
> +	/* We don't need environment variables any longer. */

Suggested replacement:
"We don't need environment variables any longer."

> +	if (clearenv() != 0)
> +		fatal("clearenv: %m");
> +
> +	/* Finally, execute choosen task. */

s/choosen/chosen/; even further: is it actually chosen in this function?
It's actually passed in an argument by pointer.

> +	switch (task->type) {
> +		case TASK_GETCONF:
> +			rc = do_getconf();
> +			break;
> +		case TASK_KILLUID:
> +			rc = do_killuid();
> +			break;
> +		case TASK_GETUGID1:
> +			rc = do_getugid1();
> +			break;
> +		case TASK_CHROOTUID1:
> +			rc = !killprevious()
> +				? do_chrootuid1()
> +				: EXIT_FAILURE;
> +			break;
> +		case TASK_GETUGID2:
> +			rc = do_getugid2();
> +			break;
> +		case TASK_CHROOTUID2:
> +			rc = !killprevious()
> +				? do_chrootuid2()
> +				: EXIT_FAILURE;
> +			break;
> +		default:
> +			fatal("unknown task %d", task->type);
> +	}
> +
> +	/* Write of all user-space buffered data */

Suggested replacement:
"Write down all the user-space buffered data"
This is the closest option to the original suggestion.

> +	fflush(stdout);
> +	fflush(stderr);
> +
> +	exit(rc);
> +}
> +
> +int
> +process_caller_task(int conn, struct task *task)
> +{
> +	int rc = EXIT_FAILURE;
> +	pid_t pid, cpid;
> +
> +	if ((pid = fork()) != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}
> +		return 0;
> +	}
> +
> +	if ((cpid = caller_task(task)) > 0) {
> +		while (1) {
> +			pid_t w;
> +			int wstatus;
> +
> +			if ((w = waitpid(cpid, &wstatus, WUNTRACED | WCONTINUED)) < 0) {
> +				err("waitpid: %m");
> +				break;
> +			}
> +
> +			if (WIFEXITED(wstatus)) {
> +				rc = WEXITSTATUS(wstatus);
> +				info("%s: process %d exited, status=%d", task2str(task->type), cpid, WEXITSTATUS(wstatus));
> +				break;
> +			}
> +
> +			if (WIFSIGNALED(wstatus)) {
> +				info("%s: process %d killed by signal %d", task2str(task->type), cpid, WTERMSIG(wstatus));
> +				break;
> +			}
> +		}
> +	}
> +
> +	if (task->env) {
> +		free(task->env[0]);
> +		free(task->env);
> +	}
> +
> +	if (task->argv) {
> +		free(task->argv[0]);
> +		free(task->argv);
> +	}
> +
> +	/* Notify client about result */

Suggested closest replacement:
"Notify the client about the results"

> +	(rc == EXIT_FAILURE)
> +		? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
> +		: send_command_response(conn, CMD_STATUS_DONE, NULL);
> +
> +	exit(rc);
> +}
> diff --git a/hasher-priv/cmdline.c b/hasher-priv/cmdline.c
> index 1cdfe23..aba802f 100644
> --- a/hasher-priv/cmdline.c
> +++ b/hasher-priv/cmdline.c
> @@ -80,6 +80,8 @@ const char *chroot_path;
>  const char **chroot_argv;
>  unsigned caller_num;
>  
> +const char **task_args;
> +
>  static unsigned
>  get_caller_num(const char *str)
>  {
> @@ -126,40 +128,57 @@ parse_cmdline(int argc, const char *argv[])
>  	if (ac < 1)
>  		show_usage("insufficient arguments");
>  
> +	task_args = NULL;
> +
>  	if (!strcmp("getconf", av[0]))
>  	{
>  		if (ac != 1)
>  			show_usage("%s: invalid usage", av[0]);
> +		task_args = av + 1;
>  		return TASK_GETCONF;
>  	} else if (!strcmp("killuid", av[0]))
>  	{
>  		if (ac != 1)
>  			show_usage("%s: invalid usage", av[0]);
> +		task_args = av + 1;
>  		return TASK_KILLUID;
>  	} else if (!strcmp("getugid1", av[0]))
>  	{
>  		if (ac != 1)
>  			show_usage("%s: invalid usage", av[0]);
> +		task_args = av + 1;
>  		return TASK_GETUGID1;
>  	} else if (!strcmp("chrootuid1", av[0]))
>  	{
>  		if (ac < 3)
>  			show_usage("%s: invalid usage", av[0]);
> -		chroot_path = av[1];
> -		chroot_argv = av + 2;
> +		task_args = av + 1;
>  		return TASK_CHROOTUID1;
>  	} else if (!strcmp("getugid2", av[0]))
>  	{
>  		if (ac != 1)
>  			show_usage("%s: invalid usage", av[0]);
> +		task_args = av + 1;
>  		return TASK_GETUGID2;
>  	} else if (!strcmp("chrootuid2", av[0]))
>  	{
>  		if (ac < 3)
>  			show_usage("%s: invalid usage", av[0]);
> -		chroot_path = av[1];
> -		chroot_argv = av + 2;
> +		task_args = av + 1;
>  		return TASK_CHROOTUID2;
>  	} else
>  		show_usage("%s: invalid argument", av[0]);
>  }
> +
> +void
> +parse_task_args(task_t task, const char *argv[])
> +{
> +	switch (task) {
> +		case TASK_CHROOTUID1:
> +		case TASK_CHROOTUID2:
> +			chroot_path = argv[0];
> +			chroot_argv = (const char **)argv + 1;
> +		default:
> +			break;
> +	}
> +}
> diff --git a/hasher-priv/communication.c b/hasher-priv/communication.c
> new file mode 100644
> index 0000000..77f93e1
> --- /dev/null
> +++ b/hasher-priv/communication.c
> @@ -0,0 +1,392 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  Functions for communication between the hasher-priv and the hasher-privd.

The proposed phrase is a bit clunky but OK.
I'll still give a suggested replacement:
"This file offers some helper functions for client-server communication."

> +
> +  SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +#include <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);
> +
> +char *
> +task2str(task_t type)
> +{
> +	size_t i;
> +
> +	for (i = 0; i < taskmap_size; i++) {
> +		if (taskmap[i].value == type)
> +			return strncpy(taskbuf, taskmap[i].name, taskbuflen - 1);
> +	}
> +	return NULL;
> +}
> +
> +task_t
> +str2task(char *s)
> +{
> +	size_t i;
> +
> +	for (i = 0; i < taskmap_size; i++) {
> +		if (!strcmp(taskmap[i].name, s))
> +			return taskmap[i].value;
> +	}
> +	return TASK_NONE;
> +}
> +
> +static int
> +send_list(int conn, cmd_t cmd, const char **argv)
> +{
> +	int i = 0;
> +	struct cmd hdr = { .type = cmd };
> +
> +	while (argv && argv[i])
> +		hdr.datalen += strlen(argv[i++]) + 1;
> +
> +	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
> +		return -1;
> +
> +	i = 0;
> +	while (argv && argv[i]) {
> +		if (xsendmsg(conn, (char *) argv[i], strlen(argv[i]) + 1) < 0)
> +			return -1;
> +		i++;
> +	}
> +
> +	return 0;
> +}
> +
> +int
> +recv_list(int conn, char ***argv, size_t datalen)
> +{
> +	char *args = calloc(1UL, datalen);
> +	if (!args) {
> +		err("calloc: %m");
> +		return -1;
> +	}
> +
> +	if (datalen > 0 && xrecvmsg(conn, args, datalen) < 0) {
> +		free(args);
> +		return -1;
> +	}
> +
> +	if (!argv) {
> +		free(args);
> +		return 0;
> +	}
> +
> +	size_t i = 0, n = 0;
> +
> +	while (args && n < datalen) {
> +		i++;
> +		n += strnlen(args + n, datalen - n) + 1;
> +	}
> +
> +	char **av = malloc(sizeof(char *) * (i + 1));
> +	if (!av) {
> +		err("malloc: %m");
> +		return -1;
> +	}
> +	av[i] = NULL;
> +
> +	i = n = 0;
> +
> +	while (args && n < datalen) {
> +		av[i++] = args + n;
> +		n += strnlen(args + n, datalen - n) + 1;
> +	}
> +
> +	*argv = av;
> +
> +	return 0;
> +}
> +
> +long int
> +send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	struct cmd_resp rs = { 0 };
> +	struct iovec iov = { 0 };
> +	struct msghdr msg = { 0 };
> +
> +	rs.status = retcode;
> +	rs.msglen = 0;
> +
> +	if (fmt && *fmt) {
> +		int len;
> +
> +		va_start(ap, fmt);
> +		len = vsnprintf(NULL, 0, fmt, ap);
> +		va_end(ap);
> +
> +		if (len < 0) {
> +			err("unable to calculate message size");
> +			return -1;
> +		}
> +
> +		rs.msglen = (size_t) len + 1;
> +	}
> +
> +	iov.iov_base = &rs;
> +	iov.iov_len  = sizeof(rs);
> +
> +	msg.msg_iov = &iov;
> +	msg.msg_iovlen = 1;
> +
> +	errno = 0;
> +	if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0) {
> +		/* The client left without waiting for an answer */
> +		if (errno == EPIPE)
> +			return 0;
> +
> +		err("sendmsg: %m");
> +		return -1;
> +	}
> +
> +	if (!rs.msglen)
> +		return 0;
> +
> +	msg.msg_iov[0].iov_base = malloc(rs.msglen);
> +
> +	if (!msg.msg_iov[0].iov_base) {
> +		err("malloc: %m");
> +		return -1;
> +	}
> +
> +	msg.msg_iov[0].iov_len  = rs.msglen;
> +
> +	va_start(ap, fmt);
> +	vsnprintf(msg.msg_iov[0].iov_base, msg.msg_iov[0].iov_len, fmt, ap);
> +	va_end(ap);
> +
> +	long int rc = 0;
> +	errno = 0;
> +	if (sendmsg_retry(conn, &msg, MSG_NOSIGNAL) < 0 && errno != EPIPE) {
> +		err("sendmsg: %m");
> +		rc = -1;
> +	}
> +
> +	free(msg.msg_iov[0].iov_base);
> +
> +	return rc;
> +}
> +
> +static int
> +recv_command_response(int conn, cmd_status_t *retcode, char **m)
> +{
> +	struct cmd_resp rs = { 0 };
> +
> +	if (xrecvmsg(conn, &rs, sizeof(rs)) < 0)
> +		return -1;
> +
> +	if (retcode)
> +		*retcode = rs.status;
> +
> +	if (!m || !rs.msglen)
> +		return 0;
> +
> +	char *x = xcalloc(1UL, rs.msglen);
> +	if (xrecvmsg(conn, x, rs.msglen) < 0)
> +		return -1;
> +
> +	*m = x;
> +	return 0;
> +}
> +
> +int
> +server_command(int conn, cmd_t cmd, const char **args)
> +{
> +	cmd_status_t status;
> +	char *msg = NULL;
> +
> +	if (send_list(conn, cmd, args) < 0)
> +		return -1;
> +
> +	if (recv_command_response(conn, &status, &msg) < 0) {
> +		free(msg);
> +		return -1;
> +	}
> +
> +	if (msg && *msg) {
> +		err("%s", msg);
> +		free(msg);
> +	}
> +
> +	return status == CMD_STATUS_FAILED ? -1 : 0;
> +}
> +
> +int
> +server_open_session(const char *dir_name, const char *file_name, unsigned num)
> +{
> +	int conn = srv_connect(dir_name, file_name);
> +
> +	if (conn < 0)
> +		return -1;
> +
> +	struct cmd hdr = {
> +		.type    = CMD_OPEN_SESSION,
> +		.datalen = sizeof(num),
> +	};
> +
> +	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
> +		return -1;
> +
> +	if (xsendmsg(conn, &num, sizeof(num)) < 0)
> +		return -1;
> +
> +	cmd_status_t status;
> +	char *msg = NULL;
> +
> +	if (recv_command_response(conn, &status, &msg) < 0) {
> +		free(msg);
> +		return -1;
> +	}
> +
> +	close(conn);
> +
> +	if (msg && *msg) {
> +		err("%s", msg);
> +		free(msg);
> +	}
> +
> +	return status == CMD_STATUS_FAILED ? -1 : 0;
> +}
> +
> +int
> +server_close_session(const char *dir_name, const char *file_name, unsigned num)
> +{
> +	int conn = srv_connect(dir_name, file_name);
> +
> +	if (conn < 0)
> +		return -1;
> +
> +	struct cmd hdr = {
> +		.type    = CMD_CLOSE_SESSION,
> +		.datalen = sizeof(num),
> +	};
> +
> +	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
> +		return -1;
> +
> +	if (xsendmsg(conn, &num, sizeof(num)) < 0)
> +		return -1;
> +
> +	cmd_status_t status;
> +	char *msg = NULL;
> +
> +	if (recv_command_response(conn, &status, &msg) < 0) {
> +		free(msg);
> +		return -1;
> +	}
> +
> +	close(conn);
> +
> +	if (msg && *msg) {
> +		err("%s", msg);
> +		free(msg);
> +	}
> +
> +	return status == CMD_STATUS_FAILED ? -1 : 0;
> +}
> +
> +int
> +server_task(int conn, task_t type)
> +{
> +	struct cmd hdr = {
> +		.type    = CMD_TASK_BEGIN,
> +		.datalen = sizeof(type),
> +	};
> +
> +	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
> +		return -1;
> +
> +	if (xsendmsg(conn, &type, hdr.datalen) < 0)
> +		return -1;
> +
> +	cmd_status_t status;
> +	char *msg = NULL;
> +
> +	if (recv_command_response(conn, &status, &msg) < 0) {
> +		free(msg);
> +		return -1;
> +	}
> +
> +	if (msg && *msg) {
> +		err("%s", msg);
> +		free(msg);
> +	}
> +
> +	return status == CMD_STATUS_FAILED ? -1 : 0;
> +}
> +
> +int
> +server_task_fds(int conn)
> +{
> +	cmd_status_t status;
> +	char *msg = NULL;
> +
> +	int myfds[3] = {
> +		STDIN_FILENO,
> +		STDOUT_FILENO,
> +		STDERR_FILENO,
> +	};
> +
> +	struct cmd hdr = {
> +		.type    = CMD_TASK_FDS,
> +		.datalen = sizeof(myfds),
> +	};
> +
> +	if (xsendmsg(conn, &hdr, sizeof(hdr)) < 0)
> +		return -1;
> +
> +	if (fds_send(conn, myfds, 3) < 0)
> +		return -1;
> +
> +	if (recv_command_response(conn, &status, &msg) < 0) {
> +		free(msg);
> +		return -1;
> +	}
> +
> +	if (msg && *msg) {
> +		err("%s", msg);
> +		free(msg);
> +	}
> +
> +	return status == CMD_STATUS_FAILED ? -1 : 0;
> +}
> diff --git a/hasher-priv/communication.h b/hasher-priv/communication.h
> new file mode 100644
> index 0000000..e6f1530
> --- /dev/null
> +++ b/hasher-priv/communication.h
> @@ -0,0 +1,77 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  Functions for communication between the hasher-priv and the hasher-privd.

Same as communication.c.

> +
> +  SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +#ifndef _COMMUNICATION_H_
> +#define _COMMUNICATION_H_
> +
> +#include <stdint.h>
> +
> +typedef enum {
> +	CMD_NONE = 0,
> +
> +	/* Master commands */
> +	CMD_OPEN_SESSION,
> +	CMD_CLOSE_SESSION,
> +
> +	/* Session commands */
> +	CMD_TASK_BEGIN,
> +	CMD_TASK_FDS,
> +	CMD_TASK_ARGUMENTS,
> +	CMD_TASK_ENVIRON,
> +	CMD_TASK_RUN,
> +
> +} cmd_t;
> +
> +typedef enum {
> +	CMD_STATUS_DONE = 0,
> +	CMD_STATUS_FAILED,
> +} cmd_status_t;
> +
> +struct cmd {
> +	cmd_t  type;
> +	size_t datalen;
> +};
> +
> +typedef enum {
> +	TASK_NONE = 0,
> +	TASK_GETCONF,
> +	TASK_KILLUID,
> +	TASK_GETUGID1,
> +	TASK_CHROOTUID1,
> +	TASK_GETUGID2,
> +	TASK_CHROOTUID2
> +} task_t;
> +
> +struct task {
> +	int type;
> +	unsigned num;
> +	int stdin;
> +	int stdout;
> +	int stderr;
> +	char **argv;
> +	char **env;
> +};
> +
> +char *task2str(task_t type);
> +task_t str2task(char *s) __attribute__((nonnull(1)));
> +
> +int recv_list(int conn, char ***argv, size_t datalen);
> +
> +long int send_command_response(int conn, cmd_status_t retcode, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
> +
> +int server_command(int conn, cmd_t cmd, const char **args);
> +int server_open_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2)));
> +int server_close_session(const char *dir_name, const char *file_name, unsigned caller_num) __attribute__((nonnull(1, 2)));
> +
> +int server_task(int conn, task_t task);
> +int server_task_fds(int conn);
> +
> +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
> +
> +#endif /* _COMMUNICATION_H_ */
> diff --git a/hasher-priv/config.c b/hasher-priv/config.c
> index e3fedcd..6b6bdb1 100644
> --- a/hasher-priv/config.c
> +++ b/hasher-priv/config.c
> @@ -1,6 +1,7 @@
>  
>  /*
>    Copyright (C) 2003-2019  Dmitry V. Levin <ldv@altlinux.org>
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
>  
>    Configuration support module for the hasher-priv program.

Priv or privd?

>  
> @@ -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_control_group = NULL;
> +char *server_pidfile = NULL;
>  const char *term;
>  const char *x11_display, *x11_key;
>  str_list_t allowed_devices;
> @@ -33,6 +38,8 @@ str_list_t allowed_mountpoints;
>  str_list_t requested_mountpoints;
>  uid_t   change_uid1, change_uid2;
>  gid_t   change_gid1, change_gid2;
> +gid_t   server_gid;
> +unsigned long server_session_timeout = 0;
>  mode_t  change_umask = 022;
>  int change_nice = 8;
>  int     makedev_console;
> @@ -42,6 +49,7 @@ int share_caller_network = 0;
>  int share_ipc = -1;
>  int share_network = -1;
>  int share_uts = -1;
> +int server_log_priority = -1;
>  change_rlimit_t change_rlimit[] = {
>  
>  /* Per-process CPU limit, in seconds.  */
> @@ -209,7 +217,7 @@ parse_rlim(const char *name, const char *value, const char *optname,
>  }
>  
>  static unsigned long
> -str2wlim(const char *name, const char *value, const char *filename)
> +str2ul(const char *name, const char *value, const char *filename)
>  {
>  	char   *p = 0;
>  	unsigned long long n;
> @@ -229,7 +237,7 @@ static void
>  modify_wlim(unsigned long *pval, const char *value,
>  	    const char *optname, const char *filename, int is_system)
>  {
> -	unsigned long val = str2wlim(optname, value, filename);
> +	unsigned long val = str2ul(optname, value, filename);
>  
>  	if (is_system || *pval == 0 || (val > 0 && val < *pval))
>  		*pval = val;
> @@ -633,3 +641,134 @@ parse_env(void)
>  	if ((e = getenv("requested_mountpoints")))
>  		parse_str_list(e, &requested_mountpoints);
>  }
> +
> +static void
> +check_server_control_group(void)
> +{
> +	struct group *gr;
> +
> +	if (!server_control_group || !*server_control_group)
> +		error(EXIT_FAILURE, 0, "config: undefined: control_group");
> +
> +	gr = getgrnam(server_control_group);
> +
> +	if (!gr || !gr->gr_name)
> +		error(EXIT_FAILURE, 0, "config: control_group: %s lookup failure", server_control_group);
> +
> +	server_gid = gr->gr_gid;
> +}
> +
> +static void
> +set_server_config(const char *name, const char *value, const char *filename)
> +{
> +	if (!strcasecmp("priority", name)) {
> +		server_log_priority = logging_level(value);
> +	} else if (!strcasecmp("session_timeout", name)) {
> +		server_session_timeout = str2ul(name, value, filename);
> +	} else if (!strcasecmp("pidfile", name)) {
> +		free(server_pidfile);
> +		server_pidfile = xstrdup(value);
> +	} else if (!strcasecmp("control_group", name)) {
> +		free(server_control_group);
> +		server_control_group = xstrdup(value);
> +	} else {
> +		bad_option_name(name, filename);
> +	}
> +}
> +
> +static void
> +read_server_config(int fd, const char *name)
> +{
> +	FILE *fp = fdopen(fd, "r");
> +	char buf[BUFSIZ];
> +	unsigned line;
> +
> +	if (!fp)
> +		error(EXIT_FAILURE, errno, "fdopen: %s", name);
> +
> +	for (line = 1; fgets(buf, BUFSIZ, fp); ++line) {
> +		const char *start, *left;
> +		char   *eq, *right, *end;
> +
> +		for (start = buf; *start && isspace(*start); ++start)
> +			;
> +
> +		if (!*start || '#' == *start)
> +			continue;
> +
> +		if (!(eq = strchr(start, '=')))
> +			error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
> +			      name, line);
> +
> +		left = start;
> +		right = eq + 1;
> +
> +		for (; eq > left; --eq)
> +			if (!isspace(eq[-1]))
> +				break;
> +
> +		if (left == eq)
> +			error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
> +			      name, line);
> +
> +		*eq = '\0';
> +		end = right + strlen(right);
> +
> +		for (; right < end; ++right)
> +			if (!isspace(*right))
> +				break;
> +
> +		for (; end > right; --end)
> +			if (!isspace(end[-1]))
> +				break;
> +
> +		*end = '\0';
> +		set_server_config(left, right, name);
> +	}
> +
> +	if (ferror(fp))
> +		error(EXIT_FAILURE, errno, "fgets: %s", name);
> +
> +	if (fclose(fp))
> +		error(EXIT_FAILURE, errno, "fclose: %s", name);
> +}
> +
> +static void
> +load_server_config(const char *name)
> +{
> +	struct stat st;
> +	int fd = open(name, O_RDONLY | O_NOFOLLOW | O_NOCTTY);
> +
> +	if (fd < 0)
> +		error(EXIT_FAILURE, errno, "open: %s", name);
> +
> +	if (fstat(fd, &st) < 0)
> +		error(EXIT_FAILURE, errno, "fstat: %s", name);
> +
> +	stat_root_ok_validator(&st, name);
> +
> +	if (!S_ISREG(st.st_mode))
> +		error(EXIT_FAILURE, 0, "%s: not a regular file", name);
> +
> +	if (st.st_size > MAX_CONFIG_SIZE)
> +		error(EXIT_FAILURE, 0, "%s: file too large: %lu",
> +		      name, (unsigned long) st.st_size);
> +
> +	read_server_config(fd, name);
> +}
> +
> +void
> +configure_server(void)
> +{
> +	safe_chdir("/", stat_root_ok_validator);
> +	safe_chdir("etc/hasher-priv", stat_root_ok_validator);
> +	load_server_config("server");
> +	check_server_control_group();
> +}
> +
> +void
> +free_server_configuration(void)
> +{
> +	free(server_pidfile);
> +	free(server_control_group);
> +}
> diff --git a/hasher-priv/epoll.c b/hasher-priv/epoll.c
> new file mode 100644
> index 0000000..6eecd71
> --- /dev/null
> +++ b/hasher-priv/epoll.c
> @@ -0,0 +1,39 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <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..9eb598c
> --- /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, PROJECT, caller_num) < 0)
> +		return EXIT_FAILURE;
> +
> +	/* Open user session */
> +	snprintf(socketname, sizeof(socketname), "hasher-priv-%d-%u", geteuid(), caller_num);
> +
> +	if ((conn = srv_connect(SOCKETDIR, socketname)) < 0)
> +		return EXIT_FAILURE;
> +
> +	if (server_task(conn, task) < 0)
> +		return EXIT_FAILURE;
> +
> +	if (server_task_fds(conn) < 0)
> +		return EXIT_FAILURE;
> +
> +	if (server_command(conn, CMD_TASK_ARGUMENTS, task_args) < 0)
> +		return EXIT_FAILURE;
> +
> +	if (server_command(conn, CMD_TASK_ENVIRON, ev) < 0)
> +		return EXIT_FAILURE;
> +
> +	if (server_command(conn, CMD_TASK_RUN, NULL) < 0)
> +		return EXIT_FAILURE;
> +
> +	/* Close session socket. */
> +	close(conn);
> +
> +	return EXIT_SUCCESS;
> +}
> diff --git a/hasher-priv/hasher-privd.c b/hasher-priv/hasher-privd.c
> new file mode 100644
> index 0000000..5c56033
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.c
> @@ -0,0 +1,375 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <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) {
> +			send_command_response(conn, CMD_STATUS_DONE, NULL);
> +			return 0;
> +		}
> +		a = &(*a)->next;
> +	}
> +
> +	info("start session for %d:%u user", uid, num);
> +
> +	if ((server_pid = fork_server(conn, uid, gid, num)) < 0)
> +		return -1;
> +
> +	*a = calloc(1L, sizeof(struct session));
> +	if (!*a) {
> +		err("calloc: %m");
> +		return -1;
> +	}
> +
> +	(*a)->caller_uid = uid;
> +	(*a)->caller_gid = gid;
> +	(*a)->caller_num = num;
> +	(*a)->server_pid = server_pid;
> +
> +	return 0;
> +}
> +
> +static int
> +close_session(int conn, unsigned num)
> +{
> +	uid_t uid;
> +	gid_t gid;
> +	struct session *e = pool;
> +
> +	if (get_peercred(conn, NULL, &uid, &gid) < 0)
> +		return -1;
> +
> +	while (e) {
> +		if (e->caller_uid == uid && e->caller_num == num) {
> +			info("close session for %d:%u user by request", uid, num);
> +			if (kill(e->server_pid, SIGTERM) < 0) {
> +				err("kill: %m");
> +				return -1;
> +			}
> +			break;
> +		}
> +		e = e->next;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +process_request(int conn)
> +{
> +	int rc;
> +	struct cmd hdr = { 0 };
> +	unsigned num = 0;
> +
> +	if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
> +		return -1;
> +
> +	if (hdr.datalen != sizeof(num)) {
> +		err("bad command");
> +		send_command_response(conn, CMD_STATUS_FAILED, "bad command");
> +		return -1;
> +	}
> +
> +	if (xrecvmsg(conn, &num, sizeof(num)) < 0)
> +		return -1;
> +
> +	switch (hdr.type) {
> +		case CMD_OPEN_SESSION:
> +			rc = start_session(conn, num);
> +			break;
> +		case CMD_CLOSE_SESSION:
> +			rc = close_session(conn, num);
> +			(rc < 0)
> +				? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
> +				: send_command_response(conn, CMD_STATUS_DONE, NULL);
> +			break;
> +		default:
> +			err("unknown command");
> +			send_command_response(conn, CMD_STATUS_FAILED, "unknown command");
> +			rc = -1;
> +	}
> +
> +	return rc;
> +}
> +
> +static void
> +finish_sessions(int sig)
> +{
> +	struct session *e = pool;
> +
> +	while (e) {
> +		if (kill(e->server_pid, sig) < 0)
> +			err("kill: %m");
> +		e = e->next;
> +	}
> +}
> +
> +static void
> +clean_session(pid_t pid)
> +{
> +	struct session *x, **a = &pool;
> +
> +	while (a && *a) {
> +		if ((*a)->server_pid == pid) {
> +			x = *a;
> +			*a = (*a)->next;
> +			free(x);
> +		}
> +		a = &(*a)->next;
> +	}
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int i;
> +	int loglevel = -1;
> +
> +	const char *pidfile = NULL;
> +	int daemonize = 1;
> +
> +	char socketpath[UNIX_PATH_MAX];
> +
> +	struct option long_options[] = {
> +		{ "help", no_argument, 0, 'h' },
> +		{ "version", no_argument, 0, 'V' },
> +		{ "foreground", no_argument, 0, 'f' },
> +		{ "loglevel", required_argument, 0, 'l' },
> +		{ "pidfile", required_argument, 0, 'p' },
> +		{ 0, 0, 0, 0 }
> +	};
> +
> +	while ((i = getopt_long(argc, argv, "hVfl:p:", long_options, NULL)) != -1) {
> +		switch (i) {
> +			case 'p':
> +				pidfile = optarg;
> +				break;
> +			case 'l':
> +				loglevel = logging_level(optarg);
> +				break;
> +			case 'f':
> +				daemonize = 0;
> +				break;
> +			case 'V':
> +				printf("%s %s\n", program_invocation_short_name, PROJECT_VERSION);
> +				return EXIT_SUCCESS;
> +			default:
> +			case 'h':
> +				printf("Usage: %s [options]\n"
> +				       " -p, --pidfile=FILE   pid file location;\n"
> +				       " -l, --loglevel=LVL   set logging level;\n"
> +				       " -f, --foreground     stay in the foreground;\n"
> +				       " -V, --version        print program version and exit;\n"
> +				       " -h, --help           show this text and exit.\n"
> +				       "\n",
> +				       program_invocation_short_name);
> +				return EXIT_SUCCESS;
> +		}
> +	}
> +
> +	configure_server();
> +
> +	if (!pidfile && server_pidfile && *server_pidfile)
> +		pidfile = server_pidfile;
> +
> +	if (loglevel < 0)
> +		loglevel = (server_log_priority >= 0)
> +			? server_log_priority
> +			: logging_level("info");
> +
> +	umask(022);
> +
> +	if (pidfile && check_pid(pidfile))
> +		error(EXIT_FAILURE, 0, "%s: already running",
> +		      program_invocation_short_name);
> +
> +	if (daemonize && daemon(0, 0) < 0)
> +		error(EXIT_FAILURE, errno, "daemon");
> +
> +	logging_init(loglevel, !daemonize);
> +
> +	if (pidfile && write_pid(pidfile) == 0)
> +		return EXIT_FAILURE;
> +
> +	sigset_t mask;
> +
> +	sigfillset(&mask);
> +	sigprocmask(SIG_SETMASK, &mask, NULL);
> +
> +	sigdelset(&mask, SIGABRT);
> +	sigdelset(&mask, SIGSEGV);
> +
> +	int fd_ep = -1;
> +
> +	if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0)
> +		fatal("epoll_create1: %m");
> +
> +	int fd_signal = -1;
> +
> +	if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0)
> +		fatal("signalfd: %m");
> +
> +	mode_t m = umask(017);
> +
> +	int fd_conn = -1;
> +
> +	if ((fd_conn = srv_listen(SOCKETDIR, PROJECT)) < 0)
> +		return EXIT_FAILURE;
> +
> +	umask(m);
> +
> +	snprintf(socketpath, sizeof(socketpath), "%s/%s", SOCKETDIR, PROJECT);
> +
> +	if (chown(socketpath, 0, server_gid))
> +		fatal("fchown: %s: %m", socketpath);
> +
> +	if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0)
> +		return EXIT_FAILURE;
> +
> +	int ep_timeout = -1;
> +	int finish_server = 0;
> +
> +	while (1) {
> +		struct epoll_event ev[42];
> +		int fdcount;
> +
> +		errno = 0;
> +		if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), ep_timeout)) < 0) {
> +			if (errno == EINTR)
> +				continue;
> +			err("epoll_wait: %m");
> +			break;
> +		}
> +
> +		for (i = 0; i < fdcount; i++) {
> +			if (!(ev[i].events & EPOLLIN)) {
> +				continue;
> +
> +			} else if (ev[i].data.fd == fd_signal) {
> +				struct signalfd_siginfo fdsi;
> +				ssize_t size;
> +
> +				size = read_retry(fd_signal, &fdsi, sizeof(fdsi));
> +
> +				if (size != sizeof(fdsi)) {
> +					err("unable to read signal info");
> +					continue;
> +				}
> +
> +				pid_t pid;
> +				int status;
> +
> +				switch (fdsi.ssi_signo) {
> +					case SIGINT:
> +					case SIGTERM:
> +						finish_server = 1;
> +						break;
> +					case SIGCHLD:
> +						if ((pid = waitpid(-1, &status, 0)) < 0)
> +							err("waitpid: %m");
> +
> +						clean_session(pid);
> +						break;
> +				}
> +
> +			} else if (ev[i].data.fd == fd_conn) {
> +				int conn;
> +
> +				if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
> +					err("accept4: %m");
> +					continue;
> +				}
> +
> +				if (set_recv_timeout(conn, 3) < 0) {
> +					close(conn);
> +					continue;
> +				}
> +
> +				process_request(conn);
> +				close(conn);
> +			}
> +		}
> +
> +		if (finish_server) {
> +			if (!pool)
> +				break;
> +
> +			if (fd_conn >= 0) {
> +				epollin_remove(fd_ep, fd_conn);
> +				fd_conn = -1;
> +				ep_timeout = 3000;
> +			}
> +
> +			finish_sessions(SIGTERM);
> +		}
> +	}
> +
> +	epollin_remove(fd_ep, fd_signal);
> +	epollin_remove(fd_ep, fd_conn);
> +
> +	if (fd_ep >= 0)
> +		close(fd_ep);
> +
> +	if (pidfile)
> +		remove_pid(pidfile);
> +
> +	logging_close();
> +	free_server_configuration();
> +
> +	return EXIT_SUCCESS;
> +}
> diff --git a/hasher-priv/io_log.c b/hasher-priv/io_log.c
> index f5aea59..2689ff0 100644
> --- a/hasher-priv/io_log.c
> +++ b/hasher-priv/io_log.c
> @@ -2,7 +2,7 @@
>  /*
>    Copyright (C) 2008-2019  Dmitry V. Levin <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..9adac47
> --- /dev/null
> +++ b/hasher-priv/logging.c
> @@ -0,0 +1,64 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  A logging functions for the hasher-privd program.

"A functions"? Either an incorrect article or an unfitting plural.

> +
> +  SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +#include <stdio.h>
> +#include <stdarg.h>
> +#include <strings.h>
> +#include <errno.h>
> +#include <syslog.h>
> +
> +#include "logging.h"
> +
> +int log_priority = -1;
> +
> +int logging_level(const char *name)
> +{
> +	if (!strcasecmp(name, "debug"))
> +		return LOG_DEBUG;
> +
> +	if (!strcasecmp(name, "info"))
> +		return LOG_INFO;
> +
> +	if (!strcasecmp(name, "warning"))
> +		return LOG_WARNING;
> +
> +	if (!strcasecmp(name, "error"))
> +		return LOG_ERR;
> +
> +	return 0;
> +}
> +
> +void logging_init(int loglevel, int stderr)
> +{
> +	int options = LOG_PID;
> +	if (stderr)
> +		options |= LOG_PERROR;
> +	log_priority = loglevel;
> +	openlog(program_invocation_short_name, options, LOG_DAEMON);
> +}
> +
> +void logging_close(void)
> +{
> +	closelog();
> +}
> +
> +void
> +message(int priority, const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	va_start(ap, fmt);
> +	if (priority <= log_priority)
> +		vsyslog(priority, fmt, ap);
> +	else if (log_priority < 0) {
> +		vfprintf(stderr, fmt, ap);
> +		fprintf(stderr, "\n");
> +	}
> +	va_end(ap);
> +}
> diff --git a/hasher-priv/logging.h b/hasher-priv/logging.h
> new file mode 100644
> index 0000000..9d28fc8
> --- /dev/null
> +++ b/hasher-priv/logging.h
> @@ -0,0 +1,55 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  A logging functions for the hasher-privd program.

Same as above.

> +
> +  SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +#ifndef _LOGGING_H_
> +#define _LOGGING_H_
> +
> +#include <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..9e23e74
> --- /dev/null
> +++ b/hasher-priv/pidfile.c
> @@ -0,0 +1,128 @@
> +
> +/*
> +    pidfile.c - 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.

Not anymore, that file is now (at patch acceptance) part of hasher-priv.
An equivalent file is part of the sysklogd package, sure, but that's
rather irrelevant.

More like this file was pulled from and possibly originally written for
the sysklogd package; I agree the message should definitely state that
to preserve attribution.

> +
> +    SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +/*
> + * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze
> + *	First version (v0.2) released
> + */
> +
> +#include <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.

Same as above.

> +
> +    SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +#ifndef _PIDFILE_H_
> +#define _PIDFILE_H_
> +
> +/* read_pid
> + *
> + * Reads the specified pidfile and returns the read pid.
> + * 0 is returned if either there's no pidfile, it's empty
> + * or no pid can be read.
> + */
> +int read_pid(const char *pidfile) __attribute__((nonnull(1)));
> +
> +/* check_pid
> + *
> + * Reads the pid using read_pid and looks up the pid in the process
> + * table (using /proc) to determine if the process already exists. If
> + * so 1 is returned, otherwise 0.
> + */
> +int check_pid(const char *pidfile) __attribute__((nonnull(1)));
> +
> +/* write_pid
> + *
> + * Writes the pid to the specified file. If that fails 0 is
> + * returned, otherwise the pid.
> + */
> +int write_pid(const char *pidfile) __attribute__((nonnull(1)));
> +
> +/* remove_pid
> + *
> + * Remove the the specified file. The result from unlink(2)
> + * is returned
> + */
> +int remove_pid(const char *pidfile) __attribute__((nonnull(1)));
> +
> +#endif /* _PIDFILE_H_ */
> diff --git a/hasher-priv/priv.h b/hasher-priv/priv.h
> index 87e72fb..f0eb9f9 100644
> --- a/hasher-priv/priv.h
> +++ b/hasher-priv/priv.h
> @@ -18,16 +18,7 @@
>  #define	MIN_CHANGE_GID	34
>  #define	MAX_CONFIG_SIZE	16384
>  
> -typedef enum
> -{
> -	TASK_NONE = 0,
> -	TASK_GETCONF,
> -	TASK_KILLUID,
> -	TASK_GETUGID1,
> -	TASK_CHROOTUID1,
> -	TASK_GETUGID2,
> -	TASK_CHROOTUID2,
> -} task_t;
> +#include "communication.h"
>  
>  typedef struct
>  {
> @@ -68,9 +59,13 @@ void    restore_tty(void);
>  int     tty_copy_winsize(int master_fd, int slave_fd);
>  int     open_pty(int *slave_fd, int chrooted, int verbose_error);
>  task_t  parse_cmdline(int ac, const char *av[]);
> -void    init_caller_data(void);
> +void    parse_task_args(task_t task, const char *argv[]);
> +int     init_caller_data(uid_t uid, gid_t gid);
> +void    free_caller_data(void);
>  void    parse_env(void);
>  void    configure(void);
> +void    configure_server(void);
> +void    free_server_configuration(void);
>  void    ch_uid(uid_t uid, uid_t *save);
>  void    ch_gid(gid_t gid, gid_t *save);
>  void    chdiruid(const char *path, VALIDATE_FPTR validator);
> @@ -85,6 +80,9 @@ void    stat_root_ok_validator(struct stat *st, const char *name);
>  void    stat_any_ok_validator(struct stat *st, const char *name);
>  void    fd_send(int ctl, int pass, const char *data, size_t len);
>  int     fd_recv(int ctl, char *data, size_t data_len);
> +int     fds_send(int conn, int *fds, size_t fds_len) __attribute__((nonnull(2)));
> +int     fds_recv(int conn, int *fds, size_t datalen) __attribute__((nonnull(2)));
> +
>  int     unix_accept(int fd);
>  int     log_listen(void);
>  void    x11_drop_display(void);
> @@ -120,9 +118,14 @@ int     do_chrootuid1(void);
>  int     do_getugid2(void);
>  int     do_chrootuid2(void);
>  
> +int process_caller_task(int, struct task *);
> +pid_t fork_server(int, uid_t, gid_t, unsigned);
> +
>  extern const char *chroot_path;
>  extern const char **chroot_argv;
>  
> +extern const char **task_args;
> +
>  extern str_list_t allowed_devices;
>  extern str_list_t allowed_mountpoints;
>  extern str_list_t requested_mountpoints;
> @@ -143,7 +146,7 @@ extern int log_fd;
>  
>  extern const char *const *chroot_prefix_list;
>  extern const char *chroot_prefix_path;
> -extern const char *caller_user, *caller_home;
> +extern char *caller_user, *caller_home;
>  extern uid_t caller_uid;
>  extern gid_t caller_gid;
>  extern unsigned caller_num;
> @@ -156,4 +159,10 @@ extern int change_nice;
>  extern change_rlimit_t change_rlimit[];
>  extern work_limit_t wlimit;
>  
> +extern int server_log_priority;
> +extern unsigned long server_session_timeout;
> +extern char *server_control_group;
> +extern char *server_pidfile;
> +extern gid_t server_gid;
> +
>  #endif /* PKG_BUILD_PRIV_H */
> diff --git a/hasher-priv/server.conf b/hasher-priv/server.conf
> new file mode 100644
> index 0000000..53ea5c3
> --- /dev/null
> +++ b/hasher-priv/server.conf
> @@ -0,0 +1,13 @@
> +# Server configuration
> +
> +# Set the default logging priority. (can override with command line arguments)
> +priority=info
> +
> +# Write a pid file. (can override with command line arguments)
> +pidfile=/var/run/hasher-privd.pid
> +
> +# Stop user's session server after {session_timeout} seconds of inactivity.
> +session_timeout=3600
> +
> +# Allow users of this group to interact with hasher-privd via the control socket.
> +control_group=hashman
> diff --git a/hasher-priv/sockets.c b/hasher-priv/sockets.c
> new file mode 100644
> index 0000000..42618a7
> --- /dev/null
> +++ b/hasher-priv/sockets.c
> @@ -0,0 +1,183 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  A сollection of helpers to simplify work with sockets.

This one's nice; however, "the use of" instead of "work with" looks even
nicer to me.

> +
> +  SPDX-License-Identifier: GPL-2.0-or-later
> +*/
> +
> +#include <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..5acdb49
> --- /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 work with 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.24.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files Alex Gladkov
  2020-06-17 22:31   ` Mikhail Novosyolov
@ 2020-09-17 13:10   ` Arseny Maslennikov
  2020-10-01 17:25     ` Alexey Gladkov
  1 sibling, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:10 UTC (permalink / raw)
  To: Alex Gladkov, ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 5338 bytes --]

On Fri, Dec 13, 2019 at 12:42:04PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion@altlinux.org>
> 
> Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> ---
>  hasher-priv/Makefile              |  4 ++
>  hasher-priv/hasher-privd.service  | 11 ++++
>  hasher-priv/hasher-privd.sysvinit | 86 +++++++++++++++++++++++++++++++
>  3 files changed, 101 insertions(+)
>  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 82aa385..c73216f 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
> @@ -72,6 +74,8 @@ install: all
>  	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
>  	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
>  	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
> +	$(MKDIR_P) -m755 $(DESTDIR)$(initdir)
> +	$(INSTALL) -p -m755 hasher-privd.sysvinit $(DESTDIR)$(initdir)/hasher-privd

The systemd service is not installed.

>  	$(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-privd.service b/hasher-priv/hasher-privd.service
> new file mode 100644
> index 0000000..e5ed9ac
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.service
> @@ -0,0 +1,11 @@
> +[Unit]
> +Description=A privileged helper for the hasher project
> +ConditionVirtualization=!container

In response to earlier reviewers: hasher-priv as of today does not work
inside a userns-unprivileged container and does not produce clear
diagnostics (and, from my own experience when I was joining ALT, the
developers did not as well). Thus, for now this condition is justified.
Perhaps in the future, when (and if) we introduce the ability to reuse a
mainstream container runtime as the hasher environment for users R and
B, it would make sense for us to lift this condition.

> +Documentation=man:hasher-priv(8)

Ah yes, I forgot. The patchset contains no changes to the man pages, so
the effort and behaviour change is not reflected. I agree it's best to
revisit them once we're done with the code, though.

> +
> +[Service]
> +ExecStart=/usr/sbin/hasher-privd

Suggested replacement:
"ExecStart=/usr/sbin/hasher-privd -f"

The service implicitly, by default, has Type=simple, which means the
following:
- the main process(-es) is defined by the ExecStart= command line(-s)
  and is intended to persist while the service is launched and active;
- its pid/tgid is tracked by the service manager and can be queried;
- the service manager puts it into its own cgroup;
- its standard output and standard error are redirected to system log;
- (follows from the above) the main process never has a controlling
  terminal or standard file descriptors pointing to any terminal, its
  sid is equal to its tgid — and so it does not have to perform
  manual steps to daemonize.

> +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..914fb53
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.sysvinit
> @@ -0,0 +1,86 @@
> +#! /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"
> +RETVAL=0
> +
> +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)
> +		start
> +		;;
> +	stop)
> +		stop
> +		;;
> +	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.24.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 3/3] Add cgroup support
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 3/3] Add cgroup support Alex Gladkov
@ 2020-09-17 13:11   ` Arseny Maslennikov
  2020-10-01 19:17     ` Alexey Gladkov
  0 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:11 UTC (permalink / raw)
  To: Alex Gladkov, ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 7807 bytes --]

On Fri, Dec 13, 2019 at 12:42:05PM +0100, Alex Gladkov wrote:
> From: Alexey Gladkov <legion@altlinux.org>
> 

Could you please explain what you're trying to do with this patch?
Even if it's obvious from the source itself, we still must have an
opportunity to discuss, and a decent explanation should stay in the
project history.

Most likely, it'll turn out we _at least_ have to pass Delegate=yes to
the systemd service:

       Delegate=
           Turns on delegation of further resource control
           partitioning to processes of the unit. Units where
           this is enabled may create and manage their own
           private subhierarchy of control groups below the
           control group of the unit itself.
Manual page systemd.resource-control(5): lines 786-791

Do we only support cgroup2 and ignore cgroup1? If yes, great, but
perhaps then we might want to have a setting to not fiddle with cgroup
trees, to support the unfortunate users that have to run Docker and
other garbage.

> Signed-off-by: Alexey Gladkov <legion@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 c73216f..e999972 100644
> --- a/hasher-priv/Makefile
> +++ b/hasher-priv/Makefile
> @@ -51,7 +51,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 d8f2dd5..722e0a6 100644
> --- a/hasher-priv/caller_task.c
> +++ b/hasher-priv/caller_task.c
> @@ -95,6 +95,9 @@ caller_task(struct task *task)
>  		return pid;
>  	}
>  
> +	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 6b6bdb1..3faf936 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_control_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("control_group", name)) {
>  		free(server_control_group);
>  		server_control_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_control_group);
> +	free(server_cgroup_template);
>  }
> diff --git a/hasher-priv/priv.h b/hasher-priv/priv.h
> index f0eb9f9..f29603a 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_control_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 53ea5c3..9e70487 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.
>  control_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.24.0
> 
> _______________________________________________
> 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] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] caller.c
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
  2020-09-17 13:10   ` Arseny Maslennikov
  2020-09-17 13:10   ` [devel] [PATCH hasher-priv v1 1/3] *literacy* Arseny Maslennikov
@ 2020-09-17 13:11   ` Arseny Maslennikov
  2020-09-17 13:55     ` Arseny Maslennikov
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller_server.c, caller_task.c Arseny Maslennikov
                     ` (5 subsequent siblings)
  8 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:11 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 2736 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> 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);
>  }

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] caller_server.c, caller_task.c
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
                     ` (2 preceding siblings ...)
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller.c Arseny Maslennikov
@ 2020-09-17 13:11   ` Arseny Maslennikov
  2020-10-01 19:47     ` Alexey Gladkov
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] config.c Arseny Maslennikov
                     ` (4 subsequent siblings)
  8 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:11 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 14672 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> diff --git a/hasher-priv/caller_server.c b/hasher-priv/caller_server.c
> new file mode 100644
> index 0000000..8182c69
> --- /dev/null
> +++ b/hasher-priv/caller_server.c
> @@ -0,0 +1,373 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  The 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->env) {
> +		free(task->env[0]);
> +		free(task->env);
> +	}
> +
> +	if (task->argv) {
> +		free(task->argv[0]);
> +		free(task->argv);
> +	}
> +}
> +
> +static int
> +cancel_task(int conn, struct task *task)
> +{
> +	free_task(task);
> +	send_command_response(conn, CMD_STATUS_FAILED, "command failed");
> +	return 0;
> +}
> +
> +static int
> +process_task(int conn)
> +{
> +	uid_t uid;
> +	gid_t gid;
> +
> +	if (get_peercred(conn, NULL, &uid, &gid) < 0)
> +		return -1;
> +
> +	if (caller_uid != uid || caller_gid != gid) {
> +		err("user (uid=%d) has no permissions to send commands"
> +		    " to the session of another user (uid=%d)",
> +		    uid, caller_uid);
> +		return -1;
> +	}
> +
> +	struct task task = { 0 };
> +	int fds[3];
> +
> +	while (1) {
> +		task_t type = TASK_NONE;
> +		struct cmd hdr = { 0 };
> +
> +		if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
> +			return cancel_task(conn, &task);
> +
> +		switch (hdr.type) {
> +			case CMD_TASK_BEGIN:
> +				if (hdr.datalen != sizeof(type))
> +					return cancel_task(conn, &task);
> +
> +				if (xrecvmsg(conn, &type, hdr.datalen) < 0)
> +					return cancel_task(conn, &task);
> +
> +				task.type = type;
> +
> +				break;
> +
> +			case CMD_TASK_FDS:
> +				if (hdr.datalen != sizeof(int) * 3)
> +					return cancel_task(conn, &task);
> +
> +				if (task.stdin)
> +					close(task.stdin);
> +
> +				if (task.stdout)
> +					close(task.stdout);
> +
> +				if (task.stderr)
> +					close(task.stderr);
> +
> +				if (fds_recv(conn, fds, 3) < 0)
> +					return cancel_task(conn, &task);
> +
> +				task.stdin  = fds[0];
> +				task.stdout = fds[1];
> +				task.stderr = fds[2];
> +
> +				break;
> +
> +			case CMD_TASK_ARGUMENTS:
> +				if (task.argv) {
> +					free(task.argv[0]);
> +					free(task.argv);
> +				}
> +
> +				if (recv_list(conn, &task.argv, hdr.datalen) < 0)
> +					return cancel_task(conn, &task);
> +
> +				if (validate_arguments(task.type, task.argv) < 0)
> +					return cancel_task(conn, &task);
> +
> +				break;
> +
> +			case CMD_TASK_ENVIRON:
> +				if (task.env) {
> +					free(task.env[0]);
> +					free(task.env);
> +				}
> +
> +				if (recv_list(conn, &task.env, hdr.datalen) < 0)
> +					return cancel_task(conn, &task);
> +
> +				break;
> +
> +			case CMD_TASK_RUN:
> +				process_caller_task(conn, &task);
> +				free_task(&task);
> +				return 0;
> +
> +			default:
> +				err("unsupported command: %d", hdr.type);
> +		}
> +
> +		send_command_response(conn, CMD_STATUS_DONE, NULL);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +caller_server(int cl_conn)
> +{
> +	int ret = 0;
> +
> +	umask(077);
> +
> +	snprintf(socketpath, sizeof(socketpath),
> +		"hasher-priv-%d-%u",
> +		caller_uid, caller_num);
> +
> +	int fd_conn   = -1;
> +	int fd_signal = -1;
> +	int fd_ep     = -1;
> +
> +	if ((fd_conn = srv_listen(SOCKETDIR, socketpath)) < 0)
> +		return -1;
> +
> +	snprintf(socketpath, sizeof(socketpath),
> +		"%s/hasher-priv-%d-%u",
> +		SOCKETDIR, caller_uid, caller_num);
> +
> +	if (chown(socketpath, caller_uid, caller_gid)) {
> +		err("fchown: %s: %m", socketpath);
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	/* Load config according to caller information. */
> +	configure();
> +
> +	sigset_t mask;
> +
> +	sigfillset(&mask);
> +	sigprocmask(SIG_SETMASK, &mask, NULL);
> +
> +	if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0) {
> +		err("signalfd: %m");
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
> +		err("epoll_create1: %m");
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0) {
> +		err("epollin_add: failed");
> +		ret = -1;
> +		goto fail;
> +	}
> +
> +	/* Tell client that caller server is ready */
> +	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");
> +					continue;
> +				}
> +
> +				int status;
> +
> +				switch (fdsi.ssi_signo) {
> +					case SIGINT:
> +					case SIGTERM:
> +						finish_server = 1;
> +						break;
> +					case SIGCHLD:
> +						if (waitpid(-1, &status, 0) < 0)
> +							err("waitpid: %m");
> +						break;
> +				}
> +
> +			} else if (ev[i].data.fd == fd_conn) {
> +				int conn;
> +
> +				if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
> +					err("accept4: %m");
> +					continue;
> +				}
> +
> +				if (set_recv_timeout(conn, 3) < 0) {
> +					close(conn);
> +					continue;
> +				}
> +
> +				if (!process_task(conn)) {
> +					/* reset timer */
> +					nsec = 0;
> +				}
> +
> +				close(conn);
> +			}
> +		}
> +	}
> +fail:
> +	epollin_remove(fd_ep, fd_signal);
> +	epollin_remove(fd_ep, fd_conn);
> +
> +	if (fd_ep >= 0)
> +		close(fd_ep);
> +
> +	unlink(socketpath);
> +
> +	return ret;
> +}
> +
> +pid_t
> +fork_server(int cl_conn, uid_t uid, gid_t gid, unsigned num)
> +{
> +	pid_t pid = fork();
> +
> +	if (pid != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}
> +		return pid;
> +	}
> +
> +	caller_num = num;
> +
> +	int ret = init_caller_data(uid, gid);
> +
> +	if (ret < 0) {

This if-block is semantically nonsensical as written.

If this comparison is reversed (ret >= 0), the resulting code looks
semantically correct; it probably was a typo. It still would be better to refactor this
piece, imo.

> +		info("%s(%d) num=%u: start session server", caller_user, caller_uid, caller_num);
> +		ret = caller_server(cl_conn);
> +		info("%s(%d): finish session server", caller_user, caller_uid);
> +	}
> +
> +	if (ret < 0) {
> +		send_command_response(cl_conn, CMD_STATUS_FAILED, NULL);
> +		ret = EXIT_FAILURE;
> +	} else {
> +		ret = EXIT_SUCCESS;
> +	}
> +
> +	free_caller_data();
> +	close(cl_conn);
> +
> +	exit(ret);
> +}
> diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c
> new file mode 100644
> index 0000000..d8f2dd5
> --- /dev/null
> +++ b/hasher-priv/caller_task.c
> @@ -0,0 +1,214 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  The 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;
> +
> +	if ((pid = fork()) != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}
> +		return pid;
> +	}

There are multiple snippets like this one in the patch.

This would look better, wouldn't it:

+	if ((pid = fork()) < 0) {
+		err("fork: %m");
+		return -1;
+	}
+
+	if (pid > 0) {
+		return pid;
+	}
+

> +
> +	if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
> +		exit(rc);
> +
> +	/* cleanup 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);
> +
> +	if (chroot_path && *chroot_path != '/')
> +		fatal("%s: invalid chroot path", chroot_path);
> +
> +	/* Fourth, parse environment for config options. */
> +	parse_env();
> +
> +	/* We don't need environment variables any longer. */
> +	if (clearenv() != 0)
> +		fatal("clearenv: %m");
> +
> +	/* Finally, execute choosen 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 of all 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;
> +
> +	if ((pid = fork()) != 0) {
> +		if (pid < 0) {
> +			err("fork: %m");
> +			return -1;
> +		}

Some resources (up to 3 file descriptors per task, memory for tasks) are
not reclaimed here, in the likely long-living hasherd session process.

Fun fact: if you fix hasher-privd to run successfully until this point,
the following shell command
`echo $(/usr/libexec/hasher-priv/hasher-priv getconf)' ??? a common
pattern in hsh(1) ??? actually hangs until the session process dies. No
wonder: since there still are open writers on the pipe to shell when the
task finishes, there's no end-of-stream.
A more clear reproducer would be:
`/usr/libexec/hasher-priv/hasher-priv getconf | cat'.

> +		return 0;
> +	}
> +
> +	if ((cpid = caller_task(task)) > 0) {

There's quite a bunch of forks involved in fulfilling any possible
request.

I suggest to link the daemon with [1] and call setproctitle in all
non-execcing descendant processes of the daemon.

/usr/sbin/hasher-privd -f
 \_ hasher-privd: privileged helper for ar/500:0
     \_ hasher-privd: process_caller_task
         \_ hasher-privd: [500:0] task handler: chrootuid1
             \_ /bin/sh /usr/bin/fakeroot /.host/entry
                 \_ ...

This looks better than a wall of "/usr/sbin/hasher-privd -f"

[1] http://git.altlinux.org/gears/s/setproctitle.git?p=setproctitle.git;a=summary

> +		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);
> +	}

We have free_task() as a static function on the struct task* in a
neighbour file. Merge the files maybe? And close task->std(in|out|err)
if > 0.

Another decent option is to publish free_task() in caller_task.c

> +
> +	/* Notify client about result */
> +	(rc == EXIT_FAILURE)
> +		? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
> +		: send_command_response(conn, CMD_STATUS_DONE, NULL);
> +
> +	exit(rc);
> +}
// Redundant

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] config.c
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
                     ` (3 preceding siblings ...)
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller_server.c, caller_task.c Arseny Maslennikov
@ 2020-09-17 13:11   ` Arseny Maslennikov
  2020-09-18 10:42     ` Dmitry V. Levin
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] hasher-privd.c Arseny Maslennikov
                     ` (3 subsequent siblings)
  8 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:11 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 5656 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> diff --git a/hasher-priv/config.c b/hasher-priv/config.c
> index e3fedcd..6b6bdb1 100644
> --- a/hasher-priv/config.c
> +++ b/hasher-priv/config.c
> @@ -1,6 +1,7 @@
>
>  /*
>    Copyright (C) 2003-2019  Dmitry V. Levin <ldv@altlinux.org>
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
>
>    Configuration support module for the hasher-priv program.
>
> @@ -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_control_group = NULL;
> +char *server_pidfile = NULL;
>  const char *term;
>  const char *x11_display, *x11_key;
>  str_list_t allowed_devices;
> @@ -33,6 +38,8 @@ str_list_t allowed_mountpoints;
>  str_list_t requested_mountpoints;
>  uid_t   change_uid1, change_uid2;
>  gid_t   change_gid1, change_gid2;
> +gid_t   server_gid;
> +unsigned long server_session_timeout = 0;
>  mode_t  change_umask = 022;
>  int change_nice = 8;
>  int     makedev_console;
> @@ -42,6 +49,7 @@ int share_caller_network = 0;
>  int share_ipc = -1;
>  int share_network = -1;
>  int share_uts = -1;
> +int server_log_priority = -1;
>  change_rlimit_t change_rlimit[] = {
>
>  /* Per-process CPU limit, in seconds.  */
> @@ -209,7 +217,7 @@ parse_rlim(const char *name, const char *value, const char *optname,
>  }
>
>  static unsigned long
> -str2wlim(const char *name, const char *value, const char *filename)
> +str2ul(const char *name, const char *value, const char *filename)
>  {
>  	char   *p = 0;
>  	unsigned long long n;
> @@ -229,7 +237,7 @@ static void
>  modify_wlim(unsigned long *pval, const char *value,
>  	    const char *optname, const char *filename, int is_system)
>  {
> -	unsigned long val = str2wlim(optname, value, filename);
> +	unsigned long val = str2ul(optname, value, filename);
>
>  	if (is_system || *pval == 0 || (val > 0 && val < *pval))
>  		*pval = val;
> @@ -633,3 +641,134 @@ parse_env(void)
>  	if ((e = getenv("requested_mountpoints")))
>  		parse_str_list(e, &requested_mountpoints);
>  }
> +
> +static void
> +check_server_control_group(void)

Bad (IOW, unlucky) naming; especially since in a later patch
hasher-privd deals with cgroups.

Is this related to the socket inode's gid in /run?

> +{
> +	struct group *gr;
> +
> +	if (!server_control_group || !*server_control_group)
> +		error(EXIT_FAILURE, 0, "config: undefined: control_group");
> +
> +	gr = getgrnam(server_control_group);
> +
> +	if (!gr || !gr->gr_name)
> +		error(EXIT_FAILURE, 0, "config: control_group: %s lookup failure", server_control_group);
> +
> +	server_gid = gr->gr_gid;
> +}
> +
> +static void
> +set_server_config(const char *name, const char *value, const char *filename)
> +{
> +	if (!strcasecmp("priority", name)) {
> +		server_log_priority = logging_level(value);
> +	} else if (!strcasecmp("session_timeout", name)) {
> +		server_session_timeout = str2ul(name, value, filename);
> +	} else if (!strcasecmp("pidfile", name)) {
> +		free(server_pidfile);
> +		server_pidfile = xstrdup(value);
> +	} else if (!strcasecmp("control_group", name)) {
> +		free(server_control_group);
> +		server_control_group = xstrdup(value);
> +	} else {
> +		bad_option_name(name, filename);
> +	}
> +}
> +
> +static void
> +read_server_config(int fd, const char *name)
> +{
> +	FILE *fp = fdopen(fd, "r");
> +	char buf[BUFSIZ];
> +	unsigned line;
> +
> +	if (!fp)
> +		error(EXIT_FAILURE, errno, "fdopen: %s", name);
> +
> +	for (line = 1; fgets(buf, BUFSIZ, fp); ++line) {
> +		const char *start, *left;
> +		char   *eq, *right, *end;
> +
> +		for (start = buf; *start && isspace(*start); ++start)
> +			;
> +
> +		if (!*start || '#' == *start)
> +			continue;
> +
> +		if (!(eq = strchr(start, '=')))
> +			error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
> +			      name, line);
> +
> +		left = start;
> +		right = eq + 1;
> +
> +		for (; eq > left; --eq)
> +			if (!isspace(eq[-1]))
> +				break;
> +
> +		if (left == eq)
> +			error(EXIT_FAILURE, 0, "%s: syntax error at line %u",
> +			      name, line);
> +
> +		*eq = '\0';
> +		end = right + strlen(right);
> +
> +		for (; right < end; ++right)
> +			if (!isspace(*right))
> +				break;
> +
> +		for (; end > right; --end)
> +			if (!isspace(end[-1]))
> +				break;
> +
> +		*end = '\0';
> +		set_server_config(left, right, name);
> +	}
> +
> +	if (ferror(fp))
> +		error(EXIT_FAILURE, errno, "fgets: %s", name);
> +
> +	if (fclose(fp))
> +		error(EXIT_FAILURE, errno, "fclose: %s", name);
> +}
> +
> +static void
> +load_server_config(const char *name)
> +{
> +	struct stat st;
> +	int fd = open(name, O_RDONLY | O_NOFOLLOW | O_NOCTTY);
> +
> +	if (fd < 0)
> +		error(EXIT_FAILURE, errno, "open: %s", name);
> +
> +	if (fstat(fd, &st) < 0)
> +		error(EXIT_FAILURE, errno, "fstat: %s", name);
> +
> +	stat_root_ok_validator(&st, name);
> +
> +	if (!S_ISREG(st.st_mode))
> +		error(EXIT_FAILURE, 0, "%s: not a regular file", name);
> +
> +	if (st.st_size > MAX_CONFIG_SIZE)
> +		error(EXIT_FAILURE, 0, "%s: file too large: %lu",
> +		      name, (unsigned long) st.st_size);
> +
> +	read_server_config(fd, name);
> +}
> +
> +void
> +configure_server(void)
> +{
> +	safe_chdir("/", stat_root_ok_validator);
> +	safe_chdir("etc/hasher-priv", stat_root_ok_validator);
> +	load_server_config("server");
> +	check_server_control_group();
> +}
> +
> +void
> +free_server_configuration(void)
> +{
> +	free(server_pidfile);
> +	free(server_control_group);
> +}

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] hasher-privd.c
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
                     ` (4 preceding siblings ...)
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] config.c Arseny Maslennikov
@ 2020-09-17 13:12   ` Arseny Maslennikov
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] logging.c Arseny Maslennikov
                     ` (2 subsequent siblings)
  8 siblings, 0 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:12 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 9039 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> diff --git a/hasher-priv/hasher-privd.c b/hasher-priv/hasher-privd.c
> new file mode 100644
> index 0000000..5c56033
> --- /dev/null
> +++ b/hasher-priv/hasher-privd.c
> @@ -0,0 +1,375 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <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)

No error response to the client.

> +		return -1;
> +
> +	while (a && *a) {
> +		if ((*a)->caller_uid == uid && (*a)->caller_num == num) {
> +			send_command_response(conn, CMD_STATUS_DONE, NULL);
> +			return 0;
> +		}
> +		a = &(*a)->next;
> +	}
> +
> +	info("start session for %d:%u user", uid, num);
> +
> +	if ((server_pid = fork_server(conn, uid, gid, num)) < 0)

No error response to the client.

> +		return -1;
> +
> +	*a = calloc(1L, sizeof(struct session));
> +	if (!*a) {
> +		err("calloc: %m");

Same.

> +		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);

Ultimately, in some exit paths from start_session() the client receives
no response.  Most of them are triggered in case of error, so one option
would be to send a failure response when rc < 0, and handle successful
responses from inside start_session, most likely in separate processes.

> +			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' },

Opinion: why the short option for pidfile path? It eats up the short
ascii-alphanum space for a rather obscure feature.

> +		{ 0, 0, 0, 0 }
> +	};
> +
> +	while ((i = getopt_long(argc, argv, "hVfl:p:", long_options, NULL)) != -1) {
> +		switch (i) {
> +			case 'p':
> +				pidfile = optarg;
> +				break;
> +			case 'l':
> +				loglevel = logging_level(optarg);
> +				break;
> +			case 'f':
> +				daemonize = 0;
> +				break;
> +			case 'V':
> +				printf("%s %s\n", program_invocation_short_name, PROJECT_VERSION);
> +				return EXIT_SUCCESS;
> +			default:
> +			case 'h':
> +				printf("Usage: %s [options]\n"
> +				       " -p, --pidfile=FILE   pid file location;\n"
> +				       " -l, --loglevel=LVL   set logging level;\n"
> +				       " -f, --foreground     stay in the foreground;\n"
> +				       " -V, --version        print program version and exit;\n"
> +				       " -h, --help           show this text and exit.\n"
> +				       "\n",
> +				       program_invocation_short_name);
> +				return EXIT_SUCCESS;
> +		}
> +	}
> +
> +	configure_server();
> +
> +	if (!pidfile && server_pidfile && *server_pidfile)
> +		pidfile = server_pidfile;
> +
> +	if (loglevel < 0)
> +		loglevel = (server_log_priority >= 0)
> +			? server_log_priority
> +			: logging_level("info");
> +
> +	umask(022);
> +
> +	if (pidfile && check_pid(pidfile))
> +		error(EXIT_FAILURE, 0, "%s: already running",
> +		      program_invocation_short_name);
> +
> +	if (daemonize && daemon(0, 0) < 0)
> +		error(EXIT_FAILURE, errno, "daemon");
> +
> +	logging_init(loglevel, !daemonize);
> +
> +	if (pidfile && write_pid(pidfile) == 0)
> +		return EXIT_FAILURE;
> +
> +	sigset_t mask;
> +
> +	sigfillset(&mask);
> +	sigprocmask(SIG_SETMASK, &mask, NULL);
> +
> +	sigdelset(&mask, SIGABRT);
> +	sigdelset(&mask, SIGSEGV);
> +
> +	int fd_ep = -1;
> +
> +	if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0)
> +		fatal("epoll_create1: %m");
> +
> +	int fd_signal = -1;
> +
> +	if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0)
> +		fatal("signalfd: %m");
> +
> +	mode_t m = umask(017);
> +
> +	int fd_conn = -1;
> +
> +	if ((fd_conn = srv_listen(SOCKETDIR, PROJECT)) < 0)
> +		return EXIT_FAILURE;
> +
> +	umask(m);
> +
> +	snprintf(socketpath, sizeof(socketpath), "%s/%s", SOCKETDIR, PROJECT);
> +
> +	if (chown(socketpath, 0, server_gid))
> +		fatal("fchown: %s: %m", socketpath);
> +
> +	if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0)
> +		return EXIT_FAILURE;
> +
> +	int ep_timeout = -1;
> +	int finish_server = 0;
> +
> +	while (1) {
> +		struct epoll_event ev[42];
> +		int fdcount;
> +
> +		errno = 0;
> +		if ((fdcount = epoll_wait(fd_ep, ev, ARRAY_SIZE(ev), ep_timeout)) < 0) {
> +			if (errno == EINTR)
> +				continue;
> +			err("epoll_wait: %m");
> +			break;
> +		}
> +
> +		for (i = 0; i < fdcount; i++) {
> +			if (!(ev[i].events & EPOLLIN)) {
> +				continue;
> +
> +			} else if (ev[i].data.fd == fd_signal) {
> +				struct signalfd_siginfo fdsi;
> +				ssize_t size;
> +
> +				size = read_retry(fd_signal, &fdsi, sizeof(fdsi));
> +
> +				if (size != sizeof(fdsi)) {
> +					err("unable to read signal info");
> +					continue;
> +				}
> +
> +				pid_t pid;
> +				int status;
> +
> +				switch (fdsi.ssi_signo) {
> +					case SIGINT:
> +					case SIGTERM:
> +						finish_server = 1;
> +						break;
> +					case SIGCHLD:
> +						if ((pid = waitpid(-1, &status, 0)) < 0)
> +							err("waitpid: %m");
> +
> +						clean_session(pid);
> +						break;
> +				}
> +
> +			} else if (ev[i].data.fd == fd_conn) {
> +				int conn;
> +
> +				if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
> +					err("accept4: %m");
> +					continue;
> +				}
> +
> +				if (set_recv_timeout(conn, 3) < 0) {
> +					close(conn);
> +					continue;
> +				}
> +
> +				process_request(conn);
> +				close(conn);
> +			}
> +		}
> +
> +		if (finish_server) {
> +			if (!pool)
> +				break;
> +
> +			if (fd_conn >= 0) {
> +				epollin_remove(fd_ep, fd_conn);
> +				fd_conn = -1;
> +				ep_timeout = 3000;
> +			}
> +
> +			finish_sessions(SIGTERM);
> +		}
> +	}
> +
> +	epollin_remove(fd_ep, fd_signal);
> +	epollin_remove(fd_ep, fd_conn);
> +
> +	if (fd_ep >= 0)
> +		close(fd_ep);
> +
> +	if (pidfile)
> +		remove_pid(pidfile);
> +
> +	logging_close();
> +	free_server_configuration();
> +
> +	return EXIT_SUCCESS;
> +}

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] logging.c
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
                     ` (5 preceding siblings ...)
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] hasher-privd.c Arseny Maslennikov
@ 2020-09-17 13:12   ` Arseny Maslennikov
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] Makefile Arseny Maslennikov
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] server.conf Arseny Maslennikov
  8 siblings, 0 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:12 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 4053 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> diff --git a/hasher-priv/logging.c b/hasher-priv/logging.c
> new file mode 100644
> index 0000000..9adac47
> --- /dev/null
> +++ b/hasher-priv/logging.c
> @@ -0,0 +1,64 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  A 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 logging_level(const char *name)
> +{
> +	if (!strcasecmp(name, "debug"))
> +		return LOG_DEBUG;
> +
> +	if (!strcasecmp(name, "info"))
> +		return LOG_INFO;
> +
> +	if (!strcasecmp(name, "warning"))
> +		return LOG_WARNING;
> +
> +	if (!strcasecmp(name, "error"))
> +		return LOG_ERR;
> +
> +	return 0;
> +}
> +
> +void logging_init(int loglevel, int stderr)
> +{
> +	int options = LOG_PID;
> +	if (stderr)
> +		options |= LOG_PERROR;
> +	log_priority = loglevel;
> +	openlog(program_invocation_short_name, options, LOG_DAEMON);
> +}
> +
> +void logging_close(void)
> +{
> +	closelog();
> +}
> +
> +void
> +message(int priority, const char *fmt, ...)
> +{
> +	va_list ap;
> +
> +	va_start(ap, fmt);
> +	if (priority <= log_priority)
> +		vsyslog(priority, fmt, ap);

If the daemon is invoked with `-f', why not log to stderr only,
prepending the message priority? That behaviour would fit better with
service managers that take care of process daemonization / pid tracking
themselves and direct the service's stderr to system log.

If log message duplication is desired, we can add a command line
argument to enforce that.

To produce messages of different priorities, we can prepend "<%d>" to
each log message.

> +	else if (log_priority < 0) {
> +		vfprintf(stderr, fmt, ap);
> +		fprintf(stderr, "\n");
> +	}
> +	va_end(ap);
> +}
> diff --git a/hasher-priv/logging.h b/hasher-priv/logging.h
> new file mode 100644
> index 0000000..9d28fc8
> --- /dev/null
> +++ b/hasher-priv/logging.h
> @@ -0,0 +1,55 @@
> +
> +/*
> +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> +
> +  A 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_ */

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Makefile
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
                     ` (6 preceding siblings ...)
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] logging.c Arseny Maslennikov
@ 2020-09-17 13:12   ` Arseny Maslennikov
  2020-09-17 15:09     ` Vladimir D. Seleznev
                       ` (2 more replies)
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] server.conf Arseny Maslennikov
  8 siblings, 3 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:12 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 4193 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
> index a815e9e..82aa385 100644
> --- a/hasher-priv/Makefile
> +++ b/hasher-priv/Makefile
> @@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec)
>  HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh
>  MAN5PAGES = $(PROJECT).conf.5
>  MAN8PAGES = $(PROJECT).8 hasher-useradd.8
> -TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> +TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)

To everyone: While the name "hasher-privd" minimises the amount of
renaming we have to do, it is too long a name, given that /proc/%d/comm
for each task is up to 16 bytes long on Linux, and is too easy to
confuse with hasher-priv, the client invoker program.

How about hasherd? hshd is too easily confused with sshd.
hasher-priv can then be mnemonised as "hasher-request-priv-operation",
hsh is the user frontend, and hasherd is the daemon.

>  
>  sysconfdir = /etc
>  libexecdir = /usr/lib
> @@ -21,6 +21,7 @@ man5dir = $(mandir)/man5
>  man8dir = $(mandir)/man8
>  configdir = $(sysconfdir)/$(PROJECT)
>  helperdir = $(libexecdir)/$(PROJECT)
> +socketdir = /var/run

Why /var/run and not /run, especially in a new project?

Even further, I would suggest that we store the socket in
/run/hasher-priv or something, setgid hashman, with 0710 rights. The
major service managers can create the directory on startup for us:
there's mkdir(1), there's RuntimeDirectory= and RuntimeDirectoryMode=.

>  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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] server.conf
  2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
                     ` (7 preceding siblings ...)
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] Makefile Arseny Maslennikov
@ 2020-09-17 13:12   ` Arseny Maslennikov
  2020-09-18 10:50     ` Dmitry V. Levin
  8 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:12 UTC (permalink / raw)
  To: Alex Gladkov, devel; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 795 bytes --]

On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> diff --git a/hasher-priv/server.conf b/hasher-priv/server.conf
> new file mode 100644
> index 0000000..53ea5c3
> --- /dev/null
> +++ b/hasher-priv/server.conf
> @@ -0,0 +1,13 @@
> +# Server configuration
> +
> +# Set the default logging priority. (can override with command line arguments)
> +priority=info
> +
> +# Write a pid file. (can override with command line arguments)
> +pidfile=/var/run/hasher-privd.pid
> +
> +# Stop user's session server after {session_timeout} seconds of inactivity.
> +session_timeout=3600
> +
> +# Allow users of this group to interact with hasher-privd via the control socket.
> +control_group=hashman

As noted earlier, in a different file: this unlucky name too strongly
associates with cgroups.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] caller.c
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller.c Arseny Maslennikov
@ 2020-09-17 13:55     ` Arseny Maslennikov
  0 siblings, 0 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-17 13:55 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: Alex Gladkov, ldv

[-- Attachment #1: Type: text/plain, Size: 3065 bytes --]

> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> > 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);
> >  }

Eeeh.
Since I did not have any comments about this file, I believe this file
is OK. Sorry for the noise.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Makefile
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] Makefile Arseny Maslennikov
@ 2020-09-17 15:09     ` Vladimir D. Seleznev
  2020-09-18 10:48     ` Dmitry V. Levin
  2020-09-18 11:33     ` Dmitry V. Levin
  2 siblings, 0 replies; 52+ messages in thread
From: Vladimir D. Seleznev @ 2020-09-17 15:09 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: arseny, Alex Gladkov, ldv

On Thu, Sep 17, 2020 at 04:12:36PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> > diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
> > index a815e9e..82aa385 100644
> > --- a/hasher-priv/Makefile
> > +++ b/hasher-priv/Makefile
> > @@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec)
> >  HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh
> >  MAN5PAGES = $(PROJECT).conf.5
> >  MAN8PAGES = $(PROJECT).8 hasher-useradd.8
> > -TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> > +TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> 
> To everyone: While the name "hasher-privd" minimises the amount of
> renaming we have to do, it is too long a name, given that /proc/%d/comm
> for each task is up to 16 bytes long on Linux, and is too easy to
> confuse with hasher-priv, the client invoker program.
> 
> How about hasherd? hshd is too easily confused with sshd.
> hasher-priv can then be mnemonised as "hasher-request-priv-operation",
> hsh is the user frontend, and hasherd is the daemon.

hshprivd?

-- 
   WBR,
   Vladimir D. Seleznev


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] config.c
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] config.c Arseny Maslennikov
@ 2020-09-18 10:42     ` Dmitry V. Levin
  0 siblings, 0 replies; 52+ messages in thread
From: Dmitry V. Levin @ 2020-09-18 10:42 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, Alex Gladkov

On Thu, Sep 17, 2020 at 04:11:56PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
[...]
> > +static void
> > +check_server_control_group(void)
> 
> Bad (IOW, unlucky) naming; especially since in a later patch
> hasher-privd deals with cgroups.

It's the group that limits access to the server,
so it's the access group.

> Is this related to the socket inode's gid in /run?

Yes, but the inode of the listening socket doesn't necessarily have to
belong to this group if the access limit is implemented using directory
permissions.


-- 
ldv


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Makefile
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] Makefile Arseny Maslennikov
  2020-09-17 15:09     ` Vladimir D. Seleznev
@ 2020-09-18 10:48     ` Dmitry V. Levin
  2020-09-18 10:54       ` Andrey Savchenko
  2020-09-18 11:33     ` Dmitry V. Levin
  2 siblings, 1 reply; 52+ messages in thread
From: Dmitry V. Levin @ 2020-09-18 10:48 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, Alex Gladkov

On Thu, Sep 17, 2020 at 04:12:36PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> > diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
> > index a815e9e..82aa385 100644
> > --- a/hasher-priv/Makefile
> > +++ b/hasher-priv/Makefile
> > @@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec)
> >  HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh
> >  MAN5PAGES = $(PROJECT).conf.5
> >  MAN8PAGES = $(PROJECT).8 hasher-useradd.8
> > -TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> > +TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> 
> To everyone: While the name "hasher-privd" minimises the amount of
> renaming we have to do, it is too long a name, given that /proc/%d/comm
> for each task is up to 16 bytes long on Linux,

Why should we care about /proc/%d/comm limitations?  Is this really an issue?


-- 
ldv


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] server.conf
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] server.conf Arseny Maslennikov
@ 2020-09-18 10:50     ` Dmitry V. Levin
  2020-09-18 10:57       ` Arseny Maslennikov
  0 siblings, 1 reply; 52+ messages in thread
From: Dmitry V. Levin @ 2020-09-18 10:50 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, Alex Gladkov

On Thu, Sep 17, 2020 at 04:12:48PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
[...]
> > +# Allow users of this group to interact with hasher-privd via the control socket.
> > +control_group=hashman
> 
> As noted earlier, in a different file: this unlucky name too strongly
> associates with cgroups.

I suggest renaming it to access_group then.


-- 
ldv


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Makefile
  2020-09-18 10:48     ` Dmitry V. Levin
@ 2020-09-18 10:54       ` Andrey Savchenko
  0 siblings, 0 replies; 52+ messages in thread
From: Andrey Savchenko @ 2020-09-18 10:54 UTC (permalink / raw)
  To: ALT Linux Team development discussions

[-- Attachment #1: Type: text/plain, Size: 1218 bytes --]

On Fri, 18 Sep 2020 13:48:19 +0300 Dmitry V. Levin wrote:
> On Thu, Sep 17, 2020 at 04:12:36PM +0300, Arseny Maslennikov wrote:
> > On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> > > diff --git a/hasher-priv/Makefile b/hasher-priv/Makefile
> > > index a815e9e..82aa385 100644
> > > --- a/hasher-priv/Makefile
> > > +++ b/hasher-priv/Makefile
> > > @@ -11,7 +11,7 @@ VERSION = $(shell sed '/^Version: */!d;s///;q' hasher-priv.spec)
> > >  HELPERS = getconf.sh getugid1.sh chrootuid1.sh getugid2.sh chrootuid2.sh
> > >  MAN5PAGES = $(PROJECT).conf.5
> > >  MAN8PAGES = $(PROJECT).8 hasher-useradd.8
> > > -TARGETS = $(PROJECT) hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> > > +TARGETS = $(PROJECT) hasher-privd hasher-useradd $(HELPERS) $(MAN5PAGES) $(MAN8PAGES)
> > 
> > To everyone: While the name "hasher-privd" minimises the amount of
> > renaming we have to do, it is too long a name, given that /proc/%d/comm
> > for each task is up to 16 bytes long on Linux,
> 
> Why should we care about /proc/%d/comm limitations?  Is this really an issue?

I agree. hasher-privd is readable and understandable name also
featuring less renames.

Best regards,
Andrew Savchenko

[-- Attachment #2: Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] server.conf
  2020-09-18 10:50     ` Dmitry V. Levin
@ 2020-09-18 10:57       ` Arseny Maslennikov
  0 siblings, 0 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-18 10:57 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: Alex Gladkov

[-- Attachment #1: Type: text/plain, Size: 530 bytes --]

On Fri, Sep 18, 2020 at 01:50:20PM +0300, Dmitry V. Levin wrote:
> On Thu, Sep 17, 2020 at 04:12:48PM +0300, Arseny Maslennikov wrote:
> > On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> [...]
> > > +# Allow users of this group to interact with hasher-privd via the control socket.
> > > +control_group=hashman
> > 
> > As noted earlier, in a different file: this unlucky name too strongly
> > associates with cgroups.
> 
> I suggest renaming it to access_group then.
> 

That name looks fine to me.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Makefile
  2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] Makefile Arseny Maslennikov
  2020-09-17 15:09     ` Vladimir D. Seleznev
  2020-09-18 10:48     ` Dmitry V. Levin
@ 2020-09-18 11:33     ` Dmitry V. Levin
  2020-09-18 12:24       ` Arseny Maslennikov
  2 siblings, 1 reply; 52+ messages in thread
From: Dmitry V. Levin @ 2020-09-18 11:33 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, Alex Gladkov

On Thu, Sep 17, 2020 at 04:12:36PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
[...]
> > @@ -21,6 +21,7 @@ man5dir = $(mandir)/man5
> >  man8dir = $(mandir)/man8
> >  configdir = $(sysconfdir)/$(PROJECT)
> >  helperdir = $(libexecdir)/$(PROJECT)
> > +socketdir = /var/run
> 
> Why /var/run and not /run, especially in a new project?

It's the same thing nowadays, isn't it?

> Even further, I would suggest that we store the socket in
> /run/hasher-priv or something, setgid hashman, with 0710 rights. The
> major service managers can create the directory on startup for us:
> there's mkdir(1), there's RuntimeDirectory= and RuntimeDirectoryMode=.

I distinctly remember we discussed this the last autumn or winter.
Yes, unix domain socket access restrictions should be implemented
using directory permissions.


-- 
ldv


^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Makefile
  2020-09-18 11:33     ` Dmitry V. Levin
@ 2020-09-18 12:24       ` Arseny Maslennikov
  0 siblings, 0 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-09-18 12:24 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: Alex Gladkov

[-- Attachment #1: Type: text/plain, Size: 1536 bytes --]

On Fri, Sep 18, 2020 at 02:33:12PM +0300, Dmitry V. Levin wrote:
> On Thu, Sep 17, 2020 at 04:12:36PM +0300, Arseny Maslennikov wrote:
> > On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> [...]
> > > @@ -21,6 +21,7 @@ man5dir = $(mandir)/man5
> > >  man8dir = $(mandir)/man8
> > >  configdir = $(sysconfdir)/$(PROJECT)
> > >  helperdir = $(libexecdir)/$(PROJECT)
> > > +socketdir = /var/run
> > 
> > Why /var/run and not /run, especially in a new project?
> 
> It's the same thing nowadays, isn't it?

Short answer: That's why I asked the question.

Long answer: Depends on what you mean by "thing".

It is true that these two paths point to the same place now, but
/var/run is only kept for compatibility with software that expects this
path to be available, so we, the packagers, don't have to patch all of
it now.

The mountpoints, however, have an obvious but important difference:
/var/run requires /var to be mounted first to work correctly, and /run
does not — so the latter path is more simple and thus preferable, and
the former path was a mistake.

> 
> > Even further, I would suggest that we store the socket in
> > /run/hasher-priv or something, setgid hashman, with 0710 rights. The
> > major service managers can create the directory on startup for us:
> > there's mkdir(1), there's RuntimeDirectory= and RuntimeDirectoryMode=.
> 
> I distinctly remember we discussed this the last autumn or winter.

The discussion probably went over my head then, or was private.


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2020-09-17 13:09 ` Arseny Maslennikov
@ 2020-10-01 17:21   ` Alexey Gladkov
  2020-10-01 17:44     ` Arseny Maslennikov
  0 siblings, 1 reply; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 17:21 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, ldv

[-- Attachment #1: Type: text/plain, Size: 5096 bytes --]

On Thu, Sep 17, 2020 at 04:09:35PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> > From: Alexey Gladkov <legion@altlinux.org>
> > 
> > The hasher-priv is a SUID utility. This is not good. Separation of the
> > server and client parts will allow us to remove SUID flag.
> > 
> > The separation of server and client is not intended to give clients
> > access over the network. This separation is only necessary to distinguish
> > privileges. Only UNIX domain socket is used.
> > 
> > A separate session process is created for each connected user. Each such
> > process ends after a certain period of inactivity.
> 
> Thank you for trying this idea out; despite the trolling attempts, this
> effort is long welcome.

I created this patchset a long time ago. I've already lost my context. It
might be better if you keep working on this patch.

> There are some issues with the patchset, which I intend to cover in
> subsequent emails. I have published[1] some fix-up commits on top of
> these patches in an attempt to ensure that, barring the issues with a
> known fix, this works; however, some bugs are definitely still unsolved
> by now, so I decided to discuss the more apparent points first.
> 
> [1] http://git.altlinux.org/people/arseny/packages/hasher-priv.git?a=summary

It looks like you've already started working on finalizing this patch :)

> 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.

Hm...

> I've not yet found a decent reproducer — the following command:
> `hsh-shell $workdir'

There is no such command. You need to send command to run /bin/sh.

> reproduces the issue reliably for me, but hsh-mkchroot, hsh-rmchroot,
> hsh-install are all OK. The root cause nevertheless is not yet
> established. It looks like this has to be patched somewhere in
> chrootuid(), but I might be wrong on this one.
> 
> > 
> > Alexey Gladkov (3):
> >   Make a daemon from the hasher-priv
> >   Add systemd and sysvinit service files
> >   Add cgroup support
> > 
> >  hasher-priv/.gitignore            |   1 +
> >  hasher-priv/DESIGN                | 281 +++++++++++++--------
> >  hasher-priv/Makefile              |  34 ++-
> >  hasher-priv/caller.c              |  81 +++---
> >  hasher-priv/caller_server.c       | 373 ++++++++++++++++++++++++++++
> >  hasher-priv/caller_task.c         | 217 +++++++++++++++++
> >  hasher-priv/cgroup.c              | 119 +++++++++
> >  hasher-priv/cmdline.c             |  27 +-
> >  hasher-priv/communication.c       | 392 ++++++++++++++++++++++++++++++
> >  hasher-priv/communication.h       |  77 ++++++
> >  hasher-priv/config.c              | 148 ++++++++++-
> >  hasher-priv/epoll.c               |  39 +++
> >  hasher-priv/epoll.h               |  18 ++
> >  hasher-priv/hasher-priv.c         |  78 ++++++
> >  hasher-priv/hasher-privd.c        | 375 ++++++++++++++++++++++++++++
> >  hasher-priv/hasher-privd.service  |  11 +
> >  hasher-priv/hasher-privd.sysvinit |  86 +++++++
> >  hasher-priv/io_log.c              |   2 +-
> >  hasher-priv/io_x11.c              |   2 +-
> >  hasher-priv/killuid.c             |   2 +-
> >  hasher-priv/logging.c             |  64 +++++
> >  hasher-priv/logging.h             |  55 +++++
> >  hasher-priv/main.c                |  75 ------
> >  hasher-priv/pass.c                | 117 ++++++++-
> >  hasher-priv/pidfile.c             | 128 ++++++++++
> >  hasher-priv/pidfile.h             |  44 ++++
> >  hasher-priv/priv.h                |  35 ++-
> >  hasher-priv/server.conf           |  22 ++
> >  hasher-priv/sockets.c             | 183 ++++++++++++++
> >  hasher-priv/sockets.h             |  32 +++
> >  hasher-priv/x11.c                 |   1 +
> >  31 files changed, 2872 insertions(+), 247 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
> > 



-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2020-09-17 13:10   ` Arseny Maslennikov
@ 2020-10-01 17:25     ` Alexey Gladkov
  2020-10-01 17:50       ` Arseny Maslennikov
  0 siblings, 1 reply; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 17:25 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: ALT Linux Team development discussions, ldv

[-- Attachment #1: Type: text/plain, Size: 5952 bytes --]

On Thu, Sep 17, 2020 at 04:10:52PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:04PM +0100, Alex Gladkov wrote:
> > From: Alexey Gladkov <legion@altlinux.org>
> > 
> > Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> > ---
> >  hasher-priv/Makefile              |  4 ++
> >  hasher-priv/hasher-privd.service  | 11 ++++
> >  hasher-priv/hasher-privd.sysvinit | 86 +++++++++++++++++++++++++++++++
> >  3 files changed, 101 insertions(+)
> >  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 82aa385..c73216f 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
> > @@ -72,6 +74,8 @@ install: all
> >  	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
> >  	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
> >  	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
> > +	$(MKDIR_P) -m755 $(DESTDIR)$(initdir)
> > +	$(INSTALL) -p -m755 hasher-privd.sysvinit $(DESTDIR)$(initdir)/hasher-privd
> 
> The systemd service is not installed.

I don't really care about systemd. I'm not an expert in creating services
for it. I hope that someone who can create and test the service. It may be
you :)

> >  	$(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-privd.service b/hasher-priv/hasher-privd.service
> > new file mode 100644
> > index 0000000..e5ed9ac
> > --- /dev/null
> > +++ b/hasher-priv/hasher-privd.service
> > @@ -0,0 +1,11 @@
> > +[Unit]
> > +Description=A privileged helper for the hasher project
> > +ConditionVirtualization=!container
> 
> In response to earlier reviewers: hasher-priv as of today does not work
> inside a userns-unprivileged container and does not produce clear
> diagnostics (and, from my own experience when I was joining ALT, the
> developers did not as well). Thus, for now this condition is justified.
> Perhaps in the future, when (and if) we introduce the ability to reuse a
> mainstream container runtime as the hasher environment for users R and
> B, it would make sense for us to lift this condition.
> 
> > +Documentation=man:hasher-priv(8)
> 
> Ah yes, I forgot. The patchset contains no changes to the man pages, so
> the effort and behaviour change is not reflected. I agree it's best to
> revisit them once we're done with the code, though.
> 
> > +
> > +[Service]
> > +ExecStart=/usr/sbin/hasher-privd
> 
> Suggested replacement:
> "ExecStart=/usr/sbin/hasher-privd -f"
> 
> The service implicitly, by default, has Type=simple, which means the
> following:
> - the main process(-es) is defined by the ExecStart= command line(-s)
>   and is intended to persist while the service is launched and active;
> - its pid/tgid is tracked by the service manager and can be queried;
> - the service manager puts it into its own cgroup;
> - its standard output and standard error are redirected to system log;
> - (follows from the above) the main process never has a controlling
>   terminal or standard file descriptors pointing to any terminal, its
>   sid is equal to its tgid — and so it does not have to perform
>   manual steps to daemonize.
> 
> > +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..914fb53
> > --- /dev/null
> > +++ b/hasher-priv/hasher-privd.sysvinit
> > @@ -0,0 +1,86 @@
> > +#! /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"
> > +RETVAL=0
> > +
> > +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)
> > +		start
> > +		;;
> > +	stop)
> > +		stop
> > +		;;
> > +	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.24.0
> > 



-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2020-10-01 17:21   ` Alexey Gladkov
@ 2020-10-01 17:44     ` Arseny Maslennikov
  2020-10-01 20:01       ` Alexey Gladkov
  0 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-10-01 17:44 UTC (permalink / raw)
  To: Alexey Gladkov; +Cc: devel, ldv

[-- Attachment #1: Type: text/plain, Size: 2528 bytes --]

On Thu, Oct 01, 2020 at 07:21:11PM +0200, Alexey Gladkov wrote:
> On Thu, Sep 17, 2020 at 04:09:35PM +0300, Arseny Maslennikov wrote:
> > On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> > > From: Alexey Gladkov <legion@altlinux.org>
> > > 
> > > The hasher-priv is a SUID utility. This is not good. Separation of the
> > > server and client parts will allow us to remove SUID flag.
> > > 
> > > The separation of server and client is not intended to give clients
> > > access over the network. This separation is only necessary to distinguish
> > > privileges. Only UNIX domain socket is used.
> > > 
> > > A separate session process is created for each connected user. Each such
> > > process ends after a certain period of inactivity.
> > 
> > Thank you for trying this idea out; despite the trolling attempts, this
> > effort is long welcome.
> 
> I created this patchset a long time ago. I've already lost my context. It
> might be better if you keep working on this patch.
> 

Great! I'd like to work on this further.

> > There are some issues with the patchset, which I intend to cover in
> > subsequent emails. I have published[1] some fix-up commits on top of
> > these patches in an attempt to ensure that, barring the issues with a
> > known fix, this works; however, some bugs are definitely still unsolved
> > by now, so I decided to discuss the more apparent points first.
> > 
> > [1] http://git.altlinux.org/people/arseny/packages/hasher-priv.git?a=summary
> 
> It looks like you've already started working on finalizing this patch :)
> 
> > 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.
> 
> Hm...
> 
> > I've not yet found a decent reproducer — the following command:
> > `hsh-shell $workdir'
> 
> There is no such command. You need to send command to run /bin/sh.

Yes, there's no such IPC command, I was referring to a shell command run
in the host system by the caller user.

> 
> > reproduces the issue reliably for me, but hsh-mkchroot, hsh-rmchroot,
> > hsh-install are all OK. The root cause nevertheless is not yet
> > established. It looks like this has to be patched somewhere in
> > chrootuid(), but I might be wrong on this one.
> > 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files
  2020-10-01 17:25     ` Alexey Gladkov
@ 2020-10-01 17:50       ` Arseny Maslennikov
  0 siblings, 0 replies; 52+ messages in thread
From: Arseny Maslennikov @ 2020-10-01 17:50 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 6685 bytes --]

On Thu, Oct 01, 2020 at 07:25:34PM +0200, Alexey Gladkov wrote:
> On Thu, Sep 17, 2020 at 04:10:52PM +0300, Arseny Maslennikov wrote:
> > On Fri, Dec 13, 2019 at 12:42:04PM +0100, Alex Gladkov wrote:
> > > From: Alexey Gladkov <legion@altlinux.org>
> > > 
> > > Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> > > ---
> > >  hasher-priv/Makefile              |  4 ++
> > >  hasher-priv/hasher-privd.service  | 11 ++++
> > >  hasher-priv/hasher-privd.sysvinit | 86 +++++++++++++++++++++++++++++++
> > >  3 files changed, 101 insertions(+)
> > >  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 82aa385..c73216f 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
> > > @@ -72,6 +74,8 @@ install: all
> > >  	$(MKDIR_P) -m750 $(DESTDIR)$(helperdir)
> > >  	$(INSTALL) -p -m700 $(PROJECT) $(DESTDIR)$(helperdir)/
> > >  	$(INSTALL) -p -m755 $(HELPERS) $(DESTDIR)$(helperdir)/
> > > +	$(MKDIR_P) -m755 $(DESTDIR)$(initdir)
> > > +	$(INSTALL) -p -m755 hasher-privd.sysvinit $(DESTDIR)$(initdir)/hasher-privd
> > 
> > The systemd service is not installed.
> 
> I don't really care about systemd. I'm not an expert in creating services
> for it. I hope that someone who can create and test the service. It may be
> you :)
> 

I understand. That note was declaring a statement, not blaming you in
any way; I can help take care of systemd support.

> > >  	$(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-privd.service b/hasher-priv/hasher-privd.service
> > > new file mode 100644
> > > index 0000000..e5ed9ac
> > > --- /dev/null
> > > +++ b/hasher-priv/hasher-privd.service
> > > @@ -0,0 +1,11 @@
> > > +[Unit]
> > > +Description=A privileged helper for the hasher project
> > > +ConditionVirtualization=!container
> > 
> > In response to earlier reviewers: hasher-priv as of today does not work
> > inside a userns-unprivileged container and does not produce clear
> > diagnostics (and, from my own experience when I was joining ALT, the
> > developers did not as well). Thus, for now this condition is justified.
> > Perhaps in the future, when (and if) we introduce the ability to reuse a
> > mainstream container runtime as the hasher environment for users R and
> > B, it would make sense for us to lift this condition.
> > 
> > > +Documentation=man:hasher-priv(8)
> > 
> > Ah yes, I forgot. The patchset contains no changes to the man pages, so
> > the effort and behaviour change is not reflected. I agree it's best to
> > revisit them once we're done with the code, though.
> > 
> > > +
> > > +[Service]
> > > +ExecStart=/usr/sbin/hasher-privd
> > 
> > Suggested replacement:
> > "ExecStart=/usr/sbin/hasher-privd -f"
> > 
> > The service implicitly, by default, has Type=simple, which means the
> > following:
> > - the main process(-es) is defined by the ExecStart= command line(-s)
> >   and is intended to persist while the service is launched and active;
> > - its pid/tgid is tracked by the service manager and can be queried;
> > - the service manager puts it into its own cgroup;
> > - its standard output and standard error are redirected to system log;
> > - (follows from the above) the main process never has a controlling
> >   terminal or standard file descriptors pointing to any terminal, its
> >   sid is equal to its tgid — and so it does not have to perform
> >   manual steps to daemonize.
> > 
> > > +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..914fb53
> > > --- /dev/null
> > > +++ b/hasher-priv/hasher-privd.sysvinit
> > > @@ -0,0 +1,86 @@
> > > +#! /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"
> > > +RETVAL=0
> > > +
> > > +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)
> > > +		start
> > > +		;;
> > > +	stop)
> > > +		stop
> > > +		;;
> > > +	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.24.0
> > > 
> 
> 
> 
> -- 
> Rgrds, legion
> 



> _______________________________________________
> 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] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 3/3] Add cgroup support
  2020-09-17 13:11   ` Arseny Maslennikov
@ 2020-10-01 19:17     ` Alexey Gladkov
  2020-10-01 20:23       ` Arseny Maslennikov
  0 siblings, 1 reply; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 19:17 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: ALT Linux Team development discussions, ldv

[-- Attachment #1: Type: text/plain, Size: 8752 bytes --]

On Thu, Sep 17, 2020 at 04:11:07PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:05PM +0100, Alex Gladkov wrote:
> > From: Alexey Gladkov <legion@altlinux.org>
> > 
> 
> Could you please explain what you're trying to do with this patch?
> Even if it's obvious from the source itself, we still must have an
> opportunity to discuss, and a decent explanation should stay in the
> project history.

I think this patch is simple enough.

> Most likely, it'll turn out we _at least_ have to pass Delegate=yes to
> the systemd service:
> 
>        Delegate=
>            Turns on delegation of further resource control
>            partitioning to processes of the unit. Units where
>            this is enabled may create and manage their own
>            private subhierarchy of control groups below the
>            control group of the unit itself.
> Manual page systemd.resource-control(5): lines 786-791

I'm pretty sure the hasher-priv shouldn't be tied to systemd. I'm also
convinced that the server will not be tied. The hasher-privd must be able
to run on systems without systemd.

> Do we only support cgroup2 and ignore cgroup1? If yes, great, but
> perhaps then we might want to have a setting to not fiddle with cgroup
> trees, to support the unfortunate users that have to run Docker and
> other garbage.

Yeah, I didn't plan on supporting legacy version of cgroups. Docker
already can work with cgroupsv2.

> 
> > Signed-off-by: Alexey Gladkov <legion@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 c73216f..e999972 100644
> > --- a/hasher-priv/Makefile
> > +++ b/hasher-priv/Makefile
> > @@ -51,7 +51,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 d8f2dd5..722e0a6 100644
> > --- a/hasher-priv/caller_task.c
> > +++ b/hasher-priv/caller_task.c
> > @@ -95,6 +95,9 @@ caller_task(struct task *task)
> >  		return pid;
> >  	}
> >  
> > +	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 6b6bdb1..3faf936 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_control_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("control_group", name)) {
> >  		free(server_control_group);
> >  		server_control_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_control_group);
> > +	free(server_cgroup_template);
> >  }
> > diff --git a/hasher-priv/priv.h b/hasher-priv/priv.h
> > index f0eb9f9..f29603a 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_control_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 53ea5c3..9e70487 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.
> >  control_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.24.0
> > 
> > _______________________________________________
> > Devel mailing list
> > Devel@lists.altlinux.org
> > https://lists.altlinux.org/mailman/listinfo/devel



-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Make a daemon from the hasher-priv
  2020-09-17 13:10   ` Arseny Maslennikov
@ 2020-10-01 19:43     ` Alexey Gladkov
  2020-10-01 21:24       ` Arseny Maslennikov
  0 siblings, 1 reply; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 19:43 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, ldv

[-- Attachment #1: Type: text/plain, Size: 5408 bytes --]

On Thu, Sep 17, 2020 at 04:10:13PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> > From: Alexey Gladkov <legion@altlinux.org>
> > 
> > All privileged operations moved to the daemon. Commands to the server
> > are sent through the unix domain socket. The credentials which the sender
> > specifies are checked by the kernel. The hasher-priv no longer SUID.
> 
> I'm going to suggest some English literacy/typo improvements in a separate
> email.
> 
> > 
> > For each user server creates a separate session process that executes
> > commands only from the user who created it. The session process ends
> 
> Since that new process will still be privileged, why the additional fork
> and the new listening socket inode? Is the strive for less complex
> daemon source code the only driver for that, albeit a perfectly viable
> one?

We have privileged server. When a user request comes in a session daemon
is forked for him that opens a socket for such user. A separate process
is created for each job. If the session daemon is inactive, then after a
session_timeout it will terminate. This is done to isolate one user from
another. You cannot DoS the main server.

> > after a certain period of inactivity.
> 
> No problem with that, but IMO we still should be careful
> about resource leaks.

The admin controls the number of users and hence the number of possible
session daemons.

> > 
> 
> 
> 
> > Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> > ---
> >  hasher-priv/.gitignore      |   1 +
> >  hasher-priv/DESIGN          | 281 ++++++++++++++++----------
> 
> I'd like to see a formal description of the client-server protocol in e.
> g. hasher-priv/IPC, that shows the intended message exchanges and
> meanings. The message contents can be rather easily inferred from the C
> headers, but the semantics cannot. This ends up as a source of
> programming errors, see the other emails.
> 
> The relevant information is probably already present in the DESIGN file,
> but, as this is documentation for humans, we should wrap it into a form
> that's easier to comprehend without wasting much time.
> 
> I'm also worried about the message format using plain C ABI structs.
> We're not going to use it over the network on machines with different
> architectures, and we're never going to support a client and server from
> different package builds, sure. But is this enough of a justification?

Don't be fooled that a socket is being used. The hasher-provd will not be
able to work over the network. The file descriptors are passed over
the socket and privileges are checked.

> >  hasher-priv/Makefile        |  28 ++-
> >  hasher-priv/caller.c        |  81 ++++----
> 
> After reading the following 2 new files...
> 
> >  hasher-priv/caller_server.c | 373 ++++++++++++++++++++++++++++++++++
> >  hasher-priv/caller_task.c   | 214 ++++++++++++++++++++
> 
> ..., I'm not sure why do they have to be separate.
> They can even be merged with caller.c, perhaps.
> 
> >  hasher-priv/cmdline.c       |  27 ++-
> >  hasher-priv/communication.c | 392 ++++++++++++++++++++++++++++++++++++
> >  hasher-priv/communication.h |  77 +++++++
> >  hasher-priv/config.c        | 143 ++++++++++++-
> >  hasher-priv/epoll.c         |  39 ++++
> >  hasher-priv/epoll.h         |  18 ++
> >  hasher-priv/hasher-priv.c   |  78 +++++++
> >  hasher-priv/hasher-privd.c  | 375 ++++++++++++++++++++++++++++++++++
> >  hasher-priv/io_log.c        |   2 +-
> >  hasher-priv/io_x11.c        |   2 +-
> >  hasher-priv/killuid.c       |   2 +-
> >  hasher-priv/logging.c       |  64 ++++++
> >  hasher-priv/logging.h       |  55 +++++
> >  hasher-priv/main.c          |  75 -------
> >  hasher-priv/pass.c          | 117 ++++++++++-
> >  hasher-priv/pidfile.c       | 128 ++++++++++++
> >  hasher-priv/pidfile.h       |  44 ++++
> >  hasher-priv/priv.h          |  33 +--
> >  hasher-priv/server.conf     |  13 ++
> >  hasher-priv/sockets.c       | 183 +++++++++++++++++
> >  hasher-priv/sockets.h       |  32 +++
> >  hasher-priv/x11.c           |   1 +
> >  28 files changed, 2632 insertions(+), 246 deletions(-)
> >  create mode 100644 hasher-priv/caller_server.c
> >  create mode 100644 hasher-priv/caller_task.c
> >  create mode 100644 hasher-priv/communication.c
> >  create mode 100644 hasher-priv/communication.h
> >  create mode 100644 hasher-priv/epoll.c
> >  create mode 100644 hasher-priv/epoll.h
> >  create mode 100644 hasher-priv/hasher-priv.c
> >  create mode 100644 hasher-priv/hasher-privd.c
> >  create mode 100644 hasher-priv/logging.c
> >  create mode 100644 hasher-priv/logging.h
> >  delete mode 100644 hasher-priv/main.c
> >  create mode 100644 hasher-priv/pidfile.c
> >  create mode 100644 hasher-priv/pidfile.h
> >  create mode 100644 hasher-priv/server.conf
> >  create mode 100644 hasher-priv/sockets.c
> >  create mode 100644 hasher-priv/sockets.h
> 
> Wow, this patch is rather big. That would be easier to review if there
> was a bunch of logically self-contained commits, which could later be
> squashed by the merging maintainer, perhaps, if there's a requirement
> that every commit has to build successfully and run well.
> 
> I'm going to post reviews in separate messages, one answer per file.
> 



-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] caller_server.c, caller_task.c
  2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller_server.c, caller_task.c Arseny Maslennikov
@ 2020-10-01 19:47     ` Alexey Gladkov
  0 siblings, 0 replies; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 19:47 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, ldv

[-- Attachment #1: Type: text/plain, Size: 16774 bytes --]

On Thu, Sep 17, 2020 at 04:11:40PM +0300, Arseny Maslennikov wrote:
> On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> > diff --git a/hasher-priv/caller_server.c b/hasher-priv/caller_server.c
> > new file mode 100644
> > index 0000000..8182c69
> > --- /dev/null
> > +++ b/hasher-priv/caller_server.c
> > @@ -0,0 +1,373 @@
> > +
> > +/*
> > +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> > +
> > +  The 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->env) {
> > +		free(task->env[0]);
> > +		free(task->env);
> > +	}
> > +
> > +	if (task->argv) {
> > +		free(task->argv[0]);
> > +		free(task->argv);
> > +	}
> > +}
> > +
> > +static int
> > +cancel_task(int conn, struct task *task)
> > +{
> > +	free_task(task);
> > +	send_command_response(conn, CMD_STATUS_FAILED, "command failed");
> > +	return 0;
> > +}
> > +
> > +static int
> > +process_task(int conn)
> > +{
> > +	uid_t uid;
> > +	gid_t gid;
> > +
> > +	if (get_peercred(conn, NULL, &uid, &gid) < 0)
> > +		return -1;
> > +
> > +	if (caller_uid != uid || caller_gid != gid) {
> > +		err("user (uid=%d) has no permissions to send commands"
> > +		    " to the session of another user (uid=%d)",
> > +		    uid, caller_uid);
> > +		return -1;
> > +	}
> > +
> > +	struct task task = { 0 };
> > +	int fds[3];
> > +
> > +	while (1) {
> > +		task_t type = TASK_NONE;
> > +		struct cmd hdr = { 0 };
> > +
> > +		if (xrecvmsg(conn, &hdr, sizeof(hdr)) < 0)
> > +			return cancel_task(conn, &task);
> > +
> > +		switch (hdr.type) {
> > +			case CMD_TASK_BEGIN:
> > +				if (hdr.datalen != sizeof(type))
> > +					return cancel_task(conn, &task);
> > +
> > +				if (xrecvmsg(conn, &type, hdr.datalen) < 0)
> > +					return cancel_task(conn, &task);
> > +
> > +				task.type = type;
> > +
> > +				break;
> > +
> > +			case CMD_TASK_FDS:
> > +				if (hdr.datalen != sizeof(int) * 3)
> > +					return cancel_task(conn, &task);
> > +
> > +				if (task.stdin)
> > +					close(task.stdin);
> > +
> > +				if (task.stdout)
> > +					close(task.stdout);
> > +
> > +				if (task.stderr)
> > +					close(task.stderr);
> > +
> > +				if (fds_recv(conn, fds, 3) < 0)
> > +					return cancel_task(conn, &task);
> > +
> > +				task.stdin  = fds[0];
> > +				task.stdout = fds[1];
> > +				task.stderr = fds[2];
> > +
> > +				break;
> > +
> > +			case CMD_TASK_ARGUMENTS:
> > +				if (task.argv) {
> > +					free(task.argv[0]);
> > +					free(task.argv);
> > +				}
> > +
> > +				if (recv_list(conn, &task.argv, hdr.datalen) < 0)
> > +					return cancel_task(conn, &task);
> > +
> > +				if (validate_arguments(task.type, task.argv) < 0)
> > +					return cancel_task(conn, &task);
> > +
> > +				break;
> > +
> > +			case CMD_TASK_ENVIRON:
> > +				if (task.env) {
> > +					free(task.env[0]);
> > +					free(task.env);
> > +				}
> > +
> > +				if (recv_list(conn, &task.env, hdr.datalen) < 0)
> > +					return cancel_task(conn, &task);
> > +
> > +				break;
> > +
> > +			case CMD_TASK_RUN:
> > +				process_caller_task(conn, &task);
> > +				free_task(&task);
> > +				return 0;
> > +
> > +			default:
> > +				err("unsupported command: %d", hdr.type);
> > +		}
> > +
> > +		send_command_response(conn, CMD_STATUS_DONE, NULL);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int
> > +caller_server(int cl_conn)
> > +{
> > +	int ret = 0;
> > +
> > +	umask(077);
> > +
> > +	snprintf(socketpath, sizeof(socketpath),
> > +		"hasher-priv-%d-%u",
> > +		caller_uid, caller_num);
> > +
> > +	int fd_conn   = -1;
> > +	int fd_signal = -1;
> > +	int fd_ep     = -1;
> > +
> > +	if ((fd_conn = srv_listen(SOCKETDIR, socketpath)) < 0)
> > +		return -1;
> > +
> > +	snprintf(socketpath, sizeof(socketpath),
> > +		"%s/hasher-priv-%d-%u",
> > +		SOCKETDIR, caller_uid, caller_num);
> > +
> > +	if (chown(socketpath, caller_uid, caller_gid)) {
> > +		err("fchown: %s: %m", socketpath);
> > +		ret = -1;
> > +		goto fail;
> > +	}
> > +
> > +	/* Load config according to caller information. */
> > +	configure();
> > +
> > +	sigset_t mask;
> > +
> > +	sigfillset(&mask);
> > +	sigprocmask(SIG_SETMASK, &mask, NULL);
> > +
> > +	if ((fd_signal = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)) < 0) {
> > +		err("signalfd: %m");
> > +		ret = -1;
> > +		goto fail;
> > +	}
> > +
> > +	if ((fd_ep = epoll_create1(EPOLL_CLOEXEC)) < 0) {
> > +		err("epoll_create1: %m");
> > +		ret = -1;
> > +		goto fail;
> > +	}
> > +
> > +	if (epollin_add(fd_ep, fd_signal) < 0 || epollin_add(fd_ep, fd_conn) < 0) {
> > +		err("epollin_add: failed");
> > +		ret = -1;
> > +		goto fail;
> > +	}
> > +
> > +	/* Tell client that caller server is ready */
> > +	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");
> > +					continue;
> > +				}
> > +
> > +				int status;
> > +
> > +				switch (fdsi.ssi_signo) {
> > +					case SIGINT:
> > +					case SIGTERM:
> > +						finish_server = 1;
> > +						break;
> > +					case SIGCHLD:
> > +						if (waitpid(-1, &status, 0) < 0)
> > +							err("waitpid: %m");
> > +						break;
> > +				}
> > +
> > +			} else if (ev[i].data.fd == fd_conn) {
> > +				int conn;
> > +
> > +				if ((conn = accept4(fd_conn, NULL, 0, SOCK_CLOEXEC)) < 0) {
> > +					err("accept4: %m");
> > +					continue;
> > +				}
> > +
> > +				if (set_recv_timeout(conn, 3) < 0) {
> > +					close(conn);
> > +					continue;
> > +				}
> > +
> > +				if (!process_task(conn)) {
> > +					/* reset timer */
> > +					nsec = 0;
> > +				}
> > +
> > +				close(conn);
> > +			}
> > +		}
> > +	}
> > +fail:
> > +	epollin_remove(fd_ep, fd_signal);
> > +	epollin_remove(fd_ep, fd_conn);
> > +
> > +	if (fd_ep >= 0)
> > +		close(fd_ep);
> > +
> > +	unlink(socketpath);
> > +
> > +	return ret;
> > +}
> > +
> > +pid_t
> > +fork_server(int cl_conn, uid_t uid, gid_t gid, unsigned num)
> > +{
> > +	pid_t pid = fork();
> > +
> > +	if (pid != 0) {
> > +		if (pid < 0) {
> > +			err("fork: %m");
> > +			return -1;
> > +		}
> > +		return pid;
> > +	}
> > +
> > +	caller_num = num;
> > +
> > +	int ret = init_caller_data(uid, gid);
> > +
> > +	if (ret < 0) {
> 
> This if-block is semantically nonsensical as written.
> 
> If this comparison is reversed (ret >= 0), the resulting code looks
> semantically correct; it probably was a typo. It still would be better to refactor this
> piece, imo.

Yep. This is a typo.

> > +		info("%s(%d) num=%u: start session server", caller_user, caller_uid, caller_num);
> > +		ret = caller_server(cl_conn);
> > +		info("%s(%d): finish session server", caller_user, caller_uid);
> > +	}
> > +
> > +	if (ret < 0) {
> > +		send_command_response(cl_conn, CMD_STATUS_FAILED, NULL);
> > +		ret = EXIT_FAILURE;
> > +	} else {
> > +		ret = EXIT_SUCCESS;
> > +	}
> > +
> > +	free_caller_data();
> > +	close(cl_conn);
> > +
> > +	exit(ret);
> > +}
> > diff --git a/hasher-priv/caller_task.c b/hasher-priv/caller_task.c
> > new file mode 100644
> > index 0000000..d8f2dd5
> > --- /dev/null
> > +++ b/hasher-priv/caller_task.c
> > @@ -0,0 +1,214 @@
> > +
> > +/*
> > +  Copyright (C) 2019  Alexey Gladkov <legion@altlinux.org>
> > +
> > +  The 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;
> > +
> > +	if ((pid = fork()) != 0) {
> > +		if (pid < 0) {
> > +			err("fork: %m");
> > +			return -1;
> > +		}
> > +		return pid;
> > +	}
> 
> There are multiple snippets like this one in the patch.
> 
> This would look better, wouldn't it:
> 
> +	if ((pid = fork()) < 0) {
> +		err("fork: %m");
> +		return -1;
> +	}
> +
> +	if (pid > 0) {
> +		return pid;
> +	}
> +
> 
> > +
> > +	if ((rc = reopen_iostreams(task->stdin, task->stdout, task->stderr)) < 0)
> > +		exit(rc);
> > +
> > +	/* cleanup 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);
> > +
> > +	if (chroot_path && *chroot_path != '/')
> > +		fatal("%s: invalid chroot path", chroot_path);
> > +
> > +	/* Fourth, parse environment for config options. */
> > +	parse_env();
> > +
> > +	/* We don't need environment variables any longer. */
> > +	if (clearenv() != 0)
> > +		fatal("clearenv: %m");
> > +
> > +	/* Finally, execute choosen 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 of all 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;
> > +
> > +	if ((pid = fork()) != 0) {
> > +		if (pid < 0) {
> > +			err("fork: %m");
> > +			return -1;
> > +		}
> 
> Some resources (up to 3 file descriptors per task, memory for tasks) are
> not reclaimed here, in the likely long-living hasherd session process.
> 
> Fun fact: if you fix hasher-privd to run successfully until this point,
> the following shell command
> `echo $(/usr/libexec/hasher-priv/hasher-priv getconf)' ??? a common
> pattern in hsh(1) ??? actually hangs until the session process dies. No
> wonder: since there still are open writers on the pipe to shell when the
> task finishes, there's no end-of-stream.
> A more clear reproducer would be:
> `/usr/libexec/hasher-priv/hasher-priv getconf | cat'.
> 
> > +		return 0;
> > +	}
> > +
> > +	if ((cpid = caller_task(task)) > 0) {
> 
> There's quite a bunch of forks involved in fulfilling any possible
> request.
> 
> I suggest to link the daemon with [1] and call setproctitle in all
> non-execcing descendant processes of the daemon.
> 
> /usr/sbin/hasher-privd -f
>  \_ hasher-privd: privileged helper for ar/500:0
>      \_ hasher-privd: process_caller_task
>          \_ hasher-privd: [500:0] task handler: chrootuid1
>              \_ /bin/sh /usr/bin/fakeroot /.host/entry
>                  \_ ...
> 
> This looks better than a wall of "/usr/sbin/hasher-privd -f"
> 
> [1] http://git.altlinux.org/gears/s/setproctitle.git?p=setproctitle.git;a=summary
> 
> > +		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);
> > +	}
> 
> We have free_task() as a static function on the struct task* in a
> neighbour file. Merge the files maybe? And close task->std(in|out|err)
> if > 0.
> 
> Another decent option is to publish free_task() in caller_task.c
> 
> > +
> > +	/* Notify client about result */
> > +	(rc == EXIT_FAILURE)
> > +		? send_command_response(conn, CMD_STATUS_FAILED, "command failed")
> > +		: send_command_response(conn, CMD_STATUS_DONE, NULL);
> > +
> > +	exit(rc);
> > +}
> // Redundant



-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2020-10-01 17:44     ` Arseny Maslennikov
@ 2020-10-01 20:01       ` Alexey Gladkov
  2020-10-01 21:53         ` Arseny Maslennikov
  0 siblings, 1 reply; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 20:01 UTC (permalink / raw)
  To: Arseny Maslennikov; +Cc: devel, ldv

[-- Attachment #1: Type: text/plain, Size: 3249 bytes --]

On Thu, Oct 01, 2020 at 08:44:00PM +0300, Arseny Maslennikov wrote:
> On Thu, Oct 01, 2020 at 07:21:11PM +0200, Alexey Gladkov wrote:
> > On Thu, Sep 17, 2020 at 04:09:35PM +0300, Arseny Maslennikov wrote:
> > > On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> > > > From: Alexey Gladkov <legion@altlinux.org>
> > > > 
> > > > The hasher-priv is a SUID utility. This is not good. Separation of the
> > > > server and client parts will allow us to remove SUID flag.
> > > > 
> > > > The separation of server and client is not intended to give clients
> > > > access over the network. This separation is only necessary to distinguish
> > > > privileges. Only UNIX domain socket is used.
> > > > 
> > > > A separate session process is created for each connected user. Each such
> > > > process ends after a certain period of inactivity.
> > > 
> > > Thank you for trying this idea out; despite the trolling attempts, this
> > > effort is long welcome.
> > 
> > I created this patchset a long time ago. I've already lost my context. It
> > might be better if you keep working on this patch.
> > 
> 
> Great! I'd like to work on this further.

You have asked many questions. I didn’t answer everything because these
patches are already 5 years old and I can hardly remember what I had in my
head when I did them. Submitting patches to the mailing list was the
second attempt to upstream them. Actually, I was afraid of losing them
altogether, so I merged some of the patches. Originally I had about 10
patches in a patchset.

I'm not sure if I have time for this rework. But we can try. We can
discuss the hasher-privd in russian if you like :)

> > > There are some issues with the patchset, which I intend to cover in
> > > subsequent emails. I have published[1] some fix-up commits on top of
> > > these patches in an attempt to ensure that, barring the issues with a
> > > known fix, this works; however, some bugs are definitely still unsolved
> > > by now, so I decided to discuss the more apparent points first.
> > > 
> > > [1] http://git.altlinux.org/people/arseny/packages/hasher-priv.git?a=summary
> > 
> > It looks like you've already started working on finalizing this patch :)
> > 
> > > 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.
> > 
> > Hm...
> > 
> > > I've not yet found a decent reproducer — the following command:
> > > `hsh-shell $workdir'
> > 
> > There is no such command. You need to send command to run /bin/sh.
> 
> Yes, there's no such IPC command, I was referring to a shell command run
> in the host system by the caller user.
> 
> > 
> > > reproduces the issue reliably for me, but hsh-mkchroot, hsh-rmchroot,
> > > hsh-install are all OK. The root cause nevertheless is not yet
> > > established. It looks like this has to be patched somewhere in
> > > chrootuid(), but I might be wrong on this one.
> > > 



-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 3/3] Add cgroup support
  2020-10-01 19:17     ` Alexey Gladkov
@ 2020-10-01 20:23       ` Arseny Maslennikov
  2020-10-02  0:42         ` Alexey Gladkov
  0 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-10-01 20:23 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 3183 bytes --]

On Thu, Oct 01, 2020 at 09:17:33PM +0200, Alexey Gladkov wrote:
> On Thu, Sep 17, 2020 at 04:11:07PM +0300, Arseny Maslennikov wrote:
> > On Fri, Dec 13, 2019 at 12:42:05PM +0100, Alex Gladkov wrote:
> > > From: Alexey Gladkov <legion@altlinux.org>
> > > 
> > 
> > Could you please explain what you're trying to do with this patch?
> > Even if it's obvious from the source itself, we still must have an
> > opportunity to discuss, and a decent explanation should stay in the
> > project history.
> 
> I think this patch is simple enough.

There's a misunderstanding here. I'm not asking to explain the
semantics (what this patch does) — I repeat, it's rather obvious from
the source itself, the patch is indeed simple. I'm trying to get how the
patch's author would describe the pragmatic value of this patch. IOW:
we see this patch does XXX. What, in Alexey's view, are we trying to
achieve by implementing XXX?

Descriptive commit messages are done (and are enforced in successful
communities, e. g. LKML) for a reason.

The above essentially is my previous comment here, reworded and clarified.

If for some reason you believe it's shameful or rude to the community to
"waste time" on textual explanations, fair enough — I'll maybe write a commit
message myself (with my take on why this might be useful) and then most
likely ACK the same patch, with authorship reattributed to you via From:
in the patch body and the new commit message. Or else NAK this
particular revision with an empty commit message and leave it up to
ldv@.
If it were up to me, I would not approve of empty commit messages in a
lasting, crucial project like hasher-privd. People are forgetful, and
commit messages exist to help.

> 
> > Most likely, it'll turn out we _at least_ have to pass Delegate=yes to
> > the systemd service:
> > 
> >        Delegate=
> >            Turns on delegation of further resource control
> >            partitioning to processes of the unit. Units where
> >            this is enabled may create and manage their own
> >            private subhierarchy of control groups below the
> >            control group of the unit itself.
> > Manual page systemd.resource-control(5): lines 786-791
> 
> I'm pretty sure the hasher-priv shouldn't be tied to systemd.

I agree.

> I'm also
> convinced that the server will not be tied. The hasher-privd must be able
> to run on systems without systemd.

Sure it must. No one is trying to drop support for anything-but-systemd
from hasher-privd.

Here I was talking about the operational details of the daemon running
_under_ systemd, not without systemd. The insight about Delegate=yes
does not interfere with non-systemd installations.

> 
> > Do we only support cgroup2 and ignore cgroup1? If yes, great, but
> > perhaps then we might want to have a setting to not fiddle with cgroup
> > trees, to support the unfortunate users that have to run Docker and
> > other garbage.
> 
> Yeah, I didn't plan on supporting legacy version of cgroups. Docker
> already can work with cgroupsv2.

Oh, I heard they were just recently working on cgroup2 support.
Okay.


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Make a daemon from the hasher-priv
  2020-10-01 19:43     ` Alexey Gladkov
@ 2020-10-01 21:24       ` Arseny Maslennikov
  2020-10-01 23:38         ` Alexey Gladkov
  0 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-10-01 21:24 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 3575 bytes --]

On Thu, Oct 01, 2020 at 09:43:04PM +0200, Alexey Gladkov wrote:
> On Thu, Sep 17, 2020 at 04:10:13PM +0300, Arseny Maslennikov wrote:
> > On Fri, Dec 13, 2019 at 12:42:03PM +0100, Alex Gladkov wrote:
> > > From: Alexey Gladkov <legion@altlinux.org>
> > > 
> > > All privileged operations moved to the daemon. Commands to the server
> > > are sent through the unix domain socket. The credentials which the sender
> > > specifies are checked by the kernel. The hasher-priv no longer SUID.
> > 
> > I'm going to suggest some English literacy/typo improvements in a separate
> > email.
> > 
> > > 
> > > For each user server creates a separate session process that executes
> > > commands only from the user who created it. The session process ends
> > 
> > Since that new process will still be privileged, why the additional fork
> > and the new listening socket inode? Is the strive for less complex
> > daemon source code the only driver for that, albeit a perfectly viable
> > one?
> 
> We have privileged server. When a user request comes in a session daemon
> is forked for him that opens a socket for such user. A separate process
> is created for each job. If the session daemon is inactive, then after a
> session_timeout it will terminate.

Yes, this is well explained already.

> This is done to isolate one user from
> another. You cannot DoS the main server.

So you mean resource exhaustion. What kind of resource?
CPU time? A malicious client can uselessly connect to the main daemon,
send IPC commands, get kicked, repeat.

Open FDs come to mind. It makes sense for the main daemon to close the
client connection after the serving "session" daemon spawn and the
response; the main daemon probably already does, it should be obvious
from the patch.

> 
> > > after a certain period of inactivity.
> > 
> > No problem with that, but IMO we still should be careful
> > about resource leaks.
> 
> The admin controls the number of users and hence the number of possible
> session daemons.
> 
> > > 
> > 
> > 
> > 
> > > Signed-off-by: Alexey Gladkov <legion@altlinux.org>
> > > ---
> > >  hasher-priv/.gitignore      |   1 +
> > >  hasher-priv/DESIGN          | 281 ++++++++++++++++----------
> > 
> > I'd like to see a formal description of the client-server protocol in e.
> > g. hasher-priv/IPC, that shows the intended message exchanges and
> > meanings. The message contents can be rather easily inferred from the C
> > headers, but the semantics cannot. This ends up as a source of
> > programming errors, see the other emails.
> > 
> > The relevant information is probably already present in the DESIGN file,
> > but, as this is documentation for humans, we should wrap it into a form
> > that's easier to comprehend without wasting much time.
> > 
> > I'm also worried about the message format using plain C ABI structs.
> > We're not going to use it over the network on machines with different
> > architectures, and we're never going to support a client and server from
> > different package builds, sure. But is this enough of a justification?
> 
> Don't be fooled that a socket is being used. The hasher-provd will not be
> able to work over the network. The file descriptors are passed over
> the socket and privileges are checked.

Yes, this was already well explained.
Quote:
>> We're not going to use it over the network on machines with different
>> architectures, and we're never going to support a client and server from
>> different package builds, sure.

> <...>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2020-10-01 20:01       ` Alexey Gladkov
@ 2020-10-01 21:53         ` Arseny Maslennikov
  2020-10-01 23:55           ` Alexey Gladkov
  0 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-10-01 21:53 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 4901 bytes --]

On Thu, Oct 01, 2020 at 10:01:29PM +0200, Alexey Gladkov wrote:
> On Thu, Oct 01, 2020 at 08:44:00PM +0300, Arseny Maslennikov wrote:
> > On Thu, Oct 01, 2020 at 07:21:11PM +0200, Alexey Gladkov wrote:
> > > On Thu, Sep 17, 2020 at 04:09:35PM +0300, Arseny Maslennikov wrote:
> > > > On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> > > > > From: Alexey Gladkov <legion@altlinux.org>
> > > > > 
> > > > > The hasher-priv is a SUID utility. This is not good. Separation of the
> > > > > server and client parts will allow us to remove SUID flag.
> > > > > 
> > > > > The separation of server and client is not intended to give clients
> > > > > access over the network. This separation is only necessary to distinguish
> > > > > privileges. Only UNIX domain socket is used.
> > > > > 
> > > > > A separate session process is created for each connected user. Each such
> > > > > process ends after a certain period of inactivity.
> > > > 
> > > > Thank you for trying this idea out; despite the trolling attempts, this
> > > > effort is long welcome.
> > > 
> > > I created this patchset a long time ago. I've already lost my context. It
> > > might be better if you keep working on this patch.
> > > 
> > 
> > Great! I'd like to work on this further.
> 
> You have asked many questions. I didn’t answer everything because these
> patches are already 5 years old and I can hardly remember what I had in my
> head when I did them. Submitting patches to the mailing list was the
> second attempt to upstream them. Actually, I was afraid of losing them
> altogether, so I merged some of the patches. Originally I had about 10
> patches in a patchset.
> 
> I'm not sure if I have time for this rework. But we can try.

So, I guess you won't mind if I would prepare a v2 which fixes some of
the issues discussed, based on my repo. We're in no hurry, since Dmitry
is currently away for the next couple of weeks.

> We can
> discuss the hasher-privd in russian if you like :)

I'm personally fine with both english and russian; looks like you're too.
The remaining concerns are:
* if everyone else interested can respond and continue the conversation
* if the community around hasher ever goes international.

I responded in english, since the patch messages were in english, and in
that case I usually take the (nowadays rare with covid) opportunity to
practice. Если же то, на что я отвечаю, пишут по-русски, то и отвечать,
наверное, следует тоже по-русски.

Если вдруг чувствуете, что лучше по-русски, можете на русский переключаться.

Ну и иногда пишешь что-то по-русски в некоторый
профессионально-технический разговор, а в реплике столько оказывается
непереводных терминов и собственных имён, что уж лучше по-английски бы писал. :)

> 
> > > > There are some issues with the patchset, which I intend to cover in
> > > > subsequent emails. I have published[1] some fix-up commits on top of
> > > > these patches in an attempt to ensure that, barring the issues with a
> > > > known fix, this works; however, some bugs are definitely still unsolved
> > > > by now, so I decided to discuss the more apparent points first.
> > > > 
> > > > [1] http://git.altlinux.org/people/arseny/packages/hasher-priv.git?a=summary
> > > 
> > > It looks like you've already started working on finalizing this patch :)
> > > 
> > > > 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.
> > > 
> > > Hm...
> > > 
> > > > I've not yet found a decent reproducer — the following command:
> > > > `hsh-shell $workdir'
> > > 
> > > There is no such command. You need to send command to run /bin/sh.
> > 
> > Yes, there's no such IPC command, I was referring to a shell command run
> > in the host system by the caller user.
> > 
> > > 
> > > > reproduces the issue reliably for me, but hsh-mkchroot, hsh-rmchroot,
> > > > hsh-install are all OK. The root cause nevertheless is not yet
> > > > established. It looks like this has to be patched somewhere in
> > > > chrootuid(), but I might be wrong on this one.
> > > > 
> 
> 
> 
> -- 
> Rgrds, legion
> 



> _______________________________________________
> 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] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 1/3] Make a daemon from the hasher-priv
  2020-10-01 21:24       ` Arseny Maslennikov
@ 2020-10-01 23:38         ` Alexey Gladkov
  0 siblings, 0 replies; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 23:38 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 1268 bytes --]

On Fri, Oct 02, 2020 at 12:24:09AM +0300, Arseny Maslennikov wrote:
> > This is done to isolate one user from
> > another. You cannot DoS the main server.
> 
> So you mean resource exhaustion. What kind of resource?

I didn't mean only them. If the user finds an issue in the session server,
he will not get control of the main daemon. It is much more convenient to
isolate the command flow in a separate process.

> CPU time? A malicious client can uselessly connect to the main daemon,
> send IPC commands, get kicked, repeat.

Yes, the user can try to send a storm of requests to the main daemon, but
the answer is cheap enough. The daemon will check the list of active
sessions and if there is already a session, it will send CMD_STATUS_DONE
to the client (see start_session).

> Open FDs come to mind. It makes sense for the main daemon to close the
> client connection after the serving "session" daemon spawn and the
> response; the main daemon probably already does, it should be obvious
> from the patch.

The main server never takes FDs from the user. The main socket serves only
for opening a session. The connection is closed immediately after a
request to start or close a session (hasher-privd.c:344).  

-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv
  2020-10-01 21:53         ` Arseny Maslennikov
@ 2020-10-01 23:55           ` Alexey Gladkov
  0 siblings, 0 replies; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-01 23:55 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 3954 bytes --]

On Fri, Oct 02, 2020 at 12:53:45AM +0300, Arseny Maslennikov wrote:
> On Thu, Oct 01, 2020 at 10:01:29PM +0200, Alexey Gladkov wrote:
> > On Thu, Oct 01, 2020 at 08:44:00PM +0300, Arseny Maslennikov wrote:
> > > On Thu, Oct 01, 2020 at 07:21:11PM +0200, Alexey Gladkov wrote:
> > > > On Thu, Sep 17, 2020 at 04:09:35PM +0300, Arseny Maslennikov wrote:
> > > > > On Fri, Dec 13, 2019 at 12:42:02PM +0100, Alex Gladkov wrote:
> > > > > > From: Alexey Gladkov <legion@altlinux.org>
> > > > > > 
> > > > > > The hasher-priv is a SUID utility. This is not good. Separation of the
> > > > > > server and client parts will allow us to remove SUID flag.
> > > > > > 
> > > > > > The separation of server and client is not intended to give clients
> > > > > > access over the network. This separation is only necessary to distinguish
> > > > > > privileges. Only UNIX domain socket is used.
> > > > > > 
> > > > > > A separate session process is created for each connected user. Each such
> > > > > > process ends after a certain period of inactivity.
> > > > > 
> > > > > Thank you for trying this idea out; despite the trolling attempts, this
> > > > > effort is long welcome.
> > > > 
> > > > I created this patchset a long time ago. I've already lost my context. It
> > > > might be better if you keep working on this patch.
> > > > 
> > > 
> > > Great! I'd like to work on this further.
> > 
> > You have asked many questions. I didn’t answer everything because these
> > patches are already 5 years old and I can hardly remember what I had in my
> > head when I did them. Submitting patches to the mailing list was the
> > second attempt to upstream them. Actually, I was afraid of losing them
> > altogether, so I merged some of the patches. Originally I had about 10
> > patches in a patchset.
> > 
> > I'm not sure if I have time for this rework. But we can try.
> 
> So, I guess you won't mind if I would prepare a v2 which fixes some of
> the issues discussed, based on my repo. We're in no hurry, since Dmitry
> is currently away for the next couple of weeks.

Sure! I have been waiting for a reaction for 5 years. We are definitely in
no hurry :)

> > We can
> > discuss the hasher-privd in russian if you like :)
> 
> I'm personally fine with both english and russian; looks like you're too.
> The remaining concerns are:
> * if everyone else interested can respond and continue the conversation
> * if the community around hasher ever goes international.

I can hardly imagine a situation that someone who is not russian speaking
would want to discuss these patches in this mailing list. If that happens
then I'll probably eat my red hat :)

> I responded in english, since the patch messages were in english, and in
> that case I usually take the (nowadays rare with covid) opportunity to
> practice. Если же то, на что я отвечаю, пишут по-русски, то и отвечать,
> наверное, следует тоже по-русски.

Я тоже стараюсь придерживаться такого подхода.

> Если вдруг чувствуете, что лучше по-русски, можете на русский переключаться.

Я пишу по-английски хуже и медленнее. Просто Дима меня совсем бы не понял,
если бы я коммиты по-русски написал :)

> Ну и иногда пишешь что-то по-русски в некоторый
> профессионально-технический разговор, а в реплике столько оказывается
> непереводных терминов и собственных имён, что уж лучше по-английски бы писал. :) 

Зато это мне лишняя практика русского :)

-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 3/3] Add cgroup support
  2020-10-01 20:23       ` Arseny Maslennikov
@ 2020-10-02  0:42         ` Alexey Gladkov
  2020-10-02 11:46           ` Arseny Maslennikov
  0 siblings, 1 reply; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-02  0:42 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 2692 bytes --]

On Thu, Oct 01, 2020 at 11:23:53PM +0300, Arseny Maslennikov wrote:
> > > Could you please explain what you're trying to do with this patch?
> > > Even if it's obvious from the source itself, we still must have an
> > > opportunity to discuss, and a decent explanation should stay in the
> > > project history.
> > 
> > I think this patch is simple enough.
> 
> There's a misunderstanding here. I'm not asking to explain the
> semantics (what this patch does) — I repeat, it's rather obvious from
> the source itself, the patch is indeed simple. I'm trying to get how the
> patch's author would describe the pragmatic value of this patch. IOW:
> we see this patch does XXX. What, in Alexey's view, are we trying to
> achieve by implementing XXX?

I remember that this patch was the result of a discussion with ldv. I
didn't want to add complex support for different versions of cgroups. The
idea was that the admin would prepare the system for use of cgroups by the
hasher-privd daemon.

I'm not considering the hasher-privd as an end user server. This is a
low-level server on which you can build different solutions. I don't mean
just hasher. With this in mind, I don't think that this server should do
everything out of the box without configuration.

Does this make sense to you?

> Descriptive commit messages are done (and are enforced in successful
> communities, e. g. LKML) for a reason.
> 
> The above essentially is my previous comment here, reworded and clarified.
> 
> If for some reason you believe it's shameful or rude to the community to
> "waste time" on textual explanations, fair enough — I'll maybe write a commit
> message myself (with my take on why this might be useful) and then most
> likely ACK the same patch, with authorship reattributed to you via From:
> in the patch body and the new commit message. Or else NAK this
> particular revision with an empty commit message and leave it up to
> ldv@.
> If it were up to me, I would not approve of empty commit messages in a
> lasting, crucial project like hasher-privd. People are forgetful, and
> commit messages exist to help.

Ok.

> > > Do we only support cgroup2 and ignore cgroup1? If yes, great, but
> > > perhaps then we might want to have a setting to not fiddle with cgroup
> > > trees, to support the unfortunate users that have to run Docker and
> > > other garbage.
> > 
> > Yeah, I didn't plan on supporting legacy version of cgroups. Docker
> > already can work with cgroupsv2.
> 
> Oh, I heard they were just recently working on cgroup2 support.

https://github.com/opencontainers/runc/blob/master/docs/cgroup-v2.md

-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 3/3] Add cgroup support
  2020-10-02  0:42         ` Alexey Gladkov
@ 2020-10-02 11:46           ` Arseny Maslennikov
  2020-10-02 12:58             ` Alexey Gladkov
  0 siblings, 1 reply; 52+ messages in thread
From: Arseny Maslennikov @ 2020-10-02 11:46 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 6153 bytes --]

On Fri, Oct 02, 2020 at 02:42:55AM +0200, Alexey Gladkov wrote:
> On Thu, Oct 01, 2020 at 11:23:53PM +0300, Arseny Maslennikov wrote:
> > > > Could you please explain what you're trying to do with this patch?
> > > > Even if it's obvious from the source itself, we still must have an
> > > > opportunity to discuss, and a decent explanation should stay in the
> > > > project history.
> > > 
> > > I think this patch is simple enough.
> > 
> > There's a misunderstanding here. I'm not asking to explain the
> > semantics (what this patch does) — I repeat, it's rather obvious from
> > the source itself, the patch is indeed simple. I'm trying to get how the
> > patch's author would describe the pragmatic value of this patch. IOW:
> > we see this patch does XXX. What, in Alexey's view, are we trying to
> > achieve by implementing XXX?
> 
> I remember that this patch was the result of a discussion with ldv.

That discussion then likely was not public; in part, that's why I'm asking.

> I didn't want to add complex support for different versions of cgroups.

I also believe that complexity would be unnecessary today.

> The
> idea was that the admin would prepare the system for use of cgroups by the
> hasher-privd daemon.

If I understood correctly ^U
To put it another way, we're doing this because the machine admin might
want hasher-privd to put the processes it spawns in cgroups
_at_an_arbitrary_path_, at the administrator's discretion.

Ok, this is a valid explanation and a valid feature. Thank you.

Might not be fully implementable on systemd-based installations,
though[1], but we'll look into it in time and work out something that
fits everyone's varying needs and circumstances. I'm not yet sure from
systemd's documentation if that program is OK with us making arbitrary
cgroup trees _in_the_root_cgroup_. But we definitely can get a cgroup
subtree to work in; this works.

[1] https://systemd.io/CGROUP_DELEGATION/

> 
> I'm not considering the hasher-privd as an end user server. This is a
> low-level server on which you can build different solutions. I don't mean
> just hasher.

Subject: the future of hasher-privd

I'm not particularly opposed to the expansion of hasher-privd's utility
scope; there are quite a lot of potential use cases: hasher-privd as a
general-purpose cgroup manager, hasher-privd as a daemon-based
NO_NEW_PRIVS-ready policy-enforcing "su -", ...

While this sounds interesting, I believe there are currently some
obstacles. Would those solutions on top of hasher-privd be
co-installable and co-existing on a single machine? E.g. two hasher-privd
init scripts with different configuration files for different things,
spawning different processes.
Or they wouldn't? Or a single hasher-privd instance — aka node, aka main
process if you will — would do both services? I don't yet have a picture
of this in my head; this will have to be thought out.

Will the decoupled, generic hasher-privd have to expand its IPC API?

If we decouple hasher-privd from hasher, this would also mean we support
arbitrary clients, so we'll have to formally define the IPC interface,
see my concerns on it in a previous mail.

As it stands now, the hasher project currently sees hasher-privd as its
vital component, a specialized tool for a special purpose, configured at
/etc/hasher-priv/. You're proposing something different.

In short, it's gonna be a long road.


> With this in mind, I don't think that this server should do
> everything out of the box without configuration.

I believe the hasher project _would_ want some sane out-of-the-box
configuration. The generic privd you describe above might not, much like
runc/crun do not, but the hasher project definitely would. Furthermore, in
my personal (but shared by many) opinion, this hasher OOTB experience
would have to be catered to the common case of an ALT Team developer,
not to public builder services (which are already expected to take care
to tune and harden their non-trivial configuration, and we can even ship
recommendations for their use case in /usr/share/doc).

The approach you suggest here could work, if e. g. the decoupled privd
is shipped with no defaults, and the hasher project ships its own
defaults for the desired operation of privd.

> 
> Does this make sense to you?

Barring the questions raised above, it does, thank you.
I'm just being thorough =) We still need to support every use case, and
not forget anything.

> 
> > Descriptive commit messages are done (and are enforced in successful
> > communities, e. g. LKML) for a reason.
> > 
> > The above essentially is my previous comment here, reworded and clarified.
> > 
> > If for some reason you believe it's shameful or rude to the community to
> > "waste time" on textual explanations, fair enough — I'll maybe write a commit
> > message myself (with my take on why this might be useful) and then most
> > likely ACK the same patch, with authorship reattributed to you via From:
> > in the patch body and the new commit message. Or else NAK this
> > particular revision with an empty commit message and leave it up to
> > ldv@.
> > If it were up to me, I would not approve of empty commit messages in a
> > lasting, crucial project like hasher-privd. People are forgetful, and
> > commit messages exist to help.
> 
> Ok.
> 
> > > > Do we only support cgroup2 and ignore cgroup1? If yes, great, but
> > > > perhaps then we might want to have a setting to not fiddle with cgroup
> > > > trees, to support the unfortunate users that have to run Docker and
> > > > other garbage.
> > > 
> > > Yeah, I didn't plan on supporting legacy version of cgroups. Docker
> > > already can work with cgroupsv2.
> > 
> > Oh, I heard they were just recently working on cgroup2 support.
> 
> https://github.com/opencontainers/runc/blob/master/docs/cgroup-v2.md
> 
> -- 
> Rgrds, legion
> 



> _______________________________________________
> 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] 52+ messages in thread

* Re: [devel] [PATCH hasher-priv v1 3/3] Add cgroup support
  2020-10-02 11:46           ` Arseny Maslennikov
@ 2020-10-02 12:58             ` Alexey Gladkov
  0 siblings, 0 replies; 52+ messages in thread
From: Alexey Gladkov @ 2020-10-02 12:58 UTC (permalink / raw)
  To: ALT Linux Team development discussions; +Cc: ldv

[-- Attachment #1: Type: text/plain, Size: 4690 bytes --]

On Fri, Oct 02, 2020 at 02:46:45PM +0300, Arseny Maslennikov wrote:
> > > There's a misunderstanding here. I'm not asking to explain the
> > > semantics (what this patch does) — I repeat, it's rather obvious from
> > > the source itself, the patch is indeed simple. I'm trying to get how the
> > > patch's author would describe the pragmatic value of this patch. IOW:
> > > we see this patch does XXX. What, in Alexey's view, are we trying to
> > > achieve by implementing XXX?
> > 
> > I remember that this patch was the result of a discussion with ldv.
> 
> That discussion then likely was not public; in part, that's why I'm asking.

Yeah. Sometimes it happens.

> > The
> > idea was that the admin would prepare the system for use of cgroups by the
> > hasher-privd daemon.
> 
> If I understood correctly ^U
> To put it another way, we're doing this because the machine admin might
> want hasher-privd to put the processes it spawns in cgroups
> _at_an_arbitrary_path_, at the administrator's discretion.
> 
> Ok, this is a valid explanation and a valid feature. Thank you.

Yes. This is an optional feature for the administrator.

> > I'm not considering the hasher-privd as an end user server. This is a
> > low-level server on which you can build different solutions. I don't mean
> > just hasher.
> 
> Subject: the future of hasher-privd
> 
> I'm not particularly opposed to the expansion of hasher-privd's utility
> scope; there are quite a lot of potential use cases: hasher-privd as a
> general-purpose cgroup manager, hasher-privd as a daemon-based
> NO_NEW_PRIVS-ready policy-enforcing "su -", ...

At the time when I made this patch and thought in the future to try to
make the hasher-privd more general-purpose.

I had thoughts to add support for seccomp via the libkafel library, extend
the use of namespaces (user, pid, time, etc).

Another thought was not directly related to hasher-privd. I was thinking
about trying to implement the creation of a chroot from docker images.

> While this sounds interesting, I believe there are currently some
> obstacles. Would those solutions on top of hasher-privd be
> co-installable and co-existing on a single machine? E.g. two hasher-privd
> init scripts with different configuration files for different things,
> spawning different processes.

The hasher-priv/hasher-privd has a global configuration. As long as
different solutions are able to use it together, they can coexist. But
this reuse of the server seems a little strange to me.

> Or they wouldn't? Or a single hasher-privd instance — aka node, aka main
> process if you will — would do both services? I don't yet have a picture
> of this in my head; this will have to be thought out.
> 
> Will the decoupled, generic hasher-privd have to expand its IPC API?

I didn't expect the API to be public. I mean it will be used by someone
other than the hasher-priv. I didn't think that far.

I propose to postpone this question. We don't even have a server yet.

> If we decouple hasher-privd from hasher, this would also mean we support
> arbitrary clients, so we'll have to formally define the IPC interface,
> see my concerns on it in a previous mail.

Yep. When this happens, we will need to make a thoughtful public API.

> As it stands now, the hasher project currently sees hasher-privd as its
> vital component, a specialized tool for a special purpose, configured at
> /etc/hasher-priv/. You're proposing something different.
> 
> In short, it's gonna be a long road.

True.
 
> > With this in mind, I don't think that this server should do
> > everything out of the box without configuration.
> 
> I believe the hasher project _would_ want some sane out-of-the-box
> configuration. The generic privd you describe above might not, much like
> runc/crun do not, but the hasher project definitely would. Furthermore, in
> my personal (but shared by many) opinion, this hasher OOTB experience
> would have to be catered to the common case of an ALT Team developer,
> not to public builder services (which are already expected to take care
> to tune and harden their non-trivial configuration, and we can even ship
> recommendations for their use case in /usr/share/doc).

I agree with you but let's not do it all at once. I have not been able to
upstream the basic server implementation. I'm afraid more global changes
will be accepted even slower.

> The approach you suggest here could work, if e. g. the decoupled privd
> is shipped with no defaults, and the hasher project ships its own
> defaults for the desired operation of privd.

-- 
Rgrds, legion


[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 52+ messages in thread

end of thread, other threads:[~2020-10-02 12:58 UTC | newest]

Thread overview: 52+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-13 11:42 [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alex Gladkov
2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 1/3] " Alex Gladkov
2020-09-17 13:10   ` Arseny Maslennikov
2020-10-01 19:43     ` Alexey Gladkov
2020-10-01 21:24       ` Arseny Maslennikov
2020-10-01 23:38         ` Alexey Gladkov
2020-09-17 13:10   ` [devel] [PATCH hasher-priv v1 1/3] *literacy* Arseny Maslennikov
2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller.c Arseny Maslennikov
2020-09-17 13:55     ` Arseny Maslennikov
2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] caller_server.c, caller_task.c Arseny Maslennikov
2020-10-01 19:47     ` Alexey Gladkov
2020-09-17 13:11   ` [devel] [PATCH hasher-priv v1 1/3] config.c Arseny Maslennikov
2020-09-18 10:42     ` Dmitry V. Levin
2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] hasher-privd.c Arseny Maslennikov
2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] logging.c Arseny Maslennikov
2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] Makefile Arseny Maslennikov
2020-09-17 15:09     ` Vladimir D. Seleznev
2020-09-18 10:48     ` Dmitry V. Levin
2020-09-18 10:54       ` Andrey Savchenko
2020-09-18 11:33     ` Dmitry V. Levin
2020-09-18 12:24       ` Arseny Maslennikov
2020-09-17 13:12   ` [devel] [PATCH hasher-priv v1 1/3] server.conf Arseny Maslennikov
2020-09-18 10:50     ` Dmitry V. Levin
2020-09-18 10:57       ` Arseny Maslennikov
2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 2/3] Add systemd and sysvinit service files Alex Gladkov
2020-06-17 22:31   ` Mikhail Novosyolov
2020-06-17 22:38     ` Mikhail Novosyolov
2020-06-17 22:50       ` Alexey Gladkov
2020-06-17 22:43     ` Alexey Gladkov
2020-06-17 22:53       ` Mikhail Novosyolov
2020-09-17 13:10   ` Arseny Maslennikov
2020-10-01 17:25     ` Alexey Gladkov
2020-10-01 17:50       ` Arseny Maslennikov
2019-12-13 11:42 ` [devel] [PATCH hasher-priv v1 3/3] Add cgroup support Alex Gladkov
2020-09-17 13:11   ` Arseny Maslennikov
2020-10-01 19:17     ` Alexey Gladkov
2020-10-01 20:23       ` Arseny Maslennikov
2020-10-02  0:42         ` Alexey Gladkov
2020-10-02 11:46           ` Arseny Maslennikov
2020-10-02 12:58             ` Alexey Gladkov
2019-12-15  8:50 ` [devel] [PATCH hasher-priv v1 0/3] Make a daemon from the hasher-priv Alexey Tourbin
2019-12-15 23:33   ` Andrey Savchenko
2019-12-16  9:35   ` Dmitry V. Levin
2019-12-29 11:03     ` Alexey Tourbin
2020-03-16 10:34 ` Alexey Gladkov
2020-06-17 22:01 ` Alexey Gladkov
2020-09-17 13:09 ` Arseny Maslennikov
2020-10-01 17:21   ` Alexey Gladkov
2020-10-01 17:44     ` Arseny Maslennikov
2020-10-01 20:01       ` Alexey Gladkov
2020-10-01 21:53         ` Arseny Maslennikov
2020-10-01 23:55           ` Alexey Gladkov

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