File servers must do some operations with the credentials of
their client.  This syscall switches the key credentials similar
to nfsd_setuser() in fs/nfsd/auth.c  with the capability of retaining a
handle to the credentials by way of an fd for an open anonymous file.
This makes switching for subsequent operations for that client more efficient.

Signed-off-by: Jim Lieb <jl...@panasas.com>
---
 include/linux/cred.h     |  15 ++++
 include/linux/syscalls.h |   2 +
 kernel/sys.c             | 175 +++++++++++++++++++++++++++++++++++++++++++++++
 kernel/sys_ni.c          |   3 +
 4 files changed, 195 insertions(+)

diff --git a/include/linux/cred.h b/include/linux/cred.h
index 04421e8..66a006e 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -138,6 +138,21 @@ struct cred {
        struct rcu_head rcu;            /* RCU deletion hook */
 };
 
+/* switch_creds() syscall parameters*/
+
+#define SWCREDS_REVERT 0
+#define SWCREDS_FROMFD 1
+#define SWCREDS_FSIDS  2
+
+/* arg for SWCREDS_FSIDS command */
+
+struct user_creds {
+       uid_t           uid;
+       gid_t           gid;
+       unsigned        ngroups;
+       gid_t           altgroups[0];
+};
+
 extern void __put_cred(struct cred *);
 extern void exit_creds(struct task_struct *);
 extern int copy_creds(struct task_struct *, unsigned long);
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 7fac04e..3550a8f 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -64,6 +64,7 @@ struct old_linux_dirent;
 struct perf_event_attr;
 struct file_handle;
 struct sigaltstack;
+struct user_creds;
 
 #include <linux/types.h>
 #include <linux/aio_abi.h>
@@ -231,6 +232,7 @@ asmlinkage long sys_setresuid(uid_t ruid, uid_t euid, uid_t 
suid);
 asmlinkage long sys_setresgid(gid_t rgid, gid_t egid, gid_t sgid);
 asmlinkage long sys_setfsuid(uid_t uid);
 asmlinkage long sys_setfsgid(gid_t gid);
+asmlinkage long sys_switch_creds(int cmd, unsigned long arg);
 asmlinkage long sys_setpgid(pid_t pid, pid_t pgid);
 asmlinkage long sys_setsid(void);
 asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist);
diff --git a/kernel/sys.c b/kernel/sys.c
index c18ecca..cebc661 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -53,6 +53,7 @@
 #include <linux/rcupdate.h>
 #include <linux/uidgid.h>
 #include <linux/cred.h>
+#include <linux/anon_inodes.h>
 
 #include <linux/kmsg_dump.h>
 /* Move somewhere else to avoid recompiling? */
@@ -798,6 +799,180 @@ change_okay:
        return old_fsgid;
 }
 
