[PATCH 04/11] capsicum: implement fgetr() and friends

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

 



Add variants of fget() and related functions where the caller
indicates the operations that will be performed on the file.

If CONFIG_SECURITY_CAPSICUM is defined, these variants build a
struct capsicum_rights instance holding the rights associated
with the file operations; this will allow a future hook to check
whether a rights-restricted file has those specific rights
available.

If CONFIG_SECURITY_CAPSICUM is not defined, these variants expand
to the underlying fget() function, with one difference: failures
are returned as an ERR_PTR value rather than just NULL.

Signed-off-by: David Drysdale <drysdale@xxxxxxxxxx>
---
 fs/file.c             | 130 +++++++++++++++++++++++++++++++++++++++++++++++
 fs/namei.c            |  49 ++++++++++++++++--
 fs/read_write.c       |   5 --
 include/linux/file.h  | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/namei.h |   9 ++++
 5 files changed, 321 insertions(+), 8 deletions(-)

diff --git a/fs/file.c b/fs/file.c
index 8f294cfac697..562cc82ba442 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -13,6 +13,7 @@
 #include <linux/mmzone.h>
 #include <linux/time.h>
 #include <linux/sched.h>
+#include <linux/security.h>
 #include <linux/slab.h>
 #include <linux/vmalloc.h>
 #include <linux/file.h>
@@ -722,6 +723,135 @@ unsigned long __fdget_pos(unsigned int fd)
 	return v;
 }
 
+#ifdef CONFIG_SECURITY_CAPSICUM
+/*
+ * The LSM might want to change the return value of fget() and friends.
+ * This function is called with the intended return value, and fget()
+ * will /actually/ return whatever is returned from here. We call an
+ * LSM hook, and return what it returns. We adjust the reference counter
+ * if necessary.
+ */
+static struct file *unwrap_file(struct file *orig,
+				const struct capsicum_rights *required_rights,
+				const struct capsicum_rights **actual_rights,
+				bool update_refcnt)
+{
+	struct file *f;
+
+	if (orig == NULL)
+		return ERR_PTR(-EBADF);
+	if (IS_ERR(orig))
+		return orig;
+	f = orig;  /* TODO: pass to an LSM hook here */
+	if (f != orig && update_refcnt) {
+		/* We're not returning the original, and the calling code
+		 * has already incremented the refcount on it, we need to
+		 * release that reference and obtain a reference to the new
+		 * return value, if any.
+		 */
+		if (!IS_ERR(f) && !atomic_long_inc_not_zero(&f->f_count))
+			f = ERR_PTR(-EBADF);
+		atomic_long_dec(&orig->f_count);
+	}
+
+	return f;
+}
+
+struct file *fget_rights(unsigned int fd, const struct capsicum_rights *rights)
+{
+	return unwrap_file(fget(fd), rights, NULL, true);
+}
+EXPORT_SYMBOL(fget_rights);
+
+struct file *fget_raw_rights(unsigned int fd,
+			     const struct capsicum_rights *rights)
+{
+	return unwrap_file(fget_raw(fd), rights, NULL, true);
+}
+EXPORT_SYMBOL(fget_raw_rights);
+
+struct fd fdget_rights(unsigned int fd, const struct capsicum_rights *rights)
+{
+	struct fd f = fdget(fd);
+	f.file = unwrap_file(f.file, rights, NULL, (f.flags & FDPUT_FPUT));
+	return f;
+}
+EXPORT_SYMBOL(fdget_rights);
+
+struct fd fdget_raw_rights(unsigned int fd,
+			   const struct capsicum_rights **actual_rights,
+			   const struct capsicum_rights *rights)
+{
+	struct fd f = fdget_raw(fd);
+	f.file = unwrap_file(f.file, rights, actual_rights,
+			     (f.flags & FDPUT_FPUT));
+	return f;
+}
+EXPORT_SYMBOL(fdget_raw_rights);
+
+struct file *_fgetr(unsigned int fd, ...)
+{
+	struct capsicum_rights rights;
+	struct file *f;
+	va_list ap;
+	va_start(ap, fd);
+	f = fget_rights(fd, cap_rights_vinit(&rights, ap));
+	va_end(ap);
+	return f;
+}
+EXPORT_SYMBOL(_fgetr);
+
+struct file *_fgetr_raw(unsigned int fd, ...)
+{
+	struct capsicum_rights rights;
+	struct file *f;
+	va_list ap;
+	va_start(ap, fd);
+	f = fget_raw_rights(fd, cap_rights_vinit(&rights, ap));
+	va_end(ap);
+	return f;
+}
+EXPORT_SYMBOL(_fgetr_raw);
+
+struct fd _fdgetr(unsigned int fd, ...)
+{
+	struct fd f;
+	struct capsicum_rights rights;
+	va_list ap;
+	va_start(ap, fd);
+	f = fdget_rights(fd, cap_rights_vinit(&rights, ap));
+	va_end(ap);
+	return f;
+}
+EXPORT_SYMBOL(_fdgetr);
+
+struct fd _fdgetr_raw(unsigned int fd, ...)
+{
+	struct fd f;
+	struct capsicum_rights rights;
+	va_list ap;
+	va_start(ap, fd);
+	f = fdget_raw_rights(fd, NULL, cap_rights_vinit(&rights, ap));
+	va_end(ap);
+	return f;
+}
+EXPORT_SYMBOL(_fdgetr_raw);
+
+struct fd _fdgetr_pos(unsigned int fd, ...)
+{
+	struct fd f;
+	struct capsicum_rights rights;
+	va_list ap;
+	f = __to_fd(__fdget_pos(fd));
+	va_start(ap, fd);
+	f.file = unwrap_file(f.file, cap_rights_vinit(&rights, ap), NULL,
+			     (f.flags & FDPUT_FPUT));
+	va_end(ap);
+	return f;
+}
+EXPORT_SYMBOL(_fdgetr_pos);
+#endif
+
 /*
  * We only lock f_pos if we have threads or if the file might be
  * shared with another process. In both cases we'll have an elevated
diff --git a/fs/namei.c b/fs/namei.c
index e6b72531dfc7..c93f7993960e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -646,6 +646,19 @@ static __always_inline void set_root(struct nameidata *nd)
 		get_fs_root(current->fs, &nd->root);
 }
 
+/*
+ * Retrieval of files against a directory file descriptor requires
+ * CAP_LOOKUP. As this is common in this file, set up the required rights once
+ * and for all.
+ */
+static struct capsicum_rights lookup_rights;
+static int __init init_lookup_rights(void)
+{
+	cap_rights_init(&lookup_rights, CAP_LOOKUP);
+	return 0;
+}
+fs_initcall(init_lookup_rights);
+
 static int link_path_walk(const char *, struct nameidata *, unsigned int);
 
 static __always_inline void set_root_rcu(struct nameidata *nd)
