Skip to content

Commit 908af31

Browse files
James Bottomleyardbiesheuvel
authored andcommitted
efivarfs: fix error on write to new variable leaving remnants
Make variable cleanup go through the fops release mechanism and use zero inode size as the indicator to delete the file. Since all EFI variables must have an initial u32 attribute, zero size occurs either because the update deleted the variable or because an unsuccessful write after create caused the size never to be set in the first place. In the case of multiple racing opens and closes, the open is counted to ensure that the zero size check is done on the last close. Even though this fixes the bug that a create either not followed by a write or followed by a write that errored would leave a remnant file for the variable, the file will appear momentarily globally visible until the last close of the fd deletes it. This is safe because the normal filesystem operations will mediate any races; however, it is still possible for a directory listing at that instant between create and close contain a zero size variable that doesn't exist in the EFI table. Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
1 parent a58e954 commit 908af31

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

fs/efivarfs/file.c

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,41 @@ static ssize_t efivarfs_file_write(struct file *file,
3636
if (IS_ERR(data))
3737
return PTR_ERR(data);
3838

39+
inode_lock(inode);
40+
if (var->removed) {
41+
/*
42+
* file got removed; don't allow a set. Caused by an
43+
* unsuccessful create or successful delete write
44+
* racing with us.
45+
*/
46+
bytes = -EIO;
47+
goto out;
48+
}
49+
3950
bytes = efivar_entry_set_get_size(var, attributes, &datasize,
4051
data, &set);
41-
if (!set && bytes) {
52+
if (!set) {
4253
if (bytes == -ENOENT)
4354
bytes = -EIO;
4455
goto out;
4556
}
4657

4758
if (bytes == -ENOENT) {
48-
drop_nlink(inode);
49-
d_delete(file->f_path.dentry);
50-
dput(file->f_path.dentry);
59+
/*
60+
* zero size signals to release that the write deleted
61+
* the variable
62+
*/
63+
i_size_write(inode, 0);
5164
} else {
52-
inode_lock(inode);
5365
i_size_write(inode, datasize + sizeof(attributes));
5466
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
55-
inode_unlock(inode);
5667
}
5768

5869
bytes = count;
5970

6071
out:
72+
inode_unlock(inode);
73+
6174
kfree(data);
6275

6376
return bytes;
@@ -106,8 +119,36 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
106119
return size;
107120
}
108121

122+
static int efivarfs_file_release(struct inode *inode, struct file *file)
123+
{
124+
struct efivar_entry *var = inode->i_private;
125+
126+
inode_lock(inode);
127+
var->removed = (--var->open_count == 0 && i_size_read(inode) == 0);
128+
inode_unlock(inode);
129+
130+
if (var->removed)
131+
simple_recursive_removal(file->f_path.dentry, NULL);
132+
133+
return 0;
134+
}
135+
136+
static int efivarfs_file_open(struct inode *inode, struct file *file)
137+
{
138+
struct efivar_entry *entry = inode->i_private;
139+
140+
file->private_data = entry;
141+
142+
inode_lock(inode);
143+
entry->open_count++;
144+
inode_unlock(inode);
145+
146+
return 0;
147+
}
148+
109149
const struct file_operations efivarfs_file_operations = {
110-
.open = simple_open,
111-
.read = efivarfs_file_read,
112-
.write = efivarfs_file_write,
150+
.open = efivarfs_file_open,
151+
.read = efivarfs_file_read,
152+
.write = efivarfs_file_write,
153+
.release = efivarfs_file_release,
113154
};

fs/efivarfs/internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ struct efi_variable {
2727
struct efivar_entry {
2828
struct efi_variable var;
2929
struct inode vfs_inode;
30+
unsigned long open_count;
31+
bool removed;
3032
};
3133

3234
static inline struct efivar_entry *efivar_entry(struct inode *inode)

fs/efivarfs/super.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ static struct inode *efivarfs_alloc_inode(struct super_block *sb)
4747
return NULL;
4848

4949
inode_init_once(&entry->vfs_inode);
50+
entry->removed = false;
5051

5152
return &entry->vfs_inode;
5253
}

0 commit comments

Comments
 (0)