summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Babenhauserheide <arne_bab@web.de>2021-12-29 23:25:42 +0100
committerArne Babenhauserheide <arne_bab@web.de>2021-12-29 23:29:05 +0100
commit500fe6e0cb1fe3b239dd0be83cd57c73d8c8c9aa (patch)
tree12a9d9dec03c8d6b8e04689fe1dae9ec4ceaa9ca
parentac29bb14011d77fc6042a8455179231450a79b9c (diff)
trans/checkperms.c: add deferred authorization translator
Setup the translator: echo HELLOWORLD > /hello && \ settrans -cga /hello $(realpath ~/Dev/hurd/trans/checkperms) --groupname=user Create the FIFOs: USER=root GROUP=user mkdir -p /run/$USER/request-permission mkdir -p /run/$USER/grant-permission mkfifo /run/$USER/request-permission/$GROUP mkfifo /run/$USER/grant-permission/$GROUP Setup the permission-granting program in a separate shell: USER=root GROUP=user while true; do PID="$(cat /run/$USER/request-permission/$GROUP)" echo Process $PID tries to access file /hello but is not in the required group $GROUP. ps-hurd -p $PID -aeux if [[ "$(read -e -p 'Grant permission and add group "'$GROUP'" for 5 minutes? [y/N]> '; echo $REPLY)" == [Yy]* ]]; then addauth -p $PID -g $GROUP echo 0 > /run/$USER/grant-permission/$GROUP (sleep 300 && rmauth -p $PID -g $GROUP 2>/dev/null) & else echo 1 > /run/$USER/grant-permission/$GROUP fi done Access the translator as user without the required group and with the group: su - user --shell /bin/bash -c 'cat /hello' cat /hello & # accept the request in the permission granting program
-rw-r--r--trans/Makefile7
-rw-r--r--trans/checkperms.c510
2 files changed, 514 insertions, 3 deletions
diff --git a/trans/Makefile b/trans/Makefile
index 6cf50e7a..9d0c54e7 100644
--- a/trans/Makefile
+++ b/trans/Makefile
@@ -20,16 +20,16 @@ dir := trans
makemode := servers
targets = symlink firmlink ifsock magic null fifo new-fifo fwd crash \
- password hello hello-mt streamio fakeroot proxy-defpager remap \
+ password hello hello-mt checkperms streamio fakeroot proxy-defpager remap \
mtab
SRCS = ifsock.c symlink.c magic.c null.c fifo.c new-fifo.c fwd.c \
- crash.c firmlink.c password.c hello.c hello-mt.c streamio.c \
+ crash.c firmlink.c password.c hello.c hello-mt.c checkperms.c streamio.c \
fakeroot.c proxy-defpager.c remap.c mtab.c
OBJS = $(SRCS:.c=.o) fsysServer.o ifsockServer.o passwordServer.o \
crashServer.o crash_replyUser.o msgServer.o \
default_pagerServer.o default_pagerUser.o \
device_replyServer.o elfcore.o startup_notifyServer.o
-HURDLIBS = ports netfs trivfs iohelp fshelp pipe ihash shouldbeinlibc
+HURDLIBS = ports netfs trivfs iohelp fshelp pipe ihash shouldbeinlibc ps
LDLIBS += -lpthread
password-MIGSFLAGS=\
"-DIO_INTRAN=trivfs_protid_t trivfs_begin_using_protid (io_t)" \
@@ -81,6 +81,7 @@ symlink: fsysServer.o
fakeroot: ../libnetfs/libnetfs.a
fifo new-fifo: ../libpipe/libpipe.a
crash fifo firmlink hello hello-mt ifsock magic mtab new-fifo null password proxy-defpager remap streamio: ../libtrivfs/libtrivfs.a
+checkperms: ../libtrivfs/libtrivfs.a ../libps/libps.a
$(targets): ../libfshelp/libfshelp.a \
../libihash/libihash.a \
../libiohelp/libiohelp.a \
diff --git a/trans/checkperms.c b/trans/checkperms.c
new file mode 100644
index 00000000..003e1f1a
--- /dev/null
+++ b/trans/checkperms.c
@@ -0,0 +1,510 @@
+/* checkperms.c - A permission-checking and granting translator
+ Copyright (C) 1998,99,2001,02,2006 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#define _GNU_SOURCE 1
+
+#include <hurd/trivfs.h>
+#include <idvec.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <argp.h>
+#include <argz.h>
+#include <error.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <portinfo.h>
+#include <grp.h>
+#include <ps.h>
+#include <portxlate.h>
+
+#include <version.h>
+
+#include "libtrivfs/trivfs_io_S.h"
+
+const char *argp_program_version = STANDARD_HURD_VERSION (checkperms);
+
+/* The message we return when we are read. */
+static const char hello[] = "Hello, perms!\n";
+static char *contents = (char *) hello;
+static size_t contents_len = sizeof hello - 1;
+static const char defaultgroupname[] = "audio";
+static char *groupname = (char *) defaultgroupname;
+
+/* This lock protects access to contents and contents_len. */
+static pthread_rwlock_t contents_lock;
+
+/* Trivfs hooks. */
+int trivfs_fstype = FSTYPE_MISC;
+int trivfs_fsid = 0;
+
+int trivfs_allow_open = O_READ;
+
+int trivfs_support_read = 1;
+int trivfs_support_write = 0;
+int trivfs_support_exec = 0;
+
+/* NOTE: This example is not robust: it is possible to trigger some
+ assertion failures because we don't implement the following:
+
+ $ cd /src/hurd/libtrivfs
+ $ grep -l 'assert.*!trivfs_support_read' *.c |
+ xargs grep '^trivfs_S_' | sed 's/^[^:]*:\([^ ]*\).*$/\1/'
+ trivfs_S_io_get_openmodes
+ trivfs_S_io_clear_some_openmodes
+ trivfs_S_io_set_some_openmodes
+ trivfs_S_io_set_all_openmodes
+ trivfs_S_io_readable
+ trivfs_S_io_select
+ $
+
+ For that reason, you should run this as an active translator
+ `settrans -ac testnode /path/to/thello' so that you can see the
+ error messages when they appear. */
+
+/* A hook for us to keep track of the file descriptor state. */
+struct open
+{
+ pthread_mutex_t lock;
+ off_t offs;
+};
+
+
+/* adapted from utils/portinfo.c::search_for_port: Locates the port
+ NAME from TASK in any other process and prints the mappings. */
+error_t
+search_for_pid (char **output, task_t task, mach_port_t name)
+{
+ error_t err;
+
+ /* These resources are freed in the function epilogue. */
+ struct ps_context *context = NULL;
+ struct proc_stat_list *procset = NULL;
+
+ static process_t proc = MACH_PORT_NULL;
+ if (proc == MACH_PORT_NULL)
+ proc = getproc ();
+
+ pid_t pid;
+ err = proc_task2pid (proc, task, &pid);
+ if (err)
+ goto out;
+
+ /* Get a list of all processes. */
+ err = ps_context_create (getproc (), &context);
+ if (err)
+ goto out;
+
+ err = proc_stat_list_create (context, &procset);
+ if (err)
+ goto out;
+
+ err = proc_stat_list_add_all (procset, 0, 0);
+ if (err)
+ goto out;
+
+ for (unsigned i = 0; i < procset->num_procs; i++)
+ {
+ /* Ignore the target process. */
+ if (procset->proc_stats[i]->pid == pid)
+ continue;
+
+ task_t xlate_task = MACH_PORT_NULL;
+ err = proc_pid2task (proc, procset->proc_stats[i]->pid, &xlate_task);
+ if (err || xlate_task == MACH_PORT_NULL)
+ continue;
+
+ struct port_name_xlator *xlator = NULL;
+ err = port_name_xlator_create (task, xlate_task, &xlator);
+ if (err)
+ goto loop_cleanup;
+
+ mach_port_t translated_port;
+ mach_msg_type_name_t translated_type;
+ err = port_name_xlator_xlate (xlator,
+ name, 0,
+ &translated_port, &translated_type);
+ if (err)
+ goto loop_cleanup;
+
+ /* The port translation was successful, print more infos. */
+ // output again, not called??
+ asprintf (output, "% 5i", procset->proc_stats[i]->pid);
+ goto out;
+
+ loop_cleanup:
+ if (xlate_task)
+ mach_port_deallocate (mach_task_self (), xlate_task);
+
+ if (xlator)
+ port_name_xlator_free (xlator);
+ }
+
+ err = 0;
+
+ out:
+ if (procset != NULL)
+ proc_stat_list_free (procset);
+
+ if (context != NULL)
+ ps_context_free (context);
+
+ return err;
+}
+
+
+int check_group (struct idvec *gids) {
+ /* Check whether the process has the checked group */
+ unsigned has_group = 0;
+ struct group _gr, *gr;
+ char buf[1024];
+ for (unsigned i = 0; i < gids->num; i++) {
+ if (getgrgid_r (gids->ids[i], &_gr, buf, sizeof buf, &gr) == 0 && gr)
+ {
+ if (strcmp(groupname, strdup (gr->gr_name)) == 0)
+ {
+ has_group += 1;
+ }
+ }
+ }
+ return has_group;
+}
+
+
+error_t request_auth (struct trivfs_protid *cred) {
+ /* specify the contents to show dynamically */
+ struct port_info info = cred->pi;
+ // struct rpc_info *rpcs = info.current_rpcs;
+ struct port_bucket *bucket = info.bucket;
+ mach_port_t portright = info.port_right;
+
+ task_t task = mach_task_self ();
+ unsigned show = 0;
+ show |= PORTINFO_DETAILS;
+ const char otherinfo_arr[1024];
+ char *otherinfo = (char *) otherinfo_arr;
+ search_for_pid (&otherinfo, task, portright);
+ // for debugging:
+ // contents_len = asprintf(&dat, "%d\n%d\n%s\n%s\n%d\n", getpid (), bucket->count, otherinfo, idvec_gids_rep(cred->user->gids, 1, 1, ","), has_group);
+ char request_filename_arr[1024];
+ char *request_filename = (char *) request_filename_arr;
+ asprintf(&request_filename, "/run/%s/request-permission/%s", idvec_uids_rep(cred->user->uids, 0, 1, ","), groupname);
+ char grant_filename_arr[1024];
+ char *grant_filename = (char *) grant_filename_arr;
+ asprintf(&grant_filename, "/run/%s/grant-permission/%s", idvec_uids_rep(cred->user->uids, 0, 1, ","), groupname);
+ // FIXME: replace system(command) by proper io_write and port lookup (I did not get it working yet)
+ char command_arr[1024];
+ char *command = (char *) command_arr;
+ asprintf(&command, "echo \"%s\" > %s ; exit $(cat \"%s\")", otherinfo, request_filename, grant_filename);
+ return system(command);
+}
+
+
+void
+trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st)
+{
+ /* Mark the node as a read-only plain file. */
+ st->st_mode &= ~(S_IFMT | ALLPERMS);
+ st->st_mode |= (S_IFREG | S_IRUSR | S_IRGRP); // | S_IROTH);
+ st->st_size = contents_len; /* No need to lock for reading one word. */
+}
+
+error_t
+trivfs_goaway (struct trivfs_control *cntl, int flags)
+{
+ exit (0);
+}
+
+
+static error_t
+open_hook (struct trivfs_peropen *peropen)
+{
+ struct open *op = malloc (sizeof (struct open));
+ if (op == NULL)
+ return ENOMEM;
+
+ /* Initialize the offset. */
+ op->offs = 0;
+ pthread_mutex_init (&op->lock, NULL);
+ peropen->hook = op;
+ return 0;
+}
+
+
+static void
+close_hook (struct trivfs_peropen *peropen)
+{
+ struct open *op = peropen->hook;
+
+ pthread_mutex_destroy (&op->lock);
+ free (op);
+}
+
+
+/* Read data from an IO object. If offset is -1, read from the object
+ maintained file pointer. If the object is not seekable, offset is
+ ignored. The amount desired to be read is in AMOUNT. */
+error_t
+trivfs_S_io_read (struct trivfs_protid *cred,
+ mach_port_t reply, mach_msg_type_name_t reply_type,
+ data_t *data, mach_msg_type_number_t *data_len,
+ loff_t offs, mach_msg_type_number_t amount)
+{
+ struct open *op;
+ error_t err;
+
+ const char contents_array[amount];
+ char *dat = (char *) contents_array;
+
+ /* Deny access if they have bad credentials. */
+ if (! cred)
+ return EOPNOTSUPP;
+ else if (! (cred->po->openmodes & O_READ))
+ return EBADF;
+
+ op = cred->po->hook;
+
+ pthread_mutex_lock (&op->lock);
+
+ /* Get the offset. */
+ if (offs == -1)
+ offs = op->offs;
+
+ pthread_rwlock_rdlock (&contents_lock);
+
+ /* Check whether the process has the checked group */
+ unsigned has_group = check_group(cred->user->gids);
+ if (has_group == 0)
+ err = request_auth (cred);
+
+ if (has_group > 0 || !err)
+ // TODO: delegate to or hand over the underlying node directly on lookup to reduce delays
+ err = io_read (cred->realnode,
+ &dat, data_len, offs, *data_len);
+
+ if (!err)
+ {
+ char *contents = (char *) dat;
+ contents_len = strlen(dat);
+
+ /* Prune the amount they want to read. */
+ if (offs > contents_len)
+ offs = contents_len;
+ if (offs + amount > contents_len)
+ amount = contents_len - offs;
+
+ if (amount > 0)
+ {
+ /* Possibly allocate a new buffer. */
+ if (*data_len < amount)
+ *data = mmap (0, amount, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0);
+ if (*data == MAP_FAILED)
+ {
+ pthread_mutex_unlock (&op->lock);
+ pthread_rwlock_unlock (&contents_lock);
+ return ENOMEM;
+ }
+
+ /* Copy the constant data into the buffer. */
+ memcpy ((char *) *data, contents + offs, amount);
+
+ /* Update the saved offset. */
+ op->offs += amount;
+ }
+ }
+ pthread_mutex_unlock (&op->lock);
+
+ pthread_rwlock_unlock (&contents_lock);
+
+ *data_len = amount;
+ return err;
+}
+
+
+/* Change current read/write offset */
+error_t
+trivfs_S_io_seek (struct trivfs_protid *cred,
+ mach_port_t reply, mach_msg_type_name_t reply_type,
+ off_t offs, int whence, off_t *new_offs)
+{
+ struct open *op;
+ error_t err = 0;
+ if (! cred)
+ return (error_t) EOPNOTSUPP;
+
+ op = cred->po->hook;
+ pthread_mutex_lock (&op->lock);
+ unsigned has_group = check_group(cred->user->gids);
+ if (has_group == 0)
+ err = request_auth (cred);
+
+ if (has_group > 0 || !err)
+ {
+ switch (whence)
+ {
+ case SEEK_CUR:
+ offs += op->offs;
+ goto check;
+ case SEEK_END:
+ offs += contents_len;
+ case SEEK_SET:
+ check:
+ if (offs >= 0)
+ {
+ *new_offs = op->offs = offs;
+ break;
+ }
+ default:
+ err = EINVAL;
+ }
+ }
+ pthread_mutex_unlock (&op->lock);
+
+ return err;
+}
+
+
+/* If this variable is set, it is called every time a new peropen
+ structure is created and initialized. */
+error_t (*trivfs_peropen_create_hook)(struct trivfs_peropen *) = open_hook;
+
+/* If this variable is set, it is called every time a peropen structure
+ is about to be destroyed. */
+void (*trivfs_peropen_destroy_hook) (struct trivfs_peropen *) = close_hook;
+
+
+/* Options processing. We accept the same options on the command line
+ and from fsys_set_options. */
+
+static const struct argp_option options[] =
+{
+ {"groupname", 'n', "STRING", 0, "Specify the group to check for"},
+ {0}
+};
+
+static error_t
+parse_opt (int opt, char *arg, struct argp_state *state)
+{
+ switch (opt)
+ {
+ default:
+ return ARGP_ERR_UNKNOWN;
+ case ARGP_KEY_INIT:
+ case ARGP_KEY_SUCCESS:
+ case ARGP_KEY_ERROR:
+ break;
+
+ case 'n':
+ {
+ char *new = strdup (arg);
+ if (new == NULL)
+ return ENOMEM;
+ pthread_rwlock_wrlock (&contents_lock);
+ groupname = new;
+ if (contents != hello)
+ free (contents);
+ contents = new;
+ contents_len = strlen (new);
+ pthread_rwlock_unlock (&contents_lock);
+ break;
+ }
+ }
+ return 0;
+}
+
+/* This will be called from libtrivfs to help construct the answer
+ to an fsys_get_options RPC. */
+error_t
+trivfs_append_args (struct trivfs_control *fsys,
+ char **argz, size_t *argz_len)
+{
+ error_t err;
+ char *opt;
+ size_t opt_len;
+ FILE *s;
+ char *c;
+
+ s = open_memstream (&opt, &opt_len);
+ fprintf (s, "--groupname='");
+
+ pthread_rwlock_rdlock (&contents_lock);
+ for (c = groupname; *c; c++)
+ switch (*c)
+ {
+ case 0x27: /* Single quote. */
+ fprintf (s, "'\"'\"'");
+ break;
+
+ default:
+ fprintf (s, "%c", *c);
+ }
+ pthread_rwlock_unlock (&contents_lock);
+
+ fprintf (s, "'");
+ fclose (s);
+
+ err = argz_add (argz, argz_len, opt);
+
+ free (opt);
+
+ return err;
+}
+
+static struct argp hello_argp =
+{ options, parse_opt, 0,
+ "A multi-threaded translator providing a warm greeting." };
+
+/* Setting this variable makes libtrivfs use our argp to
+ parse options passed in an fsys_set_options RPC. */
+struct argp *trivfs_runtime_argp = &hello_argp;
+
+
+int
+main (int argc, char **argv)
+{
+ error_t err;
+ mach_port_t bootstrap;
+ struct trivfs_control *fsys;
+
+ /* Initialize the lock that will protect CONTENTS and CONTENTS_LEN.
+ We must do this before argp_parse, because parse_opt (above) will
+ use the lock. */
+ pthread_rwlock_init (&contents_lock, NULL);
+
+ /* We use the same argp for options available at startup
+ as for options we'll accept in an fsys_set_options RPC. */
+ argp_parse (&hello_argp, argc, argv, 0, 0, 0);
+
+ task_get_bootstrap_port (mach_task_self (), &bootstrap);
+ if (bootstrap == MACH_PORT_NULL)
+ error (1, 0, "Must be started as a translator");
+
+ /* Reply to our parent */
+ err = trivfs_startup (bootstrap, O_READ, 0, 0, 0, 0, &fsys);
+ mach_port_deallocate (mach_task_self (), bootstrap);
+ if (err)
+ error (3, err, "trivfs_startup");
+
+ /* Launch. */
+ ports_manage_port_operations_multithread (fsys->pi.bucket, trivfs_demuxer,
+ 10 * 1000, /* idle thread */
+ 10 * 60 * 1000, /* idle server */
+ 0);
+
+ return 0;
+}