@@ -2135,8 +2148,12 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
 }
 EXPORT_SYMBOL(lookup_one_len);
 
-int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
-		 struct path *path, int *empty)
+static int user_path_at_empty_rights(int dfd,
+				const char __user *name,
+				unsigned flags,
+				struct path *path,
+				int *empty,
+				const struct capsicum_rights *rights)
 {
 	struct nameidata nd;
 	struct filename *tmp = getname_flags(name, flags, empty);
@@ -2153,13 +2170,39 @@ int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
 	return err;
 }
 
+int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
+		 struct path *path, int *empty)
+{
+	return user_path_at_empty_rights(dfd, name, flags, path, empty,
+					 &lookup_rights);
+}
+
 int user_path_at(int dfd, const char __user *name, unsigned flags,
 		 struct path *path)
 {
-	return user_path_at_empty(dfd, name, flags, path, NULL);
+	return user_path_at_empty_rights(dfd, name, flags, path, NULL,
+					 &lookup_rights);
 }
 EXPORT_SYMBOL(user_path_at);
 
+#ifdef CONFIG_SECURITY_CAPSICUM
+int _user_path_atr(int dfd,
+		   const char __user *name,
+		   unsigned flags,
+		   struct path *path,
+		   ...)
+{
+	struct capsicum_rights rights;
+	int rc;
+	va_list ap;
+	va_start(ap, path);
+	rc = user_path_at_empty_rights(dfd, name, flags, path, NULL,
+				       cap_rights_vinit(&rights, ap));
+	va_end(ap);
+	return rc;
+}
+#endif
+
 /*
  * NB: most callers don't do anything directly with the reference to the
  *     to struct filename, but the nd->last pointer points into the name string
diff --git a/fs/read_write.c b/fs/read_write.c
index 31c6efa43183..bd4cc3770b42 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -264,11 +264,6 @@ loff_t vfs_llseek(struct file *file, loff_t offset, int whence)
 }
 EXPORT_SYMBOL(vfs_llseek);
 
-static inline struct fd fdget_pos(int fd)
-{
-	return __to_fd(__fdget_pos(fd));
-}
-
 static inline void fdput_pos(struct fd f)
 {
 	if (f.flags & FDPUT_POS_UNLOCK)
diff --git a/include/linux/file.h b/include/linux/file.h
index 4d69123377a2..22952e26ab19 100644
--- a/include/linux/file.h
+++ b/include/linux/file.h
@@ -8,6 +8,8 @@
 #include <linux/compiler.h>
 #include <linux/types.h>
 #include <linux/posix_types.h>
+#include <linux/err.h>
+#include <linux/capsicum.h>
 
 struct file;
 
@@ -39,6 +41,21 @@ static inline void fdput(struct fd fd)
 		fput(fd.file);
 }
 
+/*
+ * The base functions for converting a file descriptor to a struct file are:
+ *  - fget() always increments refcount, doesn't work on O_PATH files.
+ *  - fget_raw() always increments refcount, and does work on O_PATH files.
+ *  - fdget() only increments refcount if needed, doesn't work on O_PATH files.
+ *  - fdget_raw() only increments refcount if needed, works on O_PATH files.
+ *  - fdget_pos() as fdget(), but also locks the file position lock (for
+ *    operations that POSIX requires to be atomic w.r.t file position).
+ * These functions return NULL on failure, and return the actual entry in the
+ * fdtable (which may be a wrapper if the file is a Capsicum capability).
+ *
+ * These functions should normally only be used when a file is being
+ * transferred (e.g. dup(2)) or manipulated as-is; normal users should stick
+ * to the fgetr() variants below.
+ */
 extern struct file *fget(unsigned int fd);
 extern struct file *fget_raw(unsigned int fd);
 extern unsigned long __fdget(unsigned int fd);
