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

gdt: Add remaining GDT flags #181

Merged
merged 4 commits into from
Sep 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Unreleased

- Add additional `DescriptorFlags` and aliases compatible with `syscall`/`sysenter` ([#181](https://github.com/rust-osdev/x86_64/pull/181))
- Fix (another) build error on latest nightly ([#186](https://github.com/rust-osdev/x86_64/pull/186))

# 0.12.1 – 2020-09-24

- Fix build error on latest nightly ([#182](https://github.com/rust-osdev/x86_64/pull/182))
Expand Down
133 changes: 105 additions & 28 deletions src/structures/gdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,55 +184,114 @@ pub enum Descriptor {
bitflags! {
/// Flags for a GDT descriptor. Not all flags are valid for all descriptor types.
pub struct DescriptorFlags: u64 {
/// For data segments, this flag sets the segment as writable. Ignored for code segments.
/// Set by the processor if this segment has been accessed. Only cleared by software.
/// _Setting_ this bit in software prevents GDT writes on first use.
const ACCESSED = 1 << 40;
phil-opp marked this conversation as resolved.
Show resolved Hide resolved
/// For 32-bit data segments, sets the segment as writable. For 32-bit code segments,
/// sets the segment as _readable_. In 64-bit mode, ignored for all segments.
const WRITABLE = 1 << 41;
/// Marks a code segment as “conforming”. This influences the privilege checks that
/// occur on control transfers.
/// For code segments, sets the segment as “conforming”, influencing the
/// privilege checks that occur on control transfers. For 32-bit data segments,
/// sets the segment as "expand down". In 64-bit mode, ignored for data segments.
const CONFORMING = 1 << 42;
/// This flag must be set for code segments.
/// This flag must be set for code segments and unset for data segments.
const EXECUTABLE = 1 << 43;
/// This flag must be set for user segments (in contrast to system segments).
const USER_SEGMENT = 1 << 44;
/// The DPL for this descriptor is Ring 3. In 64-bit mode, ignored for data segments.
const DPL_RING_3 = 3 << 45;
/// Must be set for any segment, causes a segment not present exception if not set.
const PRESENT = 1 << 47;
/// Must be set for long mode code segments.
/// Available for use by the Operating System
const AVAILABLE = 1 << 52;
/// Must be set for 64-bit code segments, unset otherwise.
const LONG_MODE = 1 << 53;
/// Use 32-bit (as opposed to 16-bit) operands. If [`LONG_MODE`][Self::LONG_MODE] is set,
/// this must be unset. In 64-bit mode, ignored for data segments.
const DEFAULT_SIZE = 1 << 54;
/// Limit field is scaled by 4096 bytes. In 64-bit mode, ignored for all segments.
const GRANULARITY = 1 << 55;

/// The DPL for this descriptor is Ring 3
const DPL_RING_3 = 3 << 45;
/// Bits `0..=15` of the limit field (ignored in 64-bit mode)
const LIMIT_0_15 = 0xFFFF;
/// Bits `16..=19` of the limit field (ignored in 64-bit mode)
const LIMIT_16_19 = 0xF << 48;
/// Bits `0..=23` of the base field (ignored in 64-bit mode, except for fs and gs)
const BASE_0_23 = 0xFF_FFFF << 16;
/// Bits `24..=31` of the base field (ignored in 64-bit mode, except for fs and gs)
const BASE_24_31 = 0xFF << 56;
}
}

/// The following constants define default values for common GDT entries. They
/// are all "flat" segments, meaning they can access the entire address space.
/// These values all set [`WRITABLE`][DescriptorFlags::WRITABLE] and
/// [`ACCESSED`][DescriptorFlags::ACCESSED]. They also match the values loaded
/// by the `syscall`/`sysret` and `sysenter`/`sysexit` instructions.
///
/// In short, these values disable segmentation, permission checks, and access
/// tracking at the GDT level. Kernels using these values should use paging to
/// implement this functionality.
impl DescriptorFlags {
// Flags that we set for all our default segments
const COMMON: Self = Self::from_bits_truncate(
Self::USER_SEGMENT.bits()
| Self::PRESENT.bits()
| Self::WRITABLE.bits()
| Self::ACCESSED.bits()
| Self::LIMIT_0_15.bits()
| Self::LIMIT_16_19.bits()
| Self::GRANULARITY.bits(),
);
/// A kernel data segment (64-bit or flat 32-bit)
pub const KERNEL_DATA: Self =
Self::from_bits_truncate(Self::COMMON.bits() | Self::DEFAULT_SIZE.bits());
/// A flat 32-bit kernel code segment
pub const KERNEL_CODE32: Self = Self::from_bits_truncate(
Self::COMMON.bits() | Self::EXECUTABLE.bits() | Self::DEFAULT_SIZE.bits(),
);
/// A 64-bit kernel code segment
pub const KERNEL_CODE64: Self = Self::from_bits_truncate(
Self::COMMON.bits() | Self::EXECUTABLE.bits() | Self::LONG_MODE.bits(),
);
/// A user data segment (64-bit or flat 32-bit)
pub const USER_DATA: Self =
Self::from_bits_truncate(Self::KERNEL_DATA.bits() | Self::DPL_RING_3.bits());
/// A flat 32-bit user code segment
pub const USER_CODE32: Self =
Self::from_bits_truncate(Self::KERNEL_CODE32.bits() | Self::DPL_RING_3.bits());
/// A 64-bit user code segment
pub const USER_CODE64: Self =
Self::from_bits_truncate(Self::KERNEL_CODE64.bits() | Self::DPL_RING_3.bits());
}

impl Descriptor {
/// Creates a segment descriptor for a long mode kernel code segment.
/// Creates a segment descriptor for a 64-bit kernel code segment. Suitable
/// for use with `syscall` or 64-bit `sysenter`.
#[inline]
pub fn kernel_code_segment() -> Descriptor {
use self::DescriptorFlags as Flags;

let flags = Flags::USER_SEGMENT | Flags::PRESENT | Flags::EXECUTABLE | Flags::LONG_MODE;
Descriptor::UserSegment(flags.bits())
pub const fn kernel_code_segment() -> Descriptor {
Descriptor::UserSegment(DescriptorFlags::KERNEL_CODE64.bits())
}
Comment on lines +272 to 274
Copy link
Member

Choose a reason for hiding this comment

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

We should probably also add a kernel_data_segment function to resolve #160.

Also, a general from_flags(flags: DescriptorFlags) -> Self function be useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch I forgot about syscall/sysenter/sysret/sysexit, this might actually impact what we do for defaults here.

I'll do some more reading

Copy link
Contributor Author

@josephlr josephlr Sep 27, 2020

Choose a reason for hiding this comment

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

So it looks like we should set all of these "ignored" flags. Per the Intel manual, syscall/sysenter/sysret/sysexit all load a specific selector from the GDT, but they don't actually read the GDT entry, they instead load fixed data in the descriptor caches. The manual then states:

It is the responsibility of OS software to ensure that the descriptors (in GDT or LDT) referenced by those selector values correspond to the fixed values loaded into the descriptor caches; the SYSCALL instruction does not ensure this
correspondence.

So we should just have our defaults match those fixed values the processor loads automatically. These are the values I had initially (i.e. exactly the values Linux uses). It now also makes sense why Linux was setting ignored bits, accessed bits, etc...

Specific details of what flags are set by the processor

Ring 3 -> Ring 0

CS

SYSCALL: SEL = IA32_START[47:32]
SYSENTER: SEL = IA32_SYSENTER_CS[15:0]

  • Base = 0
  • Limit = 0xFFFFF
  • Type = 0x1011 (execute, read, accessed)
  • S = 1
  • DPL = 0
  • P = 1
  • L = 1 (0 32-bit)
  • D = 0 (1 32-bit)
  • G = 1

SS

SYSCALL: SEL = IA32_START[47:32] + 8
SYSENTER: SEL = IA32_SYSENTER_CS[15:0] + 8

  • Base = 0
  • Limit = 0xFFFFF
  • Type = 0x0011 (data, write, accessed)
  • S = 1
  • DPL = 0
  • P = 1
  • B = 1
  • G = 1

Ring 0 -> Ring 3

CS

SYSRET: SEL = IA32_STAR[63:48] + 16
SYSRET (32-bit): SEL = IA32_STAR[63:48]
SYSEXIT: SEL = IA32_SYSENTER_CS[15:0] + 32
SYSEXIT (32-bit): SEL = IA32_SYSENTER_CS[15:0] + 16

  • Base = 0
  • Limit = 0xFFFFF
  • Type = 0x1011 (execute, read, accessed)
  • S = 1
  • DPL = 3
  • P = 1
  • L = 1 (0 32-bit)
  • D = 0 (1 32-bit)
  • G = 1

SS

SYSRET: SEL = IA32_STAR[63:48]+8
SYSEXIT: SEL = IA32_SYSENTER_CS[15:0] + 40
SYSEXIT (32-bit): SEL = IA32_SYSENTER_CS[15:0] + 24

  • Base = 0
  • Limit = 0xFFFFF
  • Type = 0x0011 (data, write, accessed)
  • S = 1
  • DPL = 3
  • P = 1
  • B = 1
  • G = 1

Interestingly, due to the constraints of these commands, if you wanted to support both, you would need a GDT that had the following segments ordered like:

  • KERNEL_CODE64
  • KERNEL_DATA
  • USER_CODE32
  • USER_DATA
  • USER_CODE64
  • USER_DATA (only need for 64-bit sysenter/sysexit)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, a general from_flags(flags: DescriptorFlags) -> Self function be useful.

A user can already do Descriptor::UserSegment(flags.bits()) do you think adding another method is useful? Should it do some sort of basic checks?

Copy link
Member

Choose a reason for hiding this comment

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

Also, a general from_flags(flags: DescriptorFlags) -> Self function be useful.

A user can already do Descriptor::UserSegment(flags.bits()) do you think adding another method is useful? Should it do some sort of basic checks?

I was mostly thinking about improving the discoverability of the DescriptorFlags structure. Right now, the relationship between the DescriptorFlags and GlobalDescriptorTable types is only implicit. You have to know the GDT details to be sure that Descriptor::UserSegment(flags.bits()) is the correct implementation (as you could put any u64 in there). An additional from_flags method would properly encode this at the type system level, which would make it less dangerous to use.

Copy link
Member

Choose a reason for hiding this comment

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

So we should just have our defaults match those fixed values the processor loads automatically. These are the values I had initially (i.e. exactly the values Linux uses). It now also makes sense why Linux was setting ignored bits, accessed bits, etc...

Thanks a lot for investigating this! I agree that it makes sense to use the sames values at Linux then.

Interestingly, due to the constraints of these commands, if you wanted to support both, you would need a GDT that had the following segments ordered like:

* KERNEL_CODE64

* KERNEL_DATA

* USER_CODE32

* USER_DATA

* USER_CODE64

* USER_DATA (only need for 64-bit sysenter/sysexit)

Unrelated to this PR, but maybe it makes sense to add a new constructor function to GlobalDescriptorTable with exactly these entries? It seems like this is the canonical GDT layout that most users will want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

An additional from_flags method would properly encode this at the type system level, which would make it less dangerous to use.

Agreed here, but to make this const_fn we will have to add additional features. I'm going to merge this as-is and add any additional constructors in a follow-up PR (which will also add new GDT constructor).


/// Creates a segment descriptor for a long mode ring 3 data segment.
/// Creates a segment descriptor for a kernel data segment (32-bit or
/// 64-bit). Suitable for use with `syscall` or `sysenter`.
#[inline]
pub fn user_data_segment() -> Descriptor {
use self::DescriptorFlags as Flags;

let flags = Flags::USER_SEGMENT | Flags::PRESENT | Flags::WRITABLE | Flags::DPL_RING_3;
Descriptor::UserSegment(flags.bits())
pub const fn kernel_data_segment() -> Descriptor {
Descriptor::UserSegment(DescriptorFlags::KERNEL_DATA.bits())
}

/// Creates a segment descriptor for a long mode ring 3 code segment.
/// Creates a segment descriptor for a ring 3 data segment (32-bit or
/// 64-bit). Suitable for use with `sysret` or `sysexit`.
#[inline]
pub fn user_code_segment() -> Descriptor {
use self::DescriptorFlags as Flags;
pub const fn user_data_segment() -> Descriptor {
Descriptor::UserSegment(DescriptorFlags::USER_DATA.bits())
}

let flags = Flags::USER_SEGMENT
| Flags::PRESENT
| Flags::EXECUTABLE
| Flags::LONG_MODE
| Flags::DPL_RING_3;
Descriptor::UserSegment(flags.bits())
/// Creates a segment descriptor for a 64-bit ring 3 code segment. Suitable
/// for use with `sysret` or `sysexit`.
#[inline]
pub const fn user_code_segment() -> Descriptor {
Descriptor::UserSegment(DescriptorFlags::USER_CODE64.bits())
}

/// Creates a TSS system descriptor for the given TSS.
Expand All @@ -258,3 +317,21 @@ impl Descriptor {
Descriptor::SystemSegment(low, high)
}
}

#[cfg(test)]
mod tests {
use super::DescriptorFlags as Flags;

#[test]
#[rustfmt::skip]
pub fn linux_kernel_defaults() {
// Make sure our defaults match the ones used by the Linux kernel.
// Constants pulled from an old version of arch/x86/kernel/cpu/common.c
assert_eq!(Flags::KERNEL_CODE64.bits(), 0x00af9b000000ffff);
assert_eq!(Flags::KERNEL_CODE32.bits(), 0x00cf9b000000ffff);
assert_eq!(Flags::KERNEL_DATA.bits(), 0x00cf93000000ffff);
assert_eq!(Flags::USER_CODE64.bits(), 0x00affb000000ffff);
assert_eq!(Flags::USER_CODE32.bits(), 0x00cffb000000ffff);
assert_eq!(Flags::USER_DATA.bits(), 0x00cff3000000ffff);
}
}