Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / fs / xattr.c
index 93dee70..1940c63 100644 (file)
 #include <linux/security.h>
 #include <linux/syscalls.h>
 #include <linux/module.h>
+#include <linux/fsnotify.h>
+#include <linux/audit.h>
+#include <linux/mount.h>
 #include <asm/uaccess.h>
 
+
+/*
+ * Check permissions for extended attribute access.  This is a bit complicated
+ * because different namespaces have very different rules.
+ */
+static int
+xattr_permission(struct inode *inode, const char *name, int mask)
+{
+       /*
+        * We can never set or remove an extended attribute on a read-only
+        * filesystem  or on an immutable / append-only inode.
+        */
+       if (mask & MAY_WRITE) {
+               if (IS_RDONLY(inode))
+                       return -EROFS;
+               if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
+                       return -EPERM;
+       }
+
+       /*
+        * No restriction for security.* and system.* from the VFS.  Decision
+        * on these is left to the underlying filesystem / security module.
+        */
+       if (!strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN) ||
+           !strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN))
+               return 0;
+
+       /*
+        * The trusted.* namespace can only accessed by a privilegued user.
+        */
+       if (!strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN))
+               return (capable(CAP_SYS_ADMIN) ? 0 : -EPERM);
+
+       if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) {
+               if (!S_ISREG(inode->i_mode) &&
+                   (!S_ISDIR(inode->i_mode) || inode->i_mode & S_ISVTX))
+                       return -EPERM;
+       }
+
+       return permission(inode, mask, NULL);
+}
+
+int
+vfs_setxattr(struct dentry *dentry, char *name, void *value,
+               size_t size, int flags)
+{
+       struct inode *inode = dentry->d_inode;
+       int error;
+
+       error = xattr_permission(inode, name, MAY_WRITE);
+       if (error)
+               return error;
+
+       mutex_lock(&inode->i_mutex);
+       error = security_inode_setxattr(dentry, name, value, size, flags);
+       if (error)
+               goto out;
+       error = -EOPNOTSUPP;
+       if (inode->i_op->setxattr) {
+               error = inode->i_op->setxattr(dentry, name, value, size, flags);
+               if (!error) {
+                       fsnotify_xattr(dentry);
+                       security_inode_post_setxattr(dentry, name, value,
+                                                    size, flags);
+               }
+       } else if (!strncmp(name, XATTR_SECURITY_PREFIX,
+                               XATTR_SECURITY_PREFIX_LEN)) {
+               const char *suffix = name + XATTR_SECURITY_PREFIX_LEN;
+               error = security_inode_setsecurity(inode, suffix, value,
+                                                  size, flags);
+               if (!error)
+                       fsnotify_xattr(dentry);
+       }
+out:
+       mutex_unlock(&inode->i_mutex);
+       return error;
+}
+EXPORT_SYMBOL_GPL(vfs_setxattr);
+
+ssize_t
+vfs_getxattr(struct dentry *dentry, char *name, void *value, size_t size)
+{
+       struct inode *inode = dentry->d_inode;
+       int error;
+
+       error = xattr_permission(inode, name, MAY_READ);
+       if (error)
+               return error;
+
+       error = security_inode_getxattr(dentry, name);
+       if (error)
+               return error;
+
+       if (inode->i_op->getxattr)
+               error = inode->i_op->getxattr(dentry, name, value, size);
+       else
+               error = -EOPNOTSUPP;
+
+       if (!strncmp(name, XATTR_SECURITY_PREFIX,
+                               XATTR_SECURITY_PREFIX_LEN)) {
+               const char *suffix = name + XATTR_SECURITY_PREFIX_LEN;
+               int ret = security_inode_getsecurity(inode, suffix, value,
+                                                    size, error);
+               /*
+                * Only overwrite the return value if a security module
+                * is actually active.
+                */
+               if (ret != -EOPNOTSUPP)
+                       error = ret;
+       }
+
+       return error;
+}
+EXPORT_SYMBOL_GPL(vfs_getxattr);
+
+int
+vfs_removexattr(struct dentry *dentry, char *name)
+{
+       struct inode *inode = dentry->d_inode;
+       int error;
+
+       if (!inode->i_op->removexattr)
+               return -EOPNOTSUPP;
+
+       error = xattr_permission(inode, name, MAY_WRITE);
+       if (error)
+               return error;
+
+       error = security_inode_removexattr(dentry, name);
+       if (error)
+               return error;
+
+       mutex_lock(&inode->i_mutex);
+       error = inode->i_op->removexattr(dentry, name);
+       mutex_unlock(&inode->i_mutex);
+
+       if (!error)
+               fsnotify_xattr(dentry);
+       return error;
+}
+EXPORT_SYMBOL_GPL(vfs_removexattr);
+
+
 /*
  * Extended attribute SET operations
  */
 static long
 setxattr(struct dentry *d, char __user *name, void __user *value,
-        size_t size, int flags)
+        size_t size, int flags, struct vfsmount *mnt)
 {
        int error;
        void *kvalue = NULL;
@@ -50,20 +196,11 @@ setxattr(struct dentry *d, char __user *name, void __user *value,
                }
        }
 
