public inbox for linux-efi@vger.kernel.org
 help / color / mirror / Atom feed
From: James Bottomley <James.Bottomley@HansenPartnership.com>
To: linux-fsdevel@vger.kernel.org, linux-efi@vger.kernel.org
Cc: Ard Biesheuvel <ardb@kernel.org>, Jeremy Kerr <jk@ozlabs.org>,
	Christian Brauner <brauner@kernel.org>,
	Al Viro <viro@zeniv.linux.org.uk>
Subject: [PATCH v2 6/6] efivarfs: fix error on write to new variable leaving remnants
Date: Mon,  6 Jan 2025 18:35:25 -0800	[thread overview]
Message-ID: <20250107023525.11466-7-James.Bottomley@HansenPartnership.com> (raw)
In-Reply-To: <20250107023525.11466-1-James.Bottomley@HansenPartnership.com>

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>

---
v2: implement counter for last close
---
 fs/efivarfs/file.c     | 60 +++++++++++++++++++++++++++++++++++-------
 fs/efivarfs/internal.h |  1 +
 2 files changed, 52 insertions(+), 9 deletions(-)

diff --git a/fs/efivarfs/file.c b/fs/efivarfs/file.c
index 23c51d62f902..cf0179d84bc5 100644
--- a/fs/efivarfs/file.c
+++ b/fs/efivarfs/file.c
@@ -36,28 +36,41 @@ static ssize_t efivarfs_file_write(struct file *file,
 	if (IS_ERR(data))
 		return PTR_ERR(data);
 
+	inode_lock(inode);
+	if (d_unhashed(file->f_path.dentry)) {
+		/*
+		 * file got removed; don't allow a set.  Caused by an
+		 * unsuccessful create or successful delete write
+		 * racing with us.
+		 */
+		bytes = -EIO;
+		goto out;
+	}
+
 	bytes = efivar_entry_set_get_size(var, attributes, &datasize,
 					  data, &set);
-	if (!set && bytes) {
+	if (!set) {
 		if (bytes == -ENOENT)
 			bytes = -EIO;
 		goto out;
 	}
 
 	if (bytes == -ENOENT) {
-		drop_nlink(inode);
-		d_delete(file->f_path.dentry);
-		dput(file->f_path.dentry);
+		/*
+		 * zero size signals to release that the write deleted
+		 * the variable
+		 */
+		i_size_write(inode, 0);
 	} else {
-		inode_lock(inode);
 		i_size_write(inode, datasize + sizeof(attributes));
 		inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
-		inode_unlock(inode);
 	}
 
 	bytes = count;
 
 out:
+	inode_unlock(inode);
+
 	kfree(data);
 
 	return bytes;
@@ -106,8 +119,37 @@ static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
 	return size;
 }
 
+static int efivarfs_file_release(struct inode *inode, struct file *file)
+{
+	bool release;
+	struct efivar_entry *var = inode->i_private;
+
+	inode_lock(inode);
+	release = (--var->open_count == 0 && i_size_read(inode) == 0);
+	inode_unlock(inode);
+
+	if (release)
+		simple_recursive_removal(file->f_path.dentry, NULL);
+
+	return 0;
+}
+
+static int efivarfs_file_open(struct inode *inode, struct file *file)
+{
+	struct efivar_entry *entry = inode->i_private;
+
+	file->private_data = entry;
+
+	inode_lock(inode);
+	entry->open_count++;
+	inode_unlock(inode);
+
+	return 0;
+}
+
 const struct file_operations efivarfs_file_operations = {
-	.open	= simple_open,
-	.read	= efivarfs_file_read,
-	.write	= efivarfs_file_write,
+	.open		= efivarfs_file_open,
+	.read		= efivarfs_file_read,
+	.write		= efivarfs_file_write,
+	.release	= efivarfs_file_release,
 };
diff --git a/fs/efivarfs/internal.h b/fs/efivarfs/internal.h
index 18a600f80992..32b83f644798 100644
--- a/fs/efivarfs/internal.h
+++ b/fs/efivarfs/internal.h
@@ -26,6 +26,7 @@ struct efi_variable {
 
 struct efivar_entry {
 	struct efi_variable var;
+	unsigned long open_count;
 };
 
 int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
-- 
2.35.3


  parent reply	other threads:[~2025-01-07  2:38 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-01-07  2:35 [PATCH v2 0/6] convert efivarfs to manage object data correctly James Bottomley
2025-01-07  2:35 ` [PATCH v2 1/6] efivarfs: remove unused efi_varaible.Attributes and .kobj James Bottomley
2025-01-07  2:35 ` [PATCH v2 2/6] efivarfs: add helper to convert from UC16 name and GUID to utf8 name James Bottomley
2025-01-07  2:35 ` [PATCH v2 3/6] efivarfs: make variable_is_present use dcache lookup James Bottomley
2025-01-07  2:35 ` [PATCH v2 4/6] efivarfs: move freeing of variable entry into evict_inode James Bottomley
2025-01-16 18:36   ` Al Viro
2025-01-16 19:05     ` James Bottomley
2025-01-16 22:13       ` James Bottomley
2025-01-19 14:50         ` Ard Biesheuvel
2025-01-19 14:57           ` James Bottomley
2025-01-19 16:31             ` Ard Biesheuvel
2025-01-19 16:46               ` James Bottomley
2025-01-07  2:35 ` [PATCH v2 5/6] efivarfs: remove unused efivarfs_list James Bottomley
2025-01-16 18:42   ` Al Viro
2025-01-16 18:55     ` James Bottomley
2025-01-07  2:35 ` James Bottomley [this message]
2025-01-16 18:45   ` [PATCH v2 6/6] efivarfs: fix error on write to new variable leaving remnants Al Viro
2025-01-16 18:54     ` James Bottomley
2025-01-16 18:59       ` Al Viro
2025-01-16 19:04         ` James Bottomley
2025-01-09  9:50 ` [PATCH v2 0/6] convert efivarfs to manage object data correctly Ard Biesheuvel
2025-01-09 14:24   ` Ard Biesheuvel
2025-01-09 15:50   ` James Bottomley
2025-01-09 16:11     ` Ard Biesheuvel
2025-01-18 13:53   ` James Bottomley

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250107023525.11466-7-James.Bottomley@HansenPartnership.com \
    --to=james.bottomley@hansenpartnership.com \
    --cc=ardb@kernel.org \
    --cc=brauner@kernel.org \
    --cc=jk@ozlabs.org \
    --cc=linux-efi@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox