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

Is this a soundness issue in Ixgbe driver? #1106

Open
tatetian opened this issue Sep 16, 2024 · 0 comments
Open

Is this a soundness issue in Ixgbe driver? #1106

tatetian opened this issue Sep 16, 2024 · 0 comments

Comments

@tatetian
Copy link

tatetian commented Sep 16, 2024

I am wondering if there is a soundness issue in the Ixgbe driver.

There are only two occurrences of unsafe in kernel/ixgbe/src/lib.rs. Both of them creates a Box<_> out of an address within a MappedPages using Box::from_raw.

for i in 0..QUEUES_IN_MP {
// This is safe because we have checked that the number of queues we want to partition from these mapped pages fit into the allocated memory,
// and that each queue starts at the end of the previous.
// We also ensure that the backing mapped pages are included in the same struct as the registers, almost as a pseudo OwningRef
let registers = unsafe {
Box::from_raw((starting_address.value() + (i * RX_QUEUE_REGISTERS_SIZE_BYTES)) as *mut RegistersRx)
};
pointers_to_queues.push(
IxgbeRxQueueRegisters {
regs: ManuallyDrop::new(registers),
backing_pages: shared_mp.clone()
}
);

The first sign of a potential memory safety problem is that the usage pattern is against the best practice as documented by Rust std.

More precisely, a value: *mut T that has been allocated with the Global allocator with Layout::for_value(&*value) may be converted into a box using Box::::from_raw(value).
...
In general, the best practice is to only use Box for pointers that originated from the global allocator.

The source address (starting_address.value() + (i * RX_QUEUE_REGISTERS_SIZE_BYTES)) given to Box::from_raw is obviously not allocated by the global allocator, but points to a MMIO region. Box is never meant to represent objects on MMIO regions.

As the source address is not on the heap, the resulting Box<_> must never be dropped. Deallocating an object that is not on the heap is obviously a UB. To prevent this from happening, the driver code (correctly) wraps the Box<_> object into a ManuallyDrop.

         IxgbeRxQueueRegisters { 
             // What if stack unwinding happens here?
             regs: ManuallyDrop::new(registers), 
             backing_pages: shared_mp.clone() 
         }

But here is the problem: what if stack unwinding is triggered after Box::from_raw but before ManuallyDrop by a kill request? This is how Theseus's OSDK paper describes stack unwinding and task killing.

Theseus starts the unwinder only upon a software or hardware exception or a request to kill a task.
...
Theseus can forcibly revoke generic resources by killing and unwinding an uncooperative task.

My understanding is that the process of starting a driver like Ixgbe and the decision of killing tasks is independent to each other. So although it is unlikely that a kill request is received between Box::from_raw and ManuallyDrop in practice, this could happen in theory. In this sense, the driver code is unsound.

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

No branches or pull requests

1 participant