+static int swcreds_release(struct inode *inode, struct file *file)
+{
+       return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+static int swcreds_show_fdinfo(struct seq_file *m, struct file *f)
+{
+       const struct group_info *group_info = f->f_cred->group_info;
+       int ret;
+
+       ret = seq_printf(m, "setcreds: fsuid = %d, fsgid = %d, ngroups = %d\n",
+                        from_kuid_munged(current_user_ns(),
+                                         f->f_cred->fsuid),
+                        from_kgid_munged(current_user_ns(),
+                                         f->f_cred->fsgid),
+                        group_info->ngroups);
+       if (!ret) {
+               int i;
+
+               for (i = 0; i < f->f_cred->group_info->ngroups; i++) {
+                       ret = seq_printf(m, "altgroup[%d] = %d\n",
+                                        i,
+                                        from_kgid_munged(current_user_ns(),
+                                                         GROUP_AT(group_info,
+                                                                  i)));
+                       if (ret)
+                               break;
+               }
+       }
+       return ret;
+}
+#endif
+
+static const struct file_operations swcreds_fops = {
+#ifdef CONFIG_PROC_FS
+       .show_fdinfo = swcreds_show_fdinfo,
+#endif
+       .release = swcreds_release
+};
+
+/* do_switch_creds - set thread creds from struct pointed to by arg
+ *
+ * Does setfsuid, setfsgid, setgroups for thread in one call and one rcu 
update.
+ * drop special root capabilities as well which are not dropped by setfsuid.
+ * This code is similar to nfsd_setuid in concept.
+ */
+
+static int do_switch_creds(unsigned long arg)
+{
+       const struct user_creds  __user *creds;
+       const gid_t __user *alt_groups;
+       const struct cred *old = current_cred();
+       int i, new_fd;
+       struct cred *new;
+       struct user_creds ncred;
+       struct group_info *group_info;
+       int retval;
+
+       creds  = (const struct user_creds  __user *)arg;
+       /* validate and copyin creds */
+       if (copy_from_user(&ncred, creds, sizeof(struct user_creds)))
+               return -EFAULT;
+
+       if (ncred.ngroups > NGROUPS_MAX)
+               return -EINVAL;
+       new = prepare_creds();
+       if (!new)
+               return -ENOMEM;
+       group_info = groups_alloc(ncred.ngroups);
+       if (!group_info) {
+               abort_creds(new);
+               return -ENOMEM;
+       }
+       new->fsuid = make_kuid(old->user_ns, ncred.uid);
+       new->fsgid = make_kgid(old->user_ns, ncred.gid);
+       alt_groups = &creds->altgroups[0];
+       for (i = 0; i < ncred.ngroups; i++) {
+               kgid_t kgid;
+               gid_t gid;
+
+               if (get_user(gid, alt_groups+i)) {
+                       retval = -EFAULT;
+                       goto bad_altgrp;
+               }
+               kgid = make_kgid(old->user_ns, gid);
+               if (!gid_valid(kgid)) {
+                       retval = -EINVAL;
+                       goto bad_altgrp;
+               }
+               GROUP_AT(group_info, i) = kgid;
+       }
+       retval = set_groups(new, group_info);
+       put_group_info(group_info);
+       if (retval < 0) {
+               abort_creds(new);
+               return retval;
+       }
+       /* We need to be the real user in capabilities as well.
+        * don't leak my root caps into the mix */
+       new->cap_effective = cap_drop_nfsd_set(new->cap_effective);
+       old = override_creds(new);
+       /* now open a anon file to preserve these creds
+        * and return the fd
+        */
+       new_fd = anon_inode_getfd("[switch_creds]",
+                                 &swcreds_fops,
+                                 NULL,
+                                 (O_RDONLY|O_CLOEXEC));
+       if (new_fd < 0) {
+               revert_creds(old);
+               abort_creds(new);
+       }
+       return new_fd;
+
+bad_altgrp:
+       put_group_info(group_info);
+       abort_creds(new);
+       return retval;
+}
+
+/**
+ *  switch_creds -- Change user (client) file system credentials
+ *
+ *  Switch the thread's current file access credentials from the argument.
+ *  The end result is the thread has the fsuid, fsgid, altgroups and
+ *  non-root capabilities of the user we want to be for subsequent syscalls.
+ *
+ *  @cmd  SWCREDS_REVERT  revert thread's creds to its real creds.
+ *                        Always returns 0
+ *        SWCREDS_FROM_FD override current creds with creds from open file.
+ *                        Returns 0 or file related error.
+ *        SWCREDS_FSIDS   set fsuid, fsgid, altgroups from arg
+ *                        Return fd or error
+ *  @arg fd or pointer to creds struct
+ *
+ *  The returned fd is a somewhat useless but minimally resource consuming
+ *  anon file that can only be used to store the new creds state.
+ *
+ *  Return 0, fd, or error.
+ */
+
+SYSCALL_DEFINE2(switch_creds, int, cmd, unsigned long, arg)
+{
+       const struct cred *old;
+
+       old = current_cred();
+       if (!ns_capable(old->user_ns, CAP_SETGID) ||
+           !ns_capable(old->user_ns, CAP_SETUID))
+               return -EPERM;
+
+       if (cmd == SWCREDS_REVERT) {
+               if (current->real_cred != current->cred) {
+                       revert_creds(current->real_cred);
+                       return 0;
+               }
+       }
+       if (cmd == SWCREDS_FROMFD) {
+               struct fd f;
+               const struct cred *file_cred;
+
+               f = fdget(arg);
+               if (unlikely(!f.file))
+                       return -EBADF;
+               file_cred = f.file->f_cred;
+               (void)override_creds(file_cred);
+               fdput(f);
+               return 0;
+       } else if (cmd == SWCREDS_FSIDS) {
+               return do_switch_creds(arg);
+       }
+       return -EINVAL;
+}
+
 /**
  * sys_getpid - return the thread group id of the current process
  *
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 7078052..7573cad 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -209,3 +209,6 @@ cond_syscall(compat_sys_open_by_handle_at);
 
 /* compare kernel pointers */
 cond_syscall(sys_kcmp);
+
+/* switch task creds */
+cond_syscall(sys_switch_creds);
-- 
1.8.3.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to