New kind of open files - "location only".
authorAl Viro <viro@zeniv.linux.org.uk>
Sun, 13 Mar 2011 07:51:11 +0000 (03:51 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Tue, 15 Mar 2011 06:21:45 +0000 (02:21 -0400)
New flag for open(2) - O_PATH.  Semantics:
* pathname is resolved, but the file itself is _NOT_ opened
as far as filesystem is concerned.
* almost all operations on the resulting descriptors shall
fail with -EBADF.  Exceptions are:
1) operations on descriptors themselves (i.e.
close(), dup(), dup2(), dup3(), fcntl(fd, F_DUPFD),
fcntl(fd, F_DUPFD_CLOEXEC, ...), fcntl(fd, F_GETFD),
fcntl(fd, F_SETFD, ...))
2) fcntl(fd, F_GETFL), for a common non-destructive way to
check if descriptor is open
3) "dfd" arguments of ...at(2) syscalls, i.e. the starting
points of pathname resolution
* closing such descriptor does *NOT* affect dnotify or
posix locks.
* permissions are checked as usual along the way to file;
no permission checks are applied to the file itself.  Of course,
giving such thing to syscall will result in permission checks (at
the moment it means checking that starting point of ....at() is
a directory and caller has exec permissions on it).

fget() and fget_light() return NULL on such descriptors; use of
fget_raw() and fget_raw_light() is needed to get them.  That protects
existing code from dealing with those things.

There are two things still missing (they come in the next commits):
one is handling of symlinks (right now we refuse to open them that
way; see the next commit for semantics related to those) and another
is descriptor passing via SCM_RIGHTS datagrams.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/fcntl.c
fs/file_table.c
fs/namei.c
fs/open.c
include/asm-generic/fcntl.h
include/linux/file.h
include/linux/fs.h

