[PATCH 1/3] switch_creds: Syscall to switch creds for file server ops

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 <jlieb@xxxxxxxxxxx>
---
 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-fsdevel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux