Creating a proper Task State Segment (TSS) structure with and without an IO Bitmap?
Asked Answered
O

1

3

Reading the documentation between Intel and AMD and looking at code makes it difficult at times to understand how to create a proper Task State Segment (TSS) that has no IO port bitmap (IOPB). There also seems to be confusion over creating a TSS with an IOPB as well since it seems ambiguous as to whether an IO Bitmap (IOPB) requires a trailing 0xff byte.

I'm aware that there are is a dependency between the TSS and a TSS Descriptor (in the GDT). The TSS descriptor governs the base address of the TSS as well as the limit. The limit in the descriptor is one less than the actual size of the structure (similar in nature to the size specified in a GDT and IDT record). The TSS limit comes into play to determine the IOPB size.

I know that:

  • The TSS descriptor limit is one less than the size of the entire TSS structure
  • 16-bit TSS doesn't have an IOPB and the structure is a fixed size
  • The basic 32-bit and 64-bit TSS structures are similar in size (the data has different meaning)
  • the 32-bit TSS can support Control-flow Enforcement by adding an extra DWORD to the base structure.
  • The IOPB offset (word) in the TSS points to an offset relative to the beginning of the Task segment.
  • The IOPB offset points to the beginning of an IOPB structure, and with Virtual Mode Enhancements (VME) enabled the 32 bytes before IOPB are the interrupt redirection table.
  • If VME is not enabled the kernel can place extra per task instance data between the end of the basic TSS structure and the IOPB offset
  • If VME is enabled the kernel can place extra per task instance data between the end of the basic TSS structure and the offset 32 bytes below the IOPB.
  • If there is an IOPB present, each 0 bit is permission for port access and 1 denies permission.

The 32-bit TSS structure can be visualized this way:

enter image description here

The link also contains the layout of the 16-bit TSS and the 64-bit TSS structures.


Questions:

  • If I want a TSS without an IOPB, what value should I fill in for the IOPB offset at +66h?
  • If I want a TSS with an IOPB do I have to add a 0xff byte to the end of the IOPB?
  • In the diagram above why does the extra byte at the end get represented as xxxxx111. If the last byte is suppose to be 0xff wouldn't that be 11111111?