-       error = -EOPNOTSUPP;
-       if (d->d_inode->i_op && d->d_inode->i_op->setxattr) {
-               down(&d->d_inode->i_sem);
-               error = security_inode_setxattr(d, kname, kvalue, size, flags);
-               if (error)
-                       goto out;
-               error = d->d_inode->i_op->setxattr(d, kname, kvalue, size, flags);
-               if (!error)
-                       security_inode_post_setxattr(d, kname, kvalue, size, flags);
-out:
-               up(&d->d_inode->i_sem);
-       }
-       if (kvalue)
-               kfree(kvalue);
+       if (MNT_IS_RDONLY(mnt))
+               return -EROFS;
+
+       error = vfs_setxattr(d, kname, kvalue, size, flags);
+       kfree(kvalue);
        return error;
 }
 
@@ -77,7 +214,7 @@ sys_setxattr(char __user *path, char __user *name, void __user *value,
        error = user_path_walk(path, &nd);
        if (error)
                return error;
-       error = setxattr(nd.dentry, name, value, size, flags);
+       error = setxattr(nd.dentry, name, value, size, flags, nd.mnt);
        path_release(&nd);
        return error;
 }
@@ -92,7 +229,7 @@ sys_lsetxattr(char __user *path, char __user *name, void __user *value,
        error = user_path_walk_link(path, &nd);
        if (error)
                return error;
-       error = setxattr(nd.dentry, name, value, size, flags);
+       error = setxattr(nd.dentry, name, value, size, flags, nd.mnt);
        path_release(&nd);
        return error;
 }
@@ -102,12 +239,15 @@ sys_fsetxattr(int fd, char __user *name, void __user *value,
              size_t size, int flags)
 {
        struct file *f;
+       struct dentry *dentry;
        int error = -EBADF;
 
        f = fget(fd);
        if (!f)
                return error;
-       error = setxattr(f->f_dentry, name, value, size, flags);
+       dentry = f->f_dentry;
+       audit_inode(NULL, dentry->d_inode, 0);
+       error = setxattr(dentry, name, value, size, flags, f->f_vfsmnt);
        fput(f);
        return error;
 }
@@ -131,29 +271,21 @@ getxattr(struct dentry *d, char __user *name, void __user *value, size_t size)
        if (size) {
                if (size > XATTR_SIZE_MAX)
                        size = XATTR_SIZE_MAX;
-               kvalue = kmalloc(size, GFP_KERNEL);
+               kvalue = kzalloc(size, GFP_KERNEL);
                if (!kvalue)
                        return -ENOMEM;
        }
 
-       error = -EOPNOTSUPP;
-       if (d->d_inode->i_op && d->d_inode->i_op->getxattr) {
-               error = security_inode_getxattr(d, kname);
-               if (error)
-                       goto out;
-               error = d->d_inode->i_op->getxattr(d, kname, kvalue, size);
-               if (error > 0) {
-                       if (size && copy_to_user(value, kvalue, error))
-                               error = -EFAULT;
-               } else if (error == -ERANGE && size >= XATTR_SIZE_MAX) {
-                       /* The file system tried to returned a value bigger
-                          than XATTR_SIZE_MAX bytes. Not possible. */
-                       error = -E2BIG;
-               }
+       error = vfs_getxattr(d, kname, kvalue, size);
+       if (error > 0) {
+               if (size && copy_to_user(value, kvalue, error))
+                       error = -EFAULT;
+       } else if (error == -ERANGE && size >= XATTR_SIZE_MAX) {
+               /* The file system tried to returned a value bigger
+                  than XATTR_SIZE_MAX bytes. Not possible. */
+               error = -E2BIG;
        }
-out:
-       if (kvalue)
-               kfree(kvalue);
+       kfree(kvalue);
        return error;
 }
 
