From: Matt Fleming <[email protected]>

It makes no sense to treat the following filenames as unique,

        VarName-abcdefab-abcd-abcd-abcd-abcdefabcdef
        VarName-ABCDEFAB-ABCD-ABCD-ABCD-ABCDEFABCDEF
        VarName-ABcDEfAB-ABcD-ABcD-ABcD-ABcDEfABcDEf
        VarName-aBcDEfAB-aBcD-aBcD-aBcD-aBcDEfaBcDEf
        ... etc ...

since the guid will be converted into a binary representation, which
has no case.

Roll our own dentry operations so that we can treat the variable name
part of filenames ("VarName" in the above example) as case-sensitive,
but the guid portion as case-insensitive. That way, efivarfs will
refuse to create the above files if any one already exists.

Reported-by: Lingzhu Xiang <[email protected]>
Cc: Matthew Garrett <[email protected]>
Cc: Jeremy Kerr <[email protected]>
Cc: Al Viro <[email protected]>
Signed-off-by: Matt Fleming <[email protected]>
---
 drivers/firmware/efivars.c | 101 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 100 insertions(+), 1 deletion(-)

diff --git a/drivers/firmware/efivars.c b/drivers/firmware/efivars.c
index a4fa409..10088fd 100644
--- a/drivers/firmware/efivars.c
+++ b/drivers/firmware/efivars.c
@@ -79,6 +79,7 @@
 #include <linux/device.h>
 #include <linux/slab.h>
 #include <linux/pstore.h>
+#include <linux/ctype.h>
 
 #include <linux/fs.h>
 #include <linux/ramfs.h>
@@ -1049,6 +1050,91 @@ static int efivarfs_unlink(struct inode *dir, struct 
dentry *dentry)
        return -EINVAL;
 };
 
+/*
+ * Compare two efivarfs file names.
+ *
+ * An efivarfs filename is composed of two parts,
+ *
+ *     1. A case-sensitive variable name
+ *     2. A case-insensitive GUID
+ *
+ * So we need to perform a case-sensitive match on part 1 and a
+ * case-insensitive match on part 2.
+ */
+static int efivarfs_d_compare(const struct dentry *parent, const struct inode 
*pinode,
+                             const struct dentry *dentry, const struct inode 
*inode,
+                             unsigned int len, const char *str,
+                             const struct qstr *name)
+{
+       const char *q;
+       int guid;
+
+       /*
+        * If the string we're being asked to compare doesn't match
+        * the expected format return "no match".
+        */
+       if (!efivarfs_valid_name(str, len))
+               return 1;
+
+       if (!(q = strchr(name->name, '-')))
+               return 1;
+
+       /* Find part 1, the variable name. */
+       guid = q - (const char *)name->name;
+
+       /* Case-sensitive compare for the variable name */
+       if (memcmp(str, name->name, guid))
+               return 1;
+
+       /* Case-insensitive compare for the GUID */
+       return strcasecmp(&name->name[guid], &str[guid]);
+}
+
+static int efivarfs_d_hash(const struct dentry *dentry,
+                          const struct inode *inode, struct qstr *qstr)
+{
+       const unsigned char *us;
+       char lower[NAME_MAX];
+       int guid;
+
+       if (!efivarfs_valid_name(qstr->name, qstr->len))
+               return -EINVAL;
+
+       if (qstr->len > NAME_MAX)
+               return -ENAMETOOLONG;
+
+       us = strchr(qstr->name, '-');
+       if (!us)
+               return -EINVAL;
+
+       /* The variable name part is case-sensitive */
+       guid = us - qstr->name;
+       memcpy(lower, qstr->name, guid);
+
+       /* GUID is case-insensitive. */
+       for (us = &qstr->name[guid]; guid < qstr->len; guid++)
+               lower[guid] = tolower(*us++);
+       lower[guid] = '\0';
+
+       qstr->hash = full_name_hash(lower, qstr->len);
+       return 0;
+}
+
+/*
+ * Retaining negative dentries for an in-memory filesystem just wastes
+ * memory and lookup time: arrange for them to be deleted immediately.
+ */
+static int efivarfs_delete_dentry(const struct dentry *dentry)
+{
+       return 1;
+}
+
+static struct dentry_operations efivarfs_d_ops = {
+       .d_compare = efivarfs_d_compare,
+       .d_hash = efivarfs_d_hash,
+       .d_delete = efivarfs_delete_dentry,
+};
+
 static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
 {
        struct inode *inode = NULL;
@@ -1064,6 +1150,7 @@ static int efivarfs_fill_super(struct super_block *sb, 
void *data, int silent)
        sb->s_blocksize_bits    = PAGE_CACHE_SHIFT;
        sb->s_magic             = EFIVARFS_MAGIC;
        sb->s_op                = &efivarfs_ops;
+       sb->s_d_op              = &efivarfs_d_ops;
        sb->s_time_gran         = 1;
 
        inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);
@@ -1154,8 +1241,20 @@ static struct file_system_type efivarfs_type = {
        .kill_sb = efivarfs_kill_sb,
 };
 
+/*
+ * Handle negative dentry.
+ */
+static struct dentry *efivarfs_lookup(struct inode *dir, struct dentry *dentry,
+                                     unsigned int flags)
+{
+       if (dentry->d_name.len > NAME_MAX)
+               return ERR_PTR(-ENAMETOOLONG);
+       d_add(dentry, NULL);
+       return NULL;
+}
+
 static const struct inode_operations efivarfs_dir_inode_operations = {
-       .lookup = simple_lookup,
+       .lookup = efivarfs_lookup,
        .unlink = efivarfs_unlink,
        .create = efivarfs_create,
 };
-- 
1.7.11.7

--
To unsubscribe from this list: send the line "unsubscribe linux-efi" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to