Outsell answered 25/2, 2019 at 23:4 Comment(4)
This is only relevant for hardware task switches, right? (Still a useful osdev FAQ, I like these canonical Q&As you've been writing up. I'm just wondering whether this matters for modern kernels that choose to do context switches in software because it's faster.)Annorah
@PeterCordes: This applies beyond hardware Task Switches on x86 (legacy mode) and Long Mode. The TSS structure is still used by modern CPUs for purposes of determining among other things - the SS:ESP/SS:RSP to be used when an interrupt, exception, gate causes a change in privilege (CPL). That is why LTR (load task register) is still valid. The IO Bitmap is still used in Long Mode to determine port privileges for code running where CPL<IOPL and CPL > 0.Outsell
@PeterCordes :In one of my edits to the answer before you commented I did suggest you still have to concern yourself with this issue when dealing with a 64-bit TSS.Outsell
Ah thanks, I knew there was some mechanism for interrupts using the kernel stack, but I'd never looked into the details!Annorah
O
5

This is a very fair question. Although at first glance the TSS with or without an IO Port Bitmap (IOPB) seems rather trivial in nature, it has been the focus of intense discussion; debate; incorrect documentation; ambiguous documentation; and information from the CPU designers that at times muddied the waters. A very good read about this subject can be found in the OS/2 Museum. Despite the name, the information isn't limited to OS/2. One take away from that article that sums it up:

It is obviously not trivial to use the IOPB correctly. In addition, an incorrectly set up IOPB is unlikely to cause obvious problems, but may disallow access to desired ports or (much worse, security-wise) allow access to undesired ports.

The sordid history of the TSS and IOPB as it pertained to security holes and bugs in 386BSD, NetBSD, OpenBSD makes for an interesting read and should be an indicator that the questions you pose are reasonable if you wish to avoid introducing bugs.


Answers to Questions

If you want no IOPB, you can simply fill the IOPB offset field with the length of your entire TSS structure (do not subtract 1). Your TSS Structure should have no trailing 0xff byte in it. The TSS limit in the TSS descriptor (as you already are aware) will be one less than that value. The Intel manuals say that there is no IOPB if the value in the IOPB offset value is greater than the TSS limit. If the value in the IOPB offset field is always 1 greater than the limit this condition is satisfied. This is how modern Microsoft Windows handles it.

If using an IOPB set an additional byte at the end to 0xff per the Intel documentation. By setting an extra byte to all 0xff would prevent any multi port access (INW/OUTW/INL/OUTL) starting in or ending in the last 8 ports. This would avoid the situation where a multi port read/write could straddle the end of the IOPB causing accesses to ports that fall outside the range of the IOPB. It would also deny multi port access that started on a port preceding the last 8 ports that crosses into the following 8 ports. If any port of a multi port access has a permission bit set to 1, the entire port access is denied (per the Intel documentation)

It is unclear what the x represents in the context of the diagram, but if those bits were set to 0 they would appear as permissible ports which isn't what you want. Again, stick with the Intel documentation and set an extra trailing byte to 0xff (all bits set to deny access).

From the Intel386 DX Microprocessor Data Sheet:

Each bit in the I/O Permission Bitmap corresponds to a single byte-wide I/O port, as illustrated in Figure 4-15a. If a bit is 0, I/O to the corresponding byte-wide port can occur without generating an exception. Otherwise the I/O instruction causes an exception 13 fault. Since every byte-wide I/O port must be protectable, all bits corresponding to a word-wide or dword-wide port must be 0 for the word-wide or dword-wide I/O to be permitted. If all the referenced bits are 0, the I/O will be allowed. If any referenced bits are 1, the attempted I/O will cause an exception 13 fault.

and

**IMPORTANT IMPLEMENTATION NOTE: Beyond the last byte of I/O mapping information in the I/O Permission Bitmap must be a byte containing all 1’s. The byte of all 1’s must be within the limit of the Intel386 DX TSS segment (see Figure 4-15a).


In NASM assembly you could create a structure that looks like:

tss_entry:
.back_link: dd 0
.esp0:      dd 0              ; Kernel stack pointer used on ring transitions
.ss0:       dd 0              ; Kernel stack segment used on ring transitions
.esp1:      dd 0
.ss1:       dd 0
.esp2:      dd 0
.ss2:       dd 0
.cr3:       dd 0
.eip:       dd 0
.eflags:    dd 0
.eax:       dd 0
.ecx:       dd 0
.edx:       dd 0
.ebx:       dd 0
.esp:       dd 0
.ebp:       dd 0
.esi:       dd 0
.edi:       dd 0
.es:        dd 0
.cs:        dd 0
.ss:        dd 0
.ds:        dd 0
.fs:        dd 0
.gs:        dd 0
.ldt:       dd 0
.trap:      dw 0
.iomap_base:dw TSS_SIZE         ; IOPB offset
;.cetssp:    dd 0              ; Need this if CET is enabled

; Insert any kernel defined task instance data here
; ...

; If using VME (Virtual Mode extensions) there need to bean additional 32 bytes
; available immediately preceding iomap. If using VME uncomment next 2 lines
;.vmeintmap:                     ; If VME enabled uncomment this line and the next
;TIMES 32    db 0                ;     32*8 bits = 256 bits (one bit for each interrupt)

.iomap:
TIMES TSS_IO_BITMAP_SIZE db 0x0
                                ; IO bitmap (IOPB) size 8192 (8*8192=65536) representing
                                ; all ports. An IO bitmap size of 0 would fault all IO
                                ; port access if IOPL < CPL (CPL=3 with v8086)
%if TSS_IO_BITMAP_SIZE > 0
.iomap_pad: db 0xff             ; Padding byte that has to be filled with 0xff
                                ; To deal with issues on some CPUs when using an IOPB
%endif
TSS_SIZE EQU $-tss_entry

Special Note:

  • If you are using a high level language and creating a TSS structure, ensure you use a packed structure (ie: using GCC's __attribute__((packed)) or MSVC's #pragma pack). Review your compiler documentation for more details. Failure to heed this advice could cause extra bytes to be added to the end of your TSS structure that could cause problems if you have an IOPB. If an IOPB is present in the TSS and extra padding bytes are added those bytes will become part of the IO bitmap and may grant/deny permissions you didn't intend. This was one of the failures that produced bugs in BSD kernels.
  • The rules for the 64-bit TSS are the same when it comes to creating a TSS with or without an IOPB. A 64-bit TSS is still used even in Long Modes (64-bit and compatibility mode) and is loaded into the Task Register the same way it is done in legacy protected mode via the LTR instruction.
Outsell answered 25/2, 2019 at 23:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.