index cb1026181bdcc29683edfaf3236f49260c0399a7..6c82e5bac03932bf11e154b9c7c9e86b774e93cd 100644 (file)
@@ -131,7 +131,7 @@ SYSCALL_DEFINE2(dup2, unsigned int, oldfd, unsigned int, newfd)
 SYSCALL_DEFINE1(dup, unsigned int, fildes)
 {
        int ret = -EBADF;
-       struct file *file = fget(fildes);
+       struct file *file = fget_raw(fildes);
 
        if (file) {
                ret = get_unused_fd();
@@ -426,15 +426,35 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
        return err;
 }
 
+static int check_fcntl_cmd(unsigned cmd)
+{
+       switch (cmd) {
+       case F_DUPFD:
+       case F_DUPFD_CLOEXEC:
+       case F_GETFD:
+       case F_SETFD:
+       case F_GETFL:
+               return 1;
+       }
+       return 0;
+}
+
 SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
 {      
        struct file *filp;
        long err = -EBADF;
 
-       filp = fget(fd);
+       filp = fget_raw(fd);
        if (!filp)
                goto out;
 
+       if (unlikely(filp->f_mode & FMODE_PATH)) {
+               if (!check_fcntl_cmd(cmd)) {
+                       fput(filp);
+                       goto out;
+               }
+       }
+
        err = security_file_fcntl(filp, cmd, arg);
        if (err) {
                fput(filp);
@@ -456,10 +476,17 @@ SYSCALL_DEFINE3(fcntl64, unsigned int, fd, unsigned int, cmd,
        long err;
 
        err = -EBADF;
-       filp = fget(fd);
+       filp = fget_raw(fd);
        if (!filp)
                goto out;
 
+       if (unlikely(filp->f_mode & FMODE_PATH)) {
+               if (!check_fcntl_cmd(cmd)) {
+                       fput(filp);
+                       goto out;
+               }
+       }
+
        err = security_file_fcntl(filp, cmd, arg);
        if (err) {
                fput(filp);
@@ -808,14 +835,14 @@ static int __init fcntl_init(void)
         * Exceptions: O_NONBLOCK is a two bit define on parisc; O_NDELAY
         * is defined as O_NONBLOCK on some platforms and not on others.
         */
-       BUILD_BUG_ON(18 - 1 /* for O_RDONLY being 0 */ != HWEIGHT32(
+       BUILD_BUG_ON(19 - 1 /* for O_RDONLY being 0 */ != HWEIGHT32(
                O_RDONLY        | O_WRONLY      | O_RDWR        |
                O_CREAT         | O_EXCL        | O_NOCTTY      |
                O_TRUNC         | O_APPEND      | /* O_NONBLOCK | */
                __O_SYNC        | O_DSYNC       | FASYNC        |
                O_DIRECT        | O_LARGEFILE   | O_DIRECTORY   |
                O_NOFOLLOW      | O_NOATIME     | O_CLOEXEC     |
-               __FMODE_EXEC
+               __FMODE_EXEC    | O_PATH
                ));
 
        fasync_cache = kmem_cache_create("fasync_cache",
index eb36b6b17e26b0add06f7182bbaf2eeb4648bac8..3c16e1ca163ea57726d21829ec04cf8886dc6387 100644 (file)
@@ -276,11 +276,10 @@ struct file *fget(unsigned int fd)
        rcu_read_lock();
        file = fcheck_files(files, fd);
        if (file) {
-               if (!atomic_long_inc_not_zero(&file->f_count)) {
-                       /* File object ref couldn't be taken */
-                       rcu_read_unlock();
-                       return NULL;
-               }
+               /* File object ref couldn't be taken */
+               if (file->f_mode & FMODE_PATH ||
+                   !atomic_long_inc_not_zero(&file->f_count))
+                       file = NULL;
        }
        rcu_read_unlock();
 
@@ -289,6 +288,23 @@ struct file *fget(unsigned int fd)
 
 EXPORT_SYMBOL(fget);
 
+struct file *fget_raw(unsigned int fd)
+{
+       struct file *file;
+       struct files_struct *files = current->files;
+
+       rcu_read_lock();
+       file = fcheck_files(files, fd);
+       if (file) {
+               /* File object ref couldn't be taken */
+               if (!atomic_long_inc_not_zero(&file->f_count))
+                       file = NULL;
+       }
+       rcu_read_unlock();
+
+       return file;
+}
+
 /*
  * Lightweight file lookup - no refcnt increment if fd table isn't shared.
  *
@@ -310,6 +326,33 @@ struct file *fget_light(unsigned int fd, int *fput_needed)
        struct file *file;
        struct files_struct *files = current->files;
 
+       *fput_needed = 0;
+       if (atomic_read(&files->count) == 1) {
+               file = fcheck_files(files, fd);
+               if (file && (file->f_mode & FMODE_PATH))
+                       file = NULL;
+       } else {
+               rcu_read_lock();
+               file = fcheck_files(files, fd);
+               if (file) {
+                       if (!(file->f_mode & FMODE_PATH) &&
+                           atomic_long_inc_not_zero(&file->f_count))
+                               *fput_needed = 1;
+                       else
+                               /* Didn't get the reference, someone's freed */
+                               file = NULL;
+               }
+               rcu_read_unlock();
+       }
+
+       return file;
+}
+
+struct file *fget_raw_light(unsigned int fd, int *fput_needed)
+{
+       struct file *file;
+       struct files_struct *files = current->files;
+
        *fput_needed = 0;
        if (atomic_read(&files->count) == 1) {
                file = fcheck_files(files, fd);
index 33be51a2ddb7d5cd3216e87b6df16e55be9b4e98..e1d9f90d97769b33ed200839ece867b3280e7429 100644 (file)
@@ -1544,7 +1544,7 @@ static int path_init(int dfd, const char *name, unsigned int flags,
        } else {
                struct dentry *dentry;
 
-               file = fget_light(dfd, &fput_needed);
+               file = fget_raw_light(dfd, &fput_needed);
                retval = -EBADF;
                if (!file)
                        goto out_fail;
index 48afc5c139d247e634d9926507d2a8d9354015db..14a51de01f5452306ee266de1f74fbab57ab5035 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
@@ -669,11 +669,16 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
                                        int (*open)(struct inode *, struct file *),
                                        const struct cred *cred)
 {
+       static const struct file_operations empty_fops = {};
        struct inode *inode;
        int error;
 
        f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK |
                                FMODE_PREAD | FMODE_PWRITE;
+
+       if (unlikely(f->f_flags & O_PATH))
+               f->f_mode = FMODE_PATH;
+
        inode = dentry->d_inode;
        if (f->f_mode & FMODE_WRITE) {
                error = __get_file_write_access(inode, mnt);
@@ -687,9 +692,15 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
        f->f_path.dentry = dentry;
        f->f_path.mnt = mnt;
        f->f_pos = 0;
-       f->f_op = fops_get(inode->i_fop);
        file_sb_list_add(f, inode->i_sb);
 
+       if (unlikely(f->f_mode & FMODE_PATH)) {
+               f->f_op = &empty_fops;
+               return f;
+       }
+
+       f->f_op = fops_get(inode->i_fop);
+
        error = security_dentry_open(f, cred);
        if (error)
                goto cleanup_all;
@@ -911,9 +922,18 @@ static inline int build_open_flags(int flags, int mode, struct open_flags *op)
        if (flags & __O_SYNC)
                flags |= O_DSYNC;
 
-       op->open_flag = flags;
+       /*
+        * If we have O_PATH in the open flag. Then we
+        * cannot have anything other than the below set of flags
+        */
+       if (flags & O_PATH) {
+               flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH;
+               acc_mode = 0;
+       } else {
+               acc_mode = MAY_OPEN | ACC_MODE(flags);
+       }
 
-       acc_mode = MAY_OPEN | ACC_MODE(flags);
+       op->open_flag = flags;
 
        /* O_TRUNC implies we need access checks for write permissions */
        if (flags & O_TRUNC)
@@ -926,7 +946,8 @@ static inline int build_open_flags(int flags, int mode, struct open_flags *op)
 
        op->acc_mode = acc_mode;
 
-       op->intent = LOOKUP_OPEN;
+       op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
+
        if (flags & O_CREAT) {
                op->intent |= LOOKUP_CREATE;
                if (flags & O_EXCL)
@@ -1053,8 +1074,10 @@ int filp_close(struct file *filp, fl_owner_t id)
        if (filp->f_op && filp->f_op->flush)
                retval = filp->f_op->flush(filp, id);
 
-       dnotify_flush(filp, id);
-       locks_remove_posix(filp, id);
+       if (likely(!(filp->f_mode & FMODE_PATH))) {
+               dnotify_flush(filp, id);
+               locks_remove_posix(filp, id);
+       }
        fput(filp);
        return retval;
 }
index 0fc16e3f0bfcc01e0f4e9016f82b0b5387d5317a..84793c7025e2604f08e354d09f3db5aff5467248 100644 (file)
 #define O_SYNC         (__O_SYNC|O_DSYNC)
 #endif
 
+#ifndef O_PATH
+#define O_PATH         010000000
+#endif
+
 #ifndef O_NDELAY
 #define O_NDELAY       O_NONBLOCK
 #endif
index e85baebf62798b6d17e5e9376656230695a434e6..21a79958541cba4c7f664063edb92ee5d6845d2e 100644 (file)
@@ -29,6 +29,8 @@ static inline void fput_light(struct file *file, int fput_needed)
 
 extern struct file *fget(unsigned int fd);
 extern struct file *fget_light(unsigned int fd, int *fput_needed);
+extern struct file *fget_raw(unsigned int fd);
+extern struct file *fget_raw_light(unsigned int fd, int *fput_needed);
 extern void set_close_on_exec(unsigned int fd, int flag);
 extern void put_filp(struct file *);
 extern int alloc_fd(unsigned start, unsigned flags);
index f2143e0942c21969776cd86836d21fae0f1cc53f..13df14e2c42e6e2a76a96d509c91f691f67e3858 100644 (file)
@@ -102,6 +102,9 @@ struct inodes_stat_t {
 /* File is huge (eg. /dev/kmem): treat loff_t as unsigned */
 #define FMODE_UNSIGNED_OFFSET  ((__force fmode_t)0x2000)
 
+/* File is opened with O_PATH; almost nothing can be done with it */
+#define FMODE_PATH             ((__force fmode_t)0x4000)
+
 /* File was opened by fanotify and shouldn't generate fanotify events */
 #define FMODE_NONOTIFY         ((__force fmode_t)0x1000000)