@@ -60,6 +77,125 @@ static inline struct fd fdget_raw(unsigned int fd)
 	return __to_fd(__fdget_raw(fd));
 }
 
+static inline struct fd fdget_pos(unsigned int fd)
+{
+	return __to_fd(__fdget_pos(fd));
+}
+
+#ifdef CONFIG_SECURITY_CAPSICUM
+/*
+ * The full unwrapping variant functions are:
+ *  - fget_rights()
+ *  - fget_raw_rights()
+ *  - fdget_rights()
+ *  - fdget_raw_rights()
+ * These versions have the same behavior as the equivalent base functions, but:
+ *  - They also take a struct capsicum_rights argument describing the details
+ *    of the operations to be performed on the file.
+ *  - They remove any Capsicum capability wrapper for the file, returning the
+ *    normal underlying file.
+ *  - They return an ERR_PTR on failure (typically with either -EBADF for an
+ *    unrecognized FD, or -ENOTCAPABLE for a Capsicum capability FD that does
+ *    not have the requisite rights).
+ *
+ * The fdget_raw_rights() function also optionally returns the actual Capsicum
+ * rights associated with the file descriptor; the caller should only access
+ * this structure while it holds a reference to the file.
+ *
+ * These functions should normally only be used:
+ *  - when the operation being performed on the file requires more detailed
+ *    specification (in particular: the ioctl(2) or fcntl(2) command invoked)
+ *  - (for fdget_raw_rights()) when a new file descriptor will be created from
+ *    this file descriptor, and so should potentially inherit its rights (if
+ *    it is a Capsicum capability file descriptor).
+ * Otherwise users should stick to the simpler fgetr() variants below.
+ */
+extern struct file *fget_rights(unsigned int fd,
+				const struct capsicum_rights *rights);
+extern struct file *fget_raw_rights(unsigned int fd,
+				    const struct capsicum_rights *rights);
+extern struct fd fdget_rights(unsigned int fd,
+			      const struct capsicum_rights *rights);
+extern struct fd fdget_raw_rights(unsigned int fd,
+				  const struct capsicum_rights **actual_rights,
+				  const struct capsicum_rights *rights);
+
+/*
+ * The simple unwrapping variant functions are:
+ *  - fgetr()
+ *  - fgetr_raw()
+ *  - fdgetr()
+ *  - fdgetr_raw()
+ *  - fdgetr_pos()
+ * These versions have the same behavior as the equivalent base functions, but:
+ *  - They also take variable arguments indicating the operations to be
+ *    performed on the file.
+ *  - They remove any Capsicum capability wrapper for the file, returning the
+ *    normal underlying file.
+ *  - They return an ERR_PTR on failure (typically with either -EBADF for an
+ *    unrecognized FD, or -ENOTCAPABLE for a Capsicum capability FD that does
+ *    not have the requisite rights).
+ *
+ * These functions should normally be used for FD->file conversion.
+ */
+#define fgetr(fd, ...)		_fgetr((fd), __VA_ARGS__, CAP_LIST_END)
+#define fgetr_raw(fd, ...)	_fgetr_raw((fd), __VA_ARGS__, CAP_LIST_END)
+#define fdgetr(fd, ...)	_fdgetr((fd), __VA_ARGS__, CAP_LIST_END)
+#define fdgetr_raw(fd, ...)	_fdgetr_raw((fd), __VA_ARGS__, CAP_LIST_END)
+#define fdgetr_pos(fd, ...)	_fdgetr_pos((fd), __VA_ARGS__, CAP_LIST_END)
+extern struct file *_fgetr(unsigned int fd, ...);
+extern struct file *_fgetr_raw(unsigned int fd, ...);
+extern struct fd _fdgetr(unsigned int fd, ...);
+extern struct fd _fdgetr_raw(unsigned int fd, ...);
+extern struct fd _fdgetr_pos(unsigned int fd, ...);
+
+#else
+/*
+ * In a non-Capsicum build, all rights-checking fget() variants fall back to the
+ * normal versions (but still return errors as ERR_PTR values not just NULL).
+ */
+static inline struct file *fget_rights(unsigned int fd,
+				       const struct capsicum_rights *rights)
+{
+	return fget(fd) ?: ERR_PTR(-EBADF);
+}
+static inline struct file *fget_raw_rights(unsigned int fd,
+					   const struct capsicum_rights *rights)
+{
+	return fget_raw(fd) ?: ERR_PTR(-EBADF);
+}
+static inline struct fd fdget_rights(unsigned int fd,
+				     const struct capsicum_rights *rights)
+{
+	struct fd f = fdget(fd);
+	if (f.file == NULL)
+		f.file = ERR_PTR(-EBADF);
+	return f;
+}
+static inline struct fd
+fdget_raw_rights(unsigned int fd,
+		 const struct capsicum_rights **actual_rights,
+		 const struct capsicum_rights *rights)
+{
+	struct fd f = fdget_raw(fd);
+	if (f.file == NULL)
+		f.file = ERR_PTR(-EBADF);
+	return f;
+}
+
+#define fgetr(fd, ...)		(fget(fd) ?: ERR_PTR(-EBADF))
+#define fgetr_raw(fd, ...)	(fget_raw(fd) ?: ERR_PTR(-EBADF))
+#define fdgetr(fd, ...)	fdget_rights((fd), NULL)
+#define fdgetr_raw(fd, ...)	fdget_raw_rights((fd), NULL, NULL)
+static inline struct fd fdgetr_pos(int fd, ...)
+{
+	struct fd f = fdget_pos(fd);
+	if (f.file == NULL)
+		f.file = ERR_PTR(-EBADF);
+	return f;
+}
+#endif
+
 extern int f_dupfd(unsigned int from, struct file *file, unsigned flags);
 extern int replace_fd(unsigned fd, struct file *file, unsigned flags);
 extern void set_close_on_exec(unsigned int fd, int flag);
diff --git a/include/linux/namei.h b/include/linux/namei.h
index cd56c50109fc..ce6f2fe11bcd 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -59,6 +59,15 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
 
 extern int user_path_at(int, const char __user *, unsigned, struct path *);
 extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty);
+#ifdef CONFIG_SECURITY_CAPSICUM
+extern int _user_path_atr(int, const char __user *, unsigned,
+			  struct path *, ...);
+#define user_path_atr(f, n, x, p, ...) \
+	_user_path_atr((f), (n), (x), (p), __VA_ARGS__, 0ULL)
+#else
+#define user_path_atr(f, n, x, p, ...) \
+	user_path_at((f), (n), (x), (p))
+#endif
 
 #define user_path(name, path) user_path_at(AT_FDCWD, name, LOOKUP_FOLLOW, path)
 #define user_lpath(name, path) user_path_at(AT_FDCWD, name, 0, path)
-- 
2.0.0.526.g5318336

--
To unsubscribe from this list: send the line "unsubscribe linux-api" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux