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

Make read/write methods from GuestPtr unsafe, and copy_{from, to} methods from SecretsPage as well #382

Merged
merged 3 commits into from
Jul 1, 2024

Conversation

p4zuu
Copy link
Collaborator

@p4zuu p4zuu commented Jun 14, 2024

Leaving as draft because I still have doubts.

Providing partial fix for #359 (tasks 7, 8, 9).

My two interrogations are:

  1. There are several uses of the following pattern:
    let guard = PerCPUPageMappingGuard::create_4k(paddr)?; // paddr possibly guest or HV controlled
    let start = guard.virt_addr();
    let guest_page = GuestPtr::<Tt>::new(start + offset);
    let mut request = unsafe { guest_page.read()? };

As already discussed with @00xc, if start + offset is valid but start + offset + size_of::<T> ends on an unmapped page or on an adjacent page belonging to another guest or the SVSM kernel (I don't really know if it's possible), read() could raise a #PF and possibly return an error and panicking, or reading content from an unauthorized page. If such scenarii are possible, we should either:

  • check that start + offset + size_of::<T> doesn't cross the page end
  • allow GuestPtr::read()/write() for partial read/write
  • more general, we should always check that a guest or the HV can't force GuestPtr::read()/write() to return an error that will be treated as a fatal error by the SVSM kernel (that would be valid DoS).
  1. In some core protocol handlers, a guest provides a physical address (example the pvalidate handler), if I understand the code correctly, the guest-provided physical address pointing to the request entries is only verified if it's mapped or not. My understanding is that a guest could make the kernel read at any mapped address to fetch the PValidateRequest (including the kernel memory). Moreover, the guest-provided entries suffer from the same issue, the paddr to pvalidate are only verified to be mapped. A guest could make the SVSM kernel pvalidate any page. For the core protocol handlers that work with guest-provided paddrs, my understanding is that we should validate that paddrs only belong to the guest's memory region. I left TODOs is some places (not all of them) to points where there is this issue.

@00xc
Copy link
Member

00xc commented Jun 17, 2024

Leaving as draft because I still have doubts.

Providing partial fix for #359 (tasks 7, 8, 9).

My two interrogations are:

  1. There are several uses of the following pattern:
    let guard = PerCPUPageMappingGuard::create_4k(paddr)?; // paddr possibly guest or HV controlled
    let start = guard.virt_addr();
    let guest_page = GuestPtr::<Tt>::new(start + offset);
    let mut request = unsafe { guest_page.read()? };

As already discussed with @00xc, if start + offset is valid but start + offset + size_of::<T> ends on an unmapped page or on an adjacent page belonging to another guest or the SVSM kernel (I don't really know if it's possible), read() could raise a #PF and possibly return an error and panicking, or reading content from an unauthorized page. If such scenarii are possible, we should either:

  • check that start + offset + size_of::<T> doesn't cross the page end
  • allow GuestPtr::read()/write() for partial read/write
  • more general, we should always check that a guest or the HV can't force GuestPtr::read()/write() to return an error that will be treated as a fatal error by the SVSM kernel (that would be valid DoS).

One case we cannot protect from (as we already discussed) is #VC handling of kernel code, as we have no way of resetting to a valid instruction pointer.

  1. In some core protocol handlers, a guest provides a physical address (example the pvalidate handler), if I understand the code correctly, the guest-provided physical address pointing to the request entries is only verified if it's mapped or not. My understanding is that a guest could make the kernel read at any mapped address to fetch the PValidateRequest (including the kernel memory). Moreover, the guest-provided entries suffer from the same issue, the paddr to pvalidate are only verified to be mapped. A guest could make the SVSM kernel pvalidate any page. For the core protocol handlers that work with guest-provided paddrs, my understanding is that we should validate that paddrs only belong to the guest's memory region. I left TODOs is some places (not all of them) to points where there is this issue.

As far as I can tell we already guard against this via valid_phys_address(), so I think we're safe for now. Although perhaps we should enforce this at the type level.

@p4zuu
Copy link
Collaborator Author