@@ -218,24 +350,27 @@ listxattr(struct dentry *d, char __user *list, size_t size)
                        return -ENOMEM;
        }
 
+       error = security_inode_listxattr(d);
+       if (error)
+               goto out;
        error = -EOPNOTSUPP;
        if (d->d_inode->i_op && d->d_inode->i_op->listxattr) {
-               error = security_inode_listxattr(d);
-               if (error)
-                       goto out;
                error = d->d_inode->i_op->listxattr(d, klist, size);
-               if (error > 0) {
-                       if (size && copy_to_user(list, klist, error))
-                               error = -EFAULT;
-               } else if (error == -ERANGE && size >= XATTR_LIST_MAX) {
-                       /* The file system tried to returned a list bigger
-                          than XATTR_LIST_MAX bytes. Not possible. */
-                       error = -E2BIG;
-               }
+       } else {
+               error = security_inode_listsecurity(d->d_inode, klist, size);
+               if (size && error > size)
+                       error = -ERANGE;
+       }
+       if (error > 0) {
+               if (size && copy_to_user(list, klist, error))
+                       error = -EFAULT;
+       } else if (error == -ERANGE && size >= XATTR_LIST_MAX) {
+               /* The file system tried to returned a list bigger
+                  than XATTR_LIST_MAX bytes. Not possible. */
+               error = -E2BIG;
        }
 out:
-       if (klist)
-               kfree(klist);
+       kfree(klist);
        return error;
 }
 
@@ -285,7 +420,7 @@ sys_flistxattr(int fd, char __user *list, size_t size)
  * Extended attribute REMOVE operations
  */
 static long
-removexattr(struct dentry *d, char __user *name)
+removexattr(struct dentry *d, char __user *name, struct vfsmount *mnt)
 {
        int error;
        char kname[XATTR_NAME_MAX + 1];
@@ -296,17 +431,10 @@ removexattr(struct dentry *d, char __user *name)
        if (error < 0)
                return error;
 
-       error = -EOPNOTSUPP;
-       if (d->d_inode->i_op && d->d_inode->i_op->removexattr) {
-               error = security_inode_removexattr(d, kname);
-               if (error)
-                       goto out;
-               down(&d->d_inode->i_sem);
-               error = d->d_inode->i_op->removexattr(d, kname);
-               up(&d->d_inode->i_sem);
-       }
-out:
-       return error;
+       if (MNT_IS_RDONLY(mnt))
+               return -EROFS;
+
+       return vfs_removexattr(d, kname);
 }
 
 asmlinkage long
@@ -318,7 +446,7 @@ sys_removexattr(char __user *path, char __user *name)
        error = user_path_walk(path, &nd);
        if (error)
                return error;
-       error = removexattr(nd.dentry, name);
+       error = removexattr(nd.dentry, name, nd.mnt);
        path_release(&nd);
        return error;
 }
@@ -332,7 +460,7 @@ sys_lremovexattr(char __user *path, char __user *name)
        error = user_path_walk_link(path, &nd);
        if (error)
                return error;
-       error = removexattr(nd.dentry, name);
+       error = removexattr(nd.dentry, name, nd.mnt);
        path_release(&nd);
        return error;
 }
@@ -341,12 +469,15 @@ asmlinkage long
 sys_fremovexattr(int fd, char __user *name)
 {
        struct file *f;
+       struct dentry *dentry;
        int error = -EBADF;
 
        f = fget(fd);
        if (!f)
                return error;
-       error = removexattr(f->f_dentry, name);
+       dentry = f->f_dentry;
+       audit_inode(NULL, dentry->d_inode, 0);
+       error = removexattr(dentry, name, f->f_vfsmnt);
        fput(f);
        return error;
 }