{"id":21195556,"url":"https://github.com/eantcal/ioperm","last_synced_at":"2025-07-10T04:30:27.124Z","repository":{"id":47372674,"uuid":"175640019","full_name":"eantcal/ioperm","owner":"eantcal","description":"Source code related to the article \"Enabling direct I/O ports access in user space\"","archived":false,"fork":false,"pushed_at":"2020-12-09T23:41:45.000Z","size":85,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2023-03-03T01:34:47.484Z","etag":null,"topics":["c","kernel-driver","linux","low-level","windows"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eantcal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-14T14:37:22.000Z","updated_at":"2022-07-20T17:56:02.000Z","dependencies_parsed_at":"2022-09-22T17:11:38.675Z","dependency_job_id":null,"html_url":"https://github.com/eantcal/ioperm","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eantcal%2Fioperm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eantcal%2Fioperm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eantcal%2Fioperm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eantcal%2Fioperm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eantcal","download_url":"https://codeload.github.com/eantcal/ioperm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225618184,"owners_count":17497473,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","kernel-driver","linux","low-level","windows"],"created_at":"2024-11-20T19:28:48.890Z","updated_at":"2024-11-20T19:28:49.589Z","avatar_url":"https://github.com/eantcal.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"Sample code related to the following article published on Computer Programming - n. 153 - Gennaio 2006 \n\n# Enabling direct I/O ports access in user space\n\nThis article describes the direct I/O access techniques in Linux and Windows user space applications. A Windows kernel driver which uses undocumented internal API is also described.\n\nIntel x86 processors in addition to memory mapped devices support also so called I/O ports mapped devices via a 'privileged' set of machine instructions. Such instructions are checked by system to guarantee processes and kernel space isolation where only (Linux and Windows) Kernel privileged code is allowed to address the I/O directly.\n\nOnly two of four privilege levels (rings) of x86 processors are typically used by Linux and Windows: ring 0 for kernel space and ring 3 for user space. While in kernel space any privileged instruction can be executed, in user space this can only be allowed through a specific interface provided by operating system.\n\nEven if user space applications normally don't access directly the I/O a specific class of applications can violate such rule. We are talking about User Mode Drivers (UMD).\n\nAn UMD is a simple user space application since it can be built, executed and debugged as any other user space application while a Kernel mode driver (KMD) requires specific tools and a very specific knowledge to be designed, implemented, deployed and debugged.\n\nEven taking into account that an UMD might represent a violation of modern operating systems architecture which splits user space and kernel space domains, an UMD might still represent a pragmatic temporary solution for experimenting low level stuff without dealing with KMD complexity.\n\nOften a better way is combining UMD and KMD for providing a solution where the KMD provides access to I/O and the UMD implements stuff 'too' complex to be implemented easily in the KMD. Sometimes this is not possible because the performances are compromised by the Kernel / User context switch overhead.\n\nAnd finally, although we cannot replace a KMD anytime we have to deal with interrupts and other bunch of low level stuff, knowing how to access I/O from user space can still be useful in many other cases.\n\n# I/O permission level and I/O permission bit map\nx86 processors use an algorithm to validate a port I/O access based on two permission checks ([1]):\n\nChecking the I/O Privilege Level (IOPL) of EFLAGS register\nChecking I/O permission bit map (IOPM) of a process task state segment (TSS)\nFor the memory mapped devices other MMU related specific mechanisms are provided (but they are out of scope, see [2] for more details on this).\n\nEFLAGS register is the 32 bit status register of processor. EFLAGS is stored into TSS when a task is suspended and it is replaced with the one contained in the new executing task's TSS. EFLAGS IOPL field controls the I/O ports address space restricting machine instruction access to such ports. Instruction as IN, INS, OUT, OUTS can be executed if the Current Privilege Level (CPL) of executing process is less than or equal to IOPL. Such instructions (plus STL and CLI instructions), are defined I/O sensitive. A general protection exception will be raised any time a non privileged process attempts to use them.\n\nBecause each process has a specific copy of EFLAGS, different process might have different privileges. User space processes (CPL=3) cannot modify directly the IOPL, while have to ask operating system to do that. Linux for example provides a syscall named iopl. A root process with CAP_SYS_RAWIO capability can modify the IOPL field getting access to the whole I/O address space.\n\nYou can find more information about iopl syscall (sys_iopl) and ioperm syscall (which modifies the I/O permission bitmap which will be soon discussed) looking Linux kernel source code.\n\nThe I/O permission bitmap is part of TSS. Base address and location are both part of TSS.\n\nBy modifying the permission bitmap is possible to enable the I/O port access for any process even less privileged or virtual-8086 processes where CPL\u003e=IOPL. The bitmap can cover the entire I/O address space or a subset of it.\n\nEach bit corresponds to an I/O address space byte. For example 1-byte size port at address 0x31 corresponds to bit 1 of byte 7 of the bitmap. A process is allowed to access a specific byte if the related bit on the bitmap is 0.\n\nLinux ioperm syscall can modify the first 0x3FF ports while the only way to get access to the remaining ports is using iopl syscall.\n\n![tss](pics/TSS.png)\n\n# Ke386IoSetAccessProcess and Ke386SetIoAccessMap\nWindows does not support syscalls like ioperm or iopl but we can try to implement a KMD which will provide similar features as shown in ioperm.c that will discuss better in the next paragraph. \n\nAlthough such driver is quite simple it is peculiar because of two undocumented Kernel API Ke386IoSetAccessProcess e Ke386SetIoAccessMap. Non official documentation about such APIs is the following:\n\n- ``void Ke386IoSetAccessProcess (PEPROCESS, int);``\nThis function ask the Kernel to enable access for the current process to the IOPM bitmap. Second argument enables (1) or disables (0) such access.\n\n- ``void Ke386SetIoAccessMap (int, IOPM *);``\nReplaces the current process IOPM bitmap (first parameter must be 1). \n\n- ``void Ke386QueryIoAccessMap (int, IOPM *);``\nReturns the current process IOPM. First argument must be 1.\n\nDescribed functions can be combined to update the I/O permission bitmap of calling process, allowing it to access to the whole I/O address space, as showing in the following fragment of code:\n\n```\nchar * pIOPM = NULL;\n//...\npIOPM = MmAllocateNonCachedMemory(65536 / 8);\nRtlZeroMemory(pIOPM, 65536 / 8);\nKe386IoSetAccessProcess(IoGetCurrentProcess(), 1);\n```\n\nThe Kernel Mode Driver\nKMD role is to provide the O/S with the access to a specific device. \n\nFrom a user space process point of view a KMD can be handled as special file and handled by syscalls like ``CreateFile``, ``ReadFile`` or ``DeviceIoControl``.\n\nFrom an implementation point of view it is a set of functions registered into and called by I/O Manager during the I/O operations on the controlled device.\n\n# A generic Windows KMD.\nA generic Windows KMD implementation includes:\n\n- DriverEntry function called by I/O Manager as soon as the driver is loaded.\n- Dispatch entry points: functions called on I/O requests which process the I/O Request Packet (IRPs).\n- Interrupt Service Routines (ISRs): which handle the device Interrupt requests (IRQs)  \n- Deferred Procedure Calls (DPCs): special routines typically called from ISR to complete a service routine task out of ISR execution context.\n\nThe driver implementation shown in the ioPermDriver does not use any ISR or DPC, while it just implements I/O control command used to get access the I/O permission bitmap.\n\nOur driver in fact exports just two specific features: enabling and disabling the direct I/O ports access, implemented via a IOCTL request. Same result can be obtained in Linux calling the syscall iopl (ioperm can be used just for the first 0x3ff ports for historical reasons). \n\nDriver entry point is implemented in the following function:\n\n```\nKe386SetIoAccessMap(1, pIOPM);\n```\n\nSuch function accepts a pointer to ``DRIVER_OBJECT`` structure which is an object built by Windows upon the driver activation. The parameter ``RegistryPath`` is a unicode string which represents the registry path name used for configuring the driver.\n\nThe returned value is processed by the I/O Manager. If such value is not ``STATUS_SUCCESS``, the driver is terminated and removed from memory. \n\nThis function creates a device object and the related name and then registers the dispatch entry points implemented in our code by the functions ``ioperm_create``, ``ioperm_close`` and ``ioperm_devcntrl``. Such functions process the IRPs built by I/O Manager as result of a I/O system request. \n\nTo build the binary code of the driver we can rely on the Microsoft Driver Development Kit (DDK).\nDDK provides a tools and libraries to create the driver binary which typically has as file extension ``.sys``. The driver is eventually installed in a specific system directory (``system32\\drivers``).\n\nIn order to complete the driver installation, it must be registered into the Windows Register. This operation can be by using a specific ``.REG`` like the following:\n```\nWindows Registry Editor Version 5.00\n\n[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\ioperm]\n\"Type\"=dword:00000001\n\"ErrorControl\"=dword:00000001\n\"Start\"=dword:00000002\n\"DisplayName\"=\"ioperm\"\n\"ImagePath\"=\"System32\\\\DRIVERS\\\\ioperm.sys\"\n```\n\nOnce installed, the configration of the driver will be visible through the Device Manager (as shown in Fig.2). \n\nWe have implemented a simple user space application for Windows and Linux (``ioPermTest``) which probes the parallel port. \nSuch device is typically mapped at port addresses ``0x278``, ``0x378``, ``0x3BC``. \nThe parallel port data register is located at offset 0 and it is a 8 bit data latch. \nA program can detect the presence of such device writing and then reading back a byte using a specific pattern \n(skipping typical values used for pull-down or pull-up like ``0x00`` or ``0xFF``).\n\n``ioPermTest`` can be compiled using either Visual Studio (Windows) or GNU C++ (Windows/Linux/etc).\n\n![Windows Device Manager](pics/iodevman.jpg)\n\n# Conclusion\nLooking back the Linux versions we discovered that the syscalls ``ioperm`` and ``iopl`` have been added since earlier versions.\nWe are not surprised of such thing as well as we are not surprised that Microsoft has decided not to do that.\n\n# References\n- [1] Intel - \"Intel Architecture Software Developer’s Manual - Volume 1:Basic Architecture\", Intel, 1999\n- [2] Intel - \"Intel Architecture Software Developer’s Manual, Volume 3\" Intel, 1999\n- [3] P.G. Viscarola, W.A. Mason - \"Windows NT - Device Driver Development\", MTP, 1999\n- [5] http://www.ddj.com/articles/1996/9605/\n- [6] http://www.beyondlogic.org/porttalk/porttalk.htm\n- [7] http://www.microsoft.com/whdc/devtools/ddk/default.mspx\n- [8] http://lxr.linux.no/linux-bk+v2.6.11.5/arch/i386/kernel/ioport.c\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feantcal%2Fioperm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feantcal%2Fioperm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feantcal%2Fioperm/lists"}