p4zuu commented Jun 18, 2024

As far as I can tell we already guard against this via valid_phys_address(), so I think we're safe for now. Although perhaps we should enforce this at the type level.

You're right, I misread init_memory_map(), I thought MEMORY_MAP contained SVSM kernel memory regions, but it actually contains guest regions, so we're good.

@p4zuu p4zuu marked this pull request as ready for review June 20, 2024 13:55
@p4zuu p4zuu changed the title DRAFT: Make read/write methods from GuestPtr unsafe, and copy_{from, to} methods from SecretsPage as well Make read/write methods from GuestPtr unsafe, and copy_{from, to} methods from SecretsPage as well Jun 20, 2024
@p4zuu
Copy link
Collaborator Author

p4zuu commented Jun 20, 2024

Several uses of GuestPtr::read()/write() are to update VMSA's calling area. If I understand correctly, VMSA can't be modified by a guest or by the hypervisor, so we can trust its location and its content. With this, I think we can implement 2 safe methods to read and write CAA (and possibly extrapolate to other VMSA fields), that would remove several unsafe that I currently added for this PR. Something like:

pub fn read_caa(va: VirtAddr) -> Result<SvsmCaa, SvsmError> {
    let calling_area = GuestPtr::<SvsmCaa>::new(virt_addr);
    // SAFETY: HV and guests can't change VMSA's content, therefore CAA.
    unsafe { calling_area.read() }
}

@00xc
Copy link
Member

00xc commented Jun 20, 2024

Several uses of GuestPtr::read()/write() are to update VMSA's calling area. If I understand correctly, VMSA can't be modified by a guest or by the hypervisor, so we can trust its location and its content.

No, I don't think that's how it works. First, the guest's VMSA is different from the SVSM's VMSA - the guest is allowed to update its VMSA. Second, the CA is located in a different physical page than the VMSA. When the guest sets the CA's physical address (via core_create_vcpu()) or updates it (via core_remap_ca()) we have checks in place to verify it is valid. That is why mapping it and reading/writing to it is safe.

Copy link
Member

@00xc 00xc left a comment

Choose a reason for hiding this comment

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

Left some suggestions. On top of that, safety comments regarding the CAA should be updated based on my previous comment above.

kernel/src/cpu/vc.rs Outdated Show resolved Hide resolved
kernel/src/debug/gdbstub.rs Outdated Show resolved Hide resolved
kernel/src/debug/gdbstub.rs Outdated Show resolved Hide resolved
kernel/src/igvm_params.rs Outdated Show resolved Hide resolved
kernel/src/igvm_params.rs Outdated Show resolved Hide resolved
kernel/src/mm/guestmem.rs Outdated Show resolved Hide resolved
kernel/src/mm/guestmem.rs Outdated Show resolved Hide resolved
kernel/src/sev/secrets_page.rs Outdated Show resolved Hide resolved
kernel/src/sev/secrets_page.rs Outdated Show resolved Hide resolved
kernel/src/svsm.rs Outdated Show resolved Hide resolved
@p4zuu
Copy link
Collaborator Author

p4zuu commented Jun 21, 2024

No, I don't think that's how it works. First, the guest's VMSA is different from the SVSM's VMSA - the guest is allowed to update its VMSA. Second, the CA is located in a different physical page than the VMSA. When the guest sets the CA's physical address (via core_create_vcpu()) or updates it (via core_remap_ca()) we have checks in place to verify it is valid. That is why mapping it and reading/writing to it is safe.

That makes sense to me, thanks. I think there is a 3rd way to update the CA: through prepare_fw_launch() raised during early setup with IGVM-provided value, so I guess we can safely trust it too, correct?

@joergroedel
Copy link
Member

    let guard = PerCPUPageMappingGuard::create_4k(paddr)?; // paddr possibly guest or HV controlled
    let start = guard.virt_addr();
    let guest_page = GuestPtr::<Tt>::new(start + offset);
    let mut request = unsafe { guest_page.read()? };

