Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Use gptprio to select boot target #13

Closed
wants to merge 9 commits into from
Closed

Use gptprio to select boot target #13

wants to merge 9 commits into from

Conversation

csssuf
Copy link
Contributor

@csssuf csssuf commented Aug 9, 2017

This implementation of gptprio is read-only, in that it doesn't update the tries field of gptprio. It also does not pass anything to shim instructing it what to boot; if desired I can add that for this PR.

Before this PR is merged, csssuf/rust-uefi#18 will need to be merged and a commit bumping Cargo.lock will need to be added to this PR.

@csssuf csssuf requested a review from bgilbert August 9, 2017 19:52
Copy link
Contributor

@bgilbert bgilbert left a comment

Choose a reason for hiding this comment

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

I think it'd be fine to defer tries and shim handoff to a followup PR.

src/util/gpt.rs Outdated
}

block_io
.read_blocks(self.my_lba, 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Leaked.

Also, why does this function read the block again rather than validating the data in the struct it was asked to validate?

src/util/gpt.rs Outdated
(self.num_partition_entries * self.sizeof_partition_entry) as usize;

block_io
.read_bytes(self.partition_entry_lba, partition_entry_table_size)
Copy link
Contributor

Choose a reason for hiding this comment

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

Leaked.

src/util/gpt.rs Outdated
pub struct GptDisk<'a> {
block_device: &'static BlockIOProtocol,
primary_header: &'a GptHeader,
alternate_header: &'a GptHeader,
Copy link
Contributor

Choose a reason for hiding this comment

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

These should be freed on drop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are; see lines 43-47. When a struct is dropped, its Drop implementation is run, and then each of its children has their Drop implementation run (see here for details). Since GptDisk itself requires no special logic to drop, it doesn't need a Drop implementation since the Drop for GptHeader will automatically be run for its children.

src/util/gpt.rs Outdated
self.block_device
.read_blocks(self.primary_header.alternate_lba, 1)
.and_then(|block| {
self.alternate_header = &*(block.as_ptr() as *const GptHeader);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should not be happening in a function called validate. If the alternate header needs to be loaded only after the primary has been validated, this function should be renamed or merged into read_from.

src/util/gpt.rs Outdated
.read_bytes(
self.primary_header.partition_entry_lba,
partition_entry_table_size,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Leaked.

Also, this is duplicated in the validation code. Perhaps read_from should load all of the bits, validate should check them without loading anything itself, and read_from can generate the partition data structures as well. (validate can be split into two parts for this if necessary.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This actually isn't leaked; read_partitions returns a slice of GptPartitionEntry references, not GptPartitionEntrys, since GPT partition entries are variable length, and therefore cannot be stored directly in a slice that way.

src/picker.rs Outdated
.and_then(parent_device_path)
.and_then(|parent_path| {
util::GptDisk::read_from(parent_path)
.and_then(|disk| disk.read_partitions().map(util::gptprio::next))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the nesting here?

src/picker.rs Outdated
let boot_result = menu::boot_menu(&option_a, &option_b)
.and_then(|option| {
match option {
Some(user_choice) => Ok(user_choice),
Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessarily a user_choice, since it could have defaulted.

src/picker.rs Outdated
Some(user_choice) => Ok(user_choice),
None => {
cons.write(
"User chose nothing and no default was set. Can't proceed.\r\n",
Copy link
Contributor

Choose a reason for hiding this comment

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

Something like no option selected and... would be a bit better.

csssuf added 4 commits August 10, 2017 11:58
On boot, determine the partition gptprio specifies, and use it as the
default. As part of the boot menu, display the default choice to the
user.
@csssuf
Copy link
Contributor Author

csssuf commented Aug 10, 2017

@bgilbert Updated. I think the new validate works better and addresses all of your concerns.

src/util/gpt.rs Outdated
let alternate_header = unsafe { &mut *(alternate_block.as_mut_ptr() as *mut GptHeader) };

out.primary_header = primary_header;
out.alternate_header = alternate_header;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you're not using out before now, it seems that you can defer its creation until after the read_blocks calls and avoid the null_mut initializations.

src/util/gpt.rs Outdated
self.alternate_header
.validate(self.primary_header.alternate_lba)?;

let partitions = self.read_partitions_raw()?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Leaked.

It still seems as though these should be loaded once and stored, rather than loaded in validate and then thrown away.

src/util/gpt.rs Outdated
@@ -40,12 +40,6 @@ pub struct GptHeader {
partition_entry_array_crc32: u32,
}

impl Drop for GptHeader {
fn drop(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This introduces a leak if let alternate_block = ... fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, there's no way for a GptHeader to know how to free itself due to the allocate_pages/allocate_pool split. The best way to get around this is probably to introduce a type wrapping GptHeader that also stores how to free itself.

src/util/gpt.rs Outdated
let bs = uefi::get_system_table().boot_services();

let num_partitions = self.primary_header.num_partition_entries as usize;
let partition_entry_table = self.read_partitions_raw()?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Leaked. On error we can do something about that, and on success... it would be nice if we had a way to clean this up after the return value is dropped.

I believe the caller has an undocumented responsibility to free the returned array.

By splitting up the validation stage into validate_headers() and
validate_partitions(), and splitting each of those into a reading and
validation stage, leaks are avoided.
partitions: &[],
};

unsafe { out.validate_headers()? };
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if we fail here and Drop tries to free raw_partitions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The implementation of free_pages (and free_pool) in rust-uefi ignores failure, so the actual UEFI call will fail, but that failure won't be passed along to picker, so from picker's perspective, everything will be fine.

src/util/gpt.rs Outdated
bs.free_pool(self.alternate_header);
}

self.block_device.free_read(self.raw_partitions);
Copy link
Contributor

Choose a reason for hiding this comment

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

The partitions array is not freed.

let primary_block = protocol.read_blocks(1, 1)?;
let primary_header = unsafe { &mut *(primary_block.as_mut_ptr() as *mut GptHeader) };

let alternate_block = protocol.read_blocks(primary_header.alternate_lba, 1)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

If you spell out the error check here you can avoid the primary_header leak.

@csssuf
Copy link
Contributor Author

csssuf commented Jan 19, 2018

Since I have a few more things to change with this PR and there's apparently no way for me to update this one, I'll reopen a new PR once I have things figured out.

@csssuf csssuf closed this Jan 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants