string: factorize skip_spaces and export it to be generally available
[safe/jmp/linux-2.6] / fs / namei.c
index 88baaf2..87f97ba 100644 (file)
@@ -169,19 +169,10 @@ void putname(const char *name)
 EXPORT_SYMBOL(putname);
 #endif
 
-
-/**
- * generic_permission  -  check for access rights on a Posix-like filesystem
- * @inode:     inode to check access rights for
- * @mask:      right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC)
- * @check_acl: optional callback to check for Posix ACLs
- *
- * Used to check for read/write/execute permissions on a file.
- * We use "fsuid" for this, letting us set arbitrary permissions
- * for filesystem access without changing the "normal" uids which
- * are used for other things..
+/*
+ * This does basic POSIX ACL permission checking
  */
-int generic_permission(struct inode *inode, int mask,
+static int acl_permission_check(struct inode *inode, int mask,
                int (*check_acl)(struct inode *inode, int mask))
 {
        umode_t                 mode = inode->i_mode;
@@ -193,9 +184,7 @@ int generic_permission(struct inode *inode, int mask,
        else {
                if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) {
                        int error = check_acl(inode, mask);
-                       if (error == -EACCES)
-                               goto check_capabilities;
-                       else if (error != -EAGAIN)
+                       if (error != -EAGAIN)
                                return error;
                }
 
@@ -208,8 +197,32 @@ int generic_permission(struct inode *inode, int mask,
         */
        if ((mask & ~mode) == 0)
                return 0;
+       return -EACCES;
+}
+
+/**
+ * generic_permission  -  check for access rights on a Posix-like filesystem
+ * @inode:     inode to check access rights for
+ * @mask:      right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC)
+ * @check_acl: optional callback to check for Posix ACLs
+ *
+ * Used to check for read/write/execute permissions on a file.
+ * We use "fsuid" for this, letting us set arbitrary permissions
+ * for filesystem access without changing the "normal" uids which
+ * are used for other things..
+ */
+int generic_permission(struct inode *inode, int mask,
+               int (*check_acl)(struct inode *inode, int mask))
+{
+       int ret;
+
+       /*
+        * Do the basic POSIX ACL permission checks.
+        */
+       ret = acl_permission_check(inode, mask, check_acl);
+       if (ret != -EACCES)
+               return ret;
 
- check_capabilities:
        /*
         * Read/write DACs are always overridable.
         * Executable DACs are overridable if at least one exec bit is set.
@@ -262,7 +275,7 @@ int inode_permission(struct inode *inode, int mask)
        if (inode->i_op->permission)
                retval = inode->i_op->permission(inode, mask);
        else
-               retval = generic_permission(inode, mask, NULL);
+               retval = generic_permission(inode, mask, inode->i_op->check_acl);
 
        if (retval)
                return retval;
@@ -432,29 +445,22 @@ static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name,
  */
 static int exec_permission_lite(struct inode *inode)
 {
-       umode_t mode = inode->i_mode;
-
-       if (inode->i_op->permission)
-               return -EAGAIN;
+       int ret;
 
-       if (current_fsuid() == inode->i_uid)
-               mode >>= 6;
-       else if (in_group_p(inode->i_gid))
-               mode >>= 3;
-
-       if (mode & MAY_EXEC)
-               goto ok;
-
-       if ((inode->i_mode & S_IXUGO) && capable(CAP_DAC_OVERRIDE))
-               goto ok;
-
-       if (S_ISDIR(inode->i_mode) && capable(CAP_DAC_OVERRIDE))
+       if (inode->i_op->permission) {
+               ret = inode->i_op->permission(inode, MAY_EXEC);
+               if (!ret)
+                       goto ok;
+               return ret;
+       }
+       ret = acl_permission_check(inode, MAY_EXEC, inode->i_op->check_acl);
+       if (!ret)
                goto ok;
 
-       if (S_ISDIR(inode->i_mode) && capable(CAP_DAC_READ_SEARCH))
+       if (capable(CAP_DAC_OVERRIDE) || capable(CAP_DAC_READ_SEARCH))
                goto ok;
 
-       return -EACCES;
+       return ret;
 ok:
        return security_inode_permission(inode, MAY_EXEC);
 }
@@ -675,23 +681,23 @@ loop:
        return err;
 }
 
-int follow_up(struct vfsmount **mnt, struct dentry **dentry)
+int follow_up(struct path *path)
 {
        struct vfsmount *parent;
        struct dentry *mountpoint;
        spin_lock(&vfsmount_lock);
-       parent=(*mnt)->mnt_parent;
-       if (parent == *mnt) {
+       parent = path->mnt->mnt_parent;
+       if (parent == path->mnt) {
                spin_unlock(&vfsmount_lock);
                return 0;
        }
        mntget(parent);
-       mountpoint=dget((*mnt)->mnt_mountpoint);
+       mountpoint = dget(path->mnt->mnt_mountpoint);
        spin_unlock(&vfsmount_lock);
-       dput(*dentry);
-       *dentry = mountpoint;
-       mntput(*mnt);
-       *mnt = parent;
+       dput(path->dentry);
+       path->dentry = mountpoint;
+       mntput(path->mnt);
+       path->mnt = parent;
        return 1;
 }
 
@@ -702,7 +708,7 @@ static int __follow_mount(struct path *path)
 {
        int res = 0;
        while (d_mountpoint(path->dentry)) {
-               struct vfsmount *mounted = lookup_mnt(path->mnt, path->dentry);
+               struct vfsmount *mounted = lookup_mnt(path);
                if (!mounted)
                        break;
                dput(path->dentry);
@@ -715,32 +721,32 @@ static int __follow_mount(struct path *path)
        return res;
 }
 
-static void follow_mount(struct vfsmount **mnt, struct dentry **dentry)
+static void follow_mount(struct path *path)
 {
-       while (d_mountpoint(*dentry)) {
-               struct vfsmount *mounted = lookup_mnt(*mnt, *dentry);
+       while (d_mountpoint(path->dentry)) {
+               struct vfsmount *mounted = lookup_mnt(path);
                if (!mounted)
                        break;
-               dput(*dentry);
-               mntput(*mnt);
-               *mnt = mounted;
-               *dentry = dget(mounted->mnt_root);
+               dput(path->dentry);
+               mntput(path->mnt);
+               path->mnt = mounted;
+               path->dentry = dget(mounted->mnt_root);
        }
 }
 
 /* no need for dcache_lock, as serialization is taken care in
  * namespace.c
  */
-int follow_down(struct vfsmount **mnt, struct dentry **dentry)
+int follow_down(struct path *path)
 {
        struct vfsmount *mounted;
 
-       mounted = lookup_mnt(*mnt, *dentry);
+       mounted = lookup_mnt(path);
        if (mounted) {
-               dput(*dentry);
-               mntput(*mnt);
-               *mnt = mounted;
-               *dentry = dget(mounted->mnt_root);
+               dput(path->dentry);
+               mntput(path->mnt);
+               path->mnt = mounted;
+               path->dentry = dget(mounted->mnt_root);
                return 1;
        }
        return 0;
@@ -779,7 +785,7 @@ static __always_inline void follow_dotdot(struct nameidata *nd)
                mntput(nd->path.mnt);
                nd->path.mnt = parent;
        }
-       follow_mount(&nd->path.mnt, &nd->path.dentry);
+       follow_mount(&nd->path);
 }
 
 /*
@@ -853,12 +859,6 @@ static int __link_path_walk(const char *name, struct nameidata *nd)
 
                nd->flags |= LOOKUP_CONTINUE;
                err = exec_permission_lite(inode);
-               if (err == -EAGAIN)
-                       err = inode_permission(nd->path.dentry->d_inode,
-                                              MAY_EXEC);
-               if (!err)
-                       err = ima_path_check(&nd->path, MAY_EXEC,
-                                            IMA_COUNT_UPDATE);
                if (err)
                        break;
 
@@ -1123,21 +1123,20 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
        nd->last_type = LAST_ROOT;
        nd->flags = flags;
        nd->depth = 0;
-       nd->root.mnt = NULL;
 
        nd->path.dentry = dentry;
        nd->path.mnt = mnt;
        path_get(&nd->path);
+       nd->root = nd->path;
+       path_get(&nd->root);
 
        retval = path_walk(name, nd);
        if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
                                nd->path.dentry->d_inode))
                audit_inode(name, nd->path.dentry);
 
-       if (nd->root.mnt) {
-               path_put(&nd->root);
-               nd->root.mnt = NULL;
-       }
+       path_put(&nd->root);
+       nd->root.mnt = NULL;
 
        return retval;
 }
@@ -1280,28 +1279,6 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
        return __lookup_hash(&this, base, NULL);
 }
 
-/**
- * lookup_one_noperm - bad hack for sysfs
- * @name:      pathname component to lookup
- * @base:      base directory to lookup from
- *
- * This is a variant of lookup_one_len that doesn't perform any permission
- * checks.   It's a horrible hack to work around the braindead sysfs
- * architecture and should not be used anywhere else.
- *
- * DON'T USE THIS FUNCTION EVER, thanks.
- */
-struct dentry *lookup_one_noperm(const char *name, struct dentry *base)
-{
-       int err;
-       struct qstr this;
-
-       err = __lookup_one_len(name, &this, base, strlen(name));
-       if (err)
-               return ERR_PTR(err);
-       return __lookup_hash(&this, base, NULL);
-}
-
 int user_path_at(int dfd, const char __user *name, unsigned flags,
                 struct path *path)
 {
@@ -1534,37 +1511,42 @@ int may_open(struct path *path, int acc_mode, int flag)
        if (error)
                return error;
 
-       error = ima_path_check(path,
-                              acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC),
+       error = ima_path_check(path, acc_mode ?
+                              acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC) :
+                              ACC_MODE(flag) & (MAY_READ | MAY_WRITE),
                               IMA_COUNT_UPDATE);
+
        if (error)
                return error;
        /*
         * An append-only file must be opened in append mode for writing.
         */
        if (IS_APPEND(inode)) {
+               error = -EPERM;
                if  ((flag & FMODE_WRITE) && !(flag & O_APPEND))
-                       return -EPERM;
+                       goto err_out;
                if (flag & O_TRUNC)
-                       return -EPERM;
+                       goto err_out;
        }
 
        /* O_NOATIME can only be set by the owner or superuser */
        if (flag & O_NOATIME)
-               if (!is_owner_or_cap(inode))
-                       return -EPERM;
+               if (!is_owner_or_cap(inode)) {
+                       error = -EPERM;
+                       goto err_out;
+               }
 
        /*
         * Ensure there are no outstanding leases on the file.
         */
        error = break_lease(inode, flag);
        if (error)
-               return error;
+               goto err_out;
 
        if (flag & O_TRUNC) {
                error = get_write_access(inode);
                if (error)
-                       return error;
+                       goto err_out;
 
                /*
                 * Refuse to truncate files with mandatory locks held on them.
@@ -1582,12 +1564,17 @@ int may_open(struct path *path, int acc_mode, int flag)
                }
                put_write_access(inode);
                if (error)
-                       return error;
+                       goto err_out;
        } else
                if (flag & FMODE_WRITE)
                        vfs_dq_init(inode);
 
        return 0;
+err_out:
+       ima_counts_put(path, acc_mode ?
+                      acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC) :
+                      ACC_MODE(flag) & (MAY_READ | MAY_WRITE));
+       return error;
 }
 
 /*
@@ -1669,6 +1656,15 @@ struct file *do_filp_open(int dfd, const char *pathname,
        int will_write;
        int flag = open_to_namei_flags(open_flag);
 
+       /*
+        * O_SYNC is implemented as __O_SYNC|O_DSYNC.  As many places only
+        * check for O_DSYNC if the need any syncing at all we enforce it's
+        * always set instead of having to deal with possibly weird behaviour
+        * for malicious applications setting only __O_SYNC.
+        */
+       if (open_flag & __O_SYNC)
+               open_flag |= O_DSYNC;
+
        if (!acc_mode)
                acc_mode = MAY_OPEN | ACC_MODE(flag);
 
@@ -1699,8 +1695,11 @@ struct file *do_filp_open(int dfd, const char *pathname,
        if (error)
                return ERR_PTR(error);
        error = path_walk(pathname, &nd);
-       if (error)
+       if (error) {
+               if (nd.root.mnt)
+                       path_put(&nd.root);
                return ERR_PTR(error);
+       }
        if (unlikely(!audit_dummy_context()))
                audit_inode(pathname, nd.path.dentry);
 
@@ -1759,7 +1758,13 @@ do_last:
                        goto exit;
                }
                filp = nameidata_to_filp(&nd, open_flag);
+               if (IS_ERR(filp))
+                       ima_counts_put(&nd.path,
+                                      acc_mode & (MAY_READ | MAY_WRITE |
+                                                  MAY_EXEC));
                mnt_drop_write(nd.path.mnt);
+               if (nd.root.mnt)
+                       path_put(&nd.root);
                return filp;
        }
 
@@ -1813,6 +1818,9 @@ ok:
                goto exit;
        }
        filp = nameidata_to_filp(&nd, open_flag);
+       if (IS_ERR(filp))
+               ima_counts_put(&nd.path,
+                              acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC));
        /*
         * It is now safe to drop the mnt write
         * because the filp has had a write taken
@@ -1820,6 +1828,8 @@ ok:
         */
        if (will_write)
                mnt_drop_write(nd.path.mnt);
+       if (nd.root.mnt)
+               path_put(&nd.root);
        return filp;
 
 exit_mutex_unlock:
@@ -1860,6 +1870,8 @@ do_link:
                 * with "intent.open".
                 */
                release_open_intent(&nd);
+               if (nd.root.mnt)
+                       path_put(&nd.root);
                return ERR_PTR(error);
        }
        nd.flags &= ~LOOKUP_PARENT;