-
Notifications
You must be signed in to change notification settings - Fork 40
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
ImmutAfterInit: misuse runtime detection #2
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I have a very similar, conflicting commit as your a7da78a in my pending ELF PR: e2acf29
The reasoning for having the initializer functions to take references rather than values had been to not rely on compiler optimizations too much when initializing large objects like the SnpCpuidTable blob. That is, my concern had been that temporaries on the stack could perhaps get created.
I see. I think this is a common pattern for Rust and should not be an issue, especially on release builds. |
TODO for me:
|
7c3cf86
to
c60a59d
Compare
I've simplified the PR:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this PR, but have three suggestions though. I don't want to delay the merge any further just because of nitpicking, so it might make sense to merge it as-is and do a follow-up patch -- up to Joerg to decide.
1.) You removed the #[repr(transparent)]
from ImmutAfterInitCell
. Have you tried whether #[cfg_attr(not(debug_assertions), repr(transparent))]
would work? This way the ImmutAfterInitCell
would remain to be guaranteed a zero-cost wrapper for non-debug builds.
2.) For not(debug_assertions)
, I would turn the new ImmutAfterInitError
into a core::convert::Infallible
. That would potentially enable the compiler to optimize the error checks at call sites away for non-debug builds. Not sure compilers are clever enough today already, but when Infallible
is made the never-type (!
) one day, they probably would.
3.) In my personal opinion, removing the unsafe
marker from the fn init()
can be debated about: from my POV it serves as an indicator to users that they need to think carefully when invoking it, i.e. that there is no possible code path where an ImmutAfterInitCell
can get dereferenced while still in uninitialized state. Your patch set here certainly helps a lot in establishing confidence into that, but it still doesn't provide a hard guarantee, which can be obtained only by reviewing the code. Having the unsafe{}
markers at the potentially problematic call sites, would at least help to draw reviewers' attention at the right spots. In either case, the move to drop unsafe
from fn init()
might perhaps have deserved a separate patch on its own. Just saying ;)
ImmutAfterInitCell states that the init() method should not be called on an already initialized instance, but KERNEL_MAPPING is initialized via ImmutAfterInitCell::new() first, and then via the aforementioned init() method. At the moment this requirement is not enforced in any way,(ImmutAfterInitCell::reinit() just calls init()), but this will not hold in the future. Create the global structure with ImmutAfterInitCell::uninit(), since there should be no users of the mapping before the call to init_kernel_mapping_info(), which happens very early in the boot process for both the stage 2 and the proper SVSM. Signed-off-by: Carlos López <carlos.lopez@suse.com>
Add debug-only runtime checks for ImmutAfterInitCell. This prevents using an uninitialized cell, as well as unintended double initialization bugs. As a consequence, ImmutAfterInitCell::init() and reinit() no longer need to be unsafe, since checks are done before accessing the inner type. To keep track of the initialization state we use an AtomicBool with relaxed ordering, for code simplicity reasons. Signed-off-by: Carlos López <carlos.lopez@suse.com>
No safety runtime checks are performed in release builds, so mark the error variant of the return type for failable functions as core::convert::Infallible. Signed-off-by: Carlos López <carlos.lopez@suse.com>
I thought about this, but I did not like the idea of having different layout guarantees for debug and release builds. Anyhow, for release builds the struct only has one field, I do not see how it can be a non-zero-cost wrapper.
Done in the updated PR.
I mean, technically the unsafe part is dereferencing the cell, not so much initializing it. I think having it being unsafe might raise awareness as you mention, but it also clutters the code in places where UB is really not possible. If we want to be extra safe we could add a dedicated method to access the cell contents and have that be unsafe. |
@nicstange do you think we could also remove the |
Remove the Copy requirement for any T under the ImmutAfterInit* types, since it is not needed. Signed-off-by: Carlos López <carlos.lopez@suse.com>
With 10b7610 we could perhaps have a slightly better initialization of the filesystem. Instead of: Lines 129 to 136 in 631ae3b
We could remove static FS_ROOT: ImmutAfterInitCell<SvsmFs> = ImmutAfterInitCell::uninit();
pub fn initialize_fs() {
let root_dir = Arc::new(RamDirectory::new());
FS_ROOT.init(&root_dir);
} |
SyncUnsafeCell
instead ofUnsafeCell
+unsafe impl Sync
.ImmutAfterInitCell
methods.ImmutAfterInitCell
by anOption
in order to be able to detect uninitialized cells.Result
from public API calls to handle double initialization and uninitialized access.