x86_64 icon indicating copy to clipboard operation
x86_64 copied to clipboard

Safe API for using I/O Permission Bitmap in TSS

Open ChocolateLoverRaj opened this issue 7 months ago • 0 comments

I want to use the I/O Permission Bitmap (IOBP) to allow a user space program to access COM1 only. But the current TSS and GDT code doesn't make it easy. This is what I did to get it to work:

/// `N` is the size of the IOBP in **bytes**
/// Do not change `iomap_base`. It is set to the correct value by the `default` function.
/// By default, the IOPB is set to all 1s, so that no port access is allowed
#[derive(Debug, Clone, Copy)]
#[repr(C, packed(4))]
pub struct TssWithIoBitMap<const N: usize> {
    pub above_iobp: TaskStateSegment,
    pub actual_io_bitmap: [u8; N],
    /// According to Section 20.5.2 in *Intel® 64 and IA-32 Architectures Software Developer’s Manual*
    /// > Last byte of bitmap must be followed by a byte with all bits set.
    io_bit_map_last_byte: u8,
}

impl<const N: usize> Default for TssWithIoBitMap<N> {
    fn default() -> Self {
        Self {
            above_iobp: {
                let mut tss = TaskStateSegment::default();
                tss.iomap_base = (offset_of!(Self, actual_io_bitmap)).try_into().unwrap();
                tss
            },
            // If a bit is 0 it means the port is allowed, 1 is not allowed
            actual_io_bitmap: [u8::MAX; N],
            io_bit_map_last_byte: u8::MAX,
        }
    }
}

and

impl Descriptor {
    /// Similar to [`Descriptor::tss_segment`], but unsafe since it does not enforce a lifetime
    /// constraint on the provided TSS.
    ///
    /// # Safety
    /// The caller must ensure that the passed pointer is valid for as long as the descriptor is
    /// being used.
    #[inline]
    pub unsafe fn tss_segment_unchecked_with_size(
        tss: *const TaskStateSegment,
        size: u64,
    ) -> Descriptor {
        use self::DescriptorFlags as Flags;
        use core::mem::size_of;

        let ptr = tss as u64;

        let mut low = Flags::PRESENT.bits();
        // base
        low.set_bits(16..40, ptr.get_bits(0..24));
        low.set_bits(56..64, ptr.get_bits(24..32));
        // limit (the `-1` in needed since the bound is inclusive)
        low.set_bits(0..16, size - 1);
        // type (0b1001 = available 64-bit tss)
        low.set_bits(40..44, 0b1001);

        let mut high = 0;
        high.set_bits(0..32, ptr.get_bits(32..64));

        Descriptor::SystemSegment(low, high)
    }
}

and then in my GDT code:

let tss_selector = gdt.append(unsafe {
    Descriptor::tss_segment_unchecked_with_size(
        tss as *const _ as *const TaskStateSegment,
        size_of::<TssWithIoBitMap<N>>() as u64,
    )
});

I only did this to test out using the IOPB. Ideally this library would make it easier and safer.

I can make a PR for this feature, but there are some things to decide first:

  • The IOPB can be variable length, which would make TSS variable length
  • Should we have a <const N: usize> generic for the IOPB in the TSS? Would this make it too inconvenient for the majority of people who don't want a IOPB?
  • IMO the iomap_base in TaskStateSegment should not be public or modifyable outside of this library because it should always be 104
  • The IOPB can be changed at any time by the kernel after initializing the tss and gdt, without needing to reload anything (I think). How can there be a safe way of doing this without unsafe when pub fn tss_segment(tss: &'static TaskStateSegment) -> Descriptor takes an immutable reference to the tss? Perhaps the function could be changed to Pin<&TaskStateSegment>? Idk much about pin though.

Also I just realized #194 exists

I actually implemented this at https://github.com/ChocolateLoverRaj/x86_64/tree/develop, I think it's a good API.

ChocolateLoverRaj avatar Mar 18 '25 01:03 ChocolateLoverRaj