Longer term we should introduce a safe interface around this pattern, which does all the mapping and bounds checking internally and avoids the unsafe keyword everywhere in the code. Just not so sure about TLB flushing, maybe we need a collector type as a base which handles that. Something like this:

{
    // Create TLB handle
    tlb = tlb_gather::new();
    // Create memory handle which can not outlive TLB handle
    mem_handle = tlb.new_mapping(phys_addr, size);
    let mut foo = mem_handle.read();
    let mut bar = mem_handle::offset(1).read();
    drop(mem_handle);
    // mem_handle goes out of scope, unmapping the memory, TLB handle makes sure the virt range is not
    // re-used until it is destroyed

    mem_handle2 = tlb.new_mapping(paddr2, size2)
    // ... use mem_handle2

    // memhandle2 goes out of scope, mapping is removed
    // tlb goes out of scope and flushes the TLB(s).
}
   

@joergroedel
Copy link
Member

But for now it looks good to me, will merge it once @00xc approves.

kernel/src/mm/guestmem.rs Outdated Show resolved Hide resolved
@00xc
Copy link
Member

00xc commented Jun 26, 2024

But for now it looks good to me, will merge it once @00xc approves.

We still have some unresolved conversations. Once that's done I'll approve.

@p4zuu
Copy link
Collaborator Author

p4zuu commented Jun 28, 2024

Longer term we should introduce a safe interface around this pattern, which does all the mapping and bounds checking internally and avoids the unsafe keyword everywhere in the code.

I agree, this pattern happens in quite a few places and there is room for improvements.

Just not so sure about TLB flushing, maybe we need a collector type as a base which handles that.

Couldn't we just simply create new read()/write() methods for PerCPUPageMappingGuard?

@p4zuu
Copy link
Collaborator Author

p4zuu commented Jun 28, 2024

That makes sense to me, thanks. I think there is a 3rd way to update the CA: through prepare_fw_launch() raised during early setup with IGVM-provided value, so I guess we can safely trust it too, correct?

Checked with Carlos that the CA address updated in prepare_fw_launch() is validated in validate_fw_memory().

@00xc
Copy link
Member

00xc commented Jul 1, 2024

Just not so sure about TLB flushing, maybe we need a collector type as a base which handles that.

Couldn't we just simply create new read()/write() methods for PerCPUPageMappingGuard?

It wouldn't solve the fact that we would need to issue a flush for each guard. It also requires making PerCPUPageMappingGuard type-aware. Not a bad idea, but it is a more intrusive change.

@00xc
Copy link
Member

00xc commented Jul 1, 2024

There are some conflicts to resolve, please rebase.

@00xc 00xc added the needs-rebase The PR needs to be rebased to the latest upstream branch label Jul 1, 2024
p4zuu added 2 commits July 1, 2024 10:36
copy_from and coy_to methods from SecretsPage copy raw content from or to
arbitrary virtual addresses. Both functions should be mark unsafe to that
callers are aware that argument's correctness has to be verified.

Signed-off-by: Thomas Leroy <[email protected]>
write_u8() can write arbitrary byte to abritrary address. Even though
it already catches writes to unmapped memory, it doesn't prevent a
malicious user to write at a dangerous place if it can control the
parameters. It should be made unsafe.

Signed-off-by: Thomas Leroy <[email protected]>
Once creating a GuestPtr, the read(), write() and write_ret() methods
could grant arbitrary read/write if an attacker (HV or guest) can
control the GuestPtr.ptr value, and the write*() parameters.

We need to verify that this is not the case for the current calls, and
to enforce an unsafe block for the future calls to make the caller
validating the parameters.

Signed-off-by: Thomas Leroy <[email protected]>
@p4zuu
Copy link
Collaborator Author

p4zuu commented Jul 1, 2024

There are some conflicts to resolve, please rebase.

Done

@00xc 00xc merged commit 61b05cf into coconut-svsm:main Jul 1, 2024
3 checks passed
@p4zuu p4zuu mentioned this pull request Jul 1, 2024
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-rebase The PR needs to be rebased to the latest upstream branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants