security: prevent permission checking of file removal via sysfs_remove_group()
authorJames Morris <jmorris@namei.org>
Thu, 26 Apr 2007 07:12:05 +0000 (00:12 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 27 Apr 2007 17:57:33 +0000 (10:57 -0700)
Prevent permission checking from being performed when the kernel wants to
unconditionally remove a sysfs group, by introducing an kernel-only variant
of lookup_one_len(), lookup_one_len_kern().

Additionally, as sysfs_remove_group() does not check the return value of
the lookup before using it, a BUG_ON has been added to pinpoint the cause
of any problems potentially caused by this (and as a form of annotation).

Signed-off-by: James Morris <jmorris@namei.org>
Cc: Nagendra Singh Tomar <nagendra_tomar@adaptec.com>
Cc: Tejun Heo <htejun@gmail.com>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Cc: Eric Paris <eparis@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
fs/namei.c
fs/sysfs/group.c
include/linux/namei.h

index ee60cc4d3453209723d6f70982e7083d7cb39477..880052cadbcd4fdeb1dc05f5018b0b5e587f6824 100644 (file)
@@ -1243,22 +1243,13 @@ int __user_path_lookup_open(const char __user *name, unsigned int lookup_flags,
        return err;
 }
 
-/*
- * Restricted form of lookup. Doesn't follow links, single-component only,
- * needs parent already locked. Doesn't follow mounts.
- * SMP-safe.
- */
-static struct dentry * __lookup_hash(struct qstr *name, struct dentry * base, struct nameidata *nd)
+static inline struct dentry *__lookup_hash_kern(struct qstr *name, struct dentry *base, struct nameidata *nd)
 {
-       struct dentry * dentry;
+       struct dentry *dentry;
        struct inode *inode;
        int err;
 
        inode = base->d_inode;
-       err = permission(inode, MAY_EXEC, nd);
-       dentry = ERR_PTR(err);
-       if (err)
-               goto out;
 
        /*
         * See if the low-level filesystem might want
@@ -1287,35 +1278,76 @@ out:
        return dentry;
 }
 
+/*
+ * Restricted form of lookup. Doesn't follow links, single-component only,
+ * needs parent already locked. Doesn't follow mounts.
+ * SMP-safe.
+ */
+static inline struct dentry * __lookup_hash(struct qstr *name, struct dentry *base, struct nameidata *nd)
+{
+       struct dentry *dentry;
+       struct inode *inode;
+       int err;
+
+       inode = base->d_inode;
+
+       err = permission(inode, MAY_EXEC, nd);
+       dentry = ERR_PTR(err);
+       if (err)
+               goto out;
+
+       dentry = __lookup_hash_kern(name, base, nd);
+out:
+       return dentry;
+}
+
 static struct dentry *lookup_hash(struct nameidata *nd)
 {
        return __lookup_hash(&nd->last, nd->dentry, nd);
 }
 
 /* SMP-safe */
-struct dentry * lookup_one_len(const char * name, struct dentry * base, int len)
+static inline int __lookup_one_len(const char *name, struct qstr *this, struct dentry *base, int len)
 {
        unsigned long hash;
-       struct qstr this;
        unsigned int c;
 
-       this.name = name;
-       this.len = len;
+       this->name = name;
+       this->len = len;
        if (!len)
-               goto access;
+               return -EACCES;
 
        hash = init_name_hash();
        while (len--) {
                c = *(const unsigned char *)name++;
                if (c == '/' || c == '\0')
-                       goto access;
+                       return -EACCES;
                hash = partial_name_hash(c, hash);
        }
-       this.hash = end_name_hash(hash);
+       this->hash = end_name_hash(hash);
+       return 0;
+}
 
+struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
+{
+       int err;
+       struct qstr this;
+
+       err = __lookup_one_len(name, &this, base, len);
+       if (err)
+               return ERR_PTR(err);
        return __lookup_hash(&this, base, NULL);
-access:
-       return ERR_PTR(-EACCES);
+}
+
+struct dentry *lookup_one_len_kern(const char *name, struct dentry *base, int len)
+{
+       int err;
+       struct qstr this;
+
+       err = __lookup_one_len(name, &this, base, len);
+       if (err)
+               return ERR_PTR(err);
+       return __lookup_hash_kern(&this, base, NULL);
 }
 
 /*
index b20951c937615fad16fd1ed5c604baed177ae036..52eed2a7a5efbf91061362b11ca54794d22e0c25 100644 (file)
@@ -70,9 +70,11 @@ void sysfs_remove_group(struct kobject * kobj,
 {
        struct dentry * dir;
 
-       if (grp->name)
-               dir = lookup_one_len(grp->name, kobj->dentry,
+       if (grp->name) {
+               dir = lookup_one_len_kern(grp->name, kobj->dentry,
                                strlen(grp->name));
+               BUG_ON(IS_ERR(dir));
+       }
        else
                dir = dget(kobj->dentry);
 
index d39a5a67e97988ff423b0b458161f1409e9edece..b7dd24917f0d5cccea5661dfefa8796bea944164 100644 (file)
@@ -82,6 +82,7 @@ extern struct file *nameidata_to_filp(struct nameidata *nd, int flags);
 extern void release_open_intent(struct nameidata *);
 
 extern struct dentry * lookup_one_len(const char *, struct dentry *, int);
+extern struct dentry *lookup_one_len_kern(const char *, struct dentry *, int);
 
 extern int follow_down(struct vfsmount **, struct dentry **);
 extern int follow_up(struct vfsmount **, struct dentry **);