Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

efivars: deal with uncommitted efi variables #35497

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

wrvsrx
Copy link

@wrvsrx wrvsrx commented Dec 7, 2024

Fix #34304.

According to linux source code, "efivarfs represents uncommitted variables with zero-length files. Reading them should return EOF.". However, st_size of the efi fd can be larger than 0. This commit deal with such case.

@YHNdnzj
Copy link
Member

YHNdnzj commented Dec 7, 2024

Sorry, but let's not tape over the problem in userspace. This is caused by kernel not flushing the efivar entry list/dentries after resuming from hibernation, and hence should really be something for kernel people to fix.

@wrvsrx
Copy link
Author

wrvsrx commented Dec 7, 2024

But it's not only related to hibernate. Previous efi_get_variable doesn't consider the case that the efi variable is uncommitted. In such case, ENOENT should be return rather than EIO.

@YHNdnzj
Copy link
Member

YHNdnzj commented Dec 7, 2024

But it's not only related to hibernate. Previous efi_get_variable doesn't consider the case that the efi variable is uncommitted. In such case, ENOENT should be return rather than EIO.

No. Again, the existence of "uncommitted vars" already deems the inconsistency in efivarfs. Note that in kernel, efivar_entry_set_get_size would read the efivar again after it's set, so that the inode/dentry metadata gets updated accordingly. Any error happening during "commit" ought to be caught there. Hibernation, OTOH, means the efivar entries would change behind the hibernated kernel's back, requiring the kernel to re-enumerate through all efivars after resume.

@YHNdnzj YHNdnzj added kernel-bug and removed please-review PR is ready for (re-)review by a maintainer labels Dec 7, 2024
@wrvsrx
Copy link
Author

wrvsrx commented Dec 7, 2024

Thanks for your explanation. Is there any work going on in linux kernel? I would like to track it.

@wrvsrx wrvsrx closed this Dec 7, 2024
@YHNdnzj
Copy link
Member

YHNdnzj commented Dec 7, 2024

cc @poettering Could you bring this up to the kernel efivarfs people?

Copy link
Member

But it's not only related to hibernate. Previous efi_get_variable doesn't consider the case that the efi variable is uncommitted. In such case, ENOENT should be return rather than EIO.

No. Again, the existence of "uncommitted vars" already deems the inconsistency in efivarfs. Note that in kernel, efivar_entry_set_get_size would read the efivar again after it's set, so that the inode/dentry metadata gets updated accordingly. Any error happening during "commit" ought to be caught there. Hibernation, OTOH, means the efivar entries would change behind the hibernated kernel's back, requiring the kernel to re-enumerate through all efivars after resume.

FYI @ardbiesheuvel

@keszybz
Copy link
Member

keszybz commented Dec 10, 2024

I think we should handle this case gracefully. I'll reopen the pull request for additional discussion.

@keszybz keszybz reopened this Dec 10, 2024
@github-actions github-actions bot added the please-review PR is ready for (re-)review by a maintainer label Dec 10, 2024
@keszybz
Copy link
Member

keszybz commented Dec 10, 2024

C.f. torvalds/linux@3fab70c

src/basic/efivars.c Outdated Show resolved Hide resolved
@keszybz keszybz added good-to-merge/with-minor-suggestions and removed please-review PR is ready for (re-)review by a maintainer labels Dec 10, 2024
v258 milestone Dec 10, 2024
Copy link
Member

@YHNdnzj YHNdnzj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this in a meeting, and basically agreed on that we can merge this still. But note that inherently this is something for kernel to fix, otherwise all metadata reported by efivarfs on that var would be spurious.

src/basic/efivars.c Outdated Show resolved Hide resolved
src/basic/efivars.c Outdated Show resolved Hide resolved
@wrvsrx
Copy link
Author

wrvsrx commented Dec 11, 2024

I will update the PR in a few days.

@poettering
Copy link
Member

can someone explain to me what an "uncommited efi var" is?

@ardbiesheuvel
Copy link

can someone explain to me what an "uncommited efi var" is?

Excellent question, I'd like to know as well.

@YHNdnzj
Copy link
Member

YHNdnzj commented Dec 12, 2024

can someone explain to me what an "uncommited efi var" is?

As explained above, I think it's utterly just inconsistency in efivarfs, where the stored metadata diverges from what's actually in firmware, in this specific case reading from non-existent var.

@wrvsrx wrvsrx force-pushed the fix-hibernate-resume branch from 1c000ac to 53c3859 Compare December 13, 2024 04:48
@wrvsrx wrvsrx requested review from YHNdnzj and keszybz December 13, 2024 04:49
@wrvsrx wrvsrx force-pushed the fix-hibernate-resume branch from 53c3859 to cd1a8cb Compare December 13, 2024 04:52
src/basic/efivars.c Outdated Show resolved Hide resolved
src/basic/efivars.c Outdated Show resolved Hide resolved
@@ -96,6 +96,23 @@ int efi_get_variable(
(void) usleep_safe(EFI_RETRY_DELAY);
}

/* According to comment of linux source code, efivarfs represents uncommitted
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not reference the concept of "uncommited" variables in a comment here, if noone knows what that precisely is supposed to be.

Maybe somebody can ping the person who added the comment about this to the kernel and ask for clarification what that is supposed to be?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll wait for the clarification to update the comment.

*/
if (n == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
"EFI variable %s is uncommitted", p);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too, that's not a useful debug message if noone knows what "uncommitted" is supposed to mean in this context

Copy link
Author

@wrvsrx wrvsrx Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll wait for the clarification to update the debug message.

@poettering
Copy link
Member

