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