was this issue every reported to evifvarfs maintainers (whoever that might be?). if that's public on some ML I'd love it to have linked here.

if not, can someone report this please?

@ardbiesheuvel
Copy link

was this issue every reported to evifvarfs maintainers (whoever that might be?). if that's public on some ML I'd love it to have linked here.

if not, can someone report this please?

I'm one of the co-maintainers but Linux VFS stuff is not my forte. I'm not aware of any reports, although James Bottomley started looking into efivarfs issues very recently.

https://lore.kernel.org/linux-efi/[email protected]/
https://lore.kernel.org/linux-efi/[email protected]/

Unfortunately kernel reports EOF if there's an inconsistency between efivarfs var list
and what's actually stored in firmware, c.f. systemd#34304.
Hence we translate EOF back to ENOENT here, as with kernel behavior before
torvalds/linux@3fab70c

If the kernel changes behaviour (to flush dentries on resume), we can drop
this at some point in the future. But note that the commit is 11
years old at this point so we'll need to deal with the current behaviour for
a long time.

Fix systemd#34304.
@wrvsrx wrvsrx force-pushed the fix-hibernate-resume branch from cd1a8cb to 1b8cf8b Compare December 18, 2024 07:01
@poettering
Copy link
Member

poettering commented Dec 18, 2024

Hey @jejb, you as @ardbiesheuvel pointed out you have been fixing bugs in efivarfs code recently. Do you have any idea what "uncommitted" efivars are supposed to be, as per torvalds/linux@3fab70c?

noone appears to know.

it appears efivarfs is simply broken in hibernation cases, i.e. appears the list is not updated after resume, and contains remains of efi vars that existed on the old boot, without being refreshed to show the ones from the current boot.

@poettering
Copy link
Member

Or do you remember, @xlz (I think you did the original patch that added this). Or maybe you know @mfleming, you signed off on that commit back in the day?

any hints welcome!

@xlz
Copy link

xlz commented Dec 18, 2024

Or do you remember, @xlz (I think you did the original patch that added this). Or maybe you know @mfleming, you signed off on that commit back in the day?

any hints welcome!

Hi!

This is specified in https://uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf section 8.2 Variable Services.

The parameter DataSize of SetVariable() has the semantic that "a size of zero causes the variable to be deleted" (presumbly in the NVRAM). This effectively means the UEFI firmware storage cannot represent empty variables, and thus efivarfs cannot commit zero-length files into the backing storage of firmware.

But efivarfs did allow zero-length files to be created (I haven't verified if this is still the case), e.g. with touch, and these files could be later modified to have content and then committed to the firmware storage. By this logic I called these initially empty files uncommitted.

The old behavior that produced EIO ENOENT for reading zero-length files in efivarfs broke some filesystem testsuite, which prompted me to submit the patch that returns EOF for ENOENT reported from the firmware. In other words, if the size of a efivarfs file is 0, this variable does not exist in the firmware storage. But the file does exist in the filesystem, so ENOENT at the fs level is inappropriate.

@poettering
Copy link
Member

@xlz ah, interesting! thanks so much for reporting back, much appreciated!

@wrvsrx
Copy link
Author

wrvsrx commented Dec 18, 2024

@poettering Do I need to remove the comments related to "uncommited efi variables"?

@poettering
Copy link
Member

@wrvsrx please update the comment along the lines of @xlz just wrote, i.e. that a zero size env var is not allowed in efi and hence the variable doesn#t really exist in the backing store as long as it is zero sized, and the kernel calls this "uncommited".

Oh, and I don't think we should return ENOENT for this, given that the env var does exist in efivarfs, even though it doesn't exist in the efi backing store. maybe return ENOMEDIUM or so in this case?

@jejb
Copy link

jejb commented Dec 18, 2024 via email

@poettering
Copy link
Member

poettering commented Dec 18, 2024

so handling this case here cleanly certainly (as proposed with the ENOMEDIUM thing) makes sense, but I am still wondering the relationship here to #34304.

Do I get this right: because the kernel when coming back from resume doesn't rescan efi vars we end up in this situation where efivarfs exposes inodes for which no backing variables actually exist? The kernel code now ends up handling this the same way as "empty" efi vars, which also exist as inodes but not in the backing store?

so what is the strategy now in this case? should we try to remove the hibernation efi var in this case? will it work and delete the inode without being annoyed about the fact that efi doesn't actually know about the thing?

@YHNdnzj
Copy link
Member

YHNdnzj commented Dec 18, 2024

Then again, we fstat() the file above and check the size to ensure it is indeed an existing variable, yet when actually trying to read it a bogus 0-sized file is returned. So, can someone report the original issue about re-enumerating efivar list after resume?

@jejb
Copy link

jejb commented Dec 18, 2024 via email

@YHNdnzj
Copy link
Member

YHNdnzj commented Dec 18, 2024

Oh, and I don't think we should return ENOENT for this, given that the env var does exist in efivarfs, even though it doesn't exist in the efi backing store. maybe return ENOMEDIUM or so in this case?

so handling this case here cleanly certainly (as proposed with the ENOMEDIUM thing) makes sense

Not really. As mentioend, the explicit fstat() and st_size check should have caught the "normal" uncommited vars. This is something else - inconsistency between efivarfs and actual firmware state. Effectively the var doesn't exist, and the caller needs to handle this in the exact same way as ENOENT (the kernel before torvalds/linux@3fab70c even returned ENOENT!). Any other errno would render the PR useless, as the caller gets unexpected errno and bails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

systemd-hibernate-resume breaks the HibernateLocation EFI variable
8 participants