Zola 2023-09-29T00:00:00+00:00 https://codentium.com/atom.xml Setting up Intel SGX 2023-09-29T00:00:00+00:00 2023-09-29T00:00:00+00:00 https://codentium.com/setting-up-intel-sgx/ <h1 id="setting-up-intel-sgx">Setting up Intel SGX</h1> <p>Since my research partially focuses on Intel SGX, I often find myself having to go through the process of setting up Intel SGX on various machines. Unfortunately, this process consists of multiple steps and has changed quite a bit over the years. In addition, most documentation focuses on specific Linux distributions, usually older versions, such as Ubuntu 20.04 LTS, and many resources handwave the process by providing a Docker image instead. In the meanwhile the Intel SGX driver has finally been mainlined into the Linux kernel <sup>[1]</sup>, and as such the <a href="https://github.com/intel/linux-sgx-driver">linux-sgx-driver</a> is no longer necessary. As a result, my notes focus on setting up Intel SGX on a system with a Linux kernel that ships the Intel SGX driver (Linux 5.11 and later).</p> <p>Furthermore, there are currently two different implementations of Intel SGX: EPID (Enhanced Privacy ID) and DCAP (Data Center Attestation Primitives). I won't go into the details of the differences between these two implementations, but you can read more about these in the <a href="https://cdrdv2-public.intel.com/671370/ww10-2016-sgx-provisioning-and-attestation-final.pdf">EPID whitepaper</a> and the <a href="https://cdrdv2-public.intel.com/671314/intel-sgx-support-for-third-party-attestation.pdf">DCAP whitepaper</a>. While developers should aim to use DCAP-based Intel SGX rather than EPID-based Intel SGX, as the latter is slowly being phased out (the Intel Xeon E-23xx series is the latest platform to support EPID-based Intel SGX), my notes will focus on setting up Intel SGX on EPID-based platforms as that is what I have mostly been working with. However, I might update these notes in the future for Intel DCAP.</p> <h1 id="finding-a-compatible-platform">Finding a compatible platform</h1> <p>First, we will need to find a platform that offers Intel SGX. This depends on both the processor and the motherboard, and more specifically the UEFI implementation provided by the OEM.</p> <p>To determine if a given processor supports Intel SGX or not, you can look up the specification of the processor on <a href="https://ark.intel.com">https://ark.intel.com</a>. If Intel SGX is supported by the processor, there will be an entry for <strong>Intel Software Guard Extensions (Intel SGX)</strong> with either <strong>Yes with Intel ME</strong> or <strong>Yes with Intel SPS</strong>.</p> <p>Here is a list of processor families with some processors in each family supporting the EPID-based version of Intel SGX:</p> <ul> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/37572/products-formerly-skylake.html">Skylake</a> (e.g. Intel Core i7-6700K)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/82879/products-formerly-kaby-lake.html">Kaby Lake</a> (e.g. Intel Core i7-7700K)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/97787/products-formerly-coffee-lake.html">Coffee Lake</a> (e.g. Intel Core i7-8700K)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/97787/products-formerly-coffee-lake.html">Coffee Lake Refresh</a> (e.g. Intel Core i9-9900K)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/90354/products-formerly-comet-lake.html">Comet Lake</a> (e.g. Intel Core i9-10900K)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/192985/products-formerly-rocket-lake.html">Rocket Lake (Intel Xeon E-23xx series)</a> (e.g. Intel Xeon E-2314)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/74979/products-formerly-ice-lake.html">Ice Lake</a> (e.g. Intel Core i7-1065G7)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/88759/products-formerly-tiger-lake.html">Tiger Lake (Intel Core 11th gen B series)</a> (e.g. Intel Core i9-11900KB)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/83915/products-formerly-geimini-lake.html">Gemini Lake</a> (e.g. Intel Celeron J4105)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/197862/products-formerly-gemini-lake-refresh.html">Gemini Lake Refresh</a> (e.g. Intel Celeron J4125)</li> </ul> <p>Here is a similar list for processors supporting the DCAP-based Intel SGX:</p> <ul> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/series/204098/3rd-generation-intel-xeon-scalable-processors.html">Ice Lake SP</a> (e.g. Intel Xeon Silver 4314)</li> <li><a href="https://ark.intel.com/content/www/us/en/ark/products/codename/126212/products-formerly-sapphire-rapids.html">Sapphire Rapids SP</a> (e.g. Intel Xeon Silver 4416+)</li> </ul> <p>Determining whether the motherboard itself supports Intel SGX beforehand is a little bit more difficult, but it usually helps to check the motherboard manual. Usually the manual will have an overview of all the options provided by the BIOS/UEFI, including an option for <strong>Software Guard eXtensions (SGX)</strong> which can be set to <strong>Disabled</strong>, <strong>Software-Controlled</strong> or <strong>Enabled</strong>. If such an option is not available, I recommend looking for a different motherboard/platform instead.</p> <blockquote> <p>Depending on the platform, it may not be possible to set the <strong>Software Guard eXtensions (SGX)</strong> setting to <strong>Enabled</strong>. Instead some platforms only allow you to configure the setting to <strong>Software-Controlled</strong> or <strong>Disabled</strong>. This is perfectly fine, as we can still enable Intel SGX from the operating system when the setting is set to <strong>Software-Controlled</strong>.</p> </blockquote> <h1 id="enabling-intel-sgx">Enabling Intel SGX</h1> <p>Enter the UEFI setup menu to enable Intel SGX. A quick way of entering the UEFI setup menu on Ubuntu, is by invoking the following command from a terminal:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo systemctl reboot --firmware-setup </span></code></pre> <p>Alternatively, you can enter the UEFI setup menu by pressing a specific key immediately after the machine is powered on (usually <span class="keycap">F2</span> or <span class="keycap">Del</span>).</p> <p>In my case the option to enable Intel SGX can be accessed by selecting the <strong>Advanced</strong> tab, followed by the <strong>CPU Configuration</strong> menu, where it is available as <strong>Software Guard Extensions (SGX)</strong>. However, the option may be available elsewhere in your UEFI setup menu depending on the exact platform. Make sure that the option is set to either <strong>Enabled</strong> or <strong>Software-Controlled</strong> as shown in Figure 1.</p> <center> <figure> <p><img src="https://codentium.com/setting-up-intel-sgx/sgx-bios.png" alt="SGX UEFI setting" /></p> <figcaption> <p><strong>Figure 1:</strong> The <strong>Software Guard Extensions (SGX)</strong> setting in the UEFI setup menu.</p> </figcaption> </figure> </center> <p>On some platforms, there may also be a setting named <strong>SGX Launch Control Policy</strong> with the options <strong>Intel Locked</strong>, <strong>Locked</strong> and <strong>Unlocked</strong>. Ensure that this setting is set to <strong>Unlocked</strong>, otherwise the SGX kernel driver will fail to load.</p> <h2 id="software-controlled-sgx">Software-Controlled SGX</h2> <p>If the option is set to <strong>Software-Controlled</strong>, then you will need to clone, build and run the <a href="https://github.com/intel/sgx-software-enable">sgx-software-enable tool</a> to enable Intel SGX.</p> <p>Make sure to install the required build dependencies:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo apt -y install build-essential git </span></code></pre> <p>Clone the repository as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git clone https://github.com/intel/sgx-software-enable </span></code></pre> <p>Then <code>cd</code> into the directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cd sgx-software-enable </span></code></pre> <p>Run <code>make</code> to build the tool:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make </span></code></pre> <p>Run the following command to enable Intel SGX:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo ./sgx-enable </span></code></pre> <p>Finally reboot the system for the setting to take effect:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo reboot </span></code></pre> <p>At this point you should have a platform with support for Intel SGX and where the feature has been enabled.</p> <h1 id="checking-your-intel-sgx-setup">Checking your Intel SGX Setup</h1> <p>Since setting up Intel SGX consists of multiple steps, it is useful if we can check this using a diagnostics tool. Fortunately, <a href="https://edp.fortanix.com">Fortanix</a> provides such a tool called <code>sgx-detect</code>. However, to install the tool, we will first need to install Rust.</p> <p>First, make sure to install <code>curl</code> (not the <code>snap</code> version):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo apt -y install curl </span></code></pre> <p>Then we can install <code>rustup</code> to manage our Rust toolchain(s):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh </span></code></pre> <p>Source the newly added environment variables:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>source &quot;$HOME/.cargo/env&quot; </span></code></pre> <p>To build the <code>sgx-detect</code> tool, we will need to install the nightly version of the Rust toolchain:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>rustup install nightly </span></code></pre> <p>In addition, we will also need to add the <code>x86_64-fortanix-unknown-sgx</code> target:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>rustup target add x86_64-fortanix-unknown-sgx --toolchain nightly </span></code></pre> <p>We will also need to install some build dependencies to the build the <code>sgx-detect</code> tool:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo apt -y install build-essential pkg-config libssl-dev protobuf-compiler </span></code></pre> <p>Finally, we can use Cargo to install the Fortanix tools, including <code>sgx-detect</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo install fortanix-sgx-tools sgxs-tools </span></code></pre> <p>To run the tool with the appropriate runner, we will need to add the following to <code>.cargo/config</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[target.x86_64-fortanix-unknown-sgx] </span><span>runner = &quot;ftxsgx-runner-cargo&quot; </span></code></pre> <p>Then we should be able to run <code>sgx-detect</code>, which should output something like the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>✔ SGX instruction set </span><span> ✔ CPU support </span><span> ✔ CPU configuration </span><span> ✔ Enclave attributes </span><span> ✔ Enclave Page Cache </span><span> SGX features </span><span> ✔ SGX2 ✔ EXINFO ✔ ENCLV ✔ OVERSUB ✔ KSS </span><span> Total EPC size: 188.0MiB </span><span>? Flexible launch control </span><span> ✔ CPU support </span><span> ? CPU configuration </span><span> ? Able to launch production mode enclave </span><span>✘ SGX system software </span><span> ✘ SGX kernel device </span><span> ✘ libsgx_enclave_common </span><span> ✘ AESM service </span><span> </span><span>🕮 Flexible launch control &gt; CPU configuration </span><span>Your hardware supports Flexible Launch Control, but whether it&#39;s enabled could not be determined. More information might be available by re-running this program with sudo. Would you like to do that? </span><span>(not supported yet) </span><span> </span><span>(run with `--verbose` for more details) </span><span> </span><span>More information: https://edp.fortanix.com/docs/installation/help/#flc-cpu-configuration </span><span> </span><span>🕮 SGX system software &gt; SGX kernel device </span><span>Permission denied while opening the SGX device (/dev/sgx/enclave, /dev/sgx or /dev/isgx). Make sure you have the necessary permissions to create SGX enclaves. If you are running in a container, make sure the device permissions are correctly set on the container. </span><span> </span><span>(run with `--verbose` for more details) </span><span> </span><span>More information: https://edp.fortanix.com/docs/installation/help/#sgx-driver </span><span> </span><span>🕮 SGX system software &gt; AESM service </span><span>AESM could not be contacted. AESM is needed for launching enclaves and generating attestations. </span><span> </span><span>Please check your AESM installation. </span><span> </span><span>(run with `--verbose` for more details) </span><span> </span><span>More information: https://edp.fortanix.com/docs/installation/help/#aesm-service </span></code></pre> <p>As we can see, we are still missing parts of the software stack, such as the AESM service and the driver.</p> <h1 id="intel-sgx-sdk">Intel SGX SDK</h1> <p>Up next, we will build and install the Intel SGX SDK for Linux.</p> <p>First, we will have to install a number of build dependencies to build the Intel SGX SDK for Linux:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo apt -y install build-essential ocaml ocamlbuild automake autoconf libtool wget python-is-python3 libssl-dev git cmake perl libcurl4-openssl-dev protobuf-compiler libprotobuf-dev debhelper cmake reprepro unzip pkgconf libboost-dev libboost-system-dev libboost-thread-dev lsb-release libsystemd0 </span></code></pre> <p>To build the Intel SGX SDK for Linux, I recommend using one of the <strong>reproducible</strong> tags of the <a href="https://github.com/intel/linux-sgx">linux-sgx repository</a>, as these are used to build the Docker images. As of writing, the latest version is <strong>sgx_2.21_reproducible</strong>. We can clone that tag as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git clone https://github.com/intel/linux-sgx.git -b sgx_2.21_reproducible </span></code></pre> <p>Then <code>cd</code> into the repository:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cd linux-sgx </span></code></pre> <p>To download the submodules and the prebuilt binaries, we need to invoke <code>make preparation</code> as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make preparation </span></code></pre> <p>Then we can build the installer for the Intel SGX SDK for Linux as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make sdk_install_pkg </span></code></pre> <p>If the installer built successfully, you should be able to run it as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./linux/installer/bin/sgx_linux_x64_sdk_2.21.100.1.bin </span></code></pre> <p>It will prompt you whether you want to install it in the current directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Do you want to install in current directory? [yes/no] : </span></code></pre> <p>Type <strong>no</strong>, followed by <span class="keycap">Enter</span>. Instead type your home directory (e.g. <code>/home/user</code>), such that it will be installed as <code>/home/user/sgxsdk</code>. It should output something like the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Unpacking Intel SGX SDK ... done. </span><span>Verifying the integrity of the install package ... done. </span><span>Installing Intel SGX SDK ... done. </span><span>/tmp/sgx-sdk-72hs80 ~ </span><span>~ </span><span>uninstall.sh script generated in /home/user/sgxsdk </span><span> </span><span>Installation is successful! The SDK package can be found in /home/user/sgxsdk </span><span> </span><span>Please set the environment variables with below command: </span><span> </span><span>source /home/user/sgxsdk/environment </span></code></pre> <p>Run the following to source the newly created environment variables:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>source ~/sgxsdk/environment </span></code></pre> <p>Finally, add the following to the bottom of your <code>~/.bashrc</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>. &quot;$HOME/sgxsdk/environment&quot; </span></code></pre> <h1 id="intel-sgx-psw">Intel SGX PSW</h1> <p>Now that we have the Intel SGX SDK installed, we can build the Intel SGX PSW which contains the platform software, i.e. the AESM service that needs to be installed. First, make sure your terminal is pointing to the <code>linux-sgx</code> repository:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cd linux-sgx </span></code></pre> <p>Then we can simply build the PSW installer package as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make psw_install_pkg </span></code></pre> <p>If everything went well, you should be able to run the installer as root:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo ./linux/installer/bin/sgx_linux_x64_psw_2.21.100.1.bin </span></code></pre> <p>After installing the Intel SGX PSW package, we should have the AESM service up and running. Invoking the <code>sgx-detect</code> command should now tell us that we have a SGX kernel device, libsgx_enclave_common as well as the AESM service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Detecting SGX, this may take a minute... </span><span>✔ SGX instruction set </span><span> ✔ CPU support </span><span> ✔ CPU configuration </span><span> ✔ Enclave attributes </span><span> ✔ Enclave Page Cache </span><span> SGX features </span><span> ✔ SGX2 ✔ EXINFO ✔ ENCLV ✔ OVERSUB ✔ KSS </span><span> Total EPC size: 188.0MiB </span><span>✔ Flexible launch control </span><span> ✔ CPU support </span><span> ? CPU configuration </span><span> ✔ Able to launch production mode enclave </span><span>✘ SGX system software </span><span> ✔ SGX kernel device (/dev/sgx_enclave) </span><span> ✔ libsgx_enclave_common </span><span> ✔ AESM service </span><span> ✘ Able to launch enclaves </span><span> ✘ Debug mode </span><span> ✔ Production mode </span><span> </span><span>🕮 SGX system software &gt; Able to launch enclaves &gt; Debug mode </span><span>The enclave could not be launched. </span><span> </span><span>(run with `--verbose` for more details) </span><span> </span><span>More information: https://edp.fortanix.com/docs/installation/help/#run-enclave-debug </span></code></pre> <p>If you see the above output where you may not be able to launch (debug) enclaves, it may help to reboot your system:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo reboot </span></code></pre> <p>Invoking the <code>sgx-detect</code> command should output the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Detecting SGX, this may take a minute... </span><span>✔ SGX instruction set </span><span> ✔ CPU support </span><span> ✔ CPU configuration </span><span> ✔ Enclave attributes </span><span> ✔ Enclave Page Cache </span><span> SGX features </span><span> ✔ SGX2 ✔ EXINFO ✔ ENCLV ✔ OVERSUB ✔ KSS </span><span> Total EPC size: 188.0MiB </span><span>✔ Flexible launch control </span><span> ✔ CPU support </span><span> ? CPU configuration </span><span> ✔ Able to launch production mode enclave </span><span>✔ SGX system software </span><span> ✔ SGX kernel device (/dev/sgx_enclave) </span><span> ✔ libsgx_enclave_common </span><span> ✔ AESM service </span><span> ✔ Able to launch enclaves </span><span> ✔ Debug mode </span><span> ✔ Production mode </span><span> ✔ Production mode (Intel whitelisted) </span><span> </span><span>You&#39;re all set to start running SGX programs! </span></code></pre> <p>At this point you should be able to build and run any of the examples in <code>~/sgxsdk/SampleCode</code>.</p> <h1 id="references">References</h1> <ol> <li><a href="https://www.phoronix.com/news/Intel-SGX-Linux-5.11">https://www.phoronix.com/news/Intel-SGX-Linux-5.11</a></li> </ol> Building the Linux Asahi kernel 2023-03-20T00:00:00+00:00 2023-03-20T00:00:00+00:00 https://codentium.com/building-the-linux-asahi-kernel/ <h1 id="introduction">Introduction</h1> <p>Since the announcement of the <a href="https://asahilinux.org/2022/03/asahi-linux-alpha-release/">Asahi Linux Alpha Release</a>, the team behind Asahi Linux provides a very simple script that you can run from a terminal on MacOS to install AsahiLinux:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>curl https://alx.sh | sh </span></code></pre> <p>If you carefully follow the instructions while running the shell script, you should end up with a functional ArchLinux-based Linux system (depending on the option you chose during the installation, of course). Since support for Apple Silicon is still being upstreamed to the Linux kernel, ArchLinux does not yet support Apple Silicon out of the box. Instead Asahi Linux provides its own packages for ArchLinux to support Apple Silicon hardware, including their fork of the Linux kernel through the <code>linux-asahi</code> package. These PKGBUILDs can be found on <a href="https://github.com/AsahiLinux/PKGBUILDs.git">Github</a>.</p> <p>As such, if you are trying to build a Linux kernel module, for example, you may need to install the <code>linux-asahi-headers</code> package (<strong>not</strong> the <code>linux-aarch64-headers</code> package):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo pacman -Syu linux-asahi-headers </span></code></pre> <p>Sometimes it might be interesting to try out a newer version of the Linux kernel, or to run the Linux kernel with custom patches. This guide shows how to build m1n1 and Linux Asahi from their respective source code using the provided PKGBUILDs, as well as how to modify the PKGBUILDs.</p> <h1 id="building-m1n1-and-the-linux-asahi-kernel-from-source">Building m1n1 and the Linux Asahi kernel from source</h1> <p>First, we clone the PKGBUILDs repository:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git clone https://github.com/AsahiLinux/PKGBUILDs.git </span></code></pre> <p>Then we point the terminal to the m1n1 directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pushd PKGBUILDs/m1n1 </span></code></pre> <p>Run <code>makepkg -s</code> to build the m1n1 package:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>makepkg -s </span></code></pre> <p>If everything went well, you should now be able to install the m1n1 package using <code>pacman</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo pacman -U m1n1-1.2.3-1-aarch64.pkg.tar.xz </span></code></pre> <p>Finally, run <code>popd</code> to go back to the original directory.</p> <p>To build the Linux kernel, we need to install version 1.62 of the Rust toolchain (at the time of writing). We first install <code>rustup</code> through <code>pacman</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo pacman -Syu rustup </span></code></pre> <p>Then we use rustup to install the toolchain:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>rustup toolchain install 1.62.0 </span></code></pre> <p>Similar to building the m1n1 package, we point the terminal to the linux-asahi directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pushd PKGBUILDs/linux-asahi </span></code></pre> <p>Run <code>makepkg -s</code> with <code>MAKEFLAGS</code> set to <code>-j8</code> (where 8 is the number of CPU cores):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>MAKEFLAGS=&quot;-j8&quot; makepkg -s </span></code></pre> <p>Install the linux-asahi package using <code>pacman</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo pacman -U linux-asahi-6.1.asahi3-1-aarch64.pkg.tar.xz linux-asahi-headers-6.1.asahi3-1-aarch64.pkg.tar.xz </span></code></pre> <p>You may also want to install the linux-asahi-edge if you are normally running linux-asahi-edge (make sure you have <code>mesa-asahi-edge</code> installed as well):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo pacman -U linux-asahi-edge-6.1.asahi3-1-aarch64.pkg.tar.xz </span></code></pre> <p>Update the GRUB configuration file to reference the newly installed Linux kernel:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo update-grub </span></code></pre> <p>Finally, run <code>popd</code> to go back to the original directory.</p> <p>If everything went well, you should be able to boot the system with the new Linux kernel.</p> <h1 id="updating-m1n1-and-linux-asahi">Updating m1n1 and Linux Asahi</h1> <p>The released versions of m1n1 can be found on <a href="https://github.com/AsahiLinux/m1n1/tags">Github</a>. For instance, to install m1n1 1.2.6, we can simply edit <code>pkgver</code> in <code>PKGBUILDs/m1n1/PKGBUILD</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pkgname=m1n1 </span><span>pkgver=1.2.6 </span><span>pkgrel=1 </span></code></pre> <p>Then we need to run <code>updpkgsums</code> to update the checksums and we should be able to install the package as before:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pushd PKGBUILDs/m1n1 </span><span>updpkgsums </span><span>makepkg -s </span><span>sudo pacman -U m1n1-v1.2.6-1-aarch64.pkg.tar.xz </span><span>popd </span></code></pre> <p>Similarly, the released versions of Linux Asahi can be found on <a href="https://github.com/AsahiLinux/linux/tags">Github</a> too. To install Linux Asahi 6.2.11, we can set <code>_rcver</code> to 6.2 and <code>_asahirel</code> to 11 in <code>PKGBUILDs/linux-asahi/PKGBUILD</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>_rcver=6.2 </span><span>#_rcrel= </span><span>_asahirel=11 </span><span>pkgrel=1 </span></code></pre> <p>Also update <code>_m1n1_version</code>, if you changed the version of m1n1:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>_m1n1_version=1.2.6 </span></code></pre> <p>Update the package checksums and install the new package as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pushd PKGBUILDs/linux-asahi </span><span>updpkgsums </span><span>MAKEFLAGS=&quot;-j8&quot; makepkg -s </span><span>sudo pacman -U linux-asahi-6.2.asahi11-1-aarch64.pkg.tar.xz linux-asahi-edge-6.2.asahi11-1-aarch64.pkg.tar.xz linux-asahi-headers-6.2.asahi11-1-aarch64.pkg.tar.xz </span><span>popd </span><span>sudo update-grub </span></code></pre> <p>If everything went well, you should now be able to boot the system with linux-asahi-6.2-11. In addition, running <code>uname -a</code> should show something like the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Linux asahi 6.2.0-asahi-11-1-edge-ARCH #2 SMP PREEMPT_DYNAMIC Mon, 20 Mar 2023 07:17:32 +0000 aarch64 GNU/Linux </span></code></pre> <h1 id="patching-the-linux-kernel">Patching the Linux kernel</h1> <p>To start writing and applying patches for the Linux kernel, we need to clone the Linux source code for the version that we installed using the PKGBUILDs (e.g. the tag for <code>linux-asahi-6.2-11</code> is <code>asahi-6.2-11</code>). We will make sure to use <code>--depth=1</code> to minimize the amount of disk space used:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git clone https://github.com/AsahiLinux/linux -b asahi-6.2-11 --depth=1 </span></code></pre> <p>After modifying the Linux source code in the <code>linux</code> directory, we can simply create a patch using <code>git diff</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git diff &gt; ../PKGBUILDs/asahi-linux/0001-my-patch.patch </span></code></pre> <p>Add the patch(es) to <code>source</code> in <code>PKGBUILDs/linux-asahi/PKGBUILD</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>source=( </span><span> https://github.com/AsahiLinux/linux/archive/${_commit_id}.tar.gz </span><span> config # the main kernel config file </span><span> config.edge # overrides for linux-asahi-edge </span><span> 0001-my-patch.patch </span></code></pre> <p>Update the package checksums:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pushd PKGBUILDs/linux-asahi </span><span>updpkgsums </span></code></pre> <p>Make sure the <code>patch</code> command is available:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo pacman -Syu patch </span></code></pre> <p>Build the package (you may need to pass <code>-f</code> if you already had the package built);</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>MAKEFLAGS=&quot;-j8&quot; makepkg -s </span></code></pre> <p>Install the patched kernel using <code>pacman</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo pacman -U linux-asahi-6.2.asahi11-1-aarch64.pkg.tar.xz linux-asahi-edge-6.2.asahi11-1-aarch64.pkg.tar.xz linux-asahi-headers-6.2.asahi11-1-aarch64.pkg.tar.xz </span></code></pre> <p>Update the GRUB configuration file to reference the newly installed Linux kernel:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo update-grub </span></code></pre> <p>Finally, run <code>popd</code> to go back to the original directory.</p> <p>If everything went well, you should be able to boot the system with the patched Linux kernel.</p> <h1 id="building-linux-kernel-modules">Building Linux kernel modules</h1> <p>If you are building Linux kernel modules for Asahi Linux, you need to make sure the build directory is present in <code>/lib/modules/$(uname -r)/build</code>.</p> <p>For the base config, you can simply copy the build directory from the PKGBUILD you built locally:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo cp -r ~/PKGBUILDs/linux-asahi/src/linux-asahi-6.2-11/build/base /lib/modules/6.2.0-asahi-11-1-ARCH/build </span></code></pre> <p>For the edge config, the command would be as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo cp -r ~/PKGBUILDs/linux-asahi/src/linux-asahi-6.2-11/build/edge /lib/modules/6.2.0-asahi-11-1-edge-ARCH/build </span></code></pre> <p>You can also create symbolic links if you prefer that instead. With the <code>build</code> directory available, you should be able to simply build external Linux kernel modules.</p> Setting up Prosody 2023-03-04T00:00:00+00:00 2023-03-04T00:00:00+00:00 https://codentium.com/guides/alpine/prosody/ <h1 id="installation">Installation</h1> <p>Install Prosody:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add prosody </span></code></pre> <p>We also need to install lua-unbound to use <code>prosodyctl</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add lua-unbound@testing </span></code></pre> <h1 id="configuration">Configuration</h1> <p>The configuration file for Prosody can be found in <code>/etc/prosody/prosody.cnf.lua</code>. Getting started with Prosody is relatively straight-forward. Just comment out the <code>VirtualHost</code> directive for &quot;localhost&quot;, and add one for your domain instead:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>--VirtualHost &quot;localhost&quot; </span><span>VirtualHost &quot;example.com&quot; </span></code></pre> <p>It is also possible to add more than one domain to your configuration.</p> <p>If we already set up Let's Encrypt certificates, we can simply run the following command to import them:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/usr/bin/prosodyctl --root cert import /etc/letsencrypt/live </span></code></pre> <p>However, when renewing the certificates, it is a good idea to add this as a <code>--deploy-hook</code> to your renewal command, e.g.:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>certbow renew --deply-hook &quot;prosodyctl --root cert import /etc/letsencrypt/live&quot; </span></code></pre> <p>The Prosody documentation has further information about <a href="https://prosody.im/doc/certificates">setting up certificates</a> as well as <a href="https://prosody.im/doc/letsencrypt">setting up Let's Encrypt with Prosody</a>.</p> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/prosody start </span><span>rc-update add prosody </span></code></pre> <p><a href="https://prosody.im/doc/creating_accounts">Create an XMPP account</a>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>prosodyctl adduser [email protected] </span></code></pre> <h1 id="port-forwarding">Port forwarding</h1> <p>The Prosody documentation provides <a href="https://prosody.im/doc/ports">a list of ports</a> used by Prosody that we need to set up using <code>ufw</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow 5000/tcp </span><span>ufw allow 5222/tcp </span><span>ufw allow 5269/tcp </span><span>ufw allow 5280/tcp </span><span>ufw allow 5281/tcp </span><span>ufw allow 5347/tcp </span><span>ufw allow 5582/tcp </span></code></pre> <h1 id="setting-up-file-sharing">Setting up file sharing</h1> <p>Setting up file sharing is as simple as enabling the <code>http_file_share</code> component:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Component &quot;xmpp-serve.example.com&quot; &quot;http_file_share&quot; </span><span>http_file_share_expires_after = 3 * 24 * 60 * 60 </span></code></pre> <p>In addition, you can set a number of parameters such as when the file expires (e.g. after three days). Here is a <a href="https://prosody.im/doc/modules/mod_http_file_share">list of the available parameters</a>.</p> <p>If you have other domains available, then make sure to add the following to each of them below their <code>VirtualHost</code> directive to enable file sharing:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>disco_items = { </span><span> { &quot;xmpp-serve.exmaple.com&quot;, &quot;file sharing service&quot; } </span><span>} </span></code></pre> Building a Linux kernel with Rust support on Gentoo 2023-01-25T00:00:00+00:00 2023-01-25T00:00:00+00:00 https://codentium.com/building-a-linux-kernel-with-rust-support-on-gentoo/ <h1 id="some-background">Some background</h1> <p>After watching Alex Gaynor and Geoffrey Thomas their <a href="https://ldpreload.com/p/kernel-modules-in-rust-lssna2019.pdf">presentation</a> and <a href="https://www.youtube.com/watch?v=RyY01fRyGhM">video</a> about writing Linux kernel modules, I have been trying to keep a close eye on their progress. Especially since I have been playing around with the now archived <a href="https://github.com/fishinabarrel/linux-kernel-module-rust">linux-kernel-module-rust</a> repository, to port over one of my toy Linux kernel modules to fiddle around with physical memory and page tables from C to Rust.</p> <p>Three years after the presentation we are now looking at initial Rust support being part of the <a href="https://lwn.net/Articles/910762/">Linux 6.1 kernel</a>. Shortly after that <a href="https://twitter.com/rnstlr">Raphael Nestler</a> wrote some interesting blog posts about building out-of-tree Rust kernel modules on ArchLinux (you can read <a href="https://blog.rnstlr.ch/building-an-out-of-tree-rust-kernel-module.html">part one</a> and <a href="https://blog.rnstlr.ch/building-an-out-of-tree-rust-kernel-module-part-two.html">part two</a> here). These articles got me wondering about whether this is possible on Gentoo. Of course, while we focus on Gentoo here, most of this knowledge is also applicable to other Linux distributions.</p> <h1 id="installing-the-linux-kernel">Installing the Linux kernel</h1> <p>As not everyone is familiar with Gentoo, we will first take a look at the various ways of installing a Linux kernel. In fact, many people, including many Gentoo users, do not seem to be aware that Gentoo ships <a href="https://packages.gentoo.org/packages/sys-kernel/gentoo-kernel-bin">sys-kernel/gentoo-kernel-bin</a>, which is a precompiled binary of the Linux kernel with Gentoo-specific patches which has been available since <a href="https://gitweb.gentoo.org/repo/gentoo.git/commit/sys-kernel/gentoo-kernel-bin?id=dd2c7f450a36804b02a766ad9185987a85e43b85">January 10, 2020</a>.</p> <p>However, while a precompiled binary of the Linux kernel exists, many users instead compile the Linux kernel from source. While Gentoo offers many flavors of the kernel source, the typical ones are <a href="https://packages.gentoo.org/packages/sys-kernel/vanilla-sources">sys-kernel/vanilla-sources</a>, which provides the Linux kernel source with no modifications, <a href="https://packages.gentoo.org/packages/sys-kernel/gentoo-sources">sys-kernel/gentoo-sources</a>, which provides the Linux kernel source with Gentoo-specific patches and <a href="https://packages.gentoo.org/packages/sys-kernel/git-sources">sys-kernel/git-sources</a>, which provides the Linux kernel source for current release candidates.</p> <p>Once installed, you would use <code>eselect kernel list</code> to list the installed kernel sources and <code>eselect kernel set 1</code> to set the first entry in the list to be the default kernel source to use. More specifically, <code>eselect kernel set 1</code> creates a symbolic link from <code>/usr/src/linux</code> to the directory containing that specific kernel source.</p> <p>After setting up the kernel source, the next step is to get a working kernel configuration. A relatively straightforward to obtain a working kernel configuration is from an existing Linux kernel that can properly boot your system, such as the kernel from another distribution like Ubuntu or ArchLinux, or from <a href="https://packages.gentoo.org/packages/sys-kernel/gentoo-kernel-bin">sys-kernel/gentoo-kernel-bin</a>. Once you having a working kernel configuration, you would typically copy it over using <code>zcat /proc/config.gz &gt; .config</code> or from your <code>/boot</code> directory, and run <code>make olddefconfig</code> before building the Linux kernel. Finally, you can perform further tweaking of the kernel configuration using <code>make menuconfig</code>.</p> <p>If you want to manually build and install the Linux kernel and the kernel modules, you can do so using the following commands:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make -j16 </span><span>make INSTALL_MOD_STRIP=1 modules_install </span><span>make install </span></code></pre> <p>To build your own initramfs, you can look into tools like <a href="https://wiki.gentoo.org/wiki/Dracut">dracut</a>. Alternatively, you can use <a href="https://wiki.gentoo.org/wiki/Genkernel">genkernel</a> to build a Linux kernel and initramfs by simply running something like the following command:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>genkernel --clean --menuconfig --install all </span></code></pre> <p>After building and installing the Linux kernel using either way, we need to run the following command to update GRUB's configuration file:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>grub-mkconfig -o /boot/grub/grub.cfg </span></code></pre> <h1 id="what-are-the-prerequisites-to-enable-rust-in-the-linux-kernel">What are the prerequisites to enable Rust in the Linux kernel?</h1> <p>The <a href="https://blog.rnstlr.ch/building-an-out-of-tree-rust-kernel-module.html">first part</a> of Raphael's blog post provides some good pointers of where to start. More specifically, it points us to a <a href="https://github.com/Rust-for-Linux/rust-out-of-tree-module">basic template</a> for an out-of-tree Linux kernel module in Rust that we can use. One of the requirements listed there is that our Linux kernel has to be built with <code>CONFIG_RUST=y</code>. To check this we can simply use <code>grep</code> on our kernel configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>grep CONFIG_RUST .config </span></code></pre> <p>If we run the above command, there is simply no output, which indicates that the option is not set. So let's run <code>make menuconfig</code> and press <span class="keycap">/</span>, type in Rust and press <span class="keycap">Enter</span> to look for <code>CONFIG_RUST</code>. It should look something like the image below:</p> <center> <figure> <p><img src="https://codentium.com/building-a-linux-kernel-with-rust-support-on-gentoo/menuconfig-rust.png" alt="menuconfig for RUST_CONFIG" /></p> <figcaption> <p><strong>Figure 1:</strong> <code>make menuconfig</code> showing the <code>CONFIG_RUST</code> option and its dependencies.</p> </figcaption> </figure> </center> <p>More specifically, it shows the following text:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Symbol: RUST [=n] </span><span>Type : bool </span><span>Defined at init/Kconfig:1913 </span><span> Prompt: Rust support </span><span> Depends on: HAVE_RUST [=y] &amp;&amp; RUST_IS_AVAILABLE [=n] &amp;&amp; !MODVERSIONS [=y] &amp;&amp; !GCC_PLUGINS [=y] &amp;&amp; !RANDSTRUCT [=n] &amp;&amp; !DEBUG_INFO_BTF [=n] </span><span> Location: </span><span>(1) -&gt; General setup </span><span> -&gt; Rust support (RUST [=n]) </span><span> Selects: CONSTRUCTORS [=n] </span></code></pre> <p>Looking more closely at <code>Depends on:</code>, we can see the other configuration options that need to be set or not for <code>CONFIG_RUST=y</code>. In our case, <code>RUST_IS_AVAILABLE</code> is set to <code>n</code>, while <code>MODVERSIONS</code> and <code>GCC_PLUGINS</code> are set to <code>y</code>. So let's tackle each of these issues, one at a time.</p> <h1 id="ensuring-a-proper-rust-toolchain-is-available">Ensuring a proper Rust toolchain is available</h1> <p>The <a href="https://docs.kernel.org/rust/quick-start.html">Linux documentation</a> provides some helpful instructions to figure out how to check if a proper Rust toolchain is available or not, among other things. In fact, we can run <code>make rustavailable</code> to check if our system is set up accordingly:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make rustavailable </span><span>*** </span><span>*** Rust bindings generator &#39;bindgen&#39; could not be found. </span><span>*** </span><span>make: *** [Makefile:1800: rustavailable] Error 1 </span></code></pre> <p>It turns out that we don't have the <a href="https://packages.gentoo.org/packages/dev-util/bindgen">dev-util/bindgen</a> package installed. So let's unmask it, since there is no stable version available yet:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;dev-util/bindgen ~amd64&quot; &gt;&gt; /etc/portage/package.accept_keywords/rust </span></code></pre> <p>Then we can install it by running <code>emerge bindgen</code>. From looking at the output, it turns out we also need to enable the <code>rustfmt</code> USE-flag:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Calculating dependencies... done! </span><span>[ebuild R ] dev-lang/rust-bin-1.66.1 USE=&quot;rustfmt*&quot; </span><span>[ebuild R ] virtual/rust-1.66.1 USE=&quot;rustfmt*&quot; </span><span>[ebuild N ~] dev-util/bindgen-0.62.0 USE=&quot;-debug&quot; ABI_X86=&quot;32 (64) (-x32)&quot; </span><span> </span><span>The following USE changes are necessary to proceed: </span><span> (see &quot;package.use&quot; in the portage(5) man page for more details) </span><span># required by dev-util/bindgen-0.62.0::gentoo </span><span># required by bindgen (argument) </span><span>&gt;=virtual/rust-1.66.1 rustfmt </span><span># required by virtual/rust-1.66.1::gentoo </span><span># required by dev-util/bindgen-0.62.0::gentoo </span><span># required by bindgen (argument) </span><span>&gt;=dev-lang/rust-bin-1.66.1 rustfmt </span></code></pre> <p>So let's enable that USE-flag:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;virtual/rust rustfmt&quot; &gt;&gt; /etc/portage/package.use/rust </span><span>echo &quot;dev-lang/rust-bin rustfmt&quot; &gt;&gt; /etc/portage/package.use/rust </span></code></pre> <p>Now we run <code>emerge bindgen</code> again, and it succeeds, and from running <code>make rustavailable</code> again, we can see that we now have a working Rust compiler as well as the bindgen crate installed:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>*** </span><span>*** Rust compiler &#39;rustc&#39; is too new. This may or may not work. </span><span>*** Your version: 1.66.1 </span><span>*** Expected version: 1.62.0 </span><span>*** </span><span>*** </span><span>*** Rust bindings generator &#39;bindgen&#39; is too new. This may or may not work. </span><span>*** Your version: 0.62.0 </span><span>*** Expected version: 0.56.0 </span><span>*** </span><span>*** </span><span>*** Source code for the &#39;core&#39; standard library could not be found </span><span>*** at &#39;/opt/rust-bin-1.66.1/lib/rustlib/src/rust/library/core/src/lib.rs&#39;. </span><span>*** </span><span>Rust is available! </span></code></pre> <p>Apparently, we should enable the <code>rust-src</code> USE-flag for <a href="https://packages.gentoo.org/packages/dev-lang/rust">dev-lang/rust</a>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;dev-lang/rust-bin rust-src&quot; &gt;&gt; /etc/portage/package.use/rust </span></code></pre> <p>In addition, the versions of the Rust compiler and the bindgen are a bit new compared to what <code>make rustavailable</code> expects. This makes sense, since the Linux kernel has to support distributions that are not rolling release distributions like <a href="https://packages.debian.org/search?keywords=rust">Debian</a>, which currently ships Rust 1.63 and 1.64, and <a href="https://packages.ubuntu.com/search?keywords=rust">Ubuntu</a>, which currently ships Rust 1.61. Unfortunately, if we try to build the Linux kernel with Rust 1.65 or 1.66.1</p> <p>However, if we run <code>eix dev-lang/rust-bin</code> and <code>eix virtual/rust</code>, we see that Gentoo only provides 1.65.0 and 1.66.1 at the time of writing. Fortunately, Rust 1.62.0 used to be available at <a href="https://gitweb.gentoo.org/repo/gentoo.git/tree/dev-lang/rust-bin/rust-bin-1.62.0.ebuild?id=0d7d7b32c9b472a25fbe998ed6ad196501d383a1">some point</a>, thus we can simply run a local ebuild repository and grab the old ebuilds.</p> <h1 id="setting-up-a-local-repository">Setting up a local repository</h1> <p>The <a href="https://wiki.gentoo.org/wiki/Creating_an_ebuild_repository">Gentoo wiki</a> already provides instructions on how to set up a local repository, but it boils down to running <code>emerge pkgdev</code> to make sure that we have <code>pkgdev</code> installed, and running <code>eselect repository create local</code> to set up the skeleton directory structure for our ebuild repository. Our ebuild repository can then be found at <code>/var/db/repos/local</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cd /var/db/repos/local </span></code></pre> <p>We then need to create the appropriate directories for <code>dev-lang/rust-bin</code> and <code>virtual/rust</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p dev-lang/rust-bin </span><span>mkdir -p virtual/rust </span></code></pre> <p>Then we can download the old ebuilds from their respective commits for <a href="https://gitweb.gentoo.org/repo/gentoo.git/tree/dev-lang/rust-bin/rust-bin-1.62.0.ebuild?id=0d7d7b32c9b472a25fbe998ed6ad196501d383a1">dev-lang/rust-bin</a> and <a href="https://gitweb.gentoo.org/repo/gentoo.git/tree/virtual/rust/rust-1.62.0.ebuild?id=6302c360cd3bbf54c716d1642e329d14046fd722">virtual/rust</a> as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>wget https://gitweb.gentoo.org/repo/gentoo.git/plain/dev-lang/rust-bin/rust-bin-1.62.0.ebuild?id=0d7d7b32c9b472a25fbe998ed6ad196501d383a1 -O dev-lang/rust-bin/rust-bin-1.62.0.ebuild </span><span>wget https://gitweb.gentoo.org/repo/gentoo.git/plain/virtual/rust/rust-1.62.0.ebuild?id=6302c360cd3bbf54c716d1642e329d14046fd722 -O virtual/rust/rust-1.62.0.ebuild </span></code></pre> <p>We then need to create a package manifest for both packages:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pushd dev-lang/rust-bin </span><span>pkgdev manifest </span><span>popd </span><span>pushd virtual/rust </span><span>pkgdev manifest </span><span>popd </span></code></pre> <p>Make sure to run <code>cp /usr/share/portage/config/repos.conf /etc/portage/repos.conf</code> if the following error appears:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pkgdev: error: repos.conf: default repo &#39;gentoo&#39; is undefined or invalid </span></code></pre> <p>Finally, we can run <code>eix-update</code> to update our eix database, and the packages should be available to install. Since bindgen-0.56.0 is still available at the time of writing, we don't have to create our own ebuild for bindgen. Now to ensure that we have the appropriate versions installed, we can pin the versions we want by writing the following to <code>/etc/portage/package.mask/kernel</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>&gt;dev-lang/rust-bin-1.62.0 </span><span>&gt;dev-util/bindgen-0.56.0 </span><span>&gt;virtual/rust-1.62.0 </span></code></pre> <p>We should also make sure to unmask our ebuilds:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;dev-lang/rust-bin ~amd64&quot; &gt;&gt; /etc/portage/package.accept_keywords/rust </span><span>echo &quot;virtual/rust ~amd64&quot; &gt;&gt; /etc/portage/package.accept_keywords/rust </span></code></pre> <p>Then we can simply install Rust and bindgen as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>emerge rust-bin bindgen virtual/rust </span></code></pre> <p>Now if we run <code>make rustavailable</code> again, it should output the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Rust is available! </span></code></pre> <h1 id="building-the-kernel-with-clang">Building the kernel with Clang</h1> <p>We can try to set <code>CONFIG_GCC_PLUGINS=n</code> by deselecting the following option in <code>make menuconfig</code> to set <code>CONFIG_RUST=y</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>General architecture-dependent options ---&gt; </span><span>[ ] GCC plugins </span></code></pre> <p>However, building the Linux kernel with gcc seems to result in the following error:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>arch/x86/tools/insn_decoder_test: error: malformed line 1318434: </span><span>tBb_+0x102&gt; </span></code></pre> <p>Let's try building the kernel (and our initramfs) with Clang instead, as that is the recommended way of building the Linux kernel to enable Rust support. Especially since the bindgen crate relies on libclang, thus LLVM and Clang to produce Rust bindings from C source code, anyway. We can simply achieve this by changing the <code>/etc/genkernel.conf</code> file. First, we will add the <code>LLVM=1 LLVM_IAS=1</code> options to <code>MAKEOPTS</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>MAKEOPTS=&quot;$(portageq envvar MAKEOPTS) LLVM=1 LLVM_IAS=1&quot; </span></code></pre> <p>Then we make sure to point the toolchain used to build the kernel to LLVM/Clang:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>KERNEL_AS=&quot;llvm-as&quot; </span><span>KERNEL_AR=&quot;llvm-ar&quot; </span><span>KERNEL_CC=&quot;clang&quot; </span><span>KERNEL_LD=&quot;ld.lld&quot; </span><span>KERNEL_NM=&quot;llvm-nm&quot; </span><span>KERNEL_OBJCOPY=&quot;llvm-objcopy&quot; </span><span>KERNEL_OBJDUMP=&quot;llvm-objdump&quot; </span><span>KERNEL_READELF=&quot;llvm-readelf&quot; </span><span>KERNEL_STRIP=&quot;llvm-strip&quot; </span><span>KERNEL_RANLIB=&quot;llvm-ranlib&quot; </span></code></pre> <p>We also do the same for building the initramfs:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>UTILS_AS=&quot;llvm-as&quot; </span><span>UTILS_AR=&quot;llvm-ar&quot; </span><span>UTILS_CC=&quot;clang&quot; </span><span>UTILS_CXX=&quot;clang++&quot; </span><span>UTILS_LD=&quot;ld.lld&quot; </span><span>UTILS_NM=&quot;llvm-nm&quot; </span><span>UTILS_OBJCOPY=&quot;llvm-objcopy&quot; </span><span>UTILS_OBJDUMP=&quot;llvm-objdump&quot; </span><span>UTILS_READELF=&quot;llvm-readelf&quot; </span><span>UTILS_RANLIB=&quot;llvm-ranlib&quot; </span></code></pre> <p>Note that we do not set the <code>UTILS_STRIP=&quot;llvm-strip&quot;</code> as this currently results in the following error:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>llvm-strip: error: Link field value 34 in section .rela.plt is not a symbol table </span><span>* ERROR: Failed to strip &#39;/var/tmp/genkernel/gk_dZRzC8UH/util-linux/image/sbin/blkid&#39;! </span><span>* ERROR: create_initramfs(): append_data(): append_util-linux(): populate_binpkg(): gkbuild(): Failed to create binpkg of util-linux-2.37.2! </span><span>* Please consult &#39;/var/log/genkernel.log&#39; for more information and any </span><span>* errors that were reported above. </span></code></pre> <p>As <code>genkernel</code> typically caches some of the build artifacts, we should also make sure to clear the cache to enforce <code>genkernel</code> to rebuild the kernel and initramfs. We can do this by setting the following option to <code>yes</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>CLEAR_CACHEDIR=&quot;yes&quot; </span></code></pre> <p>Now we should be able to run <code>genkernel</code> as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>genkernel --clean --menuconfig --install all </span></code></pre> <h1 id="building-the-kernel-with-rust-support">Building the kernel with Rust support</h1> <p>Finally, we need to make sure that <code>MODVERSIONS=n</code>, thus we need to deselect the following option in <code>make menuconfig</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Enable loading module support ---&gt; </span><span>[ ] Module versioning support </span></code></pre> <p>Now we should be able to set <code>CONFIG_RUST=y</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>General setup ---&gt; </span><span>[*] Rust support </span></code></pre> <p>Then we can simply save the kernel configuration and let <code>genkernel</code> build and install the kernel.</p> <h1 id="kernel-panic">Kernel panic?</h1> <p>After building and installing both the kernel and our initramfs using <code>genkernel</code>, we should now be able to reboot into our kernel. Unfortunately, it seems to result in a kernel panic because <code>busybox sh</code> seems to run into segmentation fault. What is going on?</p> <p>One quick way of testing what is going on with <code>busybox sh</code>, we can build and install <a href="https://packages.gentoo.org/packages/sys-apps/busybox">sys-apps/busybox</a> using Clang and then run it with <a href="https://packages.gentoo.org/packages/dev-util/valgrind">dev-util/valgrind</a>. Fortunately, the <a href="https://wiki.gentoo.org/wiki/Clang">Gentoo wiki</a> explains how to set up Portage to compile and build busybox with Clang. To do so, we first need to create <code>/etc/portage/env/compiler-clang</code> with the following contents:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>COMMON_FLAGS=&quot;-O2 -march=native&quot; </span><span>CFLAGS=&quot;${COMMON_FLAGS}&quot; </span><span>CXXFLAGS=&quot;${COMMON_FLAGS}&quot; </span><span> </span><span>CC=&quot;clang&quot; </span><span>CXX=&quot;clang++&quot; </span><span>AR=&quot;llvm-ar&quot; </span><span>NM=&quot;llvm-nm&quot; </span><span>RANLIB=&quot;llvm-ranlib&quot; </span><span> </span><span>LDFLAGS=&quot;${LDFLAGS} -fuse-ld=lld -rtlib=compiler-rt -unwindlib=libunwind -Wl,--as-needed&quot; </span></code></pre> <p>Then we can tell Portage to build busybox with Clang:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;sys-apps/busybox compiler-clang&quot; &gt;&gt; /etc/portage/package.env/compiler </span></code></pre> <p>Then we install both busybox and valgrind using <code>emerge</code>. Running <code>busybox sh</code> seems to result in the same <code>Segmentation fault.</code> If we run <code>valgrind busybox sh</code> instead, we get a better idea of what is going on:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>valgrind: mmap(0x143000, 1093632) failed in UME with error 22 (Invalid argument). </span><span>valgrind: this can be caused by executables with very large text, data or bss segments. </span></code></pre> <p>Fortunately, the following <a href="https://bugs.kde.org/show_bug.cgi?id=290061">bug report</a> seems to describe a similar issue. Let's try installing busybox without PIE set (as Clang 15 now defaults to using PIE):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>LDFLAGS=&quot;-no-pie&quot; emerge busybox </span></code></pre> <p>Now if we run <code>valgrind busybox sh</code>, we seem to run into another issue resulting in a segmentation fault:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>==1795== Invalid write of size 4 </span><span>==1795== at 0x47F0E0: ash_main (in /bin/busybox) </span><span>==1795== Address 0x4 is not stack&#39;d, malloc&#39;d or (recently) free&#39;d </span></code></pre> <p>After trying to experiment a bit by copying over the ebuild and modifying it a bit to enable the <code>USE_PORTABLE_CODE</code> and <code>DEBUG</code> configuration options when building busybox:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span> busybox_config_option y USE_PORTABLE_CODE </span><span> busybox_config_option y DEBUG </span></code></pre> <p>We do get a working <code>busybox sh</code> that no longer results in a segmentation fault. After inspecting <code>eix busybox</code>, it seems that version 1.35.0-r1 is available too. Let's unmask it and install it to see what happens:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;sys-apps/busybox **&quot; &gt;&gt; /etc/portage/package.accept_keywords/busybox </span><span>emerge busybox </span></code></pre> <p>All right, <code>busybox sh</code> seems to work fine for version 1.35.0-r1 without it resulting in any segmentation faults when built with Clang. So how do we tell <code>genkernel</code> to use this version of busybox instead? Well, <code>genkernel</code> relies on a shell script to install the software <code>/usr/share/genkernel/defaults/software.sh</code>. More specifically, we can change the version of <a href="https://packages.gentoo.org/packages/sys-apps/busybox">sys-apps/busybox</a> that <code>genkernel</code> should build and install (1.34.1-r1 at the time of writing):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>GKPKG_BUSYBOX_PV=&quot;${GKPKG_BUSYBOX_PV:-1.34.1}&quot; </span></code></pre> <p>We can simply change the above line to:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>GKPKG_BUSYBOX_PV=&quot;${GKPKG_BUSYBOX_PV:-1.35.0}&quot; </span></code></pre> <p>Then we can copy over the <code>busybox-1.35.0.tar.bz</code> archive from our distfiles to the distfiles directory used by genkernel:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cp /var/cache/distfiles/busybox-1.35.0.tar.bz2 /usr/share/genkernel/distfiles </span></code></pre> <p>Now we run <code>genkernel</code> again to rebuild and install the kernel:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>genkernel --clean --no-menuconfig --install all </span></code></pre> <h1 id="testing-our-kernel">Testing our kernel</h1> <p>After booting into our new Linux kernel with Rust support, we can run <code>zgrep RUST /proc/config.gz</code> to confirm that Rust support is enabled as shown in Figure 2.</p> <center> <figure> <p><img src="https://codentium.com/building-a-linux-kernel-with-rust-support-on-gentoo/kernel.png" alt="Booting the Linux kernel with Rust support" /></p> <figcaption> <p><strong>Figure 2:</strong> the result of running <code>zgrep RUST /proc/config.gz</code> and <code>uname -a</code> after booting our Linux kernel with Rust support enabled.</p> </figcaption> </figure> </center> <p>Now that we have Rust support enabled in our Linux kernel, we can clone the <a href="https://github.com/Rust-for-Linux/rust-out-of-tree-module">basic template</a> from before and try to compile and load it:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git clone https://github.com/Rust-for-Linux/rust-out-of-tree-module </span></code></pre> <p>Since we need to use the same Rust compiler that we used to build the Linux kernel, we might have to specify <code>PATH=&quot;/usr/bin:$PATH&quot;</code> in case the user has their own Rust compiler installed locally through <a href="https://rustup.rs/">rustup</a>. We should then be able to build the Linux kernel module as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>PATH=&quot;/usr/bin:$PATH&quot; make </span></code></pre> <p>After building the kernel module, we should then be able to load and unload it as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo insmod rust_out_of_tree.ko </span><span>sudo rmmod rust_out_of_tree.ko </span></code></pre> <p>If everything worked correctly, running <code>dmesg</code> should show like the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[54488.118458] rust_out_of_tree: loading out-of-tree module taints kernel. </span><span>[54488.118500] rust_out_of_tree: module verification failed: signature and/or required key missing - tainting kernel </span><span>[54488.119702] rust_out_of_tree: Rust out-of-tree sample (init) </span><span>[54506.127820] rust_out_of_tree: My numbers are [72, 108, 200] </span><span>[54506.127825] rust_out_of_tree: Rust out-of-tree sample (exit) </span></code></pre> <h1 id="conclusion">Conclusion</h1> <p>We had to jump through quite some hoops, but in the end we managed to build and install a Linux kernel with Rust support enabled and build and load a basic kernel module written in Rust. It turns out that we needed an older version of the Rust compiler and bindgen installed to build the Linux kernel with Rust support. Hopefully, Gentoo will make these ebuilds available again for a more streamlined experience.</p> <p>Furthermore, we had to use the Clang toolchain to build the Linux kernel, and optionally our initramfs. While genkernel seems to have proper support for this, we ran into an issue where version 1.34.1 of busybox results in a segmentation fault when compiled with Clang. Fortunately, this issue seems to be solved with version 1.35.0 and it was pretty straightforward to have genkernel use this version of busybox instead.</p> <p>Maybe the <a href="https://packages.gentoo.org/packages/sys-kernel/gentoo-kernel-bin">sys-kernel/gentoo-kernel-bin</a> package will come with Rust support too some day.</p> Accessing physical memory from userspace on Linux 2023-01-10T00:00:00+00:00 2023-01-10T00:00:00+00:00 https://codentium.com/accessing-physical-memory-from-userspace-on-linux/ <h1 id="why-access-physical-memory">Why access physical memory?</h1> <p>The ability to read from and write to arbitrary physical address is a very useful and powerful tool. It can give us some insight into what the operating system is doing under the hood, it can be used to interface with devices that use memory-mapped I/O, such as PCI devices, from userspace and it is in general incredibly useful for (hardware) reverse engineering purposes. Thus, we are going to look at how to access arbitrary physical memory on Linux using the <code>/dev/mem</code> interface. Of course, with great power comes great responsibility, and as full access to <code>/dev/mem</code> poses a huge security risk, you should not be doing this on a production system.</p> <p>Furthermore, since we are trying to access physical memory, we also need to know where the memory of our process is located in physical memory. So we will first be looking at how to map a given virtual address to its physical counterpart.</p> <h1 id="looking-up-physical-addresses">Looking up physical addresses</h1> <p>Before we can actually use the interface to access physical memory, we first need to look at how to look up the physical address for any given virtual address. Fortunately, since version 2.6.25 the Linux kernel provides such an interface through the <code>/proc/&lt;pid&gt;/pagemap</code> file. This file consists of a 64-bit entry for each virtual page describing attributes such as whether the page is present, swapped, etc. as well as the physical frame number if the page is present but not swapped as described in the <a href="https://www.kernel.org/doc/Documentation/vm/pagemap.txt">documentation</a>.</p> <p>Thus, to obtain the physical address for any given virtual address, we first need to open this file. Then we need to divide the virtual address by the page size of the platform, which is typically but not necessarily 4 kiB, and multiply it by 8 to get the file offset to seek to. Finally, we can read the 64-bit entry corresponding to that virtual address.</p> <p>Let's first start with an abstraction for this entry. Since the page can be swapped out, present or neither, we are going to abstract the entry with an enum:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>bitflags::bitflags; </span><span style="color:#f92672;">use </span><span>simple_bits::BitsExt </span><span style="color:#f92672;">as _</span><span>; </span><span> </span><span>#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>SwapType(pub </span><span style="font-style:italic;color:#66d9ef;">u8</span><span>); </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">SWAPPED</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u64 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">1 </span><span style="color:#f92672;">&lt;&lt; </span><span style="color:#ae81ff;">62</span><span>; </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">PRESENT</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u64 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">1 </span><span style="color:#f92672;">&lt;&lt; </span><span style="color:#ae81ff;">63</span><span>; </span><span> </span><span>bitflags! { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>PageEntryFlags: u64 { </span><span> const SOFT_DIRTY = 1 &lt;&lt; 55; </span><span> const EXCLUSIVE = 1 &lt;&lt; 56; </span><span> const SHARED = 1 &lt;&lt; 61; </span><span> } </span><span>} </span><span> </span><span>#[derive(Clone, Debug)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">enum </span><span>PageEntry { </span><span> Unmapped, </span><span> Present { </span><span> pfn: </span><span style="font-style:italic;color:#66d9ef;">u64</span><span>, </span><span> flags: PageEntryFlags, </span><span> }, </span><span> Swapped { </span><span> ty: SwapType, </span><span> offset: </span><span style="font-style:italic;color:#66d9ef;">u64</span><span>, </span><span> flags: PageEntryFlags, </span><span> }, </span><span>} </span></code></pre> <p>Now given the 64-bit entry, we need to map the 64-bit value to the appropriate enum variant and extract the various fields. To achieve that, we implement the <code>From&lt;u64&gt;</code> trait for our enum:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl From</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u64</span><span>&gt; </span><span style="color:#f92672;">for </span><span>PageEntry { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">from</span><span>(</span><span style="font-style:italic;color:#fd971f;">value</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u64</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> flags </span><span style="color:#f92672;">= </span><span>PageEntryFlags::from_bits_truncate(value); </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> value </span><span style="color:#f92672;">&amp; </span><span style="color:#ae81ff;">SWAPPED </span><span style="color:#f92672;">== </span><span style="color:#ae81ff;">SWAPPED </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Swapped { </span><span> ty: SwapType(value.</span><span style="color:#66d9ef;">extract_bits</span><span>(</span><span style="color:#ae81ff;">0</span><span style="color:#f92672;">..</span><span style="color:#ae81ff;">4</span><span>)), </span><span> offset: value.</span><span style="color:#66d9ef;">extract_bits</span><span>(</span><span style="color:#ae81ff;">4</span><span style="color:#f92672;">..</span><span style="color:#ae81ff;">55</span><span>), </span><span> flags, </span><span> } </span><span> } </span><span style="color:#f92672;">else if</span><span> value </span><span style="color:#f92672;">&amp; </span><span style="color:#ae81ff;">PRESENT </span><span style="color:#f92672;">== </span><span style="color:#ae81ff;">PRESENT </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Present { </span><span> pfn: value.</span><span style="color:#66d9ef;">extract_bits</span><span>(</span><span style="color:#ae81ff;">0</span><span style="color:#f92672;">..</span><span style="color:#ae81ff;">55</span><span>), </span><span> flags, </span><span> } </span><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Unmapped </span><span> } </span><span> } </span><span>} </span></code></pre> <p>In addition, since we get the physical page frame number, we implement a helper function to query the page size and use it to calculate the actual physical address:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>nix::unistd::{sysconf, SysconfVar::</span><span style="color:#ae81ff;">PAGE_SIZE</span><span>}; </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>PageEntry { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">physical_address</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Option</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u64</span><span>&gt; { </span><span> </span><span style="color:#f92672;">match </span><span>self { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Present { pfn, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> page_size </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">sysconf</span><span>(</span><span style="color:#ae81ff;">PAGE_SIZE</span><span>) </span><span> .</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">None</span><span>) </span><span> .</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="color:#ae81ff;">4096</span><span>) </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u64</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(pfn </span><span style="color:#f92672;">*</span><span> page_size) </span><span> } </span><span> </span><span style="color:#f92672;">_ =&gt; </span><span style="font-style:italic;color:#66d9ef;">None</span><span>, </span><span> } </span><span> } </span><span>} </span></code></pre> <p>Finally, instead of opening the pagemap interface ourselves and using the <a href="https://man7.org/linux/man-pages/man2/pread.2.html">pread</a> system call, we can abstract the pagemap interface with functions to open it for the current process or a given process ID as well as a function to read the entry for a given virtual address as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>libc::</span><span style="font-style:italic;color:#66d9ef;">off_t</span><span>; </span><span style="color:#f92672;">use </span><span>nix::sys::uio::pread; </span><span style="color:#f92672;">use </span><span>std::fs::File; </span><span style="color:#f92672;">use </span><span>std::os::unix::io::AsRawFd; </span><span> </span><span>#[derive(Debug)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>PageMap(File); </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>PageMap { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">with_self</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>, std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>(File::open(</span><span style="color:#e6db74;">&quot;/proc/self/pagemap&quot;</span><span>)</span><span style="color:#f92672;">?</span><span>)) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">with_pid</span><span>(</span><span style="font-style:italic;color:#fd971f;">pid</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>, std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>(File::open(format!(</span><span style="color:#e6db74;">&quot;/proc/</span><span style="color:#ae81ff;">{pid}</span><span style="color:#e6db74;">/pagemap&quot;</span><span>))</span><span style="color:#f92672;">?</span><span>)) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read_entry</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">address</span><span>: off_t) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;PageEntry, std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> page_size </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">sysconf</span><span>(</span><span style="color:#ae81ff;">PAGE_SIZE</span><span>) </span><span> .</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">None</span><span>) </span><span> .</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="color:#ae81ff;">4096</span><span>) </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">off_t</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> bytes </span><span style="color:#f92672;">= </span><span>[</span><span style="color:#ae81ff;">0</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>; </span><span style="color:#ae81ff;">8</span><span>]; </span><span> </span><span style="color:#66d9ef;">pread</span><span>(self.</span><span style="color:#ae81ff;">0.</span><span style="color:#66d9ef;">as_raw_fd</span><span>(), </span><span style="color:#f92672;">&amp;mut</span><span> bytes, </span><span style="color:#ae81ff;">8 </span><span style="color:#f92672;">*</span><span> address </span><span style="color:#f92672;">/</span><span> page_size)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(PageEntry::from(</span><span style="font-style:italic;color:#66d9ef;">u64</span><span>::from_ne_bytes(bytes))) </span><span> } </span><span>} </span></code></pre> <p>To showcase our Rust implementation, we use the <a href="https://github.com/StephanvanSchaik/mmap-rs">mmap-rs</a> crate to map in a page and use the pagemap interface to look up its physical address.</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>mmap_rs::{MmapFlags, MmapOptions}; </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), </span><span style="font-style:italic;color:#66d9ef;">Box</span><span>&lt;dyn std::error::Error&gt;&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> mapping </span><span style="color:#f92672;">= </span><span>MmapOptions::new(MmapOptions::page_size().</span><span style="color:#ae81ff;">0</span><span>) </span><span> .</span><span style="color:#66d9ef;">with_flags</span><span>(MmapFlags::</span><span style="color:#ae81ff;">COPY_ON_WRITE</span><span>) </span><span> .</span><span style="color:#66d9ef;">map_mut</span><span>()</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> mapping.</span><span style="color:#66d9ef;">fill</span><span>(</span><span style="color:#ae81ff;">0xff</span><span>); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> pagemap </span><span style="color:#f92672;">= </span><span>PageMap::with_self()</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> entry </span><span style="color:#f92672;">=</span><span> pagemap.</span><span style="color:#66d9ef;">read_entry</span><span>(mapping.</span><span style="color:#66d9ef;">as_ptr</span><span>() </span><span style="color:#f92672;">as _</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> println!(</span><span style="color:#e6db74;">&quot;entry: {:x?}&quot;</span><span>, entry.</span><span style="color:#66d9ef;">physical_address</span><span>()); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span>} </span></code></pre> <p>If we just try to run the example using <code>cargo r</code>, we see the following output:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>entry: Some(0) </span></code></pre> <p>This is because we need the <code>SYS_CAP_ADMIN</code> capability to actually observe the physical addresses, as having information about the physical addresses is useful for Rowhammer attacks. We thus need to build the above example and run it as the root user to see the physical address:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo b </span><span>sudo ./target/debug/pagemap </span></code></pre> <h1 id="accessing-physical-memory">Accessing physical memory</h1> <p>To access physical memory, we can use the <code>/dev/mem</code> interface provided by the Linux kernel. To read from or write to physical memory, we simply use the physical address as the file offset to seek to and then perform a read/write. More specifically, we can again use the <a href="https://man7.org/linux/man-pages/man2/pread.2.html">pread</a> and <a href="https://man7.org/linux/man-pages/man2/pwrite.2.html">pwrite</a> offsets to read/write without having to seek. In fact, we can provide a similar abstraction as we did for the pagemap interface in Rust:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>libc::</span><span style="font-style:italic;color:#66d9ef;">off_t</span><span>; </span><span style="color:#f92672;">use </span><span>nix::sys::uio::{pread, pwrite}; </span><span style="color:#f92672;">use </span><span>std::fs::{File, OpenOptions}; </span><span style="color:#f92672;">use </span><span>std::os::unix::io::AsRawFd; </span><span> </span><span>#[derive(Debug)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>PhysicalMemory(File); </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>PhysicalMemory { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">open</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>, std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> file </span><span style="color:#f92672;">= </span><span>OpenOptions::new() </span><span> .</span><span style="color:#66d9ef;">read</span><span>(</span><span style="color:#ae81ff;">true</span><span>) </span><span> .</span><span style="color:#66d9ef;">write</span><span>(</span><span style="color:#ae81ff;">true</span><span>) </span><span> .</span><span style="color:#66d9ef;">create</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">open</span><span>(</span><span style="color:#e6db74;">&quot;/dev/mem&quot;</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>(file)) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">bytes</span><span>: </span><span style="color:#f92672;">&amp;mut</span><span> [</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>], </span><span style="font-style:italic;color:#fd971f;">address</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u64</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> n </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">pread</span><span>(self.</span><span style="color:#ae81ff;">0.</span><span style="color:#66d9ef;">as_raw_fd</span><span>(), bytes, address </span><span style="color:#f92672;">as _</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(n) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">write</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">bytes</span><span>: </span><span style="color:#f92672;">&amp;</span><span>[</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>], </span><span style="font-style:italic;color:#fd971f;">address</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u64</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> n </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">pwrite</span><span>(self.</span><span style="color:#ae81ff;">0.</span><span style="color:#66d9ef;">as_raw_fd</span><span>(), bytes, address </span><span style="color:#f92672;">as _</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(n) </span><span> } </span><span>} </span></code></pre> <p>We can then extend our example from before such that we first look up the physical address of our mapped page through the pagemap interface, and then use that address to read from physical memory. This should hopefully give us the same bytes we wrote to our page. The example now looks like this:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>mmap_rs::{MmapFlags, MmapOptions}; </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), </span><span style="font-style:italic;color:#66d9ef;">Box</span><span>&lt;dyn std::error::Error&gt;&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> mapping </span><span style="color:#f92672;">= </span><span>MmapOptions::new(MmapOptions::page_size().</span><span style="color:#ae81ff;">0</span><span>) </span><span> .</span><span style="color:#66d9ef;">with_flags</span><span>(MmapFlags::</span><span style="color:#ae81ff;">COPY_ON_WRITE</span><span>) </span><span> .</span><span style="color:#66d9ef;">map_mut</span><span>()</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> mapping.</span><span style="color:#66d9ef;">fill</span><span>(</span><span style="color:#ae81ff;">0xff</span><span>); </span><span> mapping[</span><span style="color:#ae81ff;">0</span><span style="color:#f92672;">..</span><span style="color:#ae81ff;">4</span><span>].</span><span style="color:#66d9ef;">copy_from_slice</span><span>(</span><span style="font-style:italic;color:#66d9ef;">b</span><span style="color:#e6db74;">&quot;test&quot;</span><span>); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> pagemap </span><span style="color:#f92672;">= </span><span>PageMap::with_self()</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> entry </span><span style="color:#f92672;">=</span><span> pagemap.</span><span style="color:#66d9ef;">read_entry</span><span>(mapping.</span><span style="color:#66d9ef;">as_ptr</span><span>() </span><span style="color:#f92672;">as _</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="color:#f92672;">if </span><span style="font-style:italic;color:#66d9ef;">let Some</span><span>(physical_address) </span><span style="color:#f92672;">=</span><span> entry.</span><span style="color:#66d9ef;">physical_address</span><span>() { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> physical_memory </span><span style="color:#f92672;">= </span><span>PhysicalMemory::open()</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> bytes </span><span style="color:#f92672;">= </span><span>[</span><span style="color:#ae81ff;">0</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>; </span><span style="color:#ae81ff;">4</span><span>]; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">_ =</span><span> physical_memory.</span><span style="color:#66d9ef;">read</span><span>(</span><span style="color:#f92672;">&amp;mut</span><span> bytes, physical_address)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> println!(</span><span style="color:#e6db74;">&quot;{:x?}&quot;</span><span>, bytes); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span>} </span></code></pre> <p>We then run the above example, and... we get a permission denied error:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Error: Os { code: 1, kind: PermissionDenied, message: &quot;Operation not permitted&quot; } </span></code></pre> <p>Let's have a look at the Linux kernel source code to see what is actually going on there. In particular, when we try to read from <code>/dev/mem</code>, we end up calling the <a href="https://elixir.bootlin.com/linux/v6.1.4/source/drivers/char/mem.c#L111"><code>read_mem()</code></a> function. More specifically, it checks whether we are allowed to access the page(s) we are trying to read at lines 152-154.</p> <pre data-lang="c" style="background-color:#272822;color:#f8f8f2;" class="language-c "><code class="language-c" data-lang="c"><span> allowed </span><span style="color:#f92672;">= </span><span>page_is_allowed(p </span><span style="color:#f92672;">&gt;&gt;</span><span> PAGE_SHIFT); </span><span> </span><span style="color:#f92672;">if </span><span>(</span><span style="color:#f92672;">!</span><span>allowed) </span><span> </span><span style="color:#f92672;">goto</span><span> failed; </span></code></pre> <p>There are two possible definitions for the <a href="https://elixir.bootlin.com/linux/v6.1.4/source/drivers/char/mem.c#L63"><code>page_is_allowed()</code></a> function depending on whether the Linux kernel has been compiled with <code>CONFIG_STRICT_DEVMEM</code> or not. If not, then this function always returns 1 and as a result we can access any physical address. Otherwise, this function ends up calling the <a href="https://elixir.bootlin.com/linux/v6.1.4/source/arch/x86/mm/init.c#L842"><code>devmem_is_allowed()</code></a> function which is defined differently depending on the platform (the link points to the implementation for x86).</p> <p>On x86 the first megabyte of our physical memory is always accessible, as that traditionally contains the BIOS code and data, which some applications on Linux need access to for emulation purposes. However, since the first megabyte may contain actual free physical memory that can be used by the Linux kernel and/or userspace, accessing any such region simply results in zero filling the buffer for reads. In addition, not all physical memory is backed by DRAM, but may instead be backed by memory-mapped I/O resources. Thus, the <code>/dev/mem</code> interface also provides access to memory-mapped I/O regions, ACPI regions and BIOS regions that live above the first megabyte depending on whether <code>iomem=relaxed</code> or <code>iomem=strict</code> is passed as a kernel command line argument.</p> <h1 id="unrestricted-access-to-dev-mem">Unrestricted access to /dev/mem</h1> <p><strong>Warning</strong>: unrestricted access to <code>/dev/mem</code> is potentially dangerous and poses a huge security risk as you can access all physical memory in the system, including physical memory in use by the operating system as well as other applications, as well as to all PCI devices. Be aware of these risks and don't run this on a production system.</p> <p>First, to make sure we can access any I/O resources, we can simply add the <code>iomem=relaxed</code> kernel command line argument to our GRUB configuration in <code>/etc/default/grub</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>GRUB_CMDLINE_LINUX=&quot;iomem=relaxed&quot; </span></code></pre> <p>Run <code>sudo grub-mkconfig -o /boot/grub/grub.cfg</code> and reboot the system. This is sometimes necessary for userspace applications to access I/O devices (e.g. <a href="https://www.flashrom.org/Flashrom">flashrom</a>).</p> <p>However, the above setting doesn't give us access to any physical memory backed by DRAM. One option is to <a href="/guides/linux-dev/building-linux-kernel-ubuntu">build our own Linux kernel</a> with <code>CONFIG_STRICT_DEVMEM=n</code>. However, we can also write a Linux kernel module that uses <a href="https://docs.kernel.org/trace/kprobes.html">kernel probes</a> to alter the behaviour of the <code>devmem_is_allowed()</code> function to always return 1. Since the <code>page_is_allowed()</code> and <code>range_is_allowed()</code> functions are typically inlined, we cannot rely on kernel probes to hook these functions.</p> <p>Before we can build a Linux kernel module, we need to make sure that we have the appropriate build tools and kernel header files installed:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo apt install linux-headers-$(uname -r) build-essential </span></code></pre> <p>Then we can simply use the following <code>Makefile</code> to build our kernel module:</p> <pre data-lang="make" style="background-color:#272822;color:#f8f8f2;" class="language-make "><code class="language-make" data-lang="make"><span>obj-m </span><span style="color:#f92672;">+= </span><span style="color:#e6db74;">mem_bypass.o </span><span> </span><span>mem_bypass-objs </span><span style="color:#f92672;">+= </span><span style="color:#e6db74;">source/main.o </span><span> </span><span style="color:#a6e22e;">all</span><span style="color:#f92672;">: </span><span> make</span><span style="font-style:italic;color:#fd971f;"> -C</span><span> /lib/modules/</span><span style="color:#f92672;">$(</span><span style="color:#66d9ef;">shell </span><span>uname</span><span style="font-style:italic;color:#fd971f;"> -r</span><span style="color:#f92672;">)</span><span>/build M=</span><span style="font-style:italic;color:#f92672;">$(</span><span style="font-style:italic;color:#fd971f;">PWD</span><span style="font-style:italic;color:#f92672;">)</span><span> modules </span><span> </span><span style="color:#a6e22e;">clean</span><span style="color:#f92672;">: </span><span> make</span><span style="font-style:italic;color:#fd971f;"> -C</span><span> /lib/modules/</span><span style="color:#f92672;">$(</span><span style="color:#66d9ef;">shell </span><span>uname</span><span style="font-style:italic;color:#fd971f;"> -r</span><span style="color:#f92672;">)</span><span>/build M=</span><span style="font-style:italic;color:#f92672;">$(</span><span style="font-style:italic;color:#fd971f;">PWD</span><span style="font-style:italic;color:#f92672;">)</span><span> clean </span></code></pre> <p>The actual kernel module code in <code>source/main.c</code> looks like this:</p> <pre data-lang="c" style="background-color:#272822;color:#f8f8f2;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#f92672;">#include </span><span style="color:#e6db74;">&lt;linux/init.h&gt; </span><span style="color:#f92672;">#include </span><span style="color:#e6db74;">&lt;linux/module.h&gt; </span><span style="color:#f92672;">#include </span><span style="color:#e6db74;">&lt;linux/kernel.h&gt; </span><span style="color:#f92672;">#include </span><span style="color:#e6db74;">&lt;linux/kprobes.h&gt; </span><span> </span><span>MODULE_LICENSE(</span><span style="color:#e6db74;">&quot;GPL&quot;</span><span>); </span><span>MODULE_AUTHOR(</span><span style="color:#e6db74;">&quot;Stephan van Schaik&quot;</span><span>); </span><span>MODULE_DESCRIPTION(</span><span style="color:#e6db74;">&quot;Bypass /dev/mem checks.&quot;</span><span>); </span><span>MODULE_VERSION(</span><span style="color:#e6db74;">&quot;1.0&quot;</span><span>); </span><span> </span><span style="color:#f92672;">static </span><span style="font-style:italic;color:#66d9ef;">int </span><span style="color:#a6e22e;">bypass</span><span>(</span><span style="font-style:italic;color:#66d9ef;">struct</span><span> kretprobe_instance </span><span style="color:#f92672;">*</span><span style="font-style:italic;color:#fd971f;">probe</span><span>, </span><span style="font-style:italic;color:#66d9ef;">struct</span><span> pt_regs </span><span style="color:#f92672;">*</span><span style="font-style:italic;color:#fd971f;">regs</span><span>) </span><span>{ </span><span> regs-&gt;ax </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span> </span><span style="color:#f92672;">return </span><span style="color:#ae81ff;">0</span><span>; </span><span>} </span><span> </span><span style="color:#f92672;">static </span><span style="font-style:italic;color:#66d9ef;">struct</span><span> kretprobe probe </span><span style="color:#f92672;">= </span><span>{ </span><span> .handler </span><span style="color:#f92672;">=</span><span> bypass, </span><span> .maxactive </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">20</span><span>, </span><span>}; </span><span> </span><span style="color:#f92672;">static </span><span style="font-style:italic;color:#66d9ef;">int</span><span> __init </span><span style="color:#a6e22e;">mem_bypass_init</span><span>(</span><span style="font-style:italic;color:#66d9ef;">void</span><span>) </span><span>{ </span><span> probe.kp.symbol_name </span><span style="color:#f92672;">= </span><span style="color:#e6db74;">&quot;devmem_is_allowed&quot;</span><span>; </span><span> register_kretprobe(</span><span style="color:#f92672;">&amp;</span><span>probe); </span><span> </span><span> </span><span style="color:#f92672;">return </span><span style="color:#ae81ff;">0</span><span>; </span><span>} </span><span> </span><span style="color:#f92672;">static </span><span style="font-style:italic;color:#66d9ef;">void</span><span> __exit </span><span style="color:#a6e22e;">mem_bypass_exit</span><span>(</span><span style="font-style:italic;color:#66d9ef;">void</span><span>) </span><span>{ </span><span> unregister_kretprobe(</span><span style="color:#f92672;">&amp;</span><span>probe); </span><span>} </span><span> </span><span>module_init(mem_bypass_init); </span><span>module_exit(mem_bypass_exit); </span></code></pre> <p>Since we simply want to override the return values of the function to always return 1, we register a kretprobe. Whenever the function is about to return, the kernel invokes our <code>bypass()</code> function which simply overrides the return value, which on x86 is passed through the <code>ax</code> register. This way we are always allowed to access any physical address we want.</p> <p>We compile and load the above kernel module as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make </span><span>sudo insmod mem_bypass.ko </span></code></pre> <p>Then we can run our example from before as the root user. If everything worked, then we should see the following output:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[74, 65, 73, 74] </span></code></pre> <p>Of course, if you are done playing with <code>/dev/mem</code>, then make sure to remove the kernel module:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo rmmod mem_bypass.ko </span></code></pre> Upgrading and downgrading CPU microcode 2023-01-09T00:00:00+00:00 2023-01-09T00:00:00+00:00 https://codentium.com/upgrading-and-downgrading-cpu-microcode/ <h1 id="introduction">Introduction</h1> <p>As I often have to go through the process of downgrading CPU microcode to test and reproduce various speculative and transient-execution attacks, I am using this as an opportunity to document the process of checking what CPU microcode version is actually running, as well as how to downgrade/upgrade it from the operating system on both Linux and Microsoft Windows.</p> <p>It is important to note that in addition to the operating system loading CPU microcode, both the CPU and the BIOS/UEFI also provide their own CPU microcode blobs. Since microcode updates can only be installed if the version is newer than what is currently running, we can only downgrade to the version fused into the CPU or provided by the BIOS/UEFI at best, if we are not resorting to more drastic measures such as modifying the BIOS/UEFI or flashing an older version of the BIOS/UEFI firmware. Thus, in this case &quot;downgrading&quot; means reverting to the version provided by the BIOS/UEFI on the system by ensuring that the operating system does not load any newer CPU microcode upon booting.</p> <p>If instead you just want to get your performance back, you may want to look at the instructions on how to disable the mitigations instead.</p> <h1 id="linux">Linux</h1> <h2 id="disabling-mitigations">Disabling Mitigations</h2> <p>In <code>/etc/default/grub</code>, add <code>mitigations=off</code> to <code>GRUB_CMDLINE_LINUX</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>GRUB_CMDLINE_LINUX=&quot;mitigations=off&quot; </span></code></pre> <p>Run <code>sudo grub-mkconfig -o /boot/grub/grub.cfg</code> and reboot.</p> <p>If you want to re-enable the mitigations, remove <code>mitigations=off</code> and run <code>grub-mkconfig</code> again.</p> <h2 id="checking-the-version">Checking the Version</h2> <p>Run <code>cat /proc/pcuinfo</code>. The line that starts with <strong>microcode</strong> should indicate the microcode version that is currently running.</p> <p>Programmatically, the microcode version that is currently running can be retrieved as follows on Intel CPUs:</p> <ol> <li>Write 0 to the <code>IA32_BIOS_SIGN_ID</code> (0x8B) MSR.</li> <li>Issue <code>cpuid</code>.</li> <li>Read the <code>IA32_BIOS_SIGN_ID</code> (0x8B) MSR. The upper 32 bits contains the microcode version.</li> </ol> <p>This can be done through the <code>msr</code> kernel module.</p> <h2 id="downgrading">Downgrading</h2> <p>Run <code>cat /proc/cpuinfo</code>. Check the lines that start with <strong>cpu_family</strong>, <strong>model</strong> and <strong>stepping</strong>. Convert the decimal to hexadecimal, then the filename of your microcode blob would be something like: 06-5e-03 (Intel Core i7-6700K).</p> <p>Remove or rename the file for your CPU in /lib/firmware/intel-ucode. Then run <code>update-initramfs -u</code> to generate a new initramfs. The microcode is normally loaded by the Linux kernel during boot and the initramfs simply provides the microcode blobs for Linux to load.</p> <h2 id="upgrading">Upgrading</h2> <p>Write the microcode blob to <strong>/lib/firmware/intel-ucode/06-5e-03</strong> (replace with the correct family-model-stepping). Run <code>echo 1 &gt; /sys/devices/system/cpu/microcode/reload</code>. You should now be running the new microcode version. To make this change &quot;persistent&quot;, you can run <code>update-initramfs -u</code> to generate a new initramfs.</p> <h1 id="microsoft-windows">Microsoft Windows</h1> <h2 id="disabling-mitigations-1">Disabling Mitigations</h2> <p>To disable the mitigations on Microsoft Windows, you need to add the following two registry keys:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>reg add &quot;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management&quot; /v FeatureSettingsOverride /t REG_DWORD /d 0 /f </span><span>reg add &quot;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management&quot; /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f </span></code></pre> <p>If you want to enable the mitigations again, but leave Intel Hyper-Threading enabled, you can add the following registry keys:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>reg add &quot;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management&quot; /v FeatureSettingsOverride /t REG_DWORD /d 72 /f </span><span>reg add &quot;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management&quot; /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f </span></code></pre> <p>If you also want to disable Intel Hyper-Threading, then you can add the following registry keys:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>reg add &quot;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management&quot; /v FeatureSettingsOverride /t REG_DWORD /d 8246 /f </span><span>reg add &quot;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management&quot; /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f </span></code></pre> <p>See also <a href="https://support.microsoft.com/en-us/topic/kb4072698-windows-server-and-azure-stack-hci-guidance-to-protect-against-silicon-based-microarchitectural-and-speculative-execution-side-channel-vulnerabilities-2f965763-00e2-8f98-b632-0d96f30c8c8e">KB4072698: Windows Server and Azure Stack HCI guidance to protect against silicon-based microarchitectural and speculative execution side-channel vulnerabilities</a> for more information.</p> <h2 id="checking-the-version-1">Checking the Version</h2> <p>The <a href="https://www.intel.com/content/www/us/en/download/12136/intel-processor-identification-utility-windows-version.html">Intel Processor Identification Utility</a> is one tool that you can use to check the microcode version that is currently running.</p> <p>Programmatically, the microcode version that is currently running can be retrieved as follows on Intel CPUs:</p> <ol> <li>Write 0 to the <code>IA32_BIOS_SIGN_ID</code> (0x8B) MSR.</li> <li>Issue <code>cpuid</code>.</li> <li>Read the <code>IA32_BIOS_SIGN_ID</code> (0x8B) MSR. The upper 32 bits contains the microcode version.</li> </ol> <h1 id="downgrading-1">Downgrading</h1> <p>To downgrade the microcode you will have to follow the following instructions to ensure you have the right permissions to change the filename of the DLLs:</p> <ol> <li>Open File Explorer.</li> <li>Navigate to <strong>C:\Windows\System32</strong>.</li> <li>Find <strong>mcupdate_GenuineIntel.dll</strong> or <strong>mcupdate_AuthenticAMD.dll</strong>.</li> <li>Right-click the DLL.</li> <li>Select <strong>Properties</strong>.</li> <li>In the Properties window, go to <strong>Advanced</strong></li> <li>Click <strong>Change</strong> next to <strong>Owner: TrustedInstaller</strong>.</li> <li>In the text area named <strong>Enter the object name to select</strong>, write your username.</li> <li>Press <strong>OK</strong>.</li> <li>In the Properties window, click the <strong>Add</strong> button.</li> <li>Next to <strong>Principal</strong> click on <strong>Select a principal</strong>.</li> <li>In the text area named <strong>Enter the object name to select</strong>, write your username.</li> <li>Press <strong>OK</strong>.</li> <li>Check the <strong>Full control</strong> permission.</li> <li>Press <strong>OK</strong>.</li> <li>Press <strong>OK</strong>.</li> </ol> <p>If everything went well, you should now have the permission to change the filenames. Make sure the DLLs are not named <strong>mcupdate_GenuineIntel.dll</strong> and <strong>mcupdate_AuthenticAMD.dll</strong>. For instance, you can name them <strong>mcupdate_GenuineIntel.dll.bak</strong> and <strong>mcupdate_AuthenticAMD.dll.bak</strong>.</p> <p>Reboot Microsoft Windows to ensure that the microcode does not get loaded. Note that CPU microcode cannot be downgraded at run-time. The CPU has old microcode fused into the CPU, and other stages of the boot process have the opportunity to upgrade the CPU microcode to something more recent. These stages are the BIOS/UEFI and the operating system.</p> <p>To revert to the most recent version, you can simply change the filename back to the original name.</p> <p><strong>Note</strong>: Microsoft Windows may install new versions of these DLLs during an update. You will have to go through this process again in that case.</p> Updating Alpine Linux 2023-01-08T00:00:00+00:00 2023-01-08T00:00:00+00:00 https://codentium.com/guides/alpine/updating/ <h1 id="exporting-postgresql">Exporting PostgreSQL</h1> <p>If you have installed <a href="postgresql">PostgreSQL</a> on your server, then you may need to migrate your existing PostgreSQL database to the new version. This happens with every major version increase, as PostgreSQL may decide to modify the database format. As such, we first export the contents of our PostgreSQL database to a series of SQL queries that we can then execute to import the data into the new database. Thus, we run the following commands to export the database:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>FILENAME=dump_`date +%d-%m-%Y&quot;_&quot;%H_%M_%S`.sql </span><span>pg_dumpall -c -U postgres &gt; $FILENAME </span></code></pre> <p>The above command is also useful if you want to regularly make backups of your PostgreSQL database.</p> <h1 id="updating-alpine">Updating Alpine</h1> <p>To upgrade Alpine we simply need to modify the versions in the <code>/etc/apk/repositories</code> file. For instance, to upgrade from Alpine Linux 3.16 to 3.17, we would change <code>v3.16</code> to <code>v3.17</code> in the following two lines in the <code>/etc/apk/repositories</code> file:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>http://dl-cdn.alpinelinux.org/alpine/v3.16/main </span><span>http://dl-cdn.alpinelinux.org/alpine/v3.16/community </span></code></pre> <p>It's also possible to use the <code>sed</code> tool to simply update the version numbers:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sed -i -e &#39;s/v3\.16/v3.17/g&#39; /etc/apk/repositories </span></code></pre> <p>After updating the repositories, we can simply update the package lists by running the following command:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk update </span></code></pre> <p>While not always necessary, it is recommend to update Alpine's package manager first:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add --upgrade apk-tools </span></code></pre> <p>Then we can upgrade all installed packages, including packages that have the same version numbers using the <code>--available</code> switch (in case uClibc requires this):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk upgrade --available </span></code></pre> <p>If the Linux kernel has been updated, you may want to reboot the machine as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sync </span><span>reboot </span></code></pre> <p>Otherwise you can simply update any of the updated services.</p> <h1 id="importing-postgresql">Importing PostgreSQL</h1> <p>After updating Alpine Linux, we can restart the PostgreSQL service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/postgresql restart </span></code></pre> <p>If the major version increased in between updates, the above command will create a new database from scratch. Thus, we have to import the contents of our previous PostgreSQL database by running the following command:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cat $FILENAME | psql -U postgres -d postgres </span></code></pre> Building the Linux Kernel (Ubuntu) 2022-09-18T00:00:00+00:00 2022-09-18T00:00:00+00:00 https://codentium.com/building-linux-kernel-ubuntu/ <h1 id="introduction">Introduction</h1> <p>These are my notes on how to build your own Linux kernel for Ubuntu, as the information online is scattered all over the place and dated. Fortunately, Canonical recently followed suit and it is now possible to simply build a mainline Linux kernel, rather than having to build Canonical's fork. While this may be useful if you need more recents drivers, because drivers such as amdgpu are typically behind on Ubuntu, this guide is targeted at users who want to either write their own drivers or modify existing drivers in the Linux kernel.</p> <h1 id="installing-build-dependencies">Installing build dependencies</h1> <p>There are number of dependencies that need to be installed before we can build the Linux kernel. They can be installed by running the following command on Ubuntu:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo apt-get install libncurses-dev gawk flex bison openssl libssl-dev dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf llvm </span></code></pre> <h1 id="building-the-kernel">Building the kernel</h1> <p>Now that we have all the dependencies required to build the kernel, we can either download a tarball with the Linux source code from <a href="https://kernel.org">https://kernel.org</a> or clone the appropriate tag from the Linux kernel repository. For instance, we can clone the source code of the Linux kernel for version 6.5 as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git clone -b v6.5 --depth=1 https://github.com/torvalds/linux </span></code></pre> <p>Once we have downloaded the source code, we can simply <code>cd</code> into the directory as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cd linux </span></code></pre> <p>Before we can build the Linux kernel, we need to configure the kernel. Fortunately, we can simply copy the kernel configuration from an existing installation:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cp -v /boot/config-$(uname -r) .config </span></code></pre> <p>However, we have to tune a few configuration options before we can actually build the kernel <a href="https://stackoverflow.com/questions/67670169/compiling-kernel-gives-error-no-rule-to-make-target-debian-certs-debian-uefi-ce">1</a>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>scripts/config --disable SYSTEM_TRUSTED_KEYS </span><span>scripts/config --disable SYSTEM_REVOCATION_KEYS </span></code></pre> <p>In addition, the kernel configuration we copied may be from an older version than the kernel version we are trying to compile. We can update the kernel configuration to be compatible with the version of the forked Linux kernel tree as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make olddefconfig </span></code></pre> <p>The above command will simply pick the default option for any newly introduced configuration option. To build the Linux kernel and the kernel modules we can simply invoke <code>make</code>. However, <code>make</code> will by default only use one CPU core/thread, so we have to specify the jobs option or <code>-j</code> followed by the number of threads that we want to use. For example, to use 16 CPU cores/threads, we can invoke <code>make</code> as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>make -j16 </span></code></pre> <p>If the above command was successful, we can now install the kernel modules:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo make INSTALL_MOD_STRIP=1 modules_install </span></code></pre> <p>As well as the Linux kernel:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo make install </span></code></pre> <p>The above command will also update the initramfs and regenerate the GRUB configuration file automatically on Ubuntu. However, it is also possible to update the initramfs or regenerate the GRUB configuration file yourself.</p> <p>The commands that get passed to the kernel by GRUB during boot are located in <code>/etc/default/grub</code>. You can modify the kernel command line in <code>/etc/default/grub</code>, then you can run the following to update the GRUB configuration file:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo update-grub </span></code></pre> <p>If you want to rebuild the initramfs, you can run the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo update-initramfs -u </span></code></pre> <p>Finally, after installing the Linux kernel, the kernel modules and updating the initramfs and GRUB configuration, you can reboot your system:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo reboot </span></code></pre> <p>GRUB should now present you with the option to boot the newly installed kernel. However, depending on your installation, GRUB may not present you with a boot menu at all, e.g. on Ubuntu this is the default behavior. To change this, you can edit the <code>/etc/default/grub</code> file. More specifically, comment out the <code>GRUB_TIMEOUT_STYLE</code> variable and set <code>GRUB_TIMEOUT</code> to a non-zero value as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>#GRUB_TIMEOUT_STYLE=hidden </span><span>GRUB_TIMEOUT=10 </span></code></pre> <p>Then run the following command to regenerate the GRUB configuration file:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sudo update-grub </span></code></pre> Building the Linux Kernel (Ubuntu) 2022-09-18T00:00:00+00:00 2022-09-18T00:00:00+00:00 https://codentium.com/guides/linux-dev/building-linux-kernel-ubuntu/ <p>This article has been moved <a href="/building-linux-kernel-ubuntu">here</a>.</p> Steamcmd 2021-11-28T00:00:00+00:00 2021-11-28T00:00:00+00:00 https://codentium.com/guides/alpine/steamcmd/ <h1 id="installation">Installation</h1> <p>Make sure you have set up the <a href="./voidlinux-chroot">VoidLinux chroot</a> first.</p> <p>Since Steamcmd comes with 32-bit binaries, we first have to enable 32-bit compatibility as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./root-voidlinux.sh &quot;xbps-install -Sy void-repo-multilib&quot; </span><span>./root-voidlinux.sh &quot;xbps-install -S&quot; </span><span>./root-voidlinux.sh &quot;xbps-install libgcc-32bit libstdc++-32bit&quot; </span></code></pre> <p>Download the tarball containing Steamcmd outside of the chroot and extract it into the home directory in our chroot:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>wget http://media.steampowered.com/installer/steamcmd_linux.tar.gz </span><span>tar xf steamcmd_linux.tar.gz -C ~/chroot/voidlinux/home/$USER </span></code></pre> <p>Update steamcmd:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./voidlinux.sh &quot;/home/$USER/steamcmd.sh +quit&quot; </span></code></pre> Setting up a Valheim Server 2021-11-28T00:00:00+00:00 2021-11-28T00:00:00+00:00 https://codentium.com/guides/alpine/valheim-server/ <h1 id="installation">Installation</h1> <p>Make sure you have set up the <a href="voidlinux-chroot">VoidLinux chroot</a> and installed <a href="steamcmd">Steamcmd</a> first.</p> <p>We can then simply create a script as follows to install/update the Valheim server:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cat &gt;~/chroot/update-valheim.sh &lt;&lt;&#39;EOF&#39; </span><span>#!/bin/sh </span><span>./voidlinux.sh &quot;/home/$USER/steamcmd.sh +@sSteamCmdForcePlatformType linux +force_install_dir /home/$USER/valheim +login anonymous +app_update 896660 validate +quit&quot; </span><span>EOF </span></code></pre> <p>Ensure the script is executable:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>chmod +x ~/chroot/update-valheim.sh </span></code></pre> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>#!/bin/sh </span><span>SERVER_NAME=&quot;My server&quot; </span><span>PORT=2456 </span><span>WORLD_NAME=&quot;Dedicated&quot; </span><span>PASSWORD=&quot;secret&quot; </span><span>./voidlinux.sh &quot;/home/$USER/valheim/valheim_server.x86_64 -name $SERVER_NAME -port $PORT -world $WORLD_NAME -password $PASSWORD&quot; </span></code></pre> Setting up a VoidLinux chroot 2021-11-28T00:00:00+00:00 2021-11-28T00:00:00+00:00 https://codentium.com/guides/alpine/voidlinux-chroot/ <h1 id="installation">Installation</h1> <p>We will first create a directory for our VoidLinux installation:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p ~/chroot/voidlinux </span></code></pre> <p>Then download the glibc flavour of the rootfs tarball containing the VoidLinux root filesystem for the x86-64 architecture (check the <a href="https://voidlinux.org/download/">download page</a> for the download link):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>wget https://alpha.de.repo.voidlinux.org/live/current/void-x86_64-ROOTFS-20210930.tar.xz </span></code></pre> <p>Extract the tarball:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>tar xf void-x86_64-ROOTFS-20210930.tar.xz -C ~/chroot/voidlinux </span></code></pre> <h1 id="chroot-scripts">chroot scripts</h1> <p>We will now set up a script that mounts <code>/dev/</code>, <code>/proc/</code>, <code>/sys</code> and <code>/tmp</code>, copies <code>resolv.conf</code> for networking and then either executes the command that we pass as the first argument or otherwise defaults to <code>/bin/bash</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cat &gt;~/chroot/root-voidlinux.sh &lt;&lt; &#39;EOF&#39; </span><span>#!/bin/sh </span><span>CHROOT_PATH=&quot;/home/$USER/chroot/voidlinux&quot; </span><span>cd $CHROOT_PATH </span><span>mount | grep $CHROOT_PATH/dev &gt;/dev/null || sudo mount --rbind /dev $CHROOT_PATH/dev &amp;&amp; sudo mount --make-rslave $CHROOT_PATH/dev </span><span>mount | grep $CHROOT_PATH/proc &gt;/dev/null || sudo mount -t proc /proc $CHROOT_PATH/proc </span><span>mount | grep $CHROOT_PATH/sys &gt;/dev/null || sudo mount --rbind /sys $CHROOT_PATH/sys &amp;&amp; sudo mount --make-rslave $CHROOT_PATH/sys </span><span>mount | grep $CHROOT_PATH/tmp &gt;/dev/null || sudo mount --rbind /tmp $CHROOT_PATH/tmp </span><span>cp /etc/resolv.conf $CHROOT_PATH/etc/resolv.conf </span><span>sudo chroot $CHROOT_PATH ${1:-/bin/bash} </span><span>EOF </span></code></pre> <p>In addition, we will set up a script that simply lets us run commands as the same user as on our host system:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cat &gt;~/chroot/voidlinux.sh &lt;&lt; &#39;EOF&#39; </span><span>#!/bin/sh </span><span>CHROOT_PATH=&quot;/home/$USER/chroot/voidlinux&quot; </span><span>cd $CHROOT_PATH </span><span>mount | grep $CHROOT_PATH/dev &gt;/dev/null || sudo mount --rbind /dev $CHROOT_PATH/dev &amp;&amp; sudo mount --make-rslave $CHROOT_PATH/dev </span><span>mount | grep $CHROOT_PATH/proc &gt;/dev/null || sudo mount -t proc /proc $CHROOT_PATH/proc </span><span>mount | grep $CHROOT_PATH/sys &gt;/dev/null || sudo mount --rbind /sys $CHROOT_PATH/sys &amp;&amp; sudo mount --make-rslave $CHROOT_PATH/sys </span><span>mount | grep $CHROOT_PATH/tmp &gt;/dev/null || sudo mount --rbind /tmp $CHROOT_PATH/tmp </span><span>cp /etc/resolv.conf $CHROOT_PATH/etc/resolv.conf </span><span>sudo chroot --userspec=$USER:users $CHROOT_PATH ${1:-/bin/bash} </span><span>EOF </span></code></pre> <p>In addition, we will set up a script to undo the mounts:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cat &gt;~/chroot/cleanup-voidlinux.sh &lt;&lt; &#39;EOF&#39; </span><span>#!/bin/sh </span><span>CHROOT_PATH=&quot;/home/$USER/chroot/voidlinux&quot; </span><span>mount | grep $CHROOT_PATH/dev &gt;/dev/null &amp;&amp; sudo umount -R $CHROOT_PATH/dev </span><span>mount | grep $CHROOT_PATH/proc &gt;/dev/null &amp;&amp; sudo umount -R $CHROOT_PATH/proc </span><span>mount | grep $CHROOT_PATH/sys &gt;/dev/null &amp;&amp; sudo umount -R $CHROOT_PATH/sys </span><span>mount | grep $CHROOT_PATH/tmp &gt;/dev/null &amp;&amp; sudo umount -R $CHROOT_PATH/tmp </span><span>EOF </span></code></pre> <p>Ensure the scripts are executable:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>chmod +x ~/chroot/voidlinux.sh ~/chroot/root-voidlinux.sh ~/chroot/cleanup-voidlinux.sh </span></code></pre> <p>We can then add a user with the same name, uid and gid as the user on our host system as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./root-voidlinux.sh &quot;useradd -mU $USER -u $UID -g $GID&quot; </span></code></pre> <p>We can now simply test our chroot setup by entering the VoidLinux chroot as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./voidlinux.sh </span></code></pre> <p>Exit the shell and run the cleanup script as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./cleanup-voidlinux.sh </span></code></pre> <h1 id="updating-voidlinux">Updating VoidLinux</h1> <p>Enter the VoidLinux chroot and run the following commands to update the system:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./root-voidlinux.sh &quot;xbps-install -Su xbps&quot; </span><span>./root-voidlinux.sh &quot;xbps-install -u&quot; </span><span>./root-voidlinux.sh &quot;xbps-install base-system&quot; </span><span>./root-voidlinux.sh &quot;xbps-remove base-voidstrap&quot; </span></code></pre> <p>You can also create a script to update the VoidLinux installation:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cat &gt;~/chroot/update-voidlinux.sh &lt;&lt; &#39;EOF&#39; </span><span>#!/bin/sh </span><span>CHROOT_PATH=&quot;/home/$USER/chroot/voidlinux&quot; </span><span>cd $CHROOT_PATH </span><span>mount | grep $CHROOT_PATH/dev &gt;/dev/null || sudo mount --rbind /dev $CHROOT_PATH/dev &amp;&amp; sudo mount --make-rslave $CHROOT_PATH/dev </span><span>mount | grep $CHROOT_PATH/proc &gt;/dev/null || sudo mount -t proc /proc $CHROOT_PATH/proc </span><span>mount | grep $CHROOT_PATH/sys &gt;/dev/null || sudo mount --rbind /sys $CHROOT_PATH/sys &amp;&amp; sudo mount --make-rslave $CHROOT_PATH/sys </span><span>mount | grep $CHROOT_PATH/tmp &gt;/dev/null || sudo mount --rbind /tmp $CHROOT_PATH/tmp </span><span>cp /etc/resolv.conf $CHROOT_PATH/etc/resolv.conf </span><span>sudo chroot $CHROOT_PATH xbps-install -Su xbps </span><span>sudo chroot $CHROOT_PATH xbps-install -u </span><span>sudo chroot $CHROOT_PATH xbps-install base-system </span><span>sudo chroot $CHROOT_PATH xbps-remove base-voidstrap </span><span>EOF </span></code></pre> <p>Ensure the script is executable:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>chmod +x ~/chroot/update-voidlinux.sh </span></code></pre> <p>Edit <code>~/chroot/voidlinux/etc/default/libc-locales</code> and uncomment the line containing &quot;#en_US.UTF-8 UTF-8&quot; by removing the &quot;#&quot;, if it is not already uncommented. Update the locales as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./root-voidlinux.sh &quot;xbps-reconfigure -f glibc-locales&quot; </span></code></pre> Windows Drivers in Rust: I/O Controls 2021-10-07T00:00:00+00:00 2021-10-07T00:00:00+00:00 https://codentium.com/guides/windows-dev/windows-drivers-in-rust-io-controls/ <h1 id="introduction">Introduction</h1> <p>In <a href="/guides/windows-dev/windows-drivers-in-rust-reading-and-writing">the previous article</a> we extended our device file to support reading and writing from userspace as a way of interacting with our driver. In this article we are going to be looking at extending our interface to be able to handle device I/O controls or <code>ioctls</code>, which we pronounce as I/O controls, which is another way to let userspace interace with our driver.</p> <h1 id="what-is-an-ioctl">What is an ioctl?</h1> <p>Since our userspace process runs with fewer privileges than the operating system kernel, we have to rely on the services provided by the kernel to us. For instance, our userspace process does not have access to all the physical memory of the system, but we can ask the kernel to allocate some memory for us and map it into our address space using <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc"><code>VirtualAlloc</code></a>. Under the hood, a call to <a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc"><code>VirtualAlloc</code></a> will end up calling <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntallocatevirtualmemory"><code>NtAllocateVirtualMemory</code></a> in <code>ntdll.dll</code>, which is responsible for actually invoking the system call that jumps from userspace to the system call handler in the kernel. So how does the system call handler know that we called <code>NtAllocateVirtualMemory</code>? Well, each service routine that is available to userspace does have its own unique number that <code>ntdll.dll</code> passes along to the system call handler, together with the arguments for that service routine.</p> <p>Similarly, we have been using <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile"><code>ReadFile</code></a> to read from our device file in the previous article. This function ends up calling <a href="https://docs.microsoft.com/en-us/windows/win32/devnotes/ntreadfile"><code>NtReadFile</code></a> in <code>ntdll.dll</code>.</p> <p>However, sometimes we want to provide service routines specific to our driver that do not map well to any of the existing service routines, such as reading from or writing to our device file. Fortunately, userspace has access to <a href="https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol"><code>DeviceIoControl</code></a> which accepts a control code and optionally an input and/or an output buffer. Invoking <a href="https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol"><code>DeviceIoControl</code></a> causes the I/O manager to create an <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mj-device-control"><code>IRP_MJ_DEVICE_CONTROL</code></a> and send it to the topmost driver in the driver stac, which may be our driver, or it may eventually be dispatched to our driver. The driver then receives the control code and optionally the input buffer and/or the output buffer along with the IRP, and uses the control code to perform the corresponding operation.</p> <h1 id="dispatching-the-callbacks">Dispatching the Callbacks</h1> <p>The dispatch code is similar to the one from the previous article. We first extend the <code>dispatch_callback</code> function to handle the <code>IRP_MJ_DEVICE_CONTROL</code> major function:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#ae81ff;">IRP_MJ_DEVICE_CONTROL </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> control_request </span><span style="color:#f92672;">=</span><span> IoControlRequest { </span><span> inner: request, </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">=</span><span> data.</span><span style="color:#66d9ef;">ioctl</span><span>(</span><span style="color:#f92672;">&amp;</span><span>device, </span><span style="color:#f92672;">&amp;</span><span>control_request); </span><span> </span><span> request </span><span style="color:#f92672;">=</span><span> control_request.inner; </span><span> status </span><span> } </span></code></pre> <p>We add the <code>ioctl</code> callback to the <code>DeviceOperations</code> trait with a default implementation:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">ioctl</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoControlRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span></code></pre> <p>Finally, we implemented our <code>IoControlRequest</code> by wrapping the <code>IoRequest</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>IoControlRequest { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) inner: IoRequest, </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>Deref </span><span style="color:#f92672;">for </span><span>IoControlRequest { </span><span> </span><span style="font-style:italic;color:#66d9ef;">type </span><span>Target </span><span style="color:#f92672;">=</span><span> IoRequest; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">deref</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">Self::</span><span>Target { </span><span> </span><span style="color:#f92672;">&amp;</span><span>self.inner </span><span> } </span><span>} </span></code></pre> <p>Our <code>IoControlRequest</code> provides a <code>control_code</code> function to access the control code of the request:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>IoControlRequest { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">control_code</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; ControlCode { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> stack_location </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">stack_location</span><span>(); </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> stack_location.Parameters.DeviceIoControl.IoControlCode.</span><span style="color:#66d9ef;">into</span><span>() </span><span> } </span><span> } </span></code></pre> <h1 id="dissecting-control-codes">Dissecting Control Codes</h1> <p>While the control codes are 32-bit values, they consist of several fields. The <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/defining-i-o-control-codes">&quot;Defining I/O Control Codes&quot; - MSDN</a> article explains how the control codes are structured. Essentially, we have four fields:</p> <ul> <li>0:2 - Transfer Type: whether the I/O control is buffered I/O, direct I/O or neither.</li> <li>2:14 - Function Code: the function code that corresponds to the operation performed by our driver.</li> <li>14:16 - Required Access: whether the file needs to be opened with any access, read access, write access or both read and write access.</li> <li>16:32 - Device Type: the device type we passed to the <code>create_device</code> function.</li> </ul> <p>Let's first define the <code>RequiredAccess</code> and <code>TransferMethod</code> types, for which we just have to use the constants provided by the <code>windows-kernel-sys</code> crate:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>windows_kernel_sys::base::{ </span><span> </span><span style="color:#ae81ff;">FILE_ANY_ACCESS</span><span>, </span><span style="color:#ae81ff;">FILE_READ_DATA</span><span>, </span><span style="color:#ae81ff;">FILE_WRITE_DATA</span><span>, </span><span> </span><span style="color:#ae81ff;">METHOD_NEITHER</span><span>, </span><span style="color:#ae81ff;">METHOD_IN_DIRECT</span><span>, </span><span style="color:#ae81ff;">METHOD_OUT_DIRECT</span><span>, </span><span style="color:#ae81ff;">METHOD_BUFFERED </span><span>}; </span><span> </span><span>bitflags! { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>RequiredAccess: u32 { </span><span> const ANY_ACCESS = FILE_ANY_ACCESS; </span><span> const READ_DATA = FILE_READ_DATA; </span><span> const WRITE_DATA = FILE_WRITE_DATA; </span><span> const READ_WRITE_DATA = FILE_READ_DATA | FILE_WRITE_DATA; </span><span> } </span><span>} </span><span> </span><span>#[derive(Clone, Copy, Debug, PartialEq, Eq)] </span><span>#[repr(u32)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">enum </span><span>TransferMethod { </span><span> Neither </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">METHOD_NEITHER</span><span>, </span><span> InputDirect </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">METHOD_IN_DIRECT</span><span>, </span><span> OutputDirect </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">METHOD_OUT_DIRECT</span><span>, </span><span> Buffered </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">METHOD_BUFFERED</span><span>, </span><span>} </span></code></pre> <p>We can then create a nice struct for the <code>ControlCode</code> with the four fields:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[derive(Clone, Debug, PartialEq, Eq)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>ControlCode(pub DeviceType, pub RequiredAccess, pub </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>, pub TransferMethod); </span></code></pre> <p>Then we will define all the constants we will need to extract these fields from the 32-bit value, or vice versa to pack them into a 32-bit value:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>ControlCode { </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">METHOD_BITS</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">2</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">NUM_BITS</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">12</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">ACCESS_BITS</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">2</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">TYPE_BITS</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">16</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">METHOD_SHIFT</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">NUM_SHIFT</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">METHOD_SHIFT </span><span style="color:#f92672;">+ </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">METHOD_BITS</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">ACCESS_SHIFT</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">NUM_SHIFT </span><span style="color:#f92672;">+ </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">NUM_BITS</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">TYPE_SHIFT</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">ACCESS_SHIFT </span><span style="color:#f92672;">+ </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">ACCESS_BITS</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">METHOD_MASK</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#ae81ff;">1 </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">METHOD_BITS</span><span>) </span><span style="color:#f92672;">- </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">NUM_MASK</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#ae81ff;">1 </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">NUM_BITS</span><span>) </span><span style="color:#f92672;">- </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">ACCESS_MASK</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#ae81ff;">1 </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">ACCESS_BITS</span><span>) </span><span style="color:#f92672;">- </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">TYPE_MASK</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#ae81ff;">1 </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">TYPE_BITS</span><span>) </span><span style="color:#f92672;">- </span><span style="color:#ae81ff;">1</span><span>; </span></code></pre> <p>In addition, we will add some convenience functions to access the fields individually:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">device_type</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; DeviceType { </span><span> self.</span><span style="color:#ae81ff;">0 </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">required_access</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; RequiredAccess { </span><span> self.</span><span style="color:#ae81ff;">1 </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">number</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span>{ </span><span> self.</span><span style="color:#ae81ff;">2 </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">transfer_method</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; TransferMethod { </span><span> self.</span><span style="color:#ae81ff;">3 </span><span> } </span><span>} </span></code></pre> <p>Finally, our code to pack and unpack the fields into a 32-bit value and from a 32-bit value looks as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl From</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>&gt; </span><span style="color:#f92672;">for </span><span>ControlCode { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">from</span><span>(</span><span style="font-style:italic;color:#fd971f;">value</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> method </span><span style="color:#f92672;">= </span><span>(value </span><span style="color:#f92672;">&gt;&gt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">METHOD_SHIFT</span><span>) </span><span style="color:#f92672;">&amp; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">METHOD_MASK</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> num </span><span style="color:#f92672;">= </span><span>(value </span><span style="color:#f92672;">&gt;&gt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">NUM_SHIFT</span><span>) </span><span style="color:#f92672;">&amp; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">NUM_MASK</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> access </span><span style="color:#f92672;">= </span><span>(value </span><span style="color:#f92672;">&gt;&gt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">ACCESS_SHIFT</span><span>) </span><span style="color:#f92672;">&amp; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">ACCESS_MASK</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> ty </span><span style="color:#f92672;">= </span><span>(value </span><span style="color:#f92672;">&gt;&gt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">TYPE_SHIFT</span><span>) </span><span style="color:#f92672;">&amp; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">TYPE_MASK</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>( </span><span> ty.</span><span style="color:#66d9ef;">into</span><span>(), </span><span> RequiredAccess::from_bits(access).</span><span style="color:#66d9ef;">unwrap_or</span><span>(RequiredAccess::</span><span style="color:#ae81ff;">READ_DATA</span><span>), </span><span> num, </span><span> method.</span><span style="color:#66d9ef;">into</span><span>() </span><span> ) </span><span> } </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl Into</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>&gt; </span><span style="color:#f92672;">for </span><span>ControlCode { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">into</span><span>(</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> method </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Into</span><span>::&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>&gt;::into(self.</span><span style="color:#ae81ff;">3</span><span>) </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">METHOD_SHIFT</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> num </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#ae81ff;">2 </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">NUM_SHIFT</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> access </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#ae81ff;">1.</span><span style="color:#66d9ef;">bits</span><span>() </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">ACCESS_SHIFT</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> ty </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Into</span><span>::&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>&gt;::into(self.</span><span style="color:#ae81ff;">0</span><span>) </span><span style="color:#f92672;">&lt;&lt; </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::</span><span style="color:#ae81ff;">TYPE_SHIFT</span><span>; </span><span> </span><span> ty </span><span style="color:#f92672;">|</span><span> access </span><span style="color:#f92672;">|</span><span> num </span><span style="color:#f92672;">|</span><span> method </span><span> } </span><span>} </span></code></pre> <h1 id="extending-userptr">Extending UserPtr</h1> <p>Similar to how it is possible to specify whether you want to use direct I/O or buffered I/O for reads and writes, it is also possible to specify the transfer method for your ioctl as we have seen when dissecting the control codes. The different transfer methods are discussed in <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/buffer-descriptions-for-i-o-control-codes">&quot;Buffer Descriptions for I/O Control Codes&quot; - MSDN</a>. We will first start by turning our <code>UserPtr</code> into an enum to handle the different situations:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">enum </span><span>UserPtr { </span><span> Buffered { </span><span> ptr: </span><span style="color:#f92672;">*mut </span><span>cty::</span><span style="font-style:italic;color:#66d9ef;">c_void</span><span>, </span><span> read_size: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> write_size: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> }, </span><span> Direct { </span><span> read_ptr: </span><span style="font-style:italic;color:#66d9ef;">*const </span><span>cty::</span><span style="font-style:italic;color:#66d9ef;">c_void</span><span>, </span><span> write_ptr: </span><span style="color:#f92672;">*mut </span><span>cty::</span><span style="font-style:italic;color:#66d9ef;">c_void</span><span>, </span><span> read_size: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> write_size: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> }, </span><span> Neither, </span><span>} </span></code></pre> <p>We then add some convenience functions to construct the different variants of our enum:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>UserPtr { </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">new_buffered</span><span>( </span><span> </span><span style="font-style:italic;color:#fd971f;">ptr</span><span>: </span><span style="color:#f92672;">*mut </span><span>cty::c_void, </span><span> </span><span style="font-style:italic;color:#fd971f;">read_size</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> </span><span style="font-style:italic;color:#fd971f;">write_size</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> ) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Buffered{ </span><span> ptr, </span><span> read_size, </span><span> write_size, </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">new_direct</span><span>( </span><span> </span><span style="font-style:italic;color:#fd971f;">read_ptr</span><span>: </span><span style="color:#f92672;">*const </span><span>cty::c_void, </span><span> </span><span style="font-style:italic;color:#fd971f;">write_ptr</span><span>: </span><span style="color:#f92672;">*mut </span><span>cty::c_void, </span><span> </span><span style="font-style:italic;color:#fd971f;">read_size</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> </span><span style="font-style:italic;color:#fd971f;">write_size</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> ) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Direct { </span><span> read_ptr, </span><span> write_ptr, </span><span> read_size, </span><span> write_size, </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">new_neither</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Neither </span><span> } </span></code></pre> <p>We then have to modify the rest of our code to grab the fields from the various variants that we need. For the <code>read_size</code> and <code>write_size</code> functions that looks as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read_size</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">usize </span><span>{ </span><span> </span><span style="color:#f92672;">match </span><span>self { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Buffered { read_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; *</span><span>read_size, </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Direct { read_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; *</span><span>read_size, </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Neither </span><span style="color:#f92672;">=&gt; </span><span style="color:#ae81ff;">0</span><span>, </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">write_size</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">usize </span><span>{ </span><span> </span><span style="color:#f92672;">match </span><span>self { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Buffered { write_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; *</span><span>write_size, </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Direct { write_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; *</span><span>write_size, </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Neither </span><span style="color:#f92672;">=&gt; </span><span style="color:#ae81ff;">0</span><span>, </span><span> } </span><span> } </span></code></pre> <p>For the <code>as_slice</code> and <code>as_mut_slice</code> functions, we also have to extract the pointer of the appropriate buffer:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_slice</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span>[</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>] { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span>(ptr, size) </span><span style="color:#f92672;">= match </span><span>self { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Buffered { ptr, read_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>ptr </span><span style="color:#f92672;">as _</span><span>, </span><span style="color:#f92672;">*</span><span>read_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Direct { read_ptr, read_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>read_ptr, </span><span style="color:#f92672;">*</span><span>read_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Neither </span><span style="color:#f92672;">=&gt; </span><span>(core::ptr::null(), </span><span style="color:#ae81ff;">0</span><span>), </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> ptr.</span><span style="color:#66d9ef;">is_null</span><span>() </span><span style="color:#f92672;">||</span><span> size </span><span style="color:#f92672;">== </span><span style="color:#ae81ff;">0 </span><span>{ </span><span> </span><span style="color:#f92672;">&amp;</span><span>[] </span><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::slice::from_raw_parts(ptr </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">*const u8</span><span>, size) </span><span> } </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_mut_slice</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;mut </span><span>[</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>] { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span>(ptr, size) </span><span style="color:#f92672;">= match </span><span>self { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Buffered { ptr, write_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>ptr, </span><span style="color:#f92672;">*</span><span>write_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Direct { write_ptr, write_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>write_ptr, </span><span style="color:#f92672;">*</span><span>write_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Neither </span><span style="color:#f92672;">=&gt; </span><span>(core::ptr::null_mut(), </span><span style="color:#ae81ff;">0</span><span>), </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> ptr.</span><span style="color:#66d9ef;">is_null</span><span>() </span><span style="color:#f92672;">||</span><span> size </span><span style="color:#f92672;">== </span><span style="color:#ae81ff;">0 </span><span>{ </span><span> </span><span style="color:#f92672;">&amp;mut </span><span>[] </span><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::slice::from_raw_parts_mut(ptr </span><span style="color:#f92672;">as *mut </span><span style="font-style:italic;color:#66d9ef;">u8</span><span>, size) </span><span> } </span><span> } </span><span> } </span></code></pre> <p>Finally, we have to do the same for our <code>read</code> and <code>write</code> functions:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read</span><span>&lt;T: </span><span style="font-style:italic;color:#66d9ef;">Default</span><span>&gt;(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;T, Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span>(ptr, size) </span><span style="color:#f92672;">= match </span><span>self { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Buffered { ptr, read_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>ptr </span><span style="color:#f92672;">as _</span><span>, </span><span style="color:#f92672;">*</span><span>read_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Direct { read_ptr, read_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>read_ptr, </span><span style="color:#f92672;">*</span><span>read_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Neither </span><span style="color:#f92672;">=&gt; </span><span>(core::ptr::null(), </span><span style="color:#ae81ff;">0</span><span>), </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> ptr.</span><span style="color:#66d9ef;">is_null</span><span>() </span><span style="color:#f92672;">||</span><span> size </span><span style="color:#f92672;">== </span><span style="color:#ae81ff;">0 </span><span>{ </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_PARAMETER</span><span>); </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">if </span><span>core::mem::size_of::&lt;T&gt;() </span><span style="color:#f92672;">&gt;</span><span> size { </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_USER_BUFFER</span><span>); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> obj </span><span style="color:#f92672;">= </span><span>T::default(); </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::ptr::copy_nonoverlapping( </span><span> ptr </span><span style="color:#f92672;">as _</span><span>, </span><span> </span><span style="color:#f92672;">&amp;mut</span><span> obj, </span><span> core::mem::size_of::&lt;T&gt;(), </span><span> ); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(obj) </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">write</span><span>&lt;T&gt;(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">obj</span><span>: </span><span style="color:#f92672;">&amp;</span><span>T) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span>(ptr, size) </span><span style="color:#f92672;">= match </span><span>self { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Buffered { ptr, write_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>ptr, </span><span style="color:#f92672;">*</span><span>write_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Direct { write_ptr, write_size, </span><span style="color:#f92672;">.. </span><span>} </span><span style="color:#f92672;">=&gt; </span><span>(</span><span style="color:#f92672;">*</span><span>write_ptr, </span><span style="color:#f92672;">*</span><span>write_size), </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self</span><span>::Neither </span><span style="color:#f92672;">=&gt; </span><span>(core::ptr::null_mut(), </span><span style="color:#ae81ff;">0</span><span>), </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> ptr.</span><span style="color:#66d9ef;">is_null</span><span>() </span><span style="color:#f92672;">||</span><span> size </span><span style="color:#f92672;">== </span><span style="color:#ae81ff;">0 </span><span>{ </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_PARAMETER</span><span>); </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">if </span><span>core::mem::size_of::&lt;T&gt;() </span><span style="color:#f92672;">&gt;</span><span> size { </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_USER_BUFFER</span><span>); </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::ptr::copy_nonoverlapping( </span><span> obj, </span><span> ptr </span><span style="color:#f92672;">as _</span><span>, </span><span> core::mem::size_of::&lt;T&gt;(), </span><span> ); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span></code></pre> <p>Then in the <code>user_ptr</code> function of the <code>IoControlRequest</code> type, we first concern ourselves with grabbing all the information we need: the MDL address if available, the system buffer and the input and output size. The code looks as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">user_ptr</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; IoControlBuffers { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> stack_location </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">stack_location</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> irp </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">irp</span><span>(); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> system_buffer </span><span style="color:#f92672;">= unsafe </span><span>{ irp.AssociatedIrp.SystemBuffer }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> mdl_address </span><span style="color:#f92672;">= if !</span><span>irp.MdlAddress.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ MmGetSystemAddressForMdlSafe(irp.MdlAddress, </span><span style="color:#ae81ff;">MM_PAGE_PRIORITY</span><span>::HighPagePriority </span><span style="color:#f92672;">as _</span><span>) } </span><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> core::ptr::null_mut() </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> input_size </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> stack_location.Parameters.DeviceIoControl.InputBufferLength </span><span> } </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> output_size </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> stack_location.Parameters.DeviceIoControl.OutputBufferLength </span><span> } </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>; </span></code></pre> <p>The rest of our code depends on the transfer method specified in the control code:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">match </span><span>self.</span><span style="color:#66d9ef;">control_code</span><span>().</span><span style="color:#66d9ef;">transfer_method</span><span>() { </span><span> TransferMethod::Buffered </span><span style="color:#f92672;">=&gt; </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ UserPtr::new_buffered(system_buffer, input_size, output_size) }, </span><span> TransferMethod::InputDirect </span><span style="color:#f92672;">=&gt; </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ UserPtr::new_direct(mdl_address, system_buffer, output_size, input_size) }, </span><span> TransferMethod::OutputDirect </span><span style="color:#f92672;">=&gt; </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ UserPtr::new_direct(system_buffer, mdl_address, input_size, output_size) }, </span><span> TransferMethod::Neither </span><span style="color:#f92672;">=&gt; </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ UserPtr::new_neither() }, </span><span> } </span></code></pre> <p>While this is a lot of code that we had to implement to deal with the various transfer methods, this implementation greatly improves the ergonomics of our user buffers when dealing with them in our <code>ioctl</code> handler.</p> <h1 id="matching-control-code">Matching Control Code</h1> <p>While we already greatly improved the ergonomics of dealing with user buffers, there is another issue we want to tackle. Currently in our <code>ioctl</code> handler, we would have to match on the <code>ControlCode</code>, which is rather inconvenient. We could already perform some checks beforehand, and provide a function that just return the <code>RequiredAccess</code> and the function number of the ioctl instead of all four fields.</p> <p>Let's first extend our <code>DeviceExtension</code> to keep track of the <code>DeviceType</code> we specified when creating our device:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[repr(C)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>DeviceExtension { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) vtable: </span><span style="color:#f92672;">*const</span><span> device_operations, </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) data: </span><span style="color:#f92672;">*mut </span><span>cty::c_void, </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) device_type: DeviceType, </span><span>} </span></code></pre> <p>We can then add the following convenience function to the <code>Device</code> type:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">device_type</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; DeviceType { </span><span> self.</span><span style="color:#66d9ef;">extension</span><span>().device_type </span><span> } </span></code></pre> <p>Then in the <code>create_device</code> function, we can simply keep track of the <code>DeviceType</code> that the user specified when creating the device:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> extension </span><span style="color:#f92672;">=</span><span> device.</span><span style="color:#66d9ef;">extension_mut</span><span>(); </span><span> extension.device_type </span><span style="color:#f92672;">=</span><span> device_type; </span><span> extension.vtable </span><span style="color:#f92672;">= &amp;</span><span>DeviceOperationsVtable::&lt;T&gt;::</span><span style="color:#ae81ff;">VTABLE</span><span>; </span><span> extension.data </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Box</span><span>::into_raw(data) </span><span style="color:#f92672;">as *mut </span><span>cty::</span><span style="font-style:italic;color:#66d9ef;">c_void</span><span>; </span></code></pre> <p>Now we can check when handling <code>IRP_MJ_DEVICE_CONTROL</code>, whether the <code>DeviceType</code> matches or not:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#ae81ff;">IRP_MJ_DEVICE_CONTROL </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> control_request </span><span style="color:#f92672;">=</span><span> IoControlRequest { </span><span> inner: request, </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">= if</span><span> device.</span><span style="color:#66d9ef;">device_type</span><span>() </span><span style="color:#f92672;">==</span><span> control_request.</span><span style="color:#66d9ef;">control_code</span><span>().</span><span style="color:#66d9ef;">device_type</span><span>() { </span><span> data.</span><span style="color:#66d9ef;">ioctl</span><span>(</span><span style="color:#f92672;">&amp;</span><span>device, </span><span style="color:#f92672;">&amp;</span><span>control_request) </span><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_PARAMETER</span><span>) </span><span> }; </span></code></pre> <p>Since our <code>UserPtr</code> type already handles the transfer method, we generally don't need to deal with that field either. So we can implement the following function called <code>function()</code> that returns the required access and function number fields instead for <code>IoControlRequest</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">function</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; (RequiredAccess, </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>) { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> code </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">control_code</span><span>(); </span><span> </span><span> (code.</span><span style="color:#66d9ef;">required_access</span><span>(), code.</span><span style="color:#66d9ef;">number</span><span>()) </span><span> } </span></code></pre> <h1 id="example">Example</h1> <p>The code for our example driver can be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/blob/main/07-io-controls/src/lib.rs">Github</a>. For this example, we will be creating a driver that stores a 32-bit value as part of the device and provides three operations that allows userspace to print the value, to retrieve the value and to set the value. Our <code>MyDevice</code> struct looks as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">struct </span><span>MyDevice { </span><span> value: </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>, </span><span>} </span></code></pre> <p>Then we will define the function numbers for our operations, which have to start at 0x800 since any numbers below are Microsoft-specific:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">IOCTL_PRINT_VALUE</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0x800</span><span>; </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">IOCTL_READ_VALUE</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0x801</span><span>; </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">IOCTL_WRITE_VALUE</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0x802</span><span>; </span></code></pre> <p>Then we can implement the <code>ioctl</code> callback for <code>MyDevice</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>DeviceOperations </span><span style="color:#f92672;">for </span><span>MyDevice { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">ioctl</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoControlRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> </span><span style="color:#f92672;">match</span><span> request.</span><span style="color:#66d9ef;">function</span><span>() { </span></code></pre> <p>For the first operation, we don't have to process any input or output buffers as we just need to print the value we currently have stored:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> (</span><span style="color:#f92672;">_</span><span>, </span><span style="color:#ae81ff;">IOCTL_PRINT_VALUE</span><span>) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> println!(</span><span style="color:#e6db74;">&quot;value: </span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;">&quot;</span><span>, self.value); </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> } </span></code></pre> <p>For the other operations, we will assume buffered I/O and only handle the buffered case of <code>IoControlBuffers</code> to grab the user pointer. In any other case we return the <code>Error::INVALID_PARAMETER</code> error instead. Then we can use the user pointer to read or write the value respectively:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> (RequiredAccess::</span><span style="color:#ae81ff;">READ_DATA</span><span>, </span><span style="color:#ae81ff;">IOCTL_READ_VALUE</span><span>) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> user_ptr </span><span style="color:#f92672;">=</span><span> request.</span><span style="color:#66d9ef;">user_ptr</span><span>(); </span><span> </span><span> user_ptr.</span><span style="color:#66d9ef;">write</span><span>(</span><span style="color:#f92672;">&amp;</span><span>self.value)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(core::mem::size_of::&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>&gt;() </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>)) </span><span> } </span><span> (RequiredAccess::</span><span style="color:#ae81ff;">WRITE_DATA</span><span>, </span><span style="color:#ae81ff;">IOCTL_WRITE_VALUE</span><span>) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> user_ptr </span><span style="color:#f92672;">=</span><span> request.</span><span style="color:#66d9ef;">user_ptr</span><span>(); </span><span> </span><span> self.value </span><span style="color:#f92672;">=</span><span> user_ptr.</span><span style="color:#66d9ef;">read</span><span>()</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)) </span><span> } </span></code></pre> <p>In any other case, we don't know how to handle the request from userspace, so we simply return the <code>Error::INVALID_PARAMETER</code> error:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">_ =&gt; </span><span>{ </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_PARAMETER</span><span>); </span><span> } </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span>} </span></code></pre> <p>That concludes our driver.</p> <p>As usual, we can load our driver as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sc create example binPath=$(realpath target/x86_64-pc-windows-msvc/debug/driver.sys) type=kernel </span><span>sc start example </span></code></pre> <h1 id="userspace">Userspace</h1> <p>While we could just invoke <a href="https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol"><code>DeviceIoControl</code></a> from userspace, it has a low-level interface that requires us to write unsafe code to pass the buffers as <code>*mut std::ffi::c_void</code>. It would be nice if we could easily define the proper function prototypes for the control codes of our driver. We will use a similar approach as the approach used <a href="https://docs.rs/nix/latest/nix/sys/ioctl/index.html">nix</a> crate and define macros that help us define convenience wrappers to perform ioctls from userspace. To get an idea of how this works, we will look at the <code>ioctl_read</code> macro that we define for Microsoft Windows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[macro_export] </span><span style="color:#66d9ef;">macro_rules! </span><span>ioctl_read { </span><span> (</span><span style="color:#f92672;">$</span><span>(#[</span><span style="font-style:italic;color:#fd971f;">$attr</span><span>:</span><span style="font-style:italic;color:#66d9ef;">meta</span><span>])</span><span style="color:#f92672;">* </span><span style="font-style:italic;color:#fd971f;">$name</span><span>:</span><span style="font-style:italic;color:#66d9ef;">ident</span><span>, </span><span style="font-style:italic;color:#fd971f;">$dev_ty</span><span>:</span><span style="font-style:italic;color:#66d9ef;">expr</span><span>, </span><span style="font-style:italic;color:#fd971f;">$nr</span><span>:</span><span style="font-style:italic;color:#66d9ef;">expr</span><span>, </span><span style="font-style:italic;color:#fd971f;">$ty</span><span>:</span><span style="font-style:italic;color:#66d9ef;">ty</span><span>) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="color:#f92672;">$</span><span>(#[$attr])</span><span style="color:#f92672;">* </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span>$name(handle: </span><span style="color:#f92672;">*mut </span><span>std::ffi::</span><span style="font-style:italic;color:#66d9ef;">c_void</span><span>, data: </span><span style="color:#f92672;">*mut </span><span>$ty) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>, $crate::Error&gt; { </span></code></pre> <p>The code above generates an unsafe function that accepts a file and a pointer to the data of the type specified when using the macro. Then in this function, we take the device type and function number specified to the macro, and use the <code>ControlCode</code> type to pack everything into 32-bit value:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> code </span><span style="color:#f92672;">= </span><span>$crate::ControlCode( </span><span> $dev_ty, </span><span> $crate::RequiredAccess::</span><span style="color:#ae81ff;">READ_DATA</span><span>, </span><span> $nr, </span><span> $crate::TransferMethod::Buffered, </span><span> ).</span><span style="color:#66d9ef;">into</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> return_value </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0</span><span>; </span></code></pre> <p>Then we invoke the <a href="https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol"><code>DeviceIoControl</code></a>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">= </span><span>$crate::DeviceIoControl( </span><span> handle </span><span style="color:#f92672;">as _</span><span>, </span><span> code, </span><span> std::ptr::null_mut(), </span><span> </span><span style="color:#ae81ff;">0</span><span>, </span><span> data </span><span style="color:#f92672;">as _</span><span>, </span><span> std::mem::size_of::&lt;$ty&gt;() </span><span style="color:#f92672;">as _</span><span>, </span><span> </span><span style="color:#f92672;">&amp;mut</span><span> return_value, </span><span> std::ptr::null_mut(), </span><span> ) </span><span style="color:#f92672;">!= </span><span style="color:#ae81ff;">0</span><span>; </span></code></pre> <p>Finally, we deal with the error handling:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">match</span><span> status { </span><span> </span><span style="color:#ae81ff;">true </span><span style="color:#f92672;">=&gt; </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(return_value), </span><span> </span><span style="color:#f92672;">_ =&gt; </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(std::io::Error::last_os_error())</span><span style="color:#f92672;">?</span><span>, </span><span> } </span><span> } </span><span> } </span><span>} </span></code></pre> <p>For our userspace example, we can then use the macros as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>winioctl::{DeviceType, Error}; </span><span style="color:#f92672;">use </span><span>winioctl::{ioctl_none, ioctl_read, ioctl_write}; </span><span> </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">IOCTL_PRINT_VALUE</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0x800</span><span>; </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">IOCTL_READ_VALUE</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0x801</span><span>; </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">IOCTL_WRITE_VALUE</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0x802</span><span>; </span><span> </span><span>ioctl_none!(ioctl_print_value, DeviceType::Unknown, </span><span style="color:#ae81ff;">IOCTL_PRINT_VALUE</span><span>); </span><span>ioctl_read!(ioctl_read_value, DeviceType::Unknown, </span><span style="color:#ae81ff;">IOCTL_READ_VALUE</span><span>, </span><span style="font-style:italic;color:#66d9ef;">i32</span><span>); </span><span>ioctl_write!(ioctl_write_value, DeviceType::Unknown, </span><span style="color:#ae81ff;">IOCTL_WRITE_VALUE</span><span>, </span><span style="font-style:italic;color:#66d9ef;">i32</span><span>); </span></code></pre> <p>For our example, we read the value from the kernel driver, increment it by one and then store the value. Then we will ask the driver to print the current value. Thus, every time we run our example, it will increment the value stored by the driver. The code looks as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> file </span><span style="color:#f92672;">= </span><span>OpenOptions::new() </span><span> .</span><span style="color:#66d9ef;">read</span><span>(</span><span style="color:#ae81ff;">true</span><span>) </span><span> .</span><span style="color:#66d9ef;">write</span><span>(</span><span style="color:#ae81ff;">true</span><span>) </span><span> .</span><span style="color:#66d9ef;">create</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">open</span><span>(</span><span style="color:#e6db74;">&quot;</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">??</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Example&quot;</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> value </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0</span><span>; </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#66d9ef;">ioctl_read_value</span><span>(file.</span><span style="color:#66d9ef;">as_raw_handle</span><span>(), </span><span style="color:#f92672;">&amp;mut</span><span> value)</span><span style="color:#f92672;">?</span><span>; </span><span> } </span><span> </span><span> value </span><span style="color:#f92672;">+= </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#66d9ef;">ioctl_write_value</span><span>(file.</span><span style="color:#66d9ef;">as_raw_handle</span><span>(), </span><span style="color:#f92672;">&amp;</span><span>value)</span><span style="color:#f92672;">?</span><span>; </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#66d9ef;">ioctl_print_value</span><span>(file.</span><span style="color:#66d9ef;">as_raw_handle</span><span>())</span><span style="color:#f92672;">?</span><span>; </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span>} </span></code></pre> <p>The <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/winioctl">winioctl</a> crate and <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/blob/main/user/07-io-controls/src/main.rs">this example</a> are both available on Github.</p> <h1 id="what-s-next">What's Next?</h1> <p>TODO</p> <h1 id="references">References</h1> <ol> <li><a href="https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc"><code>VirtualAlloc</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntallocatevirtualmemory"><code>NtAllocateVirtualMemory</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile"><code>ReadFile</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows/win32/devnotes/ntreadfile"><code>NtReadFile</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol"><code>DeviceIoControl</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mj-device-control"><code>IRP_MJ_DEVICE_CONTROL</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes">&quot;Introduction to I/O Control Codes&quot; - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/buffer-descriptions-for-i-o-control-codes">&quot;Buffer Descriptions for I/O Control Codes&quot; - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/defining-i-o-control-codes">&quot;Defining I/O Control Codes&quot; - MSDN</a></li> <li><a href="https://docs.rs/nix/latest/nix/sys/ioctl/index.html"><code>nix</code> crate</a></li> </ol> Windows Drivers in Rust: Reading and Writing 2021-10-06T00:00:00+00:00 2021-10-06T00:00:00+00:00 https://codentium.com/guides/windows-dev/windows-drivers-in-rust-reading-and-writing/ <h1 id="introduction">Introduction</h1> <p>In <a href="/guides/windows-dev/windows-drivers-in-rust-creating-devices">the previous article</a> we took the first step towards an API to create devices with user data and our own callbacks. In this article we will be looking at implementing callbacks for reading from and writing to our device file.</p> <h1 id="dispatching-the-callbacks">Dispatching the Callbacks</h1> <p>Similar to how we handled the <code>IRP_MJ_CREATE</code>, <code>IRP_MJ_CLOSE</code> and <code>IRP_MJ_CLEANUP</code> major functions in the <code>dispatch_callback</code> function in the previous article, we will now have to add code to dispatch the <code>IRP_MJ_READ</code> and <code>IRP_MJ_WRITE</code> functions:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#ae81ff;">IRP_MJ_READ </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> read_request </span><span style="color:#f92672;">=</span><span> ReadRequest { </span><span> inner: request, </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">=</span><span> data.</span><span style="color:#66d9ef;">read</span><span>(</span><span style="color:#f92672;">&amp;</span><span>device, </span><span style="color:#f92672;">&amp;</span><span>read_request); </span><span> </span><span> request </span><span style="color:#f92672;">=</span><span> read_request.inner; </span><span> status </span><span> } </span><span> </span><span style="color:#ae81ff;">IRP_MJ_WRITE </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> write_request </span><span style="color:#f92672;">=</span><span> WriteRequest { </span><span> inner: request, </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">=</span><span> data.</span><span style="color:#66d9ef;">write</span><span>(</span><span style="color:#f92672;">&amp;</span><span>device, </span><span style="color:#f92672;">&amp;</span><span>write_request); </span><span> </span><span> request </span><span style="color:#f92672;">=</span><span> write_request.inner; </span><span> status </span><span> } </span></code></pre> <p>The code above is slightly different from the code we wrote before. While it still calls into the <code>read</code> and <code>write</code> functions that we will define in <code>DeviceOperations</code>, it wraps the <code>IoRequest</code> into a <code>ReadRequest</code> and <code>WriteRequest</code> respectively before passing the requests to these functions. That is because the I/O request of the <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile"><code>ReadFile</code></a> and <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile"><code>WriteFile</code></a> functions will provide us access to the data buffer that userspace supplied to these functions.</p> <p>We now proceed to extend the <code>DeviceOperations</code> trait with the <code>read</code> and <code>write</code> callbacks, as well as some default implementation:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>ReadRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">write</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>WriteRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span></code></pre> <h1 id="extending-the-requests">Extending the Requests</h1> <p>When implementing the <code>ReadRequest</code> and <code>WriteRequest</code> types, we first want to make sure that we can still use all the functions that we implemented for <code>IoRequest</code>. Fortunately, we can wrap the <code>IoRequest</code> into our <code>ReadRequest</code> and <code>WriteRequest</code> and then implement <a href="https://doc.rust-lang.org/core/ops/trait.Deref.html"><code>core::ops::Deref</code></a>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>ReadRequest { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) inner: IoRequest, </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>Deref </span><span style="color:#f92672;">for </span><span>ReadRequest { </span><span> </span><span style="font-style:italic;color:#66d9ef;">type </span><span>Target </span><span style="color:#f92672;">=</span><span> IoRequest; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">deref</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">Self::</span><span>Target { </span><span> </span><span style="color:#f92672;">&amp;</span><span>self.inner </span><span> } </span><span>} </span></code></pre> <p>With <code>ReadRequest</code> and <code>WriteRequest</code> implementing <code>Deref</code> for <code>IoRequest</code>, we can now access all the functions available for <code>IoRequest</code>, but at the same time we can extend <code>ReadRequest</code> and <code>WriteRequest</code> with functions specific to those requests.</p> <p>First, we will implement the function that gives us access to the user buffer that has been supplied to us from userspace for these requests. Fortunately, thanks to <code>Deref</code> we have access to the IRP and the current I/O stack location:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>ReadRequest { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">user_ptr</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; UserPtr { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> stack_location </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">stack_location</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> irp </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">irp</span><span>(); </span></code></pre> <p>We need these to tell apart buffered I/O from direct I/O. For direct I/O, we will get a <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/using-mdls">Memory Descriptor List (MDL)</a> instead. A MDL is a set of one or more physical pages that may be discontiguous, that is they are not necessarily neighboring pages in the physical address space. However, we can use <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmgetsystemaddressformdlsafe"><code>MmGetSystemAddressForMdlSafe</code></a> to map these physical pages to a contiguous virtual area, such that our driver can access the user buffer. Similarly, <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmgetmdlbytecount"><code>MmGetMdlByteCount</code></a> gives us the size of the MDL in bytes. The function looks like this:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">if !</span><span>irp.MdlAddress.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> ptr </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> MmGetSystemAddressForMdlSafe(irp.MdlAddress, </span><span style="color:#ae81ff;">MM_PAGE_PRIORITY</span><span>::HighPagePriority </span><span style="color:#f92672;">as _</span><span>) </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> size </span><span style="color:#f92672;">= unsafe </span><span>{ MmGetMdlByteCount(irp.MdlAddress) } </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>; </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ UserPtr::new(ptr, </span><span style="color:#ae81ff;">0</span><span>, size) } </span></code></pre> <p>For buffered I/O, we will get a <code>SystemBuffer</code> and the length of the buffer as a parameter:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> } </span><span style="color:#f92672;">else if !unsafe </span><span>{ irp.AssociatedIrp.SystemBuffer }.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> ptr </span><span style="color:#f92672;">= unsafe </span><span>{ irp.AssociatedIrp.SystemBuffer }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> size </span><span style="color:#f92672;">= unsafe </span><span>{ stack_location.Parameters.Read }.Length </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>; </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ UserPtr::new(ptr, </span><span style="color:#ae81ff;">0</span><span>, size) } </span></code></pre> <p>Alternatively, if the user did not set the <code>DO_BUFFERED_IO</code> or <code>DO_DIRECT_IO</code> flag, we would get <code>NULL</code> pointers in both cases, so we will just return a <code>UserPtr</code> that does not do anything:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ UserPtr::new(core::ptr::null_mut(), </span><span style="color:#ae81ff;">0</span><span>, </span><span style="color:#ae81ff;">0</span><span>) } </span><span> } </span><span> } </span></code></pre> <p>Similarly, we will implement an <code>offset</code> function that provides us the byte offset in the file that userspace is trying to read from or write to:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">offset</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">i64 </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> stack_location </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">stack_location</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> irp </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">irp</span><span>(); </span><span> </span><span> </span><span style="color:#f92672;">if !</span><span>irp.MdlAddress.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> (</span><span style="color:#f92672;">unsafe </span><span>{ MmGetMdlByteOffset(irp.MdlAddress) }) </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">i64 </span><span> } </span><span style="color:#f92672;">else if !unsafe </span><span>{ irp.AssociatedIrp.SystemBuffer }.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ stack_location.Parameters.Read.ByteOffset.QuadPart } </span><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> </span><span style="color:#ae81ff;">0 </span><span> } </span><span> } </span><span>} </span></code></pre> <p>The implementation for <code>WriteRequest</code> is pretty much the same.</p> <h1 id="user-pointers">User Pointers</h1> <p>If you paid close attention, you may have noticed that we have been wrapping the pointer to the user buffer and the size of the user buffer into a <code>UserPtr</code> struct. We don't want to provide access to the raw pointer itself, but preferably a byte slice or some type instead Therefore, we will be providing our own interface <code>UserPtr</code> that owns the pointer and the size, and gives us access to a byte slice or that lets us read the data into some type <code>T</code>. The basic implementation looks as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>UserPtr { </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">new</span><span>( </span><span> </span><span style="font-style:italic;color:#fd971f;">ptr</span><span>: </span><span style="color:#f92672;">*mut </span><span>cty::c_void, </span><span> </span><span style="font-style:italic;color:#fd971f;">read_size</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> </span><span style="font-style:italic;color:#fd971f;">write_size</span><span>: </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>, </span><span> ) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> ptr, </span><span> read_size, </span><span> write_size, </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read_size</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">usize </span><span>{ </span><span> self.read_size </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">write_size</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">usize </span><span>{ </span><span> self.write_size </span><span> } </span></code></pre> <p>To convert the pointer and the size to an immutable or mutable slice, we can use the <a href="https://doc.rust-lang.org/core/slice/fn.from_raw_parts.html"><code>core::slice::from_raw_parts</code></a> and <a href="https://doc.rust-lang.org/core/slice/fn.from_raw_parts_mut.html"><code>core::slice::from_raw_parts_mut</code></a> functions:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_slice</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span>[</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>] { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::slice::from_raw_parts(self.ptr </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">*const u8</span><span>, self.read_size) </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_mut_slice</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;mut </span><span>[</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>] { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::slice::from_raw_parts_mut(self.ptr </span><span style="color:#f92672;">as *mut </span><span style="font-style:italic;color:#66d9ef;">u8</span><span>, self.write_size) </span><span> } </span><span> } </span></code></pre> <p>In addition, we also provide functions to read the buffer into a type <code>T</code> or write a type <code>T</code> into the buffer, as long as the size of <code>T</code> does not exceed the size of the user buffer. We check the size of <code>T</code> and if it exceeds the size of the user buffer, we return <code>Error::INVALID_USER_BUFFER</code>. Otherwise, we use <a href="https://doc.rust-lang.org/core/ptr/fn.copy_nonoverlapping.html"><code>core::ptr::copy_nonoverlapping</code></a> to copy over the data between <code>T</code> and the user buffer. The code for that looks as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read</span><span>&lt;T: </span><span style="font-style:italic;color:#66d9ef;">Default</span><span>&gt;(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">obj</span><span>: </span><span style="color:#f92672;">&amp;mut</span><span> T) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;T, Error&gt; { </span><span> </span><span style="color:#f92672;">if </span><span>core::mem::size_of::&lt;T&gt;() </span><span style="color:#f92672;">&gt; </span><span>self.read_size { </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_USER_BUFFER</span><span>); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> obj </span><span style="color:#f92672;">= </span><span>T::default(); </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::ptr::copy_nonoverlapping( </span><span> self.ptr </span><span style="color:#f92672;">as _</span><span>, </span><span> </span><span style="color:#f92672;">&amp;mut</span><span> obj, </span><span> core::mem::size_of::&lt;T&gt;(), </span><span> ); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(obj) </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">write</span><span>&lt;T&gt;(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">obj</span><span>: </span><span style="color:#f92672;">&amp;</span><span>T) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> </span><span style="color:#f92672;">if </span><span>core::mem::size_of::&lt;T&gt;() </span><span style="color:#f92672;">&gt; </span><span>self.write_size { </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_USER_BUFFER</span><span>); </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> core::ptr::copy_nonoverlapping( </span><span> obj, </span><span> self.ptr </span><span style="color:#f92672;">as _</span><span>, </span><span> core::mem::size_of::&lt;T&gt;(), </span><span> ); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span>} </span></code></pre> <p>In the implementation above we also differentiate the <code>read_size</code> from the <code>write_size</code>. We won't need that right now, but it will be useful in the next article when we implement support for I/O controls.</p> <h1 id="example">Example</h1> <p>The code for this example can be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/blob/main/06-reading-and-writing/src/lib.rs">Github</a>. For this example, we will be implementing the <code>read</code> and <code>write</code> callbacks of our <code>DeviceOperations</code> trait to read the user data into a <code>Vec&lt;u8&gt;</code> and to write whatever data is stored inside a <code>Vec&lt;u8&gt;</code> to the user buffer: First, we extend our <code>MyDevice</code> struct to store the user data:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">struct </span><span>MyDevice { </span><span> data: </span><span style="font-style:italic;color:#66d9ef;">Vec</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>&gt;, </span><span>} </span></code></pre> <p>In the <code>read</code> callback, we will first make sure the user specified offset does not execeed the size of our buffer. Then we make sure the user specified size does not exceed the size of our buffer minus the offset. After limiting both the offset and the size, we copy over the bytes and return how many bytes we copied over to userspace. The code looks like this:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>DeviceOperations </span><span style="color:#f92672;">for </span><span>MyDevice { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">read</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>ReadRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> user_ptr </span><span style="color:#f92672;">=</span><span> request.</span><span style="color:#66d9ef;">user_ptr</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> slice </span><span style="color:#f92672;">=</span><span> user_ptr.</span><span style="color:#66d9ef;">as_mut_slice</span><span>(); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> offset </span><span style="color:#f92672;">= </span><span>(request.</span><span style="color:#66d9ef;">offset</span><span>() </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>).</span><span style="color:#66d9ef;">min</span><span>(self.data.</span><span style="color:#66d9ef;">len</span><span>()); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> size </span><span style="color:#f92672;">=</span><span> slice.</span><span style="color:#66d9ef;">len</span><span>().</span><span style="color:#66d9ef;">min</span><span>(self.data.</span><span style="color:#66d9ef;">len</span><span>() </span><span style="color:#f92672;">-</span><span> offset); </span><span> </span><span> slice[</span><span style="color:#ae81ff;">0</span><span style="color:#f92672;">..</span><span>size].</span><span style="color:#66d9ef;">copy_from_slice</span><span>(</span><span style="color:#f92672;">&amp;</span><span>self.data[offset</span><span style="color:#f92672;">..</span><span>offset </span><span style="color:#f92672;">+</span><span> size]); </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(size </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span></code></pre> <p>For our <code>write</code> callback, we simply assume the offset is always 0 and limit the user input to no more than 4096 bytes. If both constraints are met, we simply take the slice of user data and turn it into a <code>Vec&lt;u8&gt;</code> and store it:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">write</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>WriteRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> user_ptr </span><span style="color:#f92672;">=</span><span> request.</span><span style="color:#66d9ef;">user_ptr</span><span>(); </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> request.</span><span style="color:#66d9ef;">offset</span><span>() </span><span style="color:#f92672;">&gt; </span><span style="color:#ae81ff;">0 </span><span>{ </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">END_OF_FILE</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> slice </span><span style="color:#f92672;">=</span><span> user_ptr.</span><span style="color:#66d9ef;">as_slice</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> size </span><span style="color:#f92672;">=</span><span> slice.</span><span style="color:#66d9ef;">len</span><span>().</span><span style="color:#66d9ef;">min</span><span>(</span><span style="color:#ae81ff;">4096</span><span>); </span><span> </span><span> self.data </span><span style="color:#f92672;">=</span><span> slice[</span><span style="color:#ae81ff;">0</span><span style="color:#f92672;">..</span><span>size].</span><span style="color:#66d9ef;">to_vec</span><span>(); </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(size </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span>} </span></code></pre> <p>That concludes the changes to our driver.</p> <p>We can load our driver as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sc create example binPath=$(realpath target/x86_64-pc-windows-msvc/debug/driver.sys) type=kernel </span><span>sc start example </span></code></pre> <h1 id="userspace">Userspace</h1> <p>The code can be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/user/06-reading-and-writing">Github</a>. To test our driver, we will open our device file with read and write permissions. Then we write a string into the device file, which our driver will store. Up next we will allocate a buffer and try to read data from the device file into that buffer. Finally, we try to decode it as an UTF-8 string and print it if it succeeds, otherwise we simply display the data in hex:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>std::fs::OpenOptions; </span><span style="color:#f92672;">use </span><span>std::io::{Read, Write}; </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> file </span><span style="color:#f92672;">= </span><span>OpenOptions::new() </span><span> .</span><span style="color:#66d9ef;">read</span><span>(</span><span style="color:#ae81ff;">true</span><span>) </span><span> .</span><span style="color:#66d9ef;">write</span><span>(</span><span style="color:#ae81ff;">true</span><span>) </span><span> .</span><span style="color:#66d9ef;">create</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">open</span><span>(</span><span style="color:#e6db74;">&quot;</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">??</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Example&quot;</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> file.</span><span style="color:#66d9ef;">write_all</span><span>(</span><span style="color:#e6db74;">&quot;Hello, world!&quot;</span><span>.</span><span style="color:#66d9ef;">as_bytes</span><span>())</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> data </span><span style="color:#f92672;">= </span><span>vec![</span><span style="color:#ae81ff;">0</span><span style="font-style:italic;color:#66d9ef;">u8</span><span>; </span><span style="color:#ae81ff;">4096</span><span>]; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> size </span><span style="color:#f92672;">=</span><span> file.</span><span style="color:#66d9ef;">read</span><span>(</span><span style="color:#f92672;">&amp;mut</span><span> data)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="color:#f92672;">match </span><span>std::str::from_utf8(</span><span style="color:#f92672;">&amp;</span><span>data[</span><span style="color:#f92672;">..</span><span>size]) { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(s) </span><span style="color:#f92672;">=&gt; </span><span>println!(</span><span style="color:#e6db74;">&quot;read </span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;"> bytes: </span><span style="color:#ae81ff;">\&quot;{}\&quot;</span><span style="color:#e6db74;">&quot;</span><span>, size, s), </span><span> </span><span style="color:#f92672;">_ =&gt; </span><span>println!(</span><span style="color:#e6db74;">&quot;read </span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;"> bytes: {:x?}&quot;</span><span>, size, </span><span style="color:#f92672;">&amp;</span><span>data[</span><span style="color:#f92672;">..</span><span>size]), </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span>} </span></code></pre> <h1 id="what-s-next">What's Next?</h1> <p>In the <a href="/guides/windows-dev/windows-drivers-in-rust-io-controls">next article</a>, we will look at I/O controls which provide us with an alternative way to interact with our driver.</p> <h1 id="references">References</h1> <ol> <li><a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile"><code>ReadFile</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile"><code>WriteFile</code> - MSDN</a></li> <li><a href="https://doc.rust-lang.org/core/ops/trait.Deref.html"><code>core::ops::Deref</code> - Rust documentation</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/using-mdls">&quot;Using MDLs&quot; - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmgetsystemaddressformdlsafe"><code>MmGetSystemAddressForMdlSafe</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmgetmdlbytecount"><code>MmGetMdlByteCount</code> - MSDN</a></li> <li><a href="https://doc.rust-lang.org/core/slice/fn.from_raw_parts.html"><code>core::slice::from_raw_parts</code> - Rust documentation</a></li> <li><a href="https://doc.rust-lang.org/core/slice/fn.from_raw_parts_mut.html"><code>core::slice::from_raw_parts_mut</code> - Rust documentation</a></li> <li><a href="https://doc.rust-lang.org/core/ptr/fn.copy_nonoverlapping.html"><code>core::ptr::copy_nonoverlapping</code> - Rust documentation</a></li> </ol> Windows Drivers in Rust: Creating Devices 2021-09-27T00:00:00+00:00 2021-09-27T00:00:00+00:00 https://codentium.com/guides/windows-dev/windows-drivers-in-rust-creating-devices/ <h1 id="introduction">Introduction</h1> <p>In <a href="/guides/windows-dev/windows-drivers-in-rust-safe-framework">the previous article</a> we created a safe framework in Rust for writing Windows kernel drivers with a global allocator, <code>print!</code> and <code>println!</code> macros and boilerplate code to set up the <code>driver_entry</code> and <code>driver_exit</code> functions. However, up to now our driver has only been printing &quot;Hello, world!&quot; and &quot;Bye bye!&quot; whenever the driver gets loaded and unloaded. It would be more interesting if we could interact with our driver from userspace. Therefore, we are going to look at how to create device files in this article and implement a safe abstraction for setting up device files in Rust.</p> <h1 id="creating-devices">Creating Devices</h1> <p>To create and delete a device we can use the <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice"><code>IoCreateDevice</code></a> and <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iodeletedevice"><code>IoDeleteDevice</code></a> functions respectively. We will first start with declaring the <code>Device</code> struct wrapping the <code>DEVICE_OBJECT</code> pointer:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>Device { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) raw: </span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT, </span><span>} </span></code></pre> <p>Since we want to be able to store the <code>Device</code> as part of the struct implementing the <code>KernelModule</code>, we ideally want it to be thread-safe. Therefore, we will implement both <code>Send</code> and <code>Sync</code> for the <code>Device</code>, but as we have to use an <code>unsafe impl</code> we have to make sure that our implementation is guaranteed to be thread-safe:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">unsafe </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>Send </span><span style="color:#f92672;">for </span><span>Device {} </span><span style="color:#f92672;">unsafe </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>Sync </span><span style="color:#f92672;">for </span><span>Device {} </span></code></pre> <p>Then we can implement the <code>Drop</code> trait for our <code>Device</code> which simply calls <code>IoDeleteDevice</code> on the pointer. In addition, we will add a check to see if the pointer is not null. This way we can take ownership of the pointer without it being dropped, which will be useful later on:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>Drop </span><span style="color:#f92672;">for </span><span>Device { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">drop</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>) { </span><span> </span><span style="color:#f92672;">if </span><span>self.raw.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> </span><span style="color:#f92672;">return</span><span>; </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> IoDeleteDevice(self.raw); </span><span> } </span><span> } </span><span>} </span></code></pre> <p>It is also helpful to implement some functions to get the raw pointer or turn a raw pointer to a <code>DEVICE_OBJECT</code> into a <code>Device</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>Device { </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">from_raw</span><span>(</span><span style="font-style:italic;color:#fd971f;">raw</span><span>: </span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> raw, </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_raw</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">*const</span><span> DEVICE_OBJECT { </span><span> self.raw </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">*const </span><span style="color:#f92672;">_ </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_raw_mut</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT { </span><span> self.raw </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">into_raw</span><span>(</span><span style="color:#f92672;">mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT { </span><span> core::mem::replace(</span><span style="color:#f92672;">&amp;mut </span><span>self.raw, core::ptr::null_mut()) </span><span> } </span><span>} </span></code></pre> <p>Before we extend our <code>Driver</code> type with a <code>create_device</code> function to create the <code>Device</code>, we will have a look at the arguments of <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice"><code>IoCreateDevice</code></a>. We can skip over the first two arguments, as the first is the pointer of our <code>Driver</code> type and the second one is the amount of size <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice"><code>IoCreateDevice</code></a> should allocate for user-specific data associated with the <code>Device</code>. The third argument is a pointer to a <code>UNICODE_STRING</code> containing the device path, so let's create a function that conveniently creates a <code>UNICODE_STRING</code> from a slice of UTF-16 data or <code>&amp;[u16]</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">create_unicode_string</span><span>(</span><span style="font-style:italic;color:#fd971f;">s</span><span>: </span><span style="color:#f92672;">&amp;</span><span>[</span><span style="font-style:italic;color:#66d9ef;">u16</span><span>]) -&gt; UNICODE_STRING { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> len </span><span style="color:#f92672;">=</span><span> s.</span><span style="color:#66d9ef;">len</span><span>(); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> n </span><span style="color:#f92672;">= if</span><span> len </span><span style="color:#f92672;">&gt; </span><span style="color:#ae81ff;">0 </span><span style="color:#f92672;">&amp;&amp;</span><span> s[len </span><span style="color:#f92672;">- </span><span style="color:#ae81ff;">1</span><span>] </span><span style="color:#f92672;">== </span><span style="color:#ae81ff;">0 </span><span>{ len </span><span style="color:#f92672;">- </span><span style="color:#ae81ff;">1 </span><span>} </span><span style="color:#f92672;">else </span><span>{ len }; </span><span> </span><span> </span><span style="color:#ae81ff;">UNICODE_STRING </span><span>{ </span><span> Length: (n </span><span style="color:#f92672;">* </span><span style="color:#ae81ff;">2</span><span>) </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u16</span><span>, </span><span> MaximumLength: (len </span><span style="color:#f92672;">* </span><span style="color:#ae81ff;">2</span><span>) </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u16</span><span>, </span><span> Buffer: s.</span><span style="color:#66d9ef;">as_ptr</span><span>() </span><span style="color:#f92672;">as _</span><span>, </span><span> } </span><span>} </span></code></pre> <p>The fourth argument is the <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/specifying-device-types">device type</a>. For the device type we will implement our own enum in Rust, and then implement <code>Into&lt;u32&gt;</code> to map the enum values into the appropriate constants:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[derive(Clone, Copy, Debug, PartialEq, Eq)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">enum </span><span>DeviceType { </span><span> Port8042, </span><span> Acpi, </span><span> Battery, </span><span> </span><span style="color:#f92672;">... </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl Into</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>&gt; </span><span style="color:#f92672;">for </span><span>DeviceType { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">into</span><span>(</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span>{ </span><span> </span><span style="color:#f92672;">match </span><span>self { </span><span> DeviceType::Port8042 </span><span style="color:#f92672;">=&gt; </span><span>windows_kernel_sys::base::</span><span style="color:#ae81ff;">FILE_DEVICE_8042_PORT</span><span>, </span><span> DeviceType::Acpi </span><span style="color:#f92672;">=&gt; </span><span>windows_kernel_sys::base::</span><span style="color:#ae81ff;">FILE_DEVICE_ACPI</span><span>, </span><span> DeviceType::Battery </span><span style="color:#f92672;">=&gt; </span><span>windows_kernel_sys::base::</span><span style="color:#ae81ff;">FILE_DEVICE_BATTERY</span><span>, </span><span> </span><span style="color:#f92672;">... </span><span> } </span><span> } </span><span>} </span></code></pre> <p>The fifth argument is a bitwise concatenation of flags that provide information about the driver's device and of which the most common one is <code>FILE_DEVICE_SECURE_OPEN</code>. We will simply use the <code>bitflags!</code> macro from the <code>bitflags</code> crate to create a <code>DeviceFlags</code> type:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>bitflags! { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>DeviceFlags { </span><span> const SECURE_OPEN = windows_kernel_sys::base::FILE_DEVICE_SECURE_OPEN; </span><span> ... </span><span> } </span><span>} </span></code></pre> <p>The sixth argument specifies whether access to the device is exclusive or not. Instead of using a boolean, we will implement an enum to clearly differentiate between <code>Access::NonExclusive</code> and <code>Access::Exclusive</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[derive(Copy, Clone, Debug, PartialEq, Eq)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">enum </span><span>Access { </span><span> NonExclusive, </span><span> Exclusive, </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>Access { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">is_exclusive</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">bool </span><span>{ </span><span> </span><span style="color:#f92672;">match *</span><span>self { </span><span> Access::Exclusive </span><span style="color:#f92672;">=&gt; </span><span style="color:#ae81ff;">true</span><span>, </span><span> </span><span style="color:#f92672;">_ =&gt; </span><span style="color:#ae81ff;">false</span><span>, </span><span> } </span><span> } </span><span>} </span></code></pre> <p>Finally, the seventh argument is a pointer to a pointer of the <code>DEVICE_OBJECT</code>, such that the function can give us a pointer to the allocated <code>DEVICE_OBJECT</code>. Now that we have gone over all the arguments and provided safe Rust abstractions for them, we can start implementing <code>Driver::create_device()</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>Driver { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">create_device</span><span>( </span><span> </span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span> </span><span style="font-style:italic;color:#fd971f;">name</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">str</span><span>, </span><span> </span><span style="font-style:italic;color:#fd971f;">device_type</span><span>: DeviceType, </span><span> </span><span style="font-style:italic;color:#fd971f;">device_flags</span><span>: DeviceFlags, </span><span> </span><span style="font-style:italic;color:#fd971f;">access</span><span>: Access, </span><span> ) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;Device, Error&gt; { </span><span> </span><span style="color:#75715e;">// Convert the name to UTF-16 and then create a UNICODE_STRING. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> name </span><span style="color:#f92672;">= </span><span>U16CString::from_str(name).</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> name_ptr </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">create_unicode_string</span><span>(name.</span><span style="color:#66d9ef;">as_slice</span><span>()); </span><span> </span><span> </span><span style="color:#75715e;">// Create the device. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> device </span><span style="color:#f92672;">= </span><span>core::ptr::null_mut(); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> IoCreateDevice( </span><span> self.raw, </span><span> </span><span style="color:#ae81ff;">0</span><span>, </span><span> </span><span style="color:#f92672;">&amp;mut</span><span> name_ptr, </span><span> device_type.</span><span style="color:#66d9ef;">into</span><span>(), </span><span> device_flags.</span><span style="color:#66d9ef;">bits</span><span>(), </span><span> access.</span><span style="color:#66d9ef;">is_exclusive</span><span>() </span><span style="color:#f92672;">as _</span><span>, </span><span> </span><span style="color:#f92672;">&amp;mut</span><span> device, </span><span> } </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> status </span><span style="color:#f92672;">!= </span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span>{ </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(error::from_kernel_errno(status)); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(Device { </span><span> raw: device, </span><span> }); </span><span> } </span><span>} </span></code></pre> <h1 id="associating-user-data">Associating User Data</h1> <p>While we now have an API that allows us to create and drop device files, there is not much useful we can do with them yet. However, we will first have a look at associating user data with the device before we can actually deal with providing our own implementation for the device file operations. Ideally, we want to be able to define our own type that implements a trait and associate that with our device. We will call this trait <code>DeviceOperations</code> for reasons that will be more obvious later on, but for now we will leave it empty other than requiring it to be <code>Sync</code> and <code>Sized</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">trait </span><span>DeviceOperations: Sync + Sized { </span><span>} </span></code></pre> <p>If we look back at <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice"><code>IoCreateDevice</code></a>, then we can see that the second argument allows us to specify an arbitrary size for <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice"><code>IoCreateDevice</code></a> to allocate extra storage for user-specific data. We will be using this to store the data that the user wants to associate with the device. More specifically, we will use this extra storage to store a virtual table, i.e. a reference to a struct of function pointers, and a void pointer that points to the user data:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[repr(C)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>DeviceExtension { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) vtable: </span><span style="color:#f92672;">*const</span><span> device_operations, </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) data: </span><span style="color:#f92672;">*mut </span><span>cty::c_void, </span><span>} </span></code></pre> <p>In addition, we will extend <code>impl Device</code> with functions to access the <code>DeviceExtension</code> as well as the <code>vtable</code> and the user data:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">extension</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span>DeviceExtension { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#f92672;">&amp;*</span><span>((</span><span style="color:#f92672;">*</span><span>self.raw).DeviceExtension </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">*const</span><span> DeviceExtension) </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">extension_mut</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;mut</span><span> DeviceExtension { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#f92672;">&amp;mut *</span><span>((</span><span style="color:#f92672;">*</span><span>self.raw).DeviceExtension </span><span style="color:#f92672;">as *mut</span><span> DeviceExtension) </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">vtable</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span>device_operations { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#f92672;">&amp;*</span><span>(self.</span><span style="color:#66d9ef;">extension</span><span>().vtable </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">*const </span><span style="color:#f92672;">_</span><span>) </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">data</span><span>&lt;T: DeviceOperations&gt;(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span>T { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#f92672;">&amp;*</span><span>(self.</span><span style="color:#66d9ef;">extension</span><span>().data </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">*const</span><span> T) </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">data_mut</span><span>&lt;T: DeviceOperations&gt;(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;mut</span><span> T { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#f92672;">&amp;mut *</span><span>(self.</span><span style="color:#66d9ef;">extension</span><span>().data </span><span style="color:#f92672;">as *mut</span><span> T) </span><span> } </span><span> } </span></code></pre> <p>We can then extend <code>Driver::create_device</code> to accept user data of type <code>T</code> that must implement the <code>DeviceOperations</code> trait. Since we want to associate it with the device, we must ensure that it will live at least as long as the device itself. Therefore we will box it up using <code>Box::new</code> from <code>alloc::boxed::Box</code>. Then we can turn the <code>Box</code> into a raw pointer and store that pointer into the extra allocated storage for <code>DeviceExtension</code>. With these changes, our <code>Driver::create_device</code> function looks a bit like the following:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>Driver { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">create_device</span><span>&lt;T&gt;( </span><span> </span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span> </span><span style="font-style:italic;color:#fd971f;">name</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">str</span><span>, </span><span> </span><span style="font-style:italic;color:#fd971f;">device_type</span><span>: DeviceType, </span><span> </span><span style="font-style:italic;color:#fd971f;">device_flags</span><span>: DeviceFlags, </span><span> </span><span style="font-style:italic;color:#fd971f;">access</span><span>: Access, </span><span> </span><span style="font-style:italic;color:#fd971f;">data</span><span>: T, </span><span> ) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;Device, Error&gt; </span><span> </span><span style="color:#f92672;">where </span><span> T: DeviceOperations </span><span> { </span><span> </span><span style="color:#75715e;">// Box the data. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> data </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Box</span><span>::new(data); </span><span> </span><span> </span><span style="color:#f92672;">... </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> IoCreateDevice( </span><span> self.raw, </span><span> core::mem::size_of::&lt;DeviceExtension&gt;() </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u32</span><span>, </span><span> </span><span style="color:#f92672;">&amp;mut</span><span> name_ptr, </span><span> device_type.</span><span style="color:#66d9ef;">into</span><span>(), </span><span> device_flags.</span><span style="color:#66d9ef;">bits</span><span>(), </span><span> access.</span><span style="color:#66d9ef;">is_exclusive</span><span>() </span><span style="color:#f92672;">as _</span><span>, </span><span> </span><span style="color:#f92672;">&amp;mut</span><span> device, </span><span> } </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> status </span><span style="color:#f92672;">!= </span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span>{ </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(error::from_kernel_errno(status)); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> device </span><span style="color:#f92672;">= unsafe </span><span>{ Device::from_raw(device) }; </span><span> </span><span> </span><span style="color:#75715e;">// Store the boxed data and vtable. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> extension </span><span style="color:#f92672;">=</span><span> device.</span><span style="color:#66d9ef;">extension_mut</span><span>(); </span><span> extension.vtable </span><span style="color:#f92672;">= &amp;</span><span>DeviceOperationsVtable::&lt;T&gt;::</span><span style="color:#ae81ff;">VTABLE</span><span>; </span><span> extension.data </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Box</span><span>::into_raw(data) </span><span style="color:#f92672;">as *mut </span><span>cty::</span><span style="font-style:italic;color:#66d9ef;">c_void</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(device) </span><span> } </span><span>} </span></code></pre> <p>In the above code you may notice that we are already setting up the vtable, but what is the purpose of this vtable? Well, we will need it later on to handle any of the operations on the device file, but there is another issue we have to solve. The <code>Driver::create_device</code> function currently takes an argument called <code>data</code> of type <code>T</code> and then decides to <code>Box</code> it and store the raw pointer to <code>Box&lt;T&gt;</code> as part of the device extension data, but when we drop the <code>Device</code> we also have to drop this boxed data. Unfortunately, the <code>Drop</code> implementation of <code>Device</code> does not know what the original type <code>T</code> is, but it has to know that in order to call the appropriate <code>Drop</code> implementation. However, we can generate a unique vtable for each type <code>T</code> which means that the function pointers in our vtable point to functions that are aware of what <code>T</code> is. We will first declare <code>device_operations</code> with a <code>release</code> function pointer that accepts a pointer to the <code>DEVICE_OBJECT</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[repr(C)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>device_operations { </span><span> release: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span>&lt;</span><span style="color:#f92672;">extern </span><span style="color:#e6db74;">&quot;C&quot;</span><span> fn (</span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT)&gt;, </span><span>} </span></code></pre> <p>Then we will create our <code>release_callback</code> function which accesses the device extension of the <code>DEVICE_OBJECT</code>, swaps out the pointer with a <code>NULL</code> pointer and converts the swapped out raw pointer back into the original <code>Box&lt;T&gt;</code>. Then as the function goes out of scope, the <code>Box&lt;T&gt;</code> will be dropped, invoking the right <code>Drop</code> implementation for our user data:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">extern </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">release_callback</span><span>&lt;T: DeviceOperations&gt;( </span><span> </span><span style="font-style:italic;color:#fd971f;">device</span><span>: </span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT, </span><span>) { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> extension </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#f92672;">*</span><span>device).DeviceExtension </span><span style="color:#f92672;">as *mut</span><span> DeviceExtension; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> ptr </span><span style="color:#f92672;">= </span><span>core::mem::replace(</span><span style="color:#f92672;">&amp;mut </span><span>(</span><span style="color:#f92672;">*</span><span>extension).data, core::ptr::null_mut()); </span><span> </span><span style="font-style:italic;color:#66d9ef;">Box</span><span>::from_raw(ptr </span><span style="color:#f92672;">as *mut</span><span> T); </span><span> } </span><span>} </span></code></pre> <p>Then we declare the <code>DeviceOperationsVtable&lt;T&gt;</code> struct, which is simply an empty struct, except that if we are not using <code>T</code> in any of the struct members, we have to use a <code>PhantomData</code> marker:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>DeviceOperationsVtable&lt;T&gt;(core::marker::PhantomData&lt;T&gt;); </span></code></pre> <p>We can then define a <code>VTABLE</code> constant with the corresponding <code>device_operations</code> struct for <code>T</code>.</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>&lt;T: DeviceOperations&gt; DeviceOperationsVtable&lt;T&gt; { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">VTABLE</span><span>: device_operations </span><span style="color:#f92672;">=</span><span> device_operations { </span><span> release: </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(release_callback::&lt;T&gt;), </span><span> }; </span><span>} </span></code></pre> <p>As we can see in the code above, we set the <code>release</code> function pointer to <code>release_callback::&lt;T&gt;</code>, which means the function pointer now points to the <code>release_callback</code> operating on <code>T</code>. In our <code>Driver::create_device</code> function, we set the <code>vtable</code> to <code>&amp;DeviceOperatonsVtable::&lt;T&gt;::VTABLE</code>. In other words, we point the vtable of the device to the vtable that operates on <code>T</code>. Since the user calls the <code>Driver::create_device</code> function with an actual user defined type such as <code>MyDevice</code>, the vtable will end up pointing to a VTABLE that operates on <code>MyDevice</code> and thus the <code>release</code> function pointer will point to <code>release_callback::&lt;MyDevice&gt;</code>. That essentially means that the <code>release_callback</code> function will cast the raw pointer back into a <code>Box&lt;MyDevice&gt;</code> and invoke the <code>Drop</code> implementation of <code>MyDevice</code>. All that is left to do for us to complete this implementation, is to take the vtable in the <code>Drop</code> implementation of <code>Device</code> and then invoke the <code>release</code> function pointer before calling <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iodeletedevice"><code>IoDeleteDevice</code></a>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>Drop </span><span style="color:#f92672;">for </span><span>Device { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">drop</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>) { </span><span> </span><span style="color:#f92672;">if </span><span>self.raw.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> </span><span style="color:#f92672;">return</span><span>; </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> extension </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#f92672;">*</span><span>self.raw).DeviceExtension </span><span style="color:#f92672;">as *mut</span><span> DeviceExtension; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> vtable </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#f92672;">*</span><span>extension).vtable; </span><span> </span><span> </span><span style="color:#f92672;">if </span><span style="font-style:italic;color:#66d9ef;">let Some</span><span>(release) </span><span style="color:#f92672;">= </span><span>(</span><span style="color:#f92672;">*</span><span>vtable).release { </span><span> </span><span style="color:#66d9ef;">release</span><span>(self.raw); </span><span> } </span><span> </span><span> IoDeleteDevice(self.raw); </span><span> } </span><span> } </span><span>} </span></code></pre> <h1 id="handling-userspace-operations">Handling Userspace Operations</h1> <p>The entire point of creating a device file is such that userspace can interact with our device. For an application to actually do anything with our device file, it will have to call <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew"><code>CreateFile</code></a> and <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilew"><code>DeleteFile</code></a>, which we then have to handle from our driver. On the driver side, these functions will ultimately invoke the function pointers in <code>MajorFunction[IRP_MJ_CREATE]</code> and <code>MajorFunction[IRP_MJ_CLOSE]</code> that are part of the <code>DRIVER_OBJECT</code>. That means we first have to change the <code>driver_entry</code> function in our <code>kernel_module!</code> macro to set up these function pointers. We will simply point all possible major functions to the same function, which we will call <code>dispatch_device</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">for</span><span> i </span><span style="color:#f92672;">in </span><span style="color:#ae81ff;">0</span><span style="color:#f92672;">..</span><span>$crate::</span><span style="color:#ae81ff;">IRP_MJ_MAXIMUM_FUNCTION </span><span>{ </span><span> driver.MajorFunction[i </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">usize</span><span>] </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>($crate::dispatch_device); </span><span>} </span></code></pre> <p>The <code>dispatch_device</code> has two arguments, of which the first is a pointer to the <code>DEVICE_OBJECT</code> and the second is a pointer to the <code>IRP</code> struct, where IRP is short for <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/i-o-request-packets"><code>I/O request packet</code></a>. When we open or close the device file from userspace, the I/O request will generally pass through a number of operating system components and drivers, each them calling <code>IoCallDriver</code> to pass the I/O request to the next. Therefore, as the I/O request goes through the device stack, the <code>IRP</code> ends up being a stack of I/O requests. We will first create an <code>IoRequest</code> type to wrap the <code>IRP</code> pointer:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>IoRequest { </span><span> raw: </span><span style="color:#f92672;">*mut</span><span> IRP, </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>IoRequest { </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">from_raw</span><span>(</span><span style="font-style:italic;color:#fd971f;">irp</span><span>: </span><span style="color:#f92672;">*mut</span><span> IRP) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ raw: irp } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_raw</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span>IRP { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span style="color:#f92672;">&amp;*</span><span>self.raw } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_raw_mut</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;mut</span><span> IRP { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span style="color:#f92672;">&amp;mut *</span><span>self.raw } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">stack_location</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">&amp;</span><span>IO_STACK_LOCATION { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span style="color:#f92672;">&amp;*</span><span>IoGetCurrentIrpStackLocation(self.</span><span style="color:#66d9ef;">irp_mut</span><span>()) } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">major</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">u8 </span><span>{ </span><span> self.</span><span style="color:#66d9ef;">stack_location</span><span>().MajorFunction </span><span> } </span><span>} </span></code></pre> <p>Our <code>dispatch_device</code> function will first use <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iogetcurrentirpstacklocation"><code>IoGetCurrentIrpStackLocation</code></a> to get the data associated with the I/O request, more specifically the major function. Then it can dispatch the request to handler of the corresponding device.</p> <p>then handle the request and to indicate that the request has been completed it will invoke <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocompleterequest"><code>IoCompleteRequest</code></a>.</p> <p>There will be three types of I/O request we will currently be handling, which are:</p> <ul> <li><code>IRP_MJ_CREATE</code>: userspace opened a handle to the device file.</li> <li><code>IRP_MJ_CLOSE</code>: userspace closed the handle to the device file.</li> <li><code>IRP_MJ_CLEANUP</code>: the reference count reached zero, i.e. all handles to the device file have been closed.</li> </ul> <p>Let's first extend the <code>DeviceOperations</code> trait to handle these:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">trait </span><span>DeviceOperations: Sync + Sized { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">create</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">Device</span><span>: </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">close</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">cleanup</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span>} </span></code></pre> <p>As you can see in the functions above, we simply mark the I/O requests as completed. The <code>complete</code> functions simply sets the <code>Information</code> and <code>Status</code> fields of <code>IoStatus</code> in the <code>IRP</code> struct, and then invokes <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocompleterequest"><code>IoCompleteRequest</code></a>. The <code>Information</code> field contains the number of bytes returned, whereas the <code>Status</code> field contains the success or failure status of the I/O request. The <code>complete</code> function is implemented as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">complete</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">value</span><span>: </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">u32</span><span>, Error&gt;) { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> irp </span><span style="color:#f92672;">= </span><span>self.</span><span style="color:#66d9ef;">irp_mut</span><span>(); </span><span> </span><span> </span><span style="color:#f92672;">match</span><span> value { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(value) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> irp.IoStatus.Information </span><span style="color:#f92672;">=</span><span> value </span><span style="color:#f92672;">as _</span><span>; </span><span> irp.IoStatus.__bindgen_anon_1.Status </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">STATUS_SUCCESS</span><span>; </span><span> } </span><span> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(error) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> irp.IoStatus.Information </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0</span><span>; </span><span> irp.IoStatus.__bindgen_anon_1.Status </span><span style="color:#f92672;">=</span><span> error.</span><span style="color:#66d9ef;">to_kernel_errno</span><span>(); </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> IoCompleteRequest(irp, </span><span style="color:#ae81ff;">IO_NO_INCREMENT </span><span style="color:#f92672;">as _</span><span>); </span><span> } </span><span> } </span></code></pre> <p>Now we can also extend our virtual table <code>device_operations</code> by adding a <code>dispatch</code> callback that takes a pointer to the <code>DEVICE_OBJECT</code>, a pointer to the <code>IRP</code> and the major function:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[repr(C)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>device_operations { </span><span> dispatch: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span>&lt;</span><span style="color:#f92672;">extern </span><span style="color:#e6db74;">&quot;C&quot;</span><span> fn (</span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT, </span><span style="color:#f92672;">*mut</span><span> IRP, </span><span style="font-style:italic;color:#66d9ef;">u8</span><span>) -&gt; NTSTATUS&gt;, </span><span> release: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span>&lt;</span><span style="color:#f92672;">extern </span><span style="color:#e6db74;">&quot;C&quot;</span><span> fn (</span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT)&gt;, </span><span>} </span></code></pre> <p>In this callback we will basically wrap the pointers into <code>Device</code> and <code>IoRequest</code> as we don't want the user to have direct access to these pointers. Using <code>Device</code> we can access the user data which implements the <code>DeviceOperations</code> trait, and therefore we can map the major function code to the corresponding function in that trait and call that function. To prevent the <code>Drop</code> implementation of <code>Device</code> from dropping the <code>DEVICE_OBJECT</code>, we use <code>into_raw()</code> to take the pointer and swap it with a <code>NULL</code> pointer. The code looks like this:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">extern </span><span style="color:#e6db74;">&quot;C&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">dispatch_callback</span><span>&lt;T: DeviceOperations&gt;( </span><span> </span><span style="font-style:italic;color:#fd971f;">device</span><span>: </span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT, </span><span> </span><span style="font-style:italic;color:#fd971f;">irp</span><span>: </span><span style="color:#f92672;">*mut</span><span> IRP, </span><span> </span><span style="font-style:italic;color:#fd971f;">major</span><span>: </span><span style="font-style:italic;color:#66d9ef;">u8</span><span>, </span><span>) -&gt; NTSTATUS { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> device </span><span style="color:#f92672;">= unsafe </span><span>{ Device::from_raw(device) }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> data: </span><span style="color:#f92672;">&amp;mut</span><span> T </span><span style="color:#f92672;">=</span><span> device.</span><span style="color:#66d9ef;">data_mut</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> request </span><span style="color:#f92672;">= unsafe </span><span>{ IoRequest::from_raw(irp) }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">= match</span><span> major </span><span style="color:#f92672;">as _ </span><span>{ </span><span> </span><span style="color:#ae81ff;">IRP_MJ_CREATE </span><span style="color:#f92672;">=&gt;</span><span> data.</span><span style="color:#66d9ef;">create</span><span>(</span><span style="color:#f92672;">&amp;</span><span>device, </span><span style="color:#f92672;">&amp;</span><span>request), </span><span> </span><span style="color:#ae81ff;">IRP_MJ_CLOSE </span><span style="color:#f92672;">=&gt;</span><span> data.</span><span style="color:#66d9ef;">close</span><span>(</span><span style="color:#f92672;">&amp;</span><span>device, </span><span style="color:#f92672;">&amp;</span><span>request), </span><span> </span><span style="color:#ae81ff;">IRP_MJ_CLEANUP </span><span style="color:#f92672;">=&gt;</span><span> data.</span><span style="color:#66d9ef;">cleanup</span><span>(</span><span style="color:#f92672;">&amp;</span><span>device, </span><span style="color:#f92672;">&amp;</span><span>request), </span><span> </span><span style="color:#f92672;">_ =&gt; </span><span>{ </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_PARAMETER</span><span>)); </span><span> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::</span><span style="color:#ae81ff;">INVALID_PARAMETER</span><span>) </span><span> } </span><span> }; </span><span> </span><span> device.</span><span style="color:#66d9ef;">into_raw</span><span>(); </span><span> </span><span> </span><span style="color:#f92672;">match</span><span> status { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span style="color:#f92672;">=&gt; </span><span style="color:#ae81ff;">STATUS_SUCCESS</span><span>, </span><span> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(e) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">=</span><span> e.</span><span style="color:#66d9ef;">to_kernel_errno</span><span>(); </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(e)); </span><span> status </span><span> } </span><span> } </span><span>} </span></code></pre> <p>With the dispatch callback implemented, we can then extend the <code>VTABLE</code> constant:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl</span><span>&lt;T: DeviceOperations&gt; DeviceOperationsVtable&lt;T&gt; { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">VTABLE</span><span>: device_operations </span><span style="color:#f92672;">=</span><span> device_operations { </span><span> dispatch: </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(dispatch_callback::&lt;T&gt;), </span><span> release: </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(release_callback::&lt;T&gt;), </span><span> }; </span><span>} </span></code></pre> <p>Now all that is left is the actual implementation of the <code>dispatch_device</code> function. First, we will use <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iogetcurrentirpstacklocation"><code>IoGetCurrentIrpStackLocation</code></a> to get the associated data of the I/O request. In addition, we wrap the pointer to the <code>DEVICE_OBJECT</code> into a <code>Device</code>, so we can easily access the vtable. We then simply dispatch the request to the <code>dispatch</code> callback.</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;C&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">dispatch_device</span><span>( </span><span> </span><span style="font-style:italic;color:#fd971f;">device</span><span>: </span><span style="color:#f92672;">*mut</span><span> DEVICE_OBJECT, </span><span> </span><span style="font-style:italic;color:#fd971f;">irp</span><span>: </span><span style="color:#f92672;">*mut</span><span> IRP, </span><span>) -&gt; NTSTATUS { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> stack_location </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span style="color:#f92672;">&amp;*</span><span>IoGetCurrentIrpStackLocation(irp) }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> device </span><span style="color:#f92672;">= unsafe </span><span>{ Device::from_raw(device) }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> vtable </span><span style="color:#f92672;">=</span><span> device.</span><span style="color:#66d9ef;">vtable</span><span>(); </span><span> </span><span> </span><span style="color:#f92672;">match</span><span> vtable.dispatch { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(dispatch) </span><span style="color:#f92672;">=&gt; </span><span style="color:#66d9ef;">dispatch</span><span>(device.</span><span style="color:#66d9ef;">into_raw</span><span>(), irp, stack_location.MajorFunction), </span><span> </span><span style="color:#f92672;">_ =&gt; </span><span>{ </span><span> device.</span><span style="color:#66d9ef;">into_raw</span><span>(), </span><span> </span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span> } </span><span> } </span><span>} </span></code></pre> <p>That concludes our <code>dispatch_device</code> function.</p> <h1 id="symbolic-links">Symbolic Links</h1> <p>There is just one more issue and that is when we create our device, it will have an internal Windows NT name like <code>\Device\Example</code>, but that won't be accessible to userspace at all. In order for our device to be accessible to userspace, it needs a path that lives in the global namespace. Such a path looks something like the following: <code>\??\Example</code> and we simply can create a symbolic link to <code>\Device\Example</code>. Fortunately, compared to the rest we have implemented thus far, implementing the code to create and delete symbolic links is rather trivial. We just need to wrap the <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatesymboliclink"><code>IoCreateSymbolicLink</code></a> and <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iodeletesymboliclink"><code>IoDeleteSymbolicLink</code></a> functions:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>SymbolicLink { </span><span> name: U16CString, </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>SymbolicLink { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">new</span><span>(</span><span style="font-style:italic;color:#fd971f;">name</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">str</span><span>, </span><span style="font-style:italic;color:#fd971f;">target</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">str</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>, Error&gt; { </span><span> </span><span style="color:#75715e;">// Convert the name to UTF-16 and then create a UNICODE_STRING. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> name </span><span style="color:#f92672;">= </span><span>U16CString::from_str(name).</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> name_ptr </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">create_unicode_string</span><span>(name.</span><span style="color:#66d9ef;">as_slice</span><span>()); </span><span> </span><span> </span><span style="color:#75715e;">// Convert the target to UTF-16 and then create a UNICODE_STRING. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> target </span><span style="color:#f92672;">= </span><span>U16CString::from_str(target).</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> target_ptr </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">create_unicode_string</span><span>(target.</span><span style="color:#66d9ef;">as_slice</span><span>()); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> status </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> IoCreateSymbolicLink(</span><span style="color:#f92672;">&amp;mut</span><span> name_ptr, </span><span style="color:#f92672;">&amp;mut</span><span> target_ptr) </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> status </span><span style="color:#f92672;">!= </span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span>{ </span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::from_kernel_errno(status)); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> name, </span><span> }) </span><span> } </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>Drop </span><span style="color:#f92672;">for </span><span>SymbolicLink { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">drop</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>) { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let </span><span style="color:#f92672;">mut</span><span> name_ptr </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">create_unicode_string</span><span>(self.name.</span><span style="color:#66d9ef;">as_slice</span><span>()); </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> IoDeleteSymbolicLink(</span><span style="color:#f92672;">&amp;mut</span><span> name_ptr); </span><span> } </span><span> } </span><span>} </span></code></pre> <p>The code above mostly has to deal with the usual Unicode conversions as we have seen before and simply stores the path of the symbolic link, such that we can call <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iodeletesymboliclink"><code>IoDeleteSymbolicLink</code></a> in our <code>Drop</code> implementation.</p> <h1 id="extending-our-example">Extending our Example</h1> <p>Wow, that was a lot we had to go through to, but now we have a nice and clean API to use to create and delete devices. Let's extend our driver example and set up our own device that is visible to userspace. We won't do much interesting other than print whenever an application opens or closes a handle to our device file and mark the I/O request as completed:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">struct </span><span>MyDevice; </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>DeviceOperations </span><span style="color:#f92672;">for </span><span>MyDevice { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">create</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> println!(</span><span style="color:#e6db74;">&quot;userspace opened the device&quot;</span><span>); </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">close</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> println!(</span><span style="color:#e6db74;">&quot;userspace closed the device&quot;</span><span>); </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">cleanup</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">_device</span><span>: </span><span style="color:#f92672;">&amp;</span><span>Device, </span><span style="font-style:italic;color:#fd971f;">request</span><span>: </span><span style="color:#f92672;">&amp;</span><span>IoRequest) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), Error&gt; { </span><span> println!(</span><span style="color:#e6db74;">&quot;device is no longer in use by userspace&quot;</span><span>); </span><span> </span><span> request.</span><span style="color:#66d9ef;">complete</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(</span><span style="color:#ae81ff;">0</span><span>)); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span> } </span><span>} </span></code></pre> <p>First we declared <code>MyDevice</code> which is the user associated data for our <code>Device</code> and implemented the <code>create</code>, <code>close</code> and <code>cleanup</code> functions to print messages when an application interacts with our device file. We then create the device file and the symbolic link in our <code>Module</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">struct </span><span>Module { </span><span> _device: Device, </span><span> _symbolic_link: SymbolicLink, </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>KernelModule </span><span style="color:#f92672;">for </span><span>Module { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">init</span><span>(</span><span style="color:#f92672;">mut </span><span style="font-style:italic;color:#fd971f;">driver</span><span>: Driver, </span><span style="color:#f92672;">_</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">str</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>, Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> device </span><span style="color:#f92672;">=</span><span> driver.</span><span style="color:#66d9ef;">create_device</span><span>( </span><span> </span><span style="color:#e6db74;">&quot;</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Device</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Example&quot;</span><span>, </span><span> DeviceType::Unknown, </span><span> DeviceFlags::</span><span style="color:#ae81ff;">SECURE_OPEN</span><span>, </span><span> Access::NonExclusive, </span><span> MyDevice, </span><span> )</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> symbolic_link </span><span style="color:#f92672;">= </span><span>SymbolicLink::new(</span><span style="color:#e6db74;">&quot;</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">??</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Example&quot;</span><span>, </span><span style="color:#e6db74;">&quot;</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Device</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Example&quot;</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(Module { </span><span> _device: device, </span><span> _symbolic_link: symbolic_link, </span><span> }) </span><span> } </span><span>} </span></code></pre> <p>As usual, the code can also be found at <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/05-creating-devices">Github</a>.</p> <h1 id="testing">Testing</h1> <p>Run both <code>WinObj.exe</code> and <code>DebugView.exe</code> in the background. In <code>WinObj.exe</code>, click on <strong>Driver</strong> on the left. Then create and start the driver service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sc create example binPath=$(realpath target/x86_64-pc-windows-msvc/debug/driver.sys) type=kernel </span><span>sc start example </span></code></pre> <p>This should show the <strong>Example</strong> device in <code>WinObj.exe</code> as shown in <strong>Figure 1</strong>.</p> <center> <figure> <p><img src="https://codentium.com/guides/windows-dev/windows-drivers-in-rust-creating-devices/05-creating-devices-1.png" alt="WinObj" /></p> <figcaption> <p><strong>Figure 1:</strong> <code>WinObj.exe</code> should show the &quot;Example&quot; device.</p> </figcaption> </figure> </center> <p>Right-click on the <strong>Example</strong> device in WinObj.exe and then click on <strong>Properties...</strong> Then a dialog window should appear with the properties of the device. Simply click on <strong>OK</strong> to close it. This interaction should show the messages from our driver as shown in <strong>Figure 2</strong>.</p> <center> <figure> <p><img src="https://codentium.com/guides/windows-dev/windows-drivers-in-rust-creating-devices/05-creating-devices-2.png" alt="DebugView" /></p> <figcaption> <p><strong>Figure 2:</strong> Output in <code>DebugView.exe</code> after interacting with the &quot;Example&quot; device.</p> </figcaption> </figure> </center> <p>If we go to <code>WinObj.exe</code> and click on <strong>GLOBAL??</strong> on the left, it should also show the <strong>Example</strong> device with a symbolic link to <code>\Device\Example</code>.</p> <h1 id="userspace">Userspace</h1> <p>Instead of relying on <code>WinObj.exe</code>, we can also write our own userspace program in Rust to open and close the device file. It's actually very simple to do that:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>std::fs::File; </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;(), std::io::Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> _file </span><span style="color:#f92672;">= </span><span>File::open(</span><span style="color:#e6db74;">&quot;</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">??</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Example&quot;</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(()) </span><span>} </span></code></pre> <p>While the above program does not much other than opening and closing the device file, it will become very useful in later tutorials as we will be extending our device file implementation. Running the above program will produce similar messages in <code>DebugView.exe</code> as interacting with our driver using <code>WinObj.exe</code>. The above user code can also be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/blob/main/user/05-creating-devices/src/main.rs">Github</a>.</p> <h1 id="what-s-next">What's Next?</h1> <p>In the <a href="/guides/windows-dev/windows-drivers-in-rust-reading-and-writing">next article</a> we will look at how to extend our device with read and write callbacks such that we can handle reads from and writes to our device files from userspace.</p> <h1 id="references">References</h1> <ol> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatedevice"><code>IoCreateDevice</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iodeletedevice"><code>IoDeleteDevice</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/specifying-device-types">&quot;Specifying Device Types&quot; - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iogetcurrentirpstacklocation"><code>IoGetCurrentIrpStackLocation</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocompleterequest"><code>IoCompleteRequest</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/gettingstarted/i-o-request-packets">&quot;I/O Request Packets&quot; - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iocreatesymboliclink"><code>IoCreateSymbolicLink</code> - MSDN</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-iodeletesymboliclink"><code>IoDeleteSymbolicLink</code> - MSDN</a></li> </ol> Windows Drivers in Rust: Safe Framework 2021-09-25T00:00:00+00:00 2021-09-25T00:00:00+00:00 https://codentium.com/guides/windows-dev/windows-drivers-in-rust-safe-framework/ <h1 id="introduction">Introduction</h1> <p>In <a href="/guides/windows-dev/windows-drivers-in-rust-generating-bindings">the previous article</a> we looked at using <a href="https://github.com/rust-lang/rust-bindgen">bindgen</a> to generate more complete bindings for the Windows kernel API. These bindings can also be found at <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/windows-kernel-sys">Github</a>. However, the problem is that we still have to deal with writing unsafe code. In this article we will be looking at creating a Microsoft Windows kernel driver framework with safe Rust abstractions in a new crate called <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/windows-kernel-rs">windows-kernel-rs</a>.</p> <h1 id="memory-allocations">Memory Allocations</h1> <p>As mentioned before we can currently not rely on the standard Rust library which provides types and data structures such as <code>String</code>, <code>Vec</code> and <code>HashMap</code>, and it would be very convenient if we could actually use those. It turns out that these will help us implement the <code>print!</code> and <code>println!</code> macros that we are used to in Rust. Fortunately, these types are available in the <code>alloc</code> crate, which we can use once we implemented our very own allocator. In fact, the Windows kernel API provides us with functions to allocate and free our own memory called <code>ExAllocatePool</code> and <code>ExFreePool</code> which we can use to implement our own allocator in Rust. We just have to define our own type and then implement the <code>core::alloc::GlobalAlloc</code> trait.</p> <p>So we set up our new crate as before:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo new --lib windows-kernel-rs </span></code></pre> <p>Similar to our example driver we will be setting the panic handling to abort and to use our <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/windows-kernel-sys">windows-kernel-sys</a> crate, which provides the unsafe bindings.</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[profile.dev] </span><span style="color:#f92672;">panic </span><span>= </span><span style="color:#e6db74;">&quot;abort&quot; </span><span> </span><span>[profile.release] </span><span style="color:#f92672;">panic </span><span>= </span><span style="color:#e6db74;">&quot;abort&quot; </span><span> </span><span>[dependencies] </span><span style="color:#f92672;">windows-kernel-sys </span><span>= { </span><span style="color:#f92672;">path </span><span>= </span><span style="color:#e6db74;">&quot;../windows-kernel-sys&quot; </span><span>} </span></code></pre> <p>We then change <code>src/lib.rs</code> to state that we are not using the standard library and import our <code>allocator</code> module that we will be writing:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![no_std] </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">mod </span><span>allocator; </span></code></pre> <p>We then implement our own allocator called <code>KernelAllocator</code> in <code>src/allocator.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>core::alloc::{GlobalAlloc, Layout}; </span><span style="color:#f92672;">use </span><span>windows_kernel_sys::base::</span><span style="color:#ae81ff;">_POOL_TYPE </span><span style="color:#f92672;">as </span><span style="color:#ae81ff;">POOL_TYPE</span><span>; </span><span style="color:#f92672;">use </span><span>windows_kernel_sys::ntoskrnl::{ExAllocatePool, ExFreePool}; </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>KernelAllocator; </span><span> </span><span style="color:#f92672;">unsafe </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>GlobalAllocator </span><span style="color:#f92672;">for </span><span>KernelAllocator { </span><span> </span><span style="color:#f92672;">unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">alloc</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">layout</span><span>: Layout) -&gt; </span><span style="color:#f92672;">*mut </span><span style="font-style:italic;color:#66d9ef;">u8 </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> ptr </span><span style="color:#f92672;">=</span><span> ExAllocatePool(</span><span style="color:#ae81ff;">POOL_TYPE</span><span>::NonPagedPool, layout.</span><span style="color:#66d9ef;">size</span><span>() </span><span style="color:#f92672;">as </span><span style="font-style:italic;color:#66d9ef;">u64</span><span>); </span><span> </span><span> </span><span style="color:#f92672;">if</span><span> ptr.</span><span style="color:#66d9ef;">is_null</span><span>() { </span><span> panic!(</span><span style="color:#e6db74;">&quot;[kernel-alloc] failed to allocate pool.&quot;</span><span>); </span><span> } </span><span> </span><span> ptr </span><span style="color:#f92672;">as _ </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">dealloc</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">ptr</span><span>: </span><span style="color:#f92672;">*mut </span><span style="font-style:italic;color:#66d9ef;">u8</span><span>, </span><span style="font-style:italic;color:#fd971f;">_layout</span><span>: Layout) { </span><span> ExFreePool(ptr </span><span style="color:#f92672;">as _</span><span>) </span><span> } </span><span>} </span></code></pre> <p>Then we have to set up the allocator by using the <code>global_allocator</code> attribute as follows in <code>src/lib.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[global_allocator] </span><span style="font-style:italic;color:#66d9ef;">static </span><span style="color:#ae81ff;">ALLOCATOR</span><span>: allocator::KernelAllocator </span><span style="color:#f92672;">= </span><span>allocator::KernelAllocator; </span></code></pre> <p>However, if we try building this we will run into an error that there is no <code>alloc_error_handler</code>. So let's define one. We will tell Rust that we want to use the experimental <code>alloc_error_handler</code> feature as follows in <code>src/lib.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![feature(alloc_error_handler)] </span></code></pre> <p>Then we define the actual allocation error handler in <code>src/allocator.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[alloc_error_handler] </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">alloc_error</span><span>(</span><span style="color:#f92672;">_</span><span>: Layout) -&gt; </span><span style="color:#f92672;">! </span><span>{ </span><span> </span><span style="color:#f92672;">loop </span><span>{} </span><span>} </span></code></pre> <p>While we are at it, we can also move our panic handler into this crate:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[panic_handler] </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">panic</span><span>(</span><span style="font-style:italic;color:#fd971f;">_info</span><span>: </span><span style="color:#f92672;">&amp;</span><span>core::panic::PanicInfo) -&gt; </span><span style="color:#f92672;">! </span><span>{ </span><span> </span><span style="color:#f92672;">loop </span><span>{} </span><span>} </span></code></pre> <p>Then there are currently two more issues with <a href="https://github.com/Trantect/win_driver_example/issues/4"><code>_fltused</code></a> and <code>__CxxFrameHandler3</code> caused by the fact that we actually invoke the <code>panic!</code> macro from our allocator. These are actually bugs that we can temporarily resolve as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[used] </span><span>#[no_mangle] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">static</span><span> _fltused: </span><span style="font-style:italic;color:#66d9ef;">i32 </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0</span><span>; </span><span> </span><span>#[no_mangle] </span><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;system&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">__CxxFrameHandler3</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">i32 </span><span>{ </span><span> </span><span style="color:#ae81ff;">0 </span><span>} </span></code></pre> <blockquote> <p>As of writing I cannot find much about the current state of <code>__CxxFrameHandler3</code>. The <a href="https://github.com/rust-lang/rust/issues/54137">issue</a> was opened before, and this <a href="https://github.com/rust-lang/rust/pull/83607#issuecomment-810870972">pull request</a> should address the issue somewhat, but that does not seem to be entirely true.</p> </blockquote> <p>With all this our kernel allocator is in place and we can actually start using the <code>alloc</code> crate.</p> <blockquote> <p>It turns out that <code>ExAllocatePool</code> and <code>ExFreePool</code> have been <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/updating-deprecated-exallocatepool-calls">deprecated</a> since Microsoft Windows 10, version 2004 and later. Since we used bingen the replacements are actually available to us, but I still have to look into actually using them. I will update this part of the article once I get to that point.</p> </blockquote> <h1 id="printing-macros">Printing Macros</h1> <p>Now that our allocator is set up, we will actually have access to the <code>format!</code> macro which will turn out to be very useful when implementing the <code>print!</code> and <code>println!</code> macros. The details have actually been covered by both <a href="https://not-matthias.github.io/kernel-printing-with-rust/">Matthias</a> and <a href="https://os.phil-opp.com/vga-text-mode/">Philipp</a> who both provide excellent articles on implementing <code>print!</code> and <code>println!</code> macros.</p> <p>Remember that until now we have been using the <code>DbgPrint</code> function which expects a null-terminated or C-style string. In our case we will implement a function called <code>_print</code> that accepts <code>core::fmt::Arguments</code> and then uses <code>alloc::format!</code> to actually format the string, but at the same time ensure that the string is null-terminated. Thus we first import the <code>DbgPrint</code> function in <code>src/io.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>windows_kernel_sys::ntoskrnl::DbgPrint; </span></code></pre> <p>Then we will implement our <code>_print</code> function as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[doc(hidden)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">_print</span><span>(</span><span style="font-style:italic;color:#fd971f;">args</span><span>: core::fmt::Arguments) { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> s </span><span style="color:#f92672;">= </span><span>alloc::format</span><span style="color:#f92672;">!</span><span>(</span><span style="color:#e6db74;">&quot;{}</span><span style="color:#ae81ff;">\0</span><span style="color:#e6db74;">&quot;</span><span>, args); </span><span> </span><span> </span><span style="color:#75715e;">// Print the string. </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ DbgPrint(s.</span><span style="color:#66d9ef;">as_ptr</span><span>() </span><span style="color:#f92672;">as _</span><span>) }; </span><span>} </span></code></pre> <p>Note that we are using <code>#[doc(hidden)]</code> as this is more of an internal function that we are not going to use directly, but to implement the <code>print!</code> and <code>println!</code> macros. For the <code>print!</code> macro, we will use <code>format_args!</code> and then simply call this function:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[macro_export] </span><span style="color:#66d9ef;">macro_rules! </span><span>print { </span><span> (</span><span style="color:#f92672;">$</span><span>(</span><span style="font-style:italic;color:#fd971f;">$arg</span><span>:</span><span style="font-style:italic;color:#66d9ef;">tt</span><span>)</span><span style="color:#f92672;">*</span><span>) </span><span style="color:#f92672;">=&gt; </span><span>($crate::io::_print(format_args!(</span><span style="color:#f92672;">$</span><span>($arg)</span><span style="color:#f92672;">*</span><span>))); </span><span>} </span></code></pre> <p>As you may have noticed, our macro refers to the <code>_print</code> function using <code>$crate::io::_print</code>, this way when we actually import and use the macro from our driver, we don't have to also import the <code>_print</code> function itself. Now that we have the <code>print!</code> macro, we can use that to implement the <code>println!</code> macro which simply prints a newline if no arguments are given, or formats the arguments and prints a newline:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[macro_export] </span><span style="color:#66d9ef;">macro_rules! </span><span>println { </span><span> () </span><span style="color:#f92672;">=&gt; </span><span>($crate::print</span><span style="color:#f92672;">!</span><span>(</span><span style="color:#e6db74;">&quot;</span><span style="color:#ae81ff;">\n</span><span style="color:#e6db74;">&quot;</span><span>)); </span><span> (</span><span style="color:#f92672;">$</span><span>(</span><span style="font-style:italic;color:#fd971f;">$arg</span><span>:</span><span style="font-style:italic;color:#66d9ef;">tt</span><span>)</span><span style="color:#f92672;">*</span><span>) </span><span style="color:#f92672;">=&gt; </span><span>($crate::print</span><span style="color:#f92672;">!</span><span>(</span><span style="color:#e6db74;">&quot;{}</span><span style="color:#ae81ff;">\n</span><span style="color:#e6db74;">&quot;</span><span>, format_args!(</span><span style="color:#f92672;">$</span><span>($arg)</span><span style="color:#f92672;">*</span><span>))); </span><span>} </span></code></pre> <p>This completes our implementation of the <code>print!</code> and <code>println!</code> macros.</p> <h1 id="handling-errors">Handling Errors</h1> <p>The functions in the Windows kernel API uses the <code>NTSTATUS</code> type to indicate success or return an error. Therefore, we will simply wrap <code>NTSTATUS</code> using our own <code>Error</code> type in <code>src/error.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>Error(NTSTATUS) </span></code></pre> <p>We can then add existing status codes as error by defining constants and add methods to wrap <code>NTSTATUS</code> into an <code>Error</code> and to extract the <code>NTSTATUS</code> from the <code>Error</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">impl </span><span>Error { </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">UNSUCCESSFUL</span><span>: Error </span><span style="color:#f92672;">=</span><span> Error(</span><span style="color:#ae81ff;">STATUS_UNSUCCESSFUL</span><span>); </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">NOT_IMPLEMENTED</span><span>: Error </span><span style="color:#f92672;">=</span><span> Error(</span><span style="color:#ae81ff;">STATUS_NOT_IMPLEMENTED</span><span>); </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">from_kernel_errno</span><span>(</span><span style="font-style:italic;color:#fd971f;">status</span><span>: NTSTATUS) -&gt; Error { </span><span> Error(status) </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">to_kernel_errno</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; NTSTATUS { </span><span> self.</span><span style="color:#ae81ff;">0 </span><span> } </span><span>} </span></code></pre> <p>With the <code>Error</code> type defined, we can now simply use <code>Result&lt;T, Error&gt;</code> where <code>Ok</code> replaces <code>STATUS_SUCCESS</code>.</p> <h1 id="boilerplate-code">Boilerplate Code</h1> <p>The last part of our framework involves providing a macro that essentially creates the <code>driver_entry</code> and <code>driver_exit</code> functions for us. We first start by providing a trait that the user of our crate can implement that simply has an <code>init</code> and <code>cleanup</code> function.</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">trait </span><span>KernelModule: Sized + Sync { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">init</span><span>(</span><span style="font-style:italic;color:#fd971f;">driver</span><span>: Driver, </span><span style="font-style:italic;color:#fd971f;">registry_path</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">str</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>, Error&gt;; </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">cleanup</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="font-style:italic;color:#fd971f;">driver</span><span>: Driver); </span><span>} </span></code></pre> <p>Since we can't couple the <code>KernelModule</code> to the <code>DRIVER_OBJECT</code> we have to globally declare it. We will be using a trick in our macro where we declare it as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">static </span><span style="color:#f92672;">mut </span><span style="color:#ae81ff;">__MOD</span><span>: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span>&lt;$module&gt; </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">None</span><span>; </span></code></pre> <p>However, to be able to declare it as such, we have to make sure the <code>KernelModule</code> is both <code>Sized</code> and <code>Sync</code>.</p> <p>We also want to provide safe abstractions for the parameters passed to the <code>driver_entry</code> and the <code>driver_exit</code> functions. For the first parameter, we will start with a basic abstraction for the <code>DRIVER_OBJECT</code> in <code>src/driver.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>Driver { </span><span> </span><span style="color:#f92672;">pub</span><span>(</span><span style="color:#f92672;">crate</span><span>) raw: </span><span style="color:#f92672;">*mut</span><span> DRIVER_OBJECT, </span><span>} </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>Driver { </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">from_raw</span><span>(</span><span style="font-style:italic;color:#fd971f;">raw</span><span>: </span><span style="color:#f92672;">*mut</span><span> DRIVER_OBJECT) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">Self </span><span>{ </span><span> raw, </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_raw</span><span>(</span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">*const</span><span> DRIVER_OBJECT { </span><span> self.raw </span><span style="color:#f92672;">as _ </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub unsafe </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">as_raw_mut</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>) -&gt; </span><span style="color:#f92672;">*mut</span><span> DRIVER_OBJECT { </span><span> self.raw </span><span style="color:#f92672;">as _ </span><span> } </span><span>} </span></code></pre> <p>For the second parameter, we will be relying on the <a href="https://docs.rs/widestring/0.4.3/widestring/">widestring</a> crate to convert the UTF-16 string into <code>&amp;str</code>. We can then use the following code to safely wrap these parameters:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">let</span><span> driver </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> Driver::from_raw(driver) </span><span>}; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> registry_path </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> U16CString::from_ptr_str(registry_path.Buffer) </span><span>}; </span><span style="font-style:italic;color:#66d9ef;">let</span><span> registry_path </span><span style="color:#f92672;">=</span><span> registry_path.</span><span style="color:#66d9ef;">to_string_lossy</span><span>(); </span></code></pre> <p>Finally, we can implement the full macro <code>kernel_module</code> as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub use crate</span><span>::driver::Driver; </span><span style="color:#f92672;">pub use crate</span><span>::error::Error; </span><span style="color:#f92672;">pub use </span><span>widestring::U16CString; </span><span style="color:#f92672;">pub use </span><span>windows_kernel_sys::base::{</span><span style="color:#ae81ff;">DRIVER_OBJECT</span><span>, </span><span style="color:#ae81ff;">NTSTATUS</span><span>, </span><span style="color:#ae81ff;">STATUS_SUCCESS</span><span>, </span><span style="color:#ae81ff;">UNICODE_STRING</span><span>}; </span><span> </span><span>#[macro_export] </span><span style="color:#66d9ef;">macro_rules! </span><span>kernel_module { </span><span> (</span><span style="font-style:italic;color:#fd971f;">$module</span><span>:</span><span style="font-style:italic;color:#66d9ef;">ty</span><span>) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">static </span><span style="color:#f92672;">mut </span><span style="color:#ae81ff;">__MOD</span><span>: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span>&lt;$module&gt; </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">None</span><span>; </span><span> </span><span> #[no_mangle] </span><span> </span><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;system&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_entry</span><span>( </span><span> </span><span style="font-style:italic;color:#fd971f;">driver</span><span>: </span><span style="color:#f92672;">&amp;mut </span><span>$crate::DRIVER_OBJECT, </span><span> </span><span style="font-style:italic;color:#fd971f;">registry_path</span><span>: </span><span style="color:#f92672;">&amp;</span><span>$crate::UNICODE_STRING, </span><span> ) -&gt; $crate::NTSTATUS { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> driver.DriverUnload </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(driver_exit); </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> driver </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> Driver::from_raw(driver) </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> registry_path </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> $crate::U16CString::from_ptr_str(registry_path.Buffer) </span><span> }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> registry_path </span><span style="color:#f92672;">=</span><span> registry_path.</span><span style="color:#66d9ef;">to_string_lossy</span><span>(); </span><span> </span><span> </span><span style="color:#f92672;">match &lt;</span><span>$module </span><span style="color:#f92672;">as </span><span>$crate::KernelModule</span><span style="color:#f92672;">&gt;</span><span>::init(driver, registry_path.</span><span style="color:#66d9ef;">as_str</span><span>()) { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(m) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> </span><span style="color:#ae81ff;">__MOD </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(m); </span><span> } </span><span> </span><span> $crate::</span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span> } </span><span> </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(e) </span><span style="color:#f92672;">=&gt; </span><span>{ </span><span> e.</span><span style="color:#66d9ef;">to_kernel_errno</span><span>() </span><span> } </span><span> } </span><span> } </span><span> </span><span> </span><span style="color:#f92672;">pub unsafe extern </span><span style="color:#e6db74;">&quot;C&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_exit</span><span>( </span><span> </span><span style="font-style:italic;color:#fd971f;">driver</span><span>: </span><span style="color:#f92672;">*mut </span><span>$crate::DRIVER_OBJECT, </span><span> ) { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> driver </span><span style="color:#f92672;">= unsafe </span><span>{ </span><span> Driver::from_raw(driver) </span><span> }; </span><span> </span><span> </span><span style="color:#f92672;">match </span><span style="color:#ae81ff;">__MOD</span><span>.</span><span style="color:#66d9ef;">take</span><span>() { </span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(</span><span style="color:#f92672;">mut</span><span> m) </span><span style="color:#f92672;">=&gt;</span><span> m.</span><span style="color:#66d9ef;">cleanup</span><span>(driver), </span><span> </span><span style="color:#f92672;">_ =&gt; </span><span>(), </span><span> } </span><span> } </span><span> }; </span><span>} </span></code></pre> <p>The macro above generates the boilerplate for our kernel driver implementing both the <code>driver_entry</code> and <code>driver_exit</code> functions. The <code>driver_entry</code> function converts the parameters into safe types, calls the <code>init</code> function of the <code>KernelModule</code> and stores the constructed module into <code>__MOD</code>. The <code>driver_exit</code> function takes the module from <code>__MOD</code> and then calls the <code>cleanup</code> function and implicitly drops the module.</p> <h1 id="using-the-framework">Using the Framework</h1> <p>With our new framework in place, we can rewrite our driver into a much cleaner version. The code can also be found at <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/04-safe-framework">Github</a>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![no_std] </span><span> </span><span style="color:#f92672;">use </span><span>windows_kernel_rs::{Driver, Error, kernel_module, KernelModule, println}; </span><span> </span><span style="font-style:italic;color:#66d9ef;">struct </span><span>Module; </span><span> </span><span style="font-style:italic;color:#66d9ef;">impl </span><span>KernelModule </span><span style="color:#f92672;">for </span><span>Module { </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">init</span><span>(</span><span style="color:#f92672;">_</span><span>: Driver, </span><span style="color:#f92672;">_</span><span>: </span><span style="color:#f92672;">&amp;</span><span style="font-style:italic;color:#66d9ef;">str</span><span>) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;</span><span style="font-style:italic;color:#66d9ef;">Self</span><span>, Error&gt; { </span><span> println!(</span><span style="color:#e6db74;">&quot;Hello, world!&quot;</span><span>); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(Module) </span><span> } </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">cleanup</span><span>(</span><span style="color:#f92672;">&amp;mut </span><span style="font-style:italic;color:#fd971f;">self</span><span>, </span><span style="color:#f92672;">_</span><span>: Driver) { </span><span> println!(</span><span style="color:#e6db74;">&quot;Bye bye!&quot;</span><span>); </span><span> } </span><span>} </span><span> </span><span>kernel_module!(Module); </span></code></pre> <h1 id="what-s-next">What's Next?</h1> <p>We now have a nice safe framework in Rust that we can use to write Windows kernel drivers, but we haven't been doing much interesting as our driver only prints &quot;Hello, world&quot; and &quot;Bye bye&quot; whenever we load and unload it. In the <a href="/guides/windows-dev/windows-drivers-in-rust-creating-devices">next article</a> we will look at how to create device files, such that userspace can actually interact with our driver.</p> <h1 id="acknowledgements">Acknowledgements</h1> <p>Thanks to <a href="https://not-matthias.github.io/kernel-driver-with-rust/">Matthias</a> for taking the time to explore how to write kernel drivers for Microsoft Windows and especially taking the time to write up the details of the process. Especially the blog post about <a href="https://not-matthias.github.io/kernel-printing-with-rust/">kernel printing with Rust</a> has been valuable. Similarly, I would like to thank <a href="https://os.phil-opp.com/vga-text-mode/">Philipp</a> for explaining how to implement the <code>print!</code> and <code>println!</code> macros in an operating system or <code>no_std</code> environment in general. Finally, I would like to thank Alex and Geoffrey (among others) for the <a href="https://github.com/fishinabarrel/linux-kernel-module-rust">Linux Kernel Modules in Rust project</a> which explores safe abstractions in Rust for Linux kernel modules.</p> <h1 id="references">References</h1> <ol> <li><a href="https://github.com/Trantect/win_driver_example/issues/4">https://github.com/Trantect/win_driver_example/issues/4</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/updating-deprecated-exallocatepool-calls">'Updating Deprecated ExAllocatePool Calls&quot; - MSDN</a></li> <li><a href="https://not-matthias.github.io/kernel-printing-with-rust/">&quot;Kernel Printing with Rust&quot; - Not Matthias</a></li> <li><a href="https://os.phil-opp.com/vga-text-mode/">&quot;Writing an OS in Rust - VGA Text Mode&quot; - Philipp Oppermann</a></li> </ol> Windows Drivers in Rust: Hello World 2021-09-22T00:00:00+00:00 2021-09-22T00:00:00+00:00 https://codentium.com/guides/windows-dev/windows-drivers-in-rust-hello-world/ <h1 id="introduction">Introduction</h1> <p>In <a href="/guides/windows-dev/windows-drivers-in-rust-prerequisites">the previous article</a> we went through a number of prerequisites that we need installed to start writing our kernel driver for Microsoft Windows. Now that we have all of those installed, we can start writing our first kernel driver.</p> <h1 id="getting-started">Getting Started</h1> <p>To get started, we will create a new Rust project for our driver using the following command:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo new --lib driver </span></code></pre> <p>As you may have noticed, we asked <code>cargo</code> to create a library. Unfortunately, Rust does not know how to link a kernel driver by default, it just knows how to link executables, static libraries and dynamic libraries. Fortunately for us, a kernel driver is technically a dynamic link library or <code>.dll</code> file, so we can start out by creating a library.</p> <p>Next, we add the following to the <code>Cargo.toml</code> file to tell Rust to actually build a dynamic library:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[lib] </span><span style="color:#f92672;">crate-type </span><span>= [</span><span style="color:#e6db74;">&quot;cdylib&quot;</span><span>] </span></code></pre> <p>In addition, we will tell Cargo that our project depends on the Rust Nightly toolchain. To do that we will run the following in our project's directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;nightly&quot; &gt; rust-toolchain </span></code></pre> <h1 id="passing-linker-options">Passing Linker Options</h1> <p>Now we have set up an empty project that builds an actual <code>.dll</code> file. One problem with this is that Rust does not know that we are trying to create a kernel driver, so it will call the MSVC linker with the default options to link a <code>.dll</code> to be used by normal Windows applications. So we have to specify the options to actually pass to the MSVC linker. We can do this by writing the following to <code>.cargo/config</code> in your project:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>[build] </span><span>target </span><span style="color:#f92672;">= </span><span style="color:#e6db74;">&quot;x86_64-pc-windows-msvc&quot; </span><span> </span><span>rustflags </span><span style="color:#f92672;">= </span><span>[ </span><span> </span><span style="color:#f92672;">#</span><span> Pre Link Args </span><span> </span><span style="color:#e6db74;">&quot;-Z&quot;</span><span>, </span><span style="color:#e6db74;">&quot;pre-link-arg=/NOLOGO&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-Z&quot;</span><span>, </span><span style="color:#e6db74;">&quot;pre-link-arg=/NXCOMPAT&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-Z&quot;</span><span>, </span><span style="color:#e6db74;">&quot;pre-link-arg=/NODEFAULTLIB&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-Z&quot;</span><span>, </span><span style="color:#e6db74;">&quot;pre-link-arg=/SUBSYSTEM:NATIVE&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-Z&quot;</span><span>, </span><span style="color:#e6db74;">&quot;pre-link-arg=/DRIVER&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-Z&quot;</span><span>, </span><span style="color:#e6db74;">&quot;pre-link-arg=/DYNAMICBASE&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-Z&quot;</span><span>, </span><span style="color:#e6db74;">&quot;pre-link-arg=/MANIFEST:NO&quot;</span><span>, </span><span> </span><span> </span><span style="color:#f92672;">#</span><span> Post Link Args </span><span> </span><span style="color:#e6db74;">&quot;-C&quot;</span><span>, </span><span style="color:#e6db74;">&quot;link-arg=/OPT:REF,ICF&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-C&quot;</span><span>, </span><span style="color:#e6db74;">&quot;link-arg=/ENTRY:driver_entry&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-C&quot;</span><span>, </span><span style="color:#e6db74;">&quot;link-arg=/MERGE:.edata=.rdata&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-C&quot;</span><span>, </span><span style="color:#e6db74;">&quot;link-arg=/MERGE:.rustc=.data&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;-C&quot;</span><span>, </span><span style="color:#e6db74;">&quot;link-arg=/INTEGRITYCHECK&quot;</span><span>, </span><span>] </span></code></pre> <p>Here is an explanation of the <a href="https://docs.microsoft.com/en-us/cpp/build/reference/linker-options?view=msvc-160">MSVC linker options</a> that we are passing to the linker:</p> <ul> <li>The <code>/NOLOGO</code> option simply supresses the startup banner containing the copyright message and the version number.</li> <li>The <code>/NXCOMPAT</code> option marks the executable as compatible with Data Execution Prevention (DEP). DEP is a feature in Microsoft Windows that marks data pages as non-executable. This prevents an attacker from injecting malicious code into those data pages, that can then get executed.</li> <li>The <code>/NODEFAULTLIB</code> option tells the linker to not link in any default libraries, since those won't be available when writing a kernel driver.</li> <li>The <code>/SUBSYSTEM:NATIVE</code> option specifies the subsystem or the environment to use for the executable. <code>NATIVE</code> provides us with an environment suitable for building a kernel mode driver for Windows NT and derivates.</li> <li>The <code>/DRIVER</code> option tells the linker that we are building a Windows NT (and derivates) kernel mode driver.</li> <li>The <code>/DYNAMICBASE</code> option generates an executable image that can be randomly rebased at load time by using the Address Space Layout Randomization (ASLR) feature. ASLR is a feature that randomizes the virtual memory allocations, such that the locations of heap, stacks and the code are randomized, to effectively make buffer overflow attacks less trivial to exploit.</li> <li>The <code>MANIFEST:NO</code> option tells the linker to not generate a <a href="https://docs.microsoft.com/en-us/windows/win32/SbsCs/manifest-files-reference">Manifest file</a>.</li> <li>The <code>/OPT:REF,ICF</code> option specifies the linker optimizations to apply. <code>/OPT:REF</code> eliminates functions and data that are never referenced. <code>/OPT:ICF</code> performs <a href="https://devblogs.microsoft.com/oldnewthing/20161024-00/?p=94575">identiical COMDAT (common data) folding</a>.</li> <li>The <code>/ENTRY:driver_entry</code> option tells the linker that the entry point of our driver will be the <code>driver_entry</code> function.</li> <li>The <code>/MERGE:.edata=.rdata</code> option merges the <code>.edata</code> section into the <code>.rdata</code> section, while the <code>/MERGE:.rustc=.data' option merges the </code>.rustc<code>section into the</code>.data<code>section. Executables consist of a number of sections such as</code>.text<code>(code),</code>.rdata<code>(read-only or constant data),</code>.data<code>(initialized data) and</code>.bss` (uninitialized data).</li> <li>The <code>/INTEGRITYCHECK</code> option specifies that the digital signature of the binary image must be checked at load time.</li> </ul> <h1 id="the-driver-entry-point">The Driver Entry Point</h1> <p>Note that we set the entry point of our driver to the <code>driver_entry</code> function by specifying the <code>/ENTRY:driver_entry</code> option. Remove any code in <code>src/lib.rs</code> and let's get started with that function in <code>src/lib.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[no_mangle] </span><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;system&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_entry</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">u32 </span><span>{ </span><span> </span><span style="color:#ae81ff;">0 </span><span style="color:#75715e;">/* NT_STATUS_SUCCESS */ </span><span>} </span></code></pre> <p>In the code above, <code>#[no_mangle]</code> attribute tells the Rust compiler to not perform name mangling on the function name, such that it is simply visible as <code>driver_entry</code> during the linking process. <code>&quot;system&quot;</code> tells the Rust compiler that the <code>driver_entry</code> function should be using the <a href="https://doc.rust-lang.org/reference/items/external-blocks.html">&quot;system ABI&quot;</a>, which defaults to the &quot;stdcall&quot; ABI on Microsoft Windows.</p> <p>Since we are writing a kernel driver, we won't have access to the Windows API that is normally available to us when writing Windows applications or libraries. Therefore, as the Rust standard library depends on the Windows API, we cannot use it from our kernel driver. Add the following line to the top of <code>src/lib.rs</code> to tell the Rust compiler that we don't want to use the standard library:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![no_std] </span></code></pre> <p>By disabling the standard library, we also disable the panic handler that comes with it by default. Thus we have to define our own in <code>src/lib.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>core::panic::PanicInfo; </span><span> </span><span>#[panic_handler] </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">panic</span><span>(</span><span style="font-style:italic;color:#fd971f;">_info</span><span>: </span><span style="color:#f92672;">&amp;</span><span>PanicInfo) -&gt; </span><span style="color:#f92672;">! </span><span>{ </span><span> </span><span style="color:#f92672;">loop </span><span>{} </span><span>} </span></code></pre> <p>By default Rust will <a href="https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html">unwind the stack whenever a panic occurs</a>, which in our case will produce an error about Rust not being able to find <code>eh_personality</code>. Fortunately, we can simply change the panic behaviour to abort immediately by adding the following to <code>Cargo.toml</code>.</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[profile.dev] </span><span style="color:#f92672;">panic </span><span>= </span><span style="color:#e6db74;">&quot;abort&quot; </span><span> </span><span>[profile.release] </span><span style="color:#f92672;">panic </span><span>= </span><span style="color:#e6db74;">&quot;abort&quot; </span></code></pre> <h1 id="signing-the-driver">Signing the Driver</h1> <p>Running <code>cargo build</code> has a number of problems: First, it produces a <code>.dll</code> file, but the file extension for a Windows kernel driver should be <code>.sys</code>, so we have to rename it. Second, in order to be able to load our driver, the binary file has to be signed. We will be using <a href="https://github.com/sagiegurari/cargo-make">cargo-make</a> to automate these processes. Install it by running the following command (if you don't have it yet):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo install --force cargo-make </span></code></pre> <p>Then we can start writing our <code>Makefile.toml</code> file for our project. We will first add a task named <code>build-driver</code> to build our project:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[tasks.build-driver] </span><span style="color:#f92672;">script </span><span>= </span><span style="color:#e6db74;">&#39;&#39;&#39; </span><span style="color:#e6db74;">cargo b %BUILD_FLAGS% </span><span style="color:#e6db74;">&#39;&#39;&#39; </span></code></pre> <p>In order to rename the binary file, we need to know where it is located based on whether we are building it in debug or in release mode. So let's add some environment variables to the top of our <code>Makefile.toml</code>:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[env.development] </span><span style="color:#f92672;">TARGET_PATH </span><span>= </span><span style="color:#e6db74;">&quot;target/x86_64-pc-windows-msvc/debug&quot; </span><span> </span><span>[env.production] </span><span style="color:#f92672;">TARGET_PATH </span><span>= </span><span style="color:#e6db74;">&quot;target/x86_64-pc-windows-msvc/release&quot; </span><span style="color:#f92672;">BUILD_RELEASE </span><span>= </span><span style="color:#e6db74;">&quot;--release&quot; </span></code></pre> <p>Now we can write the task that actually renames the file:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[tasks.rename] </span><span style="color:#f92672;">ignore_errors </span><span>= </span><span style="color:#ae81ff;">true </span><span style="color:#f92672;">script </span><span>= </span><span style="color:#e6db74;">&#39;&#39;&#39; </span><span style="color:#e6db74;">cd %TARGET_PATH% </span><span style="color:#e6db74;">rename driver.dll driver.sys </span><span style="color:#e6db74;">&#39;&#39;&#39; </span></code></pre> <p>Finally, we will need to sign our driver, but to do that we need access to the MSVC build tools. To set up the build environment, we will need to load the <code>vcvars64.bat</code> file. However, where that file is located depends on what version of Microsoft Visual Studio you have installed:</p> <ul> <li>Microsoft Visual Studio Build Tools: <code>C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat</code></li> <li>Microsoft Visual Studio 2019 Community Edition: <code>C:\Program Files(x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat</code></li> </ul> <p>Rather than trying to automatically detect this path, we will just use an environment variable at the top of our <code>Makefile.toml</code>:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[env] </span><span style="color:#f92672;">VC_BUILD_DIR</span><span>=</span><span style="color:#e6db74;">&quot;C:</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Program Files (x86)</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Microsoft Visual Studio</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">2019</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">BuildTools</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">VC</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Auxiliary</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">Build</span><span style="color:#ae81ff;">\\</span><span style="color:#e6db74;">vcvars64.bat&quot; </span></code></pre> <p>Now that the path to the <code>vcvars64.bat</code> file has been defined, we can write our task to sign the driver. The task will simply load up the build environment, create a certificate if we don't have one, and then sign the driver with our certificate:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[tasks.sign] </span><span style="color:#f92672;">dependencies </span><span>= [</span><span style="color:#e6db74;">&quot;build-driver&quot;</span><span>, </span><span style="color:#e6db74;">&quot;rename&quot;</span><span>] </span><span style="color:#f92672;">script </span><span>= </span><span style="color:#e6db74;">&#39;&#39;&#39; </span><span style="color:#e6db74;">call &quot;%VC_BUILD_DIR%&quot; </span><span style="color:#e6db74;">if not exist DriverCertificate.cer ( makecert -r -pe -ss PrivateCertStore -n CN=DriverCertificate DriverCertificate.cer ) else ( echo Certificate already exi </span><span style="color:#e6db74;">sts. ) </span><span style="color:#e6db74;">signtool sign /a /v /s PrivateCertStore /n DriverCertificate /fd certHash /t http://timestamp.digicert.com %TARGET_PATH%/driver.sys </span><span style="color:#e6db74;">&#39;&#39;&#39; </span></code></pre> <p>At this point, we should be able to run the following command to build and sign our driver:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo make sign </span></code></pre> <h1 id="accessing-the-kernel-api">Accessing the Kernel API</h1> <p>Currently our driver isn't doing anything useful, but we cannot access the kernel libraries yet as we need bindings to the kernel API, i.e. function and type definitions in Rust, and as we have to link our kernel driver against the kernel libraries providing those functions. To solve the bindings issue, we will simply use <a href="https://github.com/Trantect/winapi-rs.git">Trantect's fork of winapi-rs</a> for now. Add the following to <code>Cargo.toml</code>:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[dependencies.winapi] </span><span style="color:#f92672;">git </span><span>= </span><span style="color:#e6db74;">&quot;https://github.com/Trantect/winapi-rs.git&quot; </span><span style="color:#f92672;">branch </span><span>= </span><span style="color:#e6db74;">&quot;feature/km&quot; </span><span style="color:#f92672;">features </span><span>= [ </span><span> </span><span style="color:#e6db74;">&quot;wdm&quot;</span><span>, </span><span> </span><span style="color:#e6db74;">&quot;ntstatus&quot;</span><span>, </span><span>] </span></code></pre> <p>The next problem is that we have to tell our linker where to find the kernel libraries to link against. These can normally be found in <code>C:\Program Files (x86)\Windows Kit\10\lib\10.\*\km</code>, but instead of defining the path ourselves, we will write a <code>build.rs</code> to find the correct path for us. We will be using the thiserror and winreg crates for this, so we will add the following to <code>Cargo.toml</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>[build</span><span style="color:#f92672;">-</span><span>dependencies] </span><span>thiserror </span><span style="color:#f92672;">= </span><span style="color:#e6db74;">&quot;1.0&quot; </span><span>winreg </span><span style="color:#f92672;">= </span><span style="color:#e6db74;">&quot;0.10&quot; </span></code></pre> <p>Our <code>build.rs</code> starts with a number of things we want to import:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>std::path::PathBuf; </span><span style="color:#f92672;">use </span><span>thiserror::Error; </span><span style="color:#f92672;">use </span><span>winreg::RegKey; </span><span style="color:#f92672;">use </span><span>winreg::enums::</span><span style="color:#ae81ff;">HKEY_LOCAL_MACHINE</span><span>; </span></code></pre> <p>Then we use thiserror to define our own <code>Error</code> type:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[derive(Debug, Error)] </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">enum </span><span>Error { </span><span> #[error(transparent)] </span><span> IoError(#[from] std::io::Error), </span><span> #[error(</span><span style="color:#e6db74;">&quot;cannot find the directory&quot;</span><span>)] </span><span> DirectoryNotFound, </span><span>} </span></code></pre> <p>We can get the <code>C:\Program Files (x86)\Windows Kits\10</code> path from the Windows registry from <code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots\KitsRoot10</code>. We will use the winreg crate to get the value of this key:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#75715e;">/// Retrieves the path to the Windows Kits directory. The default should be </span><span style="color:#75715e;">/// `C:\Program Files (x86)\Windows Kits\10`. </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">get_windows_kits_dir</span><span>() -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;PathBuf, Error&gt; { </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> hklm </span><span style="color:#f92672;">= </span><span>RegKey::predef(</span><span style="color:#ae81ff;">HKEY_LOCAL_MACHINE</span><span>); </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> key </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">r</span><span style="color:#e6db74;">&quot;SOFTWARE\Microsoft\Windows Kits\Installed Roots&quot;</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> dir: </span><span style="font-style:italic;color:#66d9ef;">String </span><span style="color:#f92672;">=</span><span> hklm.</span><span style="color:#66d9ef;">open_subkey</span><span>(key)</span><span style="color:#f92672;">?</span><span>.</span><span style="color:#66d9ef;">get_value</span><span>(</span><span style="color:#e6db74;">&quot;KitsRoot10&quot;</span><span>)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(dir.</span><span style="color:#66d9ef;">into</span><span>()) </span><span>} </span></code></pre> <p>While we are only interested in the location of the libraries for now, we will need the location of header files later. Therefore we define a <code>DirectoryType</code> type to pass to the <code>get_km_dir</code> function:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">enum </span><span>DirectoryType { </span><span> Include, </span><span> Library, </span><span>} </span></code></pre> <p>We can then call the <code>get_windows_kit_dir</code> function to get the Windows Kits directory. Based on the <code>DirectoryType</code> that we want, we can then append <code>Lib</code> or <code>Include</code> to this path. At this path we can find one or more directory versions containing the version number, so we will just pick the highest version number. Finally, we need to append <code>km</code> to the path. The following code does all of this:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#75715e;">/// Retrieves the path to the kernel mode libraries. The path may look something like: </span><span style="color:#75715e;">/// `C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\km`. </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">get_km_dir</span><span>(</span><span style="font-style:italic;color:#fd971f;">dir_type</span><span>: DirectoryType) -&gt; </span><span style="font-style:italic;color:#66d9ef;">Result</span><span>&lt;PathBuf, Error&gt; { </span><span> </span><span style="color:#75715e;">// We first append lib to the path and read the directory.. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> dir </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">get_windows_kits_dir</span><span>()</span><span style="color:#f92672;">? </span><span> .</span><span style="color:#66d9ef;">join</span><span>(</span><span style="color:#f92672;">match</span><span> dir_type { </span><span> DirectoryType::Include </span><span style="color:#f92672;">=&gt; </span><span style="color:#e6db74;">&quot;Include&quot;</span><span>, </span><span> DirectoryType::Library </span><span style="color:#f92672;">=&gt; </span><span style="color:#e6db74;">&quot;Lib&quot;</span><span>, </span><span> }) </span><span> .</span><span style="color:#66d9ef;">read_dir</span><span>()</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="color:#75715e;">// In the lib directory we may have one or more directories named after the version of Windows, </span><span> </span><span style="color:#75715e;">// we will be looking for the highest version number. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> dir </span><span style="color:#f92672;">=</span><span> dir </span><span> .</span><span style="color:#66d9ef;">filter_map</span><span>(|</span><span style="font-style:italic;color:#fd971f;">dir</span><span>| dir.</span><span style="color:#66d9ef;">ok</span><span>()) </span><span> .</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#fd971f;">dir</span><span>| dir.</span><span style="color:#66d9ef;">path</span><span>()) </span><span> .</span><span style="color:#66d9ef;">filter</span><span>(|</span><span style="font-style:italic;color:#fd971f;">dir</span><span>| { </span><span> dir.</span><span style="color:#66d9ef;">components</span><span>() </span><span> .</span><span style="color:#66d9ef;">last</span><span>() </span><span> .</span><span style="color:#66d9ef;">and_then</span><span>(|</span><span style="font-style:italic;color:#fd971f;">c</span><span>| c.</span><span style="color:#66d9ef;">as_os_str</span><span>().</span><span style="color:#66d9ef;">to_str</span><span>()) </span><span> .</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#fd971f;">c</span><span>| c.</span><span style="color:#66d9ef;">starts_with</span><span>(</span><span style="color:#e6db74;">&quot;10.&quot;</span><span>) </span><span style="color:#f92672;">&amp;&amp;</span><span> dir.</span><span style="color:#66d9ef;">join</span><span>(</span><span style="color:#e6db74;">&quot;km&quot;</span><span>).</span><span style="color:#66d9ef;">is_dir</span><span>()) </span><span> .</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> }) </span><span> .</span><span style="color:#66d9ef;">max</span><span>() </span><span> .</span><span style="color:#66d9ef;">ok_or_else</span><span>(|| Error::DirectoryNotFound)</span><span style="color:#f92672;">?</span><span>; </span><span> </span><span> </span><span style="color:#75715e;">// Finally append km to the path to get the path to the kernel mode libraries. </span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(dir.</span><span style="color:#66d9ef;">join</span><span>(</span><span style="color:#e6db74;">&quot;km&quot;</span><span>)) </span><span>} </span></code></pre> <p>Then finally in our <code>main</code> function, we use the <code>get_km_dir</code> function to get the path to the kernel libraries. Then based on the target we are building, we append the architecture to this path and emit this path as a linker path:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() { </span><span> </span><span style="color:#75715e;">// Get the path to the kernel libraries. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> dir </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">get_km_dir</span><span>(DirectoryType::Library).</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span> </span><span> </span><span style="color:#75715e;">// Append the architecture based on our target. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> target </span><span style="color:#f92672;">= </span><span>std::env::var(</span><span style="color:#e6db74;">&quot;TARGET&quot;</span><span>).</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> arch </span><span style="color:#f92672;">= if</span><span> target.</span><span style="color:#66d9ef;">contains</span><span>(</span><span style="color:#e6db74;">&quot;x86_64&quot;</span><span>) { </span><span> </span><span style="color:#e6db74;">&quot;x64&quot; </span><span> } </span><span style="color:#f92672;">else if</span><span> target.</span><span style="color:#66d9ef;">contains</span><span>(</span><span style="color:#e6db74;">&quot;i686&quot;</span><span>) { </span><span> </span><span style="color:#e6db74;">&quot;x86&quot; </span><span> } </span><span style="color:#f92672;">else </span><span>{ </span><span> panic!(</span><span style="color:#e6db74;">&quot;The target {} is currently not supported.&quot;</span><span>, target); </span><span> }; </span><span> </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> dir </span><span style="color:#f92672;">=</span><span> dir.</span><span style="color:#66d9ef;">join</span><span>(arch); </span><span> </span><span> </span><span style="color:#75715e;">// Specify the link path. </span><span> println!(</span><span style="color:#e6db74;">&quot;cargo:rustc-link-search=native=</span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;">&quot;</span><span>, dir.</span><span style="color:#66d9ef;">to_str</span><span>().</span><span style="color:#66d9ef;">unwrap</span><span>()); </span><span>} </span></code></pre> <h1 id="hello-world">Hello, world!</h1> <p>With access to the kernel API, we can now extend our driver to print <code>&quot;Hello, world!&quot;</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>winapi::km::wdm::DbgPrint; </span><span style="color:#f92672;">use </span><span>winapi::shared::ntdef::</span><span style="color:#ae81ff;">NTSTATUS</span><span>; </span><span style="color:#f92672;">use </span><span>winapi::shared::ntstatus::</span><span style="color:#ae81ff;">STATUS_SUCCESS</span><span>; </span><span> </span><span>#[no_mangle] </span><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;system&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_entry</span><span>() -&gt; NTSTATUS { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> DbgPrint(</span><span style="color:#e6db74;">&quot;Hello, world!</span><span style="color:#ae81ff;">\0</span><span style="color:#e6db74;">&quot;</span><span>.</span><span style="color:#66d9ef;">as_ptr</span><span>()); </span><span> } </span><span> </span><span> </span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span>} </span></code></pre> <blockquote> <p>When calling the <code>DbgPrint</code> function, make sure to add the <code>\0</code> add the end of the string, as the functions expects null-terminated or C-style strings. Later on we will implement the <code>print!</code> and <code>println!</code> macros that we normally expect in Rust.</p> </blockquote> <p>Finally, our driver is still a bit inconvenient as we cannot unload the driver without rebooting Microsoft Windows. The <code>driver_entry</code> function actually gets a number of arguments including a <code>DRIVER_OBJECT</code> struct which has a function pointer called <code>DriverUnload</code>. We will define a function <code>driver_exit</code> that simply prints <code>&quot;Bye bye!&quot;</code> and point <code>DriverUnload</code> to this function:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>winapi::km::wdm::{DbgPrint, </span><span style="color:#ae81ff;">DRIVER_OBJECT</span><span>}; </span><span style="color:#f92672;">use </span><span>winapi::shared::ntdef::{</span><span style="color:#ae81ff;">NT_SUCCESS</span><span>, </span><span style="color:#ae81ff;">UNICODE_STRING</span><span>}; </span><span style="color:#f92672;">use </span><span>winapi::shared::ntstatus::</span><span style="color:#ae81ff;">STATUS_SUCCESS</span><span>; </span><span> </span><span>#[no_mangle] </span><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;system&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_entry</span><span>(</span><span style="font-style:italic;color:#fd971f;">driver</span><span>: </span><span style="color:#f92672;">&amp;mut</span><span> DRIVER_OBJECT, </span><span style="color:#f92672;">_</span><span>: </span><span style="color:#f92672;">*const</span><span> UNICODE_STRING) -&gt; NTSTATUS { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> DbgPrint(</span><span style="color:#e6db74;">&quot;Hello, world!</span><span style="color:#ae81ff;">\0</span><span style="color:#e6db74;">&quot;</span><span>.</span><span style="color:#66d9ef;">as_ptr</span><span>()); </span><span> } </span><span> </span><span> driver.DriverUnload </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(driver_exit); </span><span> </span><span> </span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span>} </span><span> </span><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;system&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_exit</span><span>(</span><span style="font-style:italic;color:#fd971f;">driver</span><span>: </span><span style="color:#f92672;">&amp;mut</span><span> DRIVER_OBJECT) { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> DbgPrint(</span><span style="color:#e6db74;">&quot;Bye bye!</span><span style="color:#ae81ff;">\0</span><span style="color:#e6db74;">&quot;</span><span>.</span><span style="color:#66d9ef;">as_ptr</span><span>()); </span><span> } </span><span>} </span></code></pre> <h1 id="loading-the-driver">Loading the Driver</h1> <p>Now that we have our driver code set up, we can simply build it as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo make sign </span></code></pre> <p>The driver can be found at <code>target/x86_64-pc-windows-msvc/debug/driver.sys</code>, but how do we actually load our driver and see the debug messages? Well, let's first run the <a href="https://live.sysinternals.com/Dbgview.exe">DbgView.exe</a> utility as an administrator so we can actually capture our debug messages. In the <strong>Capture</strong> submenu, make sure that <strong>Capture Kernel</strong> and <strong>Enable Verbose Kernel Output</strong> are enabled.</p> <p>Now that we are capturing the debug messages, we can actually load our driver and to do that we will be using the service controller or <code>sc.exe</code>. Run the following command to create the service for our driver:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sc create example binPath=$(realpath target/x86_64-pc-windows-msvc/debug/driver.sys) type=kernel </span></code></pre> <p>We can then start the service as follows, which loads our driver:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sc start example </span></code></pre> <p>We can then stop the service as follows, which unloads our driver:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sc stop example </span></code></pre> <p>Then after stopping it, we can delete the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sc delete example </span></code></pre> <p>If you closely watch DebugView, you should see the <code>&quot;Hello, world!&quot;</code> and <code>&quot;Bye bye!&quot;</code> messages from our kernel driver.</p> <p>The code can be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/02-hello-world">Github</a>.</p> <h1 id="what-s-next">What's Next?</h1> <p>We have now programmed our very first kernel driver for Microsoft Windows that is capable of printing some debug messages. In <a href="/guides/windows-dev/windows-drivers-in-rust-generating-bindings">the next article</a> we will look at how to actually generate more complete bindings for the kernel API, so we can drop the winapi-rs crate that we are currently using and don't have to define the functions and types ourselves.</p> <h1 id="acknowledgements">Acknowledgements</h1> <p>Thanks to <a href="https://not-matthias.github.io/kernel-driver-with-rust/">Matthias</a> for taking the time to explore how to write kernel drivers for Microsoft Windows and especially taking the time to write up the details of the process.</p> <h1 id="references">References</h1> <ol> <li><a href="https://docs.microsoft.com/en-us/cpp/build/reference/linker-options?view=msvc-160">https://docs.microsoft.com/en-us/cpp/build/reference/linker-options?view=msvc-160</a></li> <li><a href="https://docs.microsoft.com/en-us/windows/win32/SbsCs/manifest-files-reference">https://docs.microsoft.com/en-us/windows/win32/SbsCs/manifest-files-reference</a></li> <li><a href="https://devblogs.microsoft.com/oldnewthing/20161024-00/?p=94575">https://devblogs.microsoft.com/oldnewthing/20161024-00/?p=94575</a></li> <li><a href="https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html">https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html</a></li> </ol> Windows Drivers in Rust: Prerequisites 2021-09-22T00:00:00+00:00 2021-09-22T00:00:00+00:00 https://codentium.com/guides/windows-dev/windows-drivers-in-rust-prerequisites/ <h1 id="introduction">Introduction</h1> <p>While I am definitely not the first to write about <a href="https://not-matthias.github.io/kernel-driver-with-rust/">writing kernel drivers for Microsoft Windows in Rust</a>, I feel there are still a number of gaps that have to be addressed to make this more approachable. That is why I will be covering how to write kernel drivers in Rust for Microsoft Windows in a series of articles that together hopefully end up being a more complete guide.</p> <p>Before we can even get started developing Windows drivers in Rust, we first have to install a number of prerequisites, namely:</p> <ul> <li>Microsoft Visual Studio</li> <li>Windows SDK</li> <li>Windows Driver Kit</li> <li>LLVM and Clang (for bindgen)</li> <li>Microsoft Windows running in Testing Mode (to load self-signed Windows drivers)</li> <li>Sysinternals DebugView</li> </ul> <h1 id="setting-up-visual-studio">Setting up Visual Studio</h1> <p>Download <a href="https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&amp;rel=16">Build Tools for Visual Studio</a> on the Windows box, and run the installer. When the installer presents you with workloads to select, you can select &quot;Desktop development with C++&quot; as that will pretty much install everything you will need. Then click the button on the bottom-right of the installer to download and install Visual Studio Build Tools. This may take a while to install, so this is probably a good moment to brew yourself a cup of coffee.</p> <p>If for some reason, you want to modify the workloads or individual components later on, you can:</p> <ol> <li>Select the <strong>Start</strong> button in the bottom-left, and then <strong>Settings</strong> to open your settings </li> <li>Type <code>Apps &amp; Features</code> in the search bar to find the Apps &amp; Features setting. Scroll down to the <strong>Microsoft Visual Studio Installer</strong>.</li> <li>Select <strong>Microsoft Visual Studio Installer</strong> and then click on the <strong>Modify</strong> button.</li> <li>This should open the same installer as before, allowing you to change your workloads/individual components.</li> </ol> <blockquote> <p>If you prefer a more complete installation of Visual Studio instead, you can download and install <a href="https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&amp;rel=16">Visual Studo 2019 Community Edition</a> rather than the Build Tools for Visual Studio.</p> </blockquote> <h1 id="windows-driver-kit">Windows Driver Kit</h1> <p>To install the Windows Driver Kit, we need a matching version of the Windows SDK first. Unfortunately, Visual Studio does not ship the most recent version of the Windows SDK yet, so we have to install it separately. Download the <a href="https://go.microsoft.com/fwlink/?linkid=2166460">Windows SDK ISO</a> and right-click the file and select <strong>Mount</strong>. Then once the ISO has been mounted, you can run <code>WinSDKSetup.exe</code> to install the Windows SDK.</p> <p>Now that we have the right version of the Windows SDK installed, we can proceed to download and install the <a href="https://go.microsoft.com/fwlink/?linkid=2166289">Windows WDK</a>. You should now have version 10.22000.1 of the Windows SDK and the WDK.</p> <h1 id="llvm">LLVM</h1> <p>While we won't need LLVM until we look at generating bindings for the Windows Driver API in <a href="windows-drivers-in-rust-generating-bindings">Windows Drivers in Rust: Generating Bindings</a>, it would be nice to have it already installed for when we get to using <a href="https://github.com/rust-lang/rust-bindgen">bindgen</a>, as it depends on libclang to generate Rust bindings from the C/C++ headers. The available releases for LLVM can be found on <a href="https://releases.llvm.org/download.html">their download page</a>. At the time of writing the most recent release of LLVM is 12.0.1, which is what we will be using. We will be downloading <a href="https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.1/LLVM-12.0.1-win64.exe">the Windows installer</a> to install LLVM, which is named <code>LLVM-12.0.1-win64.exe</code>. Download and run the installer to install LLVM and Clang.</p> <blockquote> <p>While MSYS2, WSL and Microsoft Visual Studio also provide their own versions of LLVM and Clang, these are not necessarily suitable for our needs. For instance, bindgen will not be able to parse the Windows Driver headers using the MSYS2 version at all due to compatibility reasons. Similarly, Microsoft Visual Studio seems to provide a 32-bit version of libclang.dll, while we need a 64-bit version.</p> </blockquote> <h1 id="testing-mode">Testing Mode</h1> <p>Open PowerShell in Administrator mode and run the following to allow Microsoft Windows to self-signed Windows driver:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Bcdedit.exe -set TESTSIGNING ON </span></code></pre> <p>After running the command above, you have to reboot your system and Microsoft Windows should boot up in testing mode. If the changes are successful, the bottom-right corner should indicate that Microsoft Windows is now running in <strong>Test Mode</strong>.</p> <p>In case you want to revert the change, you can run the following command in PowerShell running as an administrator:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Bcdedit.exe -set TESTSIGNING OFF </span></code></pre> <h1 id="sysinternals">Sysinternals</h1> <p>In order to see debug messages from our drivers, we will need a tool called <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/debugview">DebugView from Sysinternals</a>. Download <a href="https://live.sysinternals.com/Dbgview.exe">DebugView</a> to your system. It can simply be run in administrator mode to start capturing the debug messages.</p> <p>In addition, we will need a tool called <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/winobj">WinObj from Sysinternals</a>, which will help us investigate the object manager namespace later on. Download <a href="https://live.sysinternals.com/Winobj.exe">WinObj</a> to your system.</p> <h1 id="what-s-next">What's Next?</h1> <p>In <a href="/guides/windows-dev/windows-drivers-in-rust-hello-world">the next article</a> we will be looking at actually setting up our driver, such that we end up with a minimal driver that can print debug messages.</p> <h1 id="references">References</h1> <ol> <li><a href="https://not-matthias.github.io/kernel-driver-with-rust/">https://not-matthias.github.io/kernel-driver-with-rust/</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/install/the-testsigning-boot-configuration-option">https://docs.microsoft.com/en-us/windows-hardware/drivers/install/the-testsigning-boot-configuration-option</a></li> </ol> Windows Drivers in Rust: Generating Bindings 2021-09-21T00:00:00+00:00 2021-09-21T00:00:00+00:00 https://codentium.com/guides/windows-dev/windows-drivers-in-rust-generating-bindings/ <h1 id="introduction">Introduction</h1> <p>In <a href="/guides/windows-dev/windows-drivers-in-rust-hello-world">the previous article</a> we got our first driver to work and print some debug messages. However, at the time of writing there aren't any crates that really cover the Windows kernel API too well. Of course, we could write the bindings ourselves whenever we need them, but that is a lot of work. Wouldn't it be nice if we could just generate the Rust bindings from the C header files? Well, that is why we are going to be using <a href="https://github.com/rust-lang/rust-bindgen">bindgen</a>, which does exactly that!</p> <blockquote> <p>If you just would like to use the bindings, they can be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/windows-kernel-sys">Github</a>. This serves mostly as a documentation of the process, which may be useful in case you have to generate Rust bindings using bindgen yourself.</p> </blockquote> <h1 id="getting-started">Getting Started</h1> <p>We will create a new crate for our unsafe bindings called <code>windows-kernel-sys</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo new --lib windows-kernel-sys </span></code></pre> <p>For this project, we will also be using Rust Nightly:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo nightly &gt; rust-toolchain </span></code></pre> <p>In addition, we copy over the <code>build.rs</code> from our previous driver, but make sure that the <code>main</code> function is empty:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() { </span><span>} </span></code></pre> <p>Finally, we will add the same build dependencies as before to <code>Cargo.toml</code>:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[build-dependencies] </span><span style="color:#f92672;">thiserror </span><span>= </span><span style="color:#e6db74;">&quot;1.0&quot; </span><span style="color:#f92672;">winreg </span><span>= </span><span style="color:#e6db74;">&quot;0.10&quot; </span></code></pre> <h1 id="generating-bindings">Generating Bindings</h1> <p>As mentioned before, we will be using bindgen, so let's add that to our build dependencies in <code>Cargo.toml</code>:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[build-dependencies] </span><span style="color:#f92672;">bindgen </span><span>= </span><span style="color:#e6db74;">&quot;0.59&quot; </span></code></pre> <p>Then we create a file named <code>src/wrapper.h</code> which will include the headers for which we want to generate bindings:</p> <pre data-lang="c" style="background-color:#272822;color:#f8f8f2;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#f92672;">#define </span><span>_AMD64_ </span><span> </span><span style="color:#f92672;">#include </span><span style="color:#e6db74;">&quot;ntdef.h&quot; </span><span style="color:#f92672;">#include </span><span style="color:#e6db74;">&quot;ntifs.h&quot; </span></code></pre> <p>We will then use bindgen to generate the type definitions from this header file. We will do this in the <code>generate_base</code> function in <code>build.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">generate_base</span><span>() { </span><span> </span><span style="color:#75715e;">// Tell Cargo to re-run this if src/wrapper.h gets changed. </span><span> println!(</span><span style="color:#e6db74;">&quot;cargo:rerun-if-changed=src/wrapper.h&quot;</span><span>); </span><span> </span><span> </span><span style="color:#75715e;">// Find the include directory containing the kernel headers. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> include_dir </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">get_km_dir</span><span>(DirectoryType::Include).</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span> </span><span> </span><span style="color:#75715e;">// Get the build directory. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> out_path </span><span style="color:#f92672;">= </span><span>PathBuf::from( </span><span> std::env::var_os(</span><span style="color:#e6db74;">&quot;OUT_DIR&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">expect</span><span>(</span><span style="color:#e6db74;">&quot;the environment variable OUT_DIR is undefined&quot;</span><span>) </span><span> ); </span><span> </span><span> </span><span style="color:#75715e;">// Generate the bindings </span><span> bindgen::Builder::default() </span><span> .</span><span style="color:#66d9ef;">header</span><span>(</span><span style="color:#e6db74;">&quot;src/wrapper.h&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">use_core</span><span>() </span><span> .</span><span style="color:#66d9ef;">derive_debug</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">layout_tests</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">ctypes_prefix</span><span>(</span><span style="color:#e6db74;">&quot;cty&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">default_enum_style</span><span>(bindgen::EnumVariation::ModuleConsts) </span><span> .</span><span style="color:#66d9ef;">clang_arg</span><span>(format!(</span><span style="color:#e6db74;">&quot;-I</span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;">&quot;</span><span>, include_dir.</span><span style="color:#66d9ef;">to_str</span><span>().</span><span style="color:#66d9ef;">unwrap</span><span>())) </span><span> .</span><span style="color:#66d9ef;">parse_callbacks</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Box</span><span>::new(bindgen::CargoCallbacks)) </span><span> .</span><span style="color:#66d9ef;">ignore_functions</span><span>() </span><span> .</span><span style="color:#66d9ef;">generate</span><span>() </span><span> .</span><span style="color:#66d9ef;">unwrap</span><span>() </span><span> .</span><span style="color:#66d9ef;">write_to_file</span><span>(out_path.</span><span style="color:#66d9ef;">join</span><span>(</span><span style="color:#e6db74;">&quot;base.rs&quot;</span><span>)) </span><span> .</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span>} </span></code></pre> <p>The function above tells Cargo to re-run <code>build.rs</code> whenever <code>src/wrapper.h</code> changes. Then we look up the include directory that contains the kernel headers as well as our build directory. Finally, we pass a lot of options to <code>bindgen::Builder</code>:</p> <ul> <li><code>.header(&quot;src/wrapper.h&quot;)</code>: we want to generate the bindings from this header.</li> <li><code>.use_core()</code>: use <code>core</code> instead of <code>std</code>.</li> <li><code>.derive_debug(false)</code>: don't derive <code>Debug</code> for our types.</li> <li><code>.layout_tests(false)</code>: don't generate layout tests.</li> <li><code>.ctypes_prefix(&quot;cty&quot;)</code>: we will be importing the C-types from the cty crate.</li> <li><code>.default_enum_style(bindgen::EnumVariation::ModuleConsts)</code>: generate enums as a module containing the constants.</li> <li><code>.clang_arg(format!(&quot;-I{}&quot;, include_dir.to_str().unwrap())</code>: tell Clang where to find the kernel headers.</li> <li><code>.parse_callbacks(Box::new(bindgen::CargoCallbacks))</code>: outputs re-run if changed for the files included by our header, such that when any of the headers change, <code>build.rs</code> gets executed to regenerate the bindings.</li> <li><code>.ignore_functions()</code>: ignore any function declarations, we will just generate the types for now.</li> </ul> <p>Then in our <code>main</code> function, we can simply call this function:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() { </span><span> </span><span style="color:#66d9ef;">generate_base</span><span>(); </span><span>} </span></code></pre> <p>Unfortunately, if we run <code>cargo build</code>, then bindgen will panic with <code>&quot;Unable to get layout information?&quot;</code>. This is caused by <code>union _KGDTENTRY64 *GdtBase;</code> and <code>union _KIDTENTRY64 *IdtBase;</code> in <code>ntddk.h</code>, which are forward declarations of these unions without any declaration of the actual union. Fortunately, the definitions of these unions can be found online for <a href="http://librecrops.github.io/lost-sdk/files/KGDTENTRY64.h.html">_KGDTENTRY64</a> and <a href="http://librecrops.github.io/lost-sdk/files/KIDTENTRY64.h.html">_KIDTENTRY64</a>, so we can just copy them over into <code>src/wrapper.h</code>:</p> <pre data-lang="c" style="background-color:#272822;color:#f8f8f2;" class="language-c "><code class="language-c" data-lang="c"><span style="font-style:italic;color:#66d9ef;">typedef union</span><span> _KGDTENTRY64 </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">struct </span><span> { </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> LimitLow; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> BaseLow; </span><span> </span><span style="font-style:italic;color:#66d9ef;">union </span><span> { </span><span> </span><span style="font-style:italic;color:#66d9ef;">struct </span><span> { </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned char</span><span> BaseMiddle; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned char</span><span> Flags1; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned char</span><span> Flags2; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned char</span><span> BaseHigh; </span><span> } Bytes; </span><span> </span><span style="font-style:italic;color:#66d9ef;">struct </span><span> { </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> BaseMiddle </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">8</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> Type </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">5</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> Dpl </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">2</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> Present </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> LimitHigh </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">4</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> System </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> LongMode </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> DefaultBig </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> Granularity </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> BaseHigh </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">8</span><span>; </span><span> } Bits; </span><span> }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> BaseUpper; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> MustBeZero; </span><span> }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned __int64</span><span> Alignment; </span><span>} KGDTENTRY64, </span><span style="color:#f92672;">*</span><span>PKGDTENTRY64; </span><span> </span><span style="font-style:italic;color:#66d9ef;">typedef union</span><span> _KIDTENTRY64 </span><span>{ </span><span> </span><span style="font-style:italic;color:#66d9ef;">struct </span><span> { </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> OffsetLow; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> Selector; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> IstIndex </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">3</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> Reserved0 </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">5</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> Type </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">5</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> Dpl </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">2</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> Present </span><span style="color:#f92672;">: </span><span style="color:#ae81ff;">1</span><span>; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned short</span><span> OffsetMiddle; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> OffsetHigh; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned long</span><span> Reserved1; </span><span> }; </span><span> </span><span style="font-style:italic;color:#66d9ef;">unsigned __int64</span><span> Alignment; </span><span>} KIDTENTRY64, </span><span style="color:#f92672;">*</span><span>PKIDTENTRY64; </span></code></pre> <p>Now if we call <code>cargo build</code>, the generation actually succeeds.</p> <h1 id="importing-bindings">Importing Bindings</h1> <p>First, we will add the cty crate as a dependency for our crate:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[dependencies] </span><span style="color:#f92672;">cty </span><span>= </span><span style="color:#e6db74;">&quot;0.2&quot; </span></code></pre> <p>Then in <code>src/base.rs</code>, we can include the generated bindings:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![allow(non_upper_case_globals)] </span><span>#![allow(non_camel_case_types)] </span><span>#![allow(non_snake_case)] </span><span> </span><span style="color:#f92672;">pub use </span><span>cty::</span><span style="color:#f92672;">*</span><span>; </span><span> </span><span>include!(concat!(env!(</span><span style="color:#e6db74;">&quot;OUT_DIR&quot;</span><span>), </span><span style="color:#e6db74;">&quot;/base.rs&quot;</span><span>)); </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">const </span><span style="color:#ae81ff;">STATUS_SUCCESS</span><span>: </span><span style="color:#ae81ff;">NTSTATUS </span><span style="color:#f92672;">= </span><span style="color:#ae81ff;">0x00000000</span><span>; </span></code></pre> <p>We can then export the module in <code>src/lib.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![no_std] </span><span> </span><span>#![feature(untagged_unions)] </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">mod </span><span>base; </span><span> </span><span style="color:#f92672;">pub use </span><span>cty::</span><span style="color:#f92672;">*</span><span>; </span></code></pre> <h1 id="generating-functions">Generating Functions</h1> <p>Now that we have the <code>base</code> modules with all the types, we can proceed with generating the function declarations for <code>ntoskrnl.lib</code>. As generating bindings is rather slow, we are going to feature gate the different kernel libraries that can be used. Let's define the features in <code>Cargo.toml</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[features] </span><span>default = [&quot;ntoskrnl&quot;] </span><span>ntoskrnl = [] </span></code></pre> <p>We will now implement the <code>generate_ntoskrnl</code> function in our <code>build.rs</code> to generate the function bindings:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">generate_ntoskrnl</span><span>() { </span><span> </span><span style="color:#75715e;">// Tell Cargo to re-run this if src/wrapper.h gets changed. </span><span> println!(</span><span style="color:#e6db74;">&quot;cargo:rerun-if-changed=src/wrapper.h&quot;</span><span>); </span><span> </span><span> </span><span style="color:#75715e;">// Link against ntoskrnl. </span><span> println!(</span><span style="color:#e6db74;">&quot;cargo:rustc-link-lib=ntoskrnl&quot;</span><span>); </span><span> </span><span> </span><span style="color:#75715e;">// Find the include directory containing the kernel headers. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> include_dir </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">get_km_dir</span><span>(DirectoryType::Include).</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span> </span><span> </span><span style="color:#75715e;">// Get the build directory. </span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> out_path </span><span style="color:#f92672;">= </span><span>PathBuf::from( </span><span> std::env::var_os(</span><span style="color:#e6db74;">&quot;OUT_DIR&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">expect</span><span>(</span><span style="color:#e6db74;">&quot;the environment variable OUT_DIR is undefined&quot;</span><span>) </span><span> ); </span><span> </span><span> </span><span style="color:#75715e;">// Generate the bindings </span><span> bindgen::Builder::default() </span><span> .</span><span style="color:#66d9ef;">header</span><span>(</span><span style="color:#e6db74;">&quot;src/wrapper.h&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">use_core</span><span>() </span><span> .</span><span style="color:#66d9ef;">derive_debug</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">layout_tests</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">ctypes_prefix</span><span>(</span><span style="color:#e6db74;">&quot;cty&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">default_enum_style</span><span>(bindgen::EnumVariation::ModuleConsts) </span><span> .</span><span style="color:#66d9ef;">clang_arg</span><span>(format!(</span><span style="color:#e6db74;">&quot;-I</span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;">&quot;</span><span>, include_dir.</span><span style="color:#66d9ef;">to_str</span><span>().</span><span style="color:#66d9ef;">unwrap</span><span>())) </span><span> .</span><span style="color:#66d9ef;">parse_callbacks</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Box</span><span>::new(bindgen::CargoCallbacks)) </span><span> .</span><span style="color:#66d9ef;">blocklist_type</span><span>(</span><span style="color:#e6db74;">&quot;.*&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">allowlist_function</span><span>(</span><span style="color:#e6db74;">&quot;.*&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">allowlist_recursively</span><span>(</span><span style="color:#ae81ff;">false</span><span>) </span><span> .</span><span style="color:#66d9ef;">generate</span><span>() </span><span> .</span><span style="color:#66d9ef;">unwrap</span><span>() </span><span> .</span><span style="color:#66d9ef;">write_to_file</span><span>(out_path.</span><span style="color:#66d9ef;">join</span><span>(</span><span style="color:#e6db74;">&quot;base.rs&quot;</span><span>)) </span><span> .</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span>} </span></code></pre> <p>The code above is pretty much the same as the <code>generate_base</code> function, except that we tell Cargo to link against <code>ntoskrnl</code> and that we now generate the function declarations rather than the type declarations. Then we can change the <code>main</code> function in <code>build.rs</code> to generate the bindings:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() { </span><span> </span><span style="color:#66d9ef;">generate_base</span><span>(); </span><span> </span><span> #[cfg(feature </span><span style="color:#f92672;">= </span><span style="color:#e6db74;">&quot;ntoskrnl&quot;</span><span>)] </span><span> </span><span style="color:#66d9ef;">generate_ntoskrnl</span><span>(); </span><span>} </span></code></pre> <p>We can then include the ntoskrnl bindings in our crate from <code>src/ntoskrnl.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![allow(non_upper_case_globals)] </span><span>#![allow(non_camel_case_types)] </span><span>#![allow(non_snake_case)] </span><span> </span><span style="color:#f92672;">use crate</span><span>::base::</span><span style="color:#f92672;">*</span><span>; </span><span> </span><span>include!(concat!(env!(</span><span style="color:#e6db74;">&quot;OUT_DIR&quot;</span><span>), </span><span style="color:#e6db74;">&quot;/ntoskrnl.rs&quot;</span><span>)); </span></code></pre> <p>Then in <code>src/lib.rs</code>, we can export the <code>ntoskrnl</code> module:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>#[cfg(feature = &quot;ntoskrnl&quot;)] </span><span>pub mod ntoskrnl; </span></code></pre> <h1 id="dealing-with-inline-functions">Dealing with Inline Functions</h1> <p>One limitation of bindgen is that it does not generate bindings to inline functions by default. Fortunately, this is easy to solve by creating a C file that contains non-inline functions that simply call the inline functions. For instance, to wrap <code>IoGetCurrentIrpStackLocation</code>, we can write the following <code>src/wrapper.c</code>:</p> <pre data-lang="c" style="background-color:#272822;color:#f8f8f2;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#f92672;">#include </span><span style="color:#e6db74;">&quot;wrapper.h&quot; </span><span> </span><span>PIO_STACK_LOCATION </span><span style="color:#a6e22e;">_IoGetCurrentIrpStackLocation</span><span>(irp</span><span style="color:#f92672;">: </span><span style="font-style:italic;color:#fd971f;">PIRP</span><span>) { </span><span> </span><span style="color:#f92672;">return </span><span>IoGetCurrentIrpStackLocation(); </span><span>} </span></code></pre> <p>Then we can use the cc crate to compile the C source file. Let's add the build dependency to our <code>Cargo.toml</code>:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[build-dependencies] </span><span style="color:#f92672;">cc </span><span>= </span><span style="color:#e6db74;">&quot;1.0&quot; </span></code></pre> <p>Then we can use the cc crate from the <code>generate_ntoskrnl</code> function in <code>build.rs</code> to build the wrapper:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span> cc::Build::new() </span><span> .</span><span style="color:#66d9ef;">flag</span><span>(</span><span style="color:#e6db74;">&quot;/kernel&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">include</span><span>(include_dir) </span><span> .</span><span style="color:#66d9ef;">file</span><span>(</span><span style="color:#e6db74;">&quot;src/wrapper.c&quot;</span><span>) </span><span> .</span><span style="color:#66d9ef;">compile</span><span>(</span><span style="color:#e6db74;">&quot;wrapper_ntoskrnl&quot;</span><span>); </span></code></pre> <p>Then we can define the function in <code>src/ntoskrnl.rs</code>:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[link(name </span><span style="color:#f92672;">= </span><span style="color:#e6db74;">&quot;wrapper_ntoskrnl&quot;</span><span>)] </span><span style="color:#f92672;">extern </span><span style="color:#e6db74;">&quot;C&quot; </span><span>{ </span><span> </span><span style="color:#f92672;">pub </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">_IoGetCurrentIrpStackLocation</span><span>(</span><span style="font-style:italic;color:#fd971f;">irp</span><span>: PIRP) -&gt; PIO_STACK_LOCATION; </span><span>} </span><span> </span><span style="color:#f92672;">pub use </span><span>self::_IoGetCurrentIrpStackLocation </span><span style="color:#f92672;">as</span><span> IoGetCurrentIrpStackLocation; </span></code></pre> <h1 id="using-the-bindings">Using the Bindings</h1> <p>If we go back to our driver, we can now drop the winapi-rs crate and use our own bindings as a dependency. Let's add them to the <code>Cargo.toml</code>:</p> <pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[dependencies] </span><span style="color:#f92672;">windows-kernel-sys </span><span>= { </span><span style="color:#f92672;">path </span><span>= </span><span style="color:#e6db74;">&quot;../windows-kernel-sys&quot; </span><span>} </span></code></pre> <p>Then we have to change our <code>src/lib.rs</code> as follows:</p> <pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>windows_kernel_sys::base::{</span><span style="color:#ae81ff;">DRIVER_OBJECT</span><span>, </span><span style="color:#ae81ff;">NTSTATUS</span><span>, </span><span style="color:#ae81ff;">UNICODE_STRING</span><span>, </span><span style="color:#ae81ff;">STATUS_SUCCESS</span><span>}; </span><span style="color:#f92672;">use </span><span>windows_kernel_sys::ntoskrnl::DbgPrint; </span><span> </span><span>#[no_mangle] </span><span style="color:#f92672;">pub extern </span><span style="color:#e6db74;">&quot;system&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_entry</span><span>(</span><span style="font-style:italic;color:#fd971f;">driver</span><span>: </span><span style="color:#f92672;">&amp;mut</span><span> DRIVER_OBJECT, </span><span style="color:#f92672;">_</span><span>: </span><span style="color:#f92672;">*const</span><span> UNICODE_STRING) -&gt; NTSTATUS { </span><span> </span><span style="color:#f92672;">unsafe </span><span>{ </span><span> DbgPrint(</span><span style="color:#e6db74;">&quot;Hello, world!</span><span style="color:#ae81ff;">\0</span><span style="color:#e6db74;">&quot;</span><span>.</span><span style="color:#66d9ef;">as_ptr</span><span>() </span><span style="color:#f92672;">as _</span><span>); </span><span> } </span><span> </span><span> driver.DriverUnload </span><span style="color:#f92672;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(driver_exit); </span><span> </span><span> </span><span style="color:#ae81ff;">STATUS_SUCCESS </span><span>} </span><span> </span><span style="color:#f92672;">pub unsafe extern </span><span style="color:#e6db74;">&quot;C&quot; </span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">driver_exit</span><span>(</span><span style="font-style:italic;color:#fd971f;">driver</span><span>: </span><span style="color:#f92672;">*mut</span><span> DRIVER_OBJECT) { </span><span> DbgPrint(</span><span style="color:#e6db74;">&quot;Bye bye!</span><span style="color:#ae81ff;">\0</span><span style="color:#e6db74;">&quot;</span><span>.</span><span style="color:#66d9ef;">as_ptr</span><span>() </span><span style="color:#f92672;">as _</span><span>); </span><span>} </span></code></pre> <p>If everything went well, the above will build against our own bindings:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo make sign </span></code></pre> <p>The code can be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/03-generating-bindings">Github</a>. More complete bindings can also be found on <a href="https://github.com/StephanvanSchaik/windows-kernel-rs/tree/main/windows-kernel-sys">Github</a>.</p> <h1 id="what-s-next">What's Next?</h1> <p>Now that we have our own bindings for the Windows kernel API, we can start implementing safe abstractions in Rust on top of our unsafe bindings. In the <a href="/guides/windows-dev/windows-drivers-in-rust-safe-framework">next article</a> we will be looking at how to implement a safe framework for writing Windows kernel drivers in Rust. More specifically, we will implement a global allocator such that we can use the heap-based types from the <code>alloc</code> crate such as strings and <code>Vec</code>, we will implement the <code>print!</code> and <code>println!</code> macros and a macro to produce the unsafe boilerplate code for the Windows kernel driver.</p> <h1 id="acknowledgements">Acknowledgements</h1> <p>Thanks to <a href="https://github.com/hussein-aitlahcen/windows-kernel-rs">Hussein Aitlahcen</a> for his tremendous work on figuring out how to get bindgen to work with the Windows kernel headers.</p> <h1 id="references">References</h1> <ol> <li><a href="https://github.com/hussein-aitlahcen/windows-kernel-rs">https://github.com/hussein-aitlahcen/windows-kernel-rs</a></li> <li><a href="https://rust-lang.github.io/rust-bindgen/">https://rust-lang.github.io/rust-bindgen/</a></li> <li><a href="http://librecrops.github.io/lost-sdk/files/KGDTENTRY64.h.html">http://librecrops.github.io/lost-sdk/files/KGDTENTRY64.h.html</a></li> <li><a href="http://librecrops.github.io/lost-sdk/files/KIDTENTRY64.h.html">http://librecrops.github.io/lost-sdk/files/KIDTENTRY64.h.html</a></li> </ol> Setting up Windows for Remote Rust Development 2021-09-18T00:00:00+00:00 2021-09-18T00:00:00+00:00 https://codentium.com/guides/windows-dev/setting-up-windows-for-remote-rust-development/ <h1 id="introduction">Introduction</h1> <p>Having used Linux Gentoo with i3 as a tiling window manager, Vim as a text editor and terminals for too long, Microsoft Windows' GUI feels rather clunky to use when developing anything on Microsoft Windows. It would be ideal if we could just SSH into our Windows box and have an experience similar to the one we have on our Linux installation with access to tools such as Git, Vim and coreutils from our terminal. Yet at the same time, we want to be able to use Visual Studio to build native Windows binaries for our Rust applications. In this guide we will be setting up Visual Studio, Rust, MSYS 2 and OpenSSH server on our Windows box, such that we have a nice remote environment to use.</p> <h1 id="remote-desktop-protocol">Remote Desktop Protocol</h1> <p>While we will be setting up a Linux-like environment on Microsoft Windows for our Rust development, we sometimes really have to resort to the GUI. For instance, to set up our Windows box in the first place. Therefore, we will first enable Remote Desktop on our Windows box:</p> <blockquote> <p>Unfortunately, the Remote Desktop feature does not seem to be available on Microsoft Windows 10 Home Edition out of the box. While it is possible to use something like the <a href="https://github.com/stascorp/rdpwrap">RDP Wrapper Library</a> to get Remote Desktop to work, I am assuming you have either Microsoft Windows 10 Pro Edition or Enterprise Edition installed.</p> </blockquote> <ol> <li>On the Windows box, select the <strong>Start</strong> button (Windows logo) in the bottom-right and then click the <strong>Settings</strong> (cog wheel icon) on the left.</li> <li>Select the <strong>System</strong> (computer icon) button and then scroll down to <strong>Remote Desktop</strong>.</li> <li>Click on the slider to enable Remote Desktop.</li> <li>Select <strong>Select users that can remotely access this PC</strong> and add your account. If your account is an administrator, it will have access to Remote Desktop by default.</li> <li>Select <strong>Power &amp; sleep</strong> on the left, and set Sleep to never, such that the PC never goes to sleep.</li> </ol> <p>Now that Remote Desktop has been set up, we need to note down the IP address that has been assigned to our Windows box:</p> <ol> <li>On the Windows box, type <code>PowerShell.exe</code> in the search bar at the bottom-left.</li> <li>Select <strong>Windows PowerShell</strong>, this will open PowerShell and present you with a blue terminal.</li> <li>Type <code>ipconfig</code> in PowerShell and note down the IPv4 address. We will be using this to connect to the Windows box from Linux.</li> </ol> <p>On Linux, you can either use <a href="http://www.rdesktop.org/">rdesktop</a> or <a href="https://www.freerdp.com/">freerdp</a> to connect to the Windows box. My recommendation is to install freerdp, as rdesktop will likely end up giving you the error message: &quot;Failed to connect, CredSSP required by server.&quot; Install freerdp using your package manager:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>emerge freerdp </span></code></pre> <p>Assuming your username is USER and the IP address of your Windows box is 192.168.1.42 (feel free to replace these arguments), you can then run the following command to connect to your Windows box over RDP:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>xfreerdp /u:&quot;USER&quot; /v:192.168.1.42:3389 /size:1920x1080 -grab-keyboard </span></code></pre> <h1 id="setting-up-visual-studio">Setting up Visual Studio</h1> <p>Download <a href="https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&amp;rel=16">Build Tools for Visual Studio</a> on the Windows box, and run the installer. When the installer presents you with workloads to select, you can select &quot;Desktop development with C++&quot; as that will pretty much install everything you will need. Then click the button on the bottom-right of the installer to download and install Visual Studio Build Tools. This may take a while to install, so this is probably a good moment to brew yourself a cup of coffee.</p> <p>If for some reason, you want to modify the workloads or individual components later on, you can:</p> <ol> <li>Select the <strong>Start</strong> button in the bottom-left, and then <strong>Settings</strong> to open your settings </li> <li>Type <code>Apps &amp; Features</code> in the search bar to find the Apps &amp; Features setting. Scroll down to the <strong>Microsoft Visual Studio Installer</strong>.</li> <li>Select <strong>Microsoft Visual Studio Installer</strong> and then click on the <strong>Modify</strong> button.</li> <li>This should open the same installer as before, allowing you to change your workloads/individual components.</li> </ol> <blockquote> <p>If you prefer a more complete installation of Visual Studio instead, you can download and install <a href="https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&amp;rel=16">Visual Studo 2019 Community Edition</a> rather than the Build Tools for Visual Studio.</p> </blockquote> <h1 id="setting-up-msys2">Setting up MSYS2</h1> <p>Download and install <a href="https://www.msys2.org/">MSYS2</a>.</p> <p>Open the MSYS2 shell and type the following command to make sure it is up-to-date:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pacman -Syu </span></code></pre> <p>Then install any tools you want such as Git and Vim:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pacman -Sy vim git </span></code></pre> <p>Then we will be setting up MSYS2 to use the conventional location of our user directory at <code>C:\Users\USER</code>, as this is also where our <code>.ssh</code> directory will live. In the MSYS2 shell, change <code>db_home: cygwin desc</code> in <code>/etc/nsswitch.conf</code> to the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>db_home: windows </span></code></pre> <p>Then we will be adding a <code>HOME</code> environment variable for our account (this will be used by MSYS2 over OpenSSH):</p> <ol> <li>Select <strong>Start</strong> and then <strong>Settings</strong>.</li> <li>Then search for <code>Edit environment variables for your account</code>.</li> <li>Click the <strong>New...</strong> button to add a new user variable.</li> <li>The variable name should be <code>HOME</code> and the value should be your user directory on Microsoft Windows (e.g. <code>C:\Users\USER</code>).</li> </ol> <p>Now if you start up a MSYS2 shell, your home directory should be <code>C:\Users\USER</code>.</p> <p>Copy over the default <code>.bashrc</code>, <code>.bash_profile</code> and <code>.profile</code> files from the old home directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cp /home/USER/{.bash_profile, .bashrc, .profile} ~/ </span></code></pre> <h1 id="setting-up-openssh">Setting up OpenSSH</h1> <p>Run PowerShell as an administrator. Then run the following command to make sure that OpenSSH is available:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Get-WindowsCapability -Online | ? Name -like &#39;OpenSSH*&#39; </span></code></pre> <p>This should return the following output if neither the client or the server are installed:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Name : OpenSSH.Client~~~~0.0.1.0 </span><span>State : NotPresent </span><span> </span><span>Name : OpenSSH.Server~~~~0.0.1.0 </span><span>State : NotPresent </span></code></pre> <p>Then install the server component as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 </span></code></pre> <p>While we don't necessarily need the client component, you can install it as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 </span></code></pre> <p>These should return the following output:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Path : </span><span>Online : True </span><span>RestartNeeded : False </span></code></pre> <p>Start the sshd service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Start-Service sshd </span></code></pre> <p>Set up the sshd service to automatically start up whenever the Windows box boots:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Set-Service -Name sshd -StartupType &#39;Automatic&#39; </span></code></pre> <p>Confirm that the firewall has been configured:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Get-NetFirewallRule -Name *ssh* </span></code></pre> <p>If there is no firewall rule named &quot;OpenSSH-Server-In-TCP&quot;, then you can create it as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>New-NetFirewallRule -Name sshd -DisplayName &#39;OpenSSH Server (sshd)&#39; -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 </span></code></pre> <p>By default, OpenSSH server will use the Windows command line, but we want to present the Bash shell from MSYS2 instead. Where MSYS2 is installed (e.g. <code>C:\msys64\</code>) we will create a batch script named <code>msys2.bat</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@echo off </span><span>IF DEFINED SSH_TTY (C:\msys64\usr\bin\bash.exe -li %*) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe %*) </span></code></pre> <p>Let's dissect the above batch script to get some understanding of what it does:</p> <ul> <li><code>@echo off</code> disables the commands from being echoed back to stdout.</li> <li><code>IF DEFINED SSH_TTY () ELSE ()</code> checks if the <code>SSH_TTY</code> environment variable is set. If it is set, it means that we have a tty and should start an interactive Bash shell. Otherwise, the user may be running something like <code>scp</code>, in which case we default to PowerShell. Without this check we would end up breaking commands like <code>scp</code>.</li> </ul> <p>Point the default shell to use for OpenSSH connections to our batch script:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>New-ItemProperty -Path &quot;HKLM:\SOFTWARE\OpenSSH&quot; -Name DefaultShell -Value &quot;C:\msys64\msys2.bat&quot; -PropertyType String -Force </span></code></pre> <p>Now when you ssh into the Windows box, it should present you with Bash from MSYS2.</p> <h1 id="using-public-keys">Using Public Keys</h1> <p>The default sshd configuration on Microsoft Windows is a bit odd, as it will not look at <code>$HOME/.ssh/authorized_keys</code> for administrator accounts, but instead at <code>C:\ProgramData\ssh\administrators_authorized_keys</code>. To get consistent behaviour with what we expect coming from Linux, we will edit the configuration to always use <code>$HOME/.ssh/authorized_keys</code> instead. On Microsoft Windows, the <code>sshd_config</code> lives at <code>C:\ProgramData\ssh\sshd_config</code>. Since the editing that file requires administrator rights, the easiest way to edit it is to run PowerShell as an administrator, point to the directory <code>C:ProgramData\ssh\sshd_config</code> and run <code>notepad</code> to open it:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>notepad sshd_config </span></code></pre> <p>Open <code>sshd_config</code> and remove the following lines:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Match Group administrators </span><span> AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys </span></code></pre> <p>In addition, you should also disable password authentication:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>PasswordAuthentication no </span></code></pre> <p>Run PowerShell as an administrator, and run the following to restart OpenSSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Restart-Service ssh </span></code></pre> <p>Then put your public key(s) in <code>C:\Users\USER\.ssh\authorized_keys</code> (note the dot before ssh). If everything is set up correctly, you should be able to connect over SSH without needing to enter a password, and it should never ask for a password when the account does not exist or when the wrong private key is being used to connect.</p> <h1 id="setting-up-rust">Setting up Rust</h1> <p>Now connect to your Windows box over SSH and install <a href="https://rustup.rs/">rustup</a>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh </span></code></pre> <p>Just press enter to select the default option. rustup will simply use the <code>stable-x86_64-pc-windows-msvc</code> toolchain by default.</p> <p>Add the following line to <code>~/.bash_profile</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>export PATH=&quot;$HOME/.cargo/bin&quot;:$PATH </span></code></pre> <p>Then you can type the following to reload your bash profile:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>source ~/.bash_profile </span></code></pre> <p>Commands like <code>cargo</code>, <code>rustc</code> and <code>rustup</code> should work now. You can make sure that <code>stable-x86_64-pc-windows-msvc</code> is the default toolchain as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>rustup toolchain list </span></code></pre> <p>Finally, you should be able to use Rust:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cargo new hello </span><span>cd hello </span><span>cargo run </span></code></pre> <h1 id="why-not-wsl">Why not WSL?</h1> <p>Personally, I prefer MSYS2 due to my familiarity with MSYS2, as I have been using MinGW and MSYS2 on Microsoft Windows before WSL even became an option. Even though WSL feels more like an environment for Microsoft Windows users to use and develop Linux applications without having to use a VM or install a Linux distribution, WSL would also work for our purpose as the goal is to get an environment similar to Linux that we can ssh into and use to build native Windows applications. Therefore, I will also describe the changes needed to use something like WSL instead of MSYS2 for completeness.</p> <p>While it is possible to use OpenSSH from inside your WSL installation using something like <a href="https://github.com/sorah/subsystemctl">subsystemctl</a> and <a href="https://github.com/arkane-systems/genie">genie</a> in combination with Windows Task Scheduler, my recommendation is to use the OpenSSH Server that comes with Microsoft Windows 10 instead. To set up OpenSSH on Microsoft Windows 10, you can simply follow the instructions outlined before. You can then use the following batch script as a default shell for OpenSSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@echo off </span><span>IF DEFINED SSH_TTY (C:\Windows\System32\wsl.exe ~ -d Alpine %*) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe %*) </span></code></pre> <p>Replace <code>Alpine</code> in the above batch script with the name of your WSL distribution to run the shell of the WSL environment you want. If you now use ssh to connect to your Windows box, you should end up in your WSL environment. Similarly, because we check whether the <code>SSH_TTY</code> environment variable is set or not, <code>scp</code> will work, but copy to or from C:\Users\USER by default. With some more work, it is probably to make scp use the WSL root filesystem instead. We can do this by using the first instance of PowerShell to first change the directory and then run a second instance of PowerShell to execute the SSH command.</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@echo off </span><span>IF DEFINED SSH_TTY (C:\Windows\System32\wsl.exe ~ -d Arch %*) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe &quot;cd C:\Users\USER\Desktop\rootfs\root; PowerShell %*&quot;) </span></code></pre> <p>If we run scp now, it will copy files from/to /root in our WSL environment by default.</p> <p><strong>Note</strong>: from the WSL environment's perspective the <code>authorized_keys</code> are in <code>/mnt/c/Users/USER/.ssh/authorized_keys</code>.</p> <p>To set up rustup to use MSVC in WSL, you can refer to <a href="https://github.com/strickczq/msvc-wsl-rust">https://github.com/strickczq/msvc-wsl-rust</a>.</p> <h1 id="references">References</h1> <ol> <li><a href="https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/remote-desktop-allow-access">https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/remote-desktop-allow-access</a></li> <li><a href="https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse">https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse</a></li> <li><a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">https://docs.microsoft.com/en-us/windows/wsl/install-win10</a></li> </ol> Setting up mail autoconfiguration 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/automail/ <h1 id="installation">Installation</h1> <p>Install the build dependencies:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add build-base git openssl-dev </span></code></pre> <p>Install Rustup:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh </span><span>source $HOME/.cargo/env </span></code></pre> <p>Clone the repository:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>git clone https://github.com/StephanvanSchaik/automail </span><span>cd automail </span></code></pre> <p>Build automail:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>RUSTFLAGS=&quot;-C target-feature=-crt-static&quot; cargo build --release </span></code></pre> <p>Install the binary:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cp target/release/automail /usr/bin </span></code></pre> <p>Create the automail user:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>useradd -MU automail </span></code></pre> <p>Edit <code>/etc/init.d/automail</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>#!/sbin/openrc-run </span><span> </span><span>name=$RC_SVCNAME </span><span>cfgfile=&quot;/etc/$RC_SVCNAME/$RC_SVCNAME.conf&quot; </span><span>command=&quot;/usr/bin/automail&quot; </span><span>command_args=&quot;&quot; </span><span>command_user=&quot;automail&quot; </span><span>pidfile=&quot;/run/$RC_SVCNAME/$RC_SVCNAME.pid&quot; </span><span>start_stop_daemon_args=&quot;&quot; </span><span>command_background=&quot;yes&quot; </span><span> </span><span>depend() { </span><span> need net </span><span>} </span><span> </span><span>start_pre() { </span><span> checkpath --directory --owner $command_user:$command_user --mode 0775 \ </span><span> /run/$RC_SVCNAME /var/log/$RC_SVCNAME </span><span>} </span></code></pre> <p>Mark <code>/etc/init.d/automail</code> as executable:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>chmod +x /etc/init.d/automail </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Create the <code>/etc/automail</code> directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir /etc/automail </span></code></pre> <p>Edit <code>/etc/automail/Rocket.toml</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[default] </span><span>address = &quot;0.0.0.0&quot; </span><span>port = 3721 </span></code></pre> <p>Edit <code>/etc/automail/config.toml</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[ssl] </span><span>key = &quot;/etc/letsencrypt/live/example.com/privkey.pem&quot; </span><span>cert = &quot;/etc/letsencrypt/live/example.com/cert.pem&quot; </span><span>chain = &quot;/etc/letsencrypt/live/example.com/chain.pem&quot; </span><span> </span><span>[[domain]] </span><span>domain = &quot;example.com&quot; </span><span>name = &quot;Example Mail Configuratioon&quot; </span><span>short_name = &quot;Example&quot; </span><span> </span><span>[[domain.server]] </span><span>protocol = &quot;imap&quot; </span><span>hostname = &quot;imap.example.com&quot; </span><span>port = 993 </span><span>auth = &quot;plain&quot; </span><span>encrypt = &quot;ssl&quot; </span><span> </span><span>[[domain.server]] </span><span>protocol = &quot;imap&quot; </span><span>hostname = &quot;imap.example.com&quot; </span><span>port = 143 </span><span>auth = &quot;plain&quot; </span><span>encrypt = &quot;starttls&quot; </span><span> </span><span>[[domain.server]] </span><span>protocol = &quot;pop3&quot; </span><span>hostname = &quot;pop3.example.com&quot; </span><span>port = 995 </span><span>auth = &quot;plain&quot; </span><span>encrypt = &quot;ssl&quot; </span><span> </span><span>[[domain.server]] </span><span>protocol = &quot;pop3&quot; </span><span>hostname = &quot;pop3.example.com&quot; </span><span>port = 110 </span><span>auth = &quot;plain&quot; </span><span>encrypt = &quot;starttls&quot; </span><span> </span><span>[[domain.server]] </span><span>protocol = &quot;smtp&quot; </span><span>hostname = &quot;smtp.example.com&quot; </span><span>port = 587 </span><span>auth = &quot;plain&quot; </span><span>encrypt = &quot;starttls&quot; </span><span> </span><span>[[domain.server]] </span><span>protocol = &quot;smtp&quot; </span><span>hostname = &quot;smtp.example.com&quot; </span><span>port = 465 </span><span>auth = &quot;plain&quot; </span><span>encrypt = &quot;ssl&quot; </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/automail start </span><span>rc-update add automail </span></code></pre> <p>Edit <code>/etc/nginx/sites-available/01_mail.example.com</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>index index.html; </span><span> </span><span>server { </span><span> listen 80; </span><span> listen [::]:80; </span><span> server_name autoconfig.example.com autodiscover.example.com mail.example.com; </span><span> return 301 https://$host$request_uri; </span><span>} </span><span> </span><span>server { </span><span> listen 443 ssl; </span><span> listen [::]:443 ssl; </span><span> server_name autoconfig.example.com autodiscover.example.com mail.example.com; </span><span>ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot </span><span>ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot </span><span> </span><span> location / { </span><span> proxy_pass http://localhost:3721; </span><span> } </span><span> </span><span> location /.well-known { </span><span> alias /var/www/example.com/htdocs/.well-known; </span><span> } </span><span>} </span></code></pre> <p>Create the symlink:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ln -s /etc/nginx/sites-available/01_mail.example.com /etc/nginx/sites-enabled/01_mail.example.com </span></code></pre> <p>Reload the Nginx configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/nginx reload </span></code></pre> <p>Add the following DNS-record:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>_autodiscover._tcp SRV 0 1 443 autodiscover.example.com. </span></code></pre> <p>Add the subdomains to the Let's Encrypt certificate.</p> <h1 id="testing">Testing</h1> <p>Use an e-mail client with an account wizard like Thunderbird to test the setup. It should now be able to automatically detect the right settings for e-mail.</p> <p>You should also be able to download a MobileConfig file for Apple devices via <a href="https://mail.example.com/[email protected]">https://mail.example.com/[email protected]</a>.</p> Setting up ClamAV 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/clamav/ <h1 id="installation">Installation</h1> <p>Install ClamAV:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add acf-clamav clamsmtp </span></code></pre> <p>Add the postfix user to the clamav group:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>usermod -aG clamav postfix </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Start the services:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/clamd start </span><span>/etc/init.d/clamsmtpd start </span><span>rc-update add clamd </span><span>rc-update add clamsmtpd </span></code></pre> <p>Check if the ClamAV service is listening:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>netstat -anp | grep clamsmtpd </span></code></pre> <p>Edit <code>/etc/postfix/main.cf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>content_filter = scan:[127.0.0.1]:10025 </span></code></pre> <p>Add the following to <code>/etc/postfix/master.cf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span># AV scan filter (used by content_filter) </span><span>scan unix - - n - 16 smtp </span><span> -o smtp_send_xforward_command=yes </span><span> -o smtp_enforce_tls=no </span><span># For injecting mail back into postfix from the filter </span><span>127.0.0.1:10026 inet n - n - 16 smtpd </span><span> -o content_filter= </span><span> -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks </span><span> -o smtpd_helo_restrictions= </span><span> -o smtpd_client_restrictions= </span><span> -o smtpd_sender_restrictions= </span><span> -o smtpd_recipient_restrictions=permit_mynetworks,reject </span><span> -o mynetworks_style=host </span><span> -o smtpd_authorized_xforward_hosts=127.0.0.0/8 </span></code></pre> <p>Reload the Postfix configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/postfix reload </span></code></pre> <h1 id="testing">Testing</h1> <p>To test your ClamAV installation, you can send an e-mail from another domain to yourself and check the e-mail headers. They should contain a <code>X-Virus-Scanned</code> field if ClamAV is operational.</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>X-Virus-Scanned: ClamAV using ClamSMTP </span></code></pre> Setting up Dovecot 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/dovecot/ <h1 id="installation">Installation</h1> <p>Install <code>dovecot</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add dovecot dovecot-lmtpd dovecot-pgsql dovecot-pigeonhole-plugin dovecot-pop3d </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Edit <code>/etc/dovecot/dovecot.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>protocols = imap pop3 lmtp </span></code></pre> <p>Edit <code>/etc/dovecot/conf.d/10-mail.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>first_valid_uid = 105 </span><span>last_valid_uid = 105 </span><span>first_valid_gid = 107 </span><span>last_valid_gid = 107 </span><span>mail_location = maildir:/var/mail/domains/%d/%n </span></code></pre> <p>Edit the imap service in <code>/etc/dovecot/conf.d/10-master.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>service imap { </span><span> vsz_limit = 1024M </span><span>} </span><span> </span><span>service lmtp { </span><span> unix_listener /var/spool/postfix/private/dovecot-lmtp { </span><span> mode = 0666 </span><span> user = postfix </span><span> group = postfix </span><span> } </span><span> </span><span> user = vmail </span><span> group = postdrop </span><span>} </span><span> </span><span>service auth { </span><span> unix_listener auth-userdb { </span><span> } </span><span> </span><span> unix_listener /var/spool/postfix/private/auth { </span><span> mode = 0666 </span><span> user = postfix </span><span> group = postfix </span><span> } </span><span> </span><span> unix_listener /var/spool/postfix/auth-master { </span><span> mode = 0660 </span><span> user = vmail </span><span> group = postfix </span><span> } </span><span> </span><span> user root </span><span>} </span></code></pre> <p>Edit the mailboxes in <code>/etc/dovecot/conf.d/15-mailboxes.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>namespace inbox { </span><span> inbox = yes </span><span> </span><span> mailbox Trash { </span><span> auto = create </span><span> special_use = \Trash </span><span> } </span><span> </span><span> mailbox Spam { </span><span> auto = no </span><span> special_use = \Junk </span><span> } </span><span> </span><span> mailbox Sent { </span><span> auto = subscribe </span><span> special_use = \Sent </span><span> } </span><span>} </span></code></pre> <p>Edit <code>/etc/dovecot/conf.d/10-auth.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>auth_mechanisms = plain login </span><span>auth_username_format = %Lu </span><span>disable_plaintext_auth = no </span></code></pre> <p>Edit <code>/etc/dovecot/conf.d/20-lmtp.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>protocol lmtp { </span><span> # Space separated list of plugins to load (default is global mail_plugins). </span><span> mail_plugins = $mail_plugins sieve </span><span>} </span></code></pre> <p>Edit <code>/etc/dovecot/conf.d/20-managesieve.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>protocols = $protocols sieve </span></code></pre> <p>Make sure to include <code>auth-sql.conf.ext</code> from <code>/etc/dovecot/conf.d/10-auth.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>!include auth-sql.conf.ext </span></code></pre> <p>Edit <code>/etc/dovecot/conf.d/auth-sql.conf.ext</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>passdb { </span><span> driver = sql </span><span> args = /etc/dovecot/dovecot-sql.conf.ext </span><span>} </span><span> </span><span>userdb { </span><span> args = uid=105 gid=107 home=/var/mail/domains/%d/%n </span><span> driver = static </span><span>} </span></code></pre> <p>Edit <code>/etc/dovecot/dovecot-sql.conf.ext</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>connect = host=localhost dbname=postfix user=postfix password=password </span><span>default_pass_scheme = SHA512-CRYPT </span><span>password_query = \ </span><span> SELECT username,password \ </span><span> FROM mailbox \ </span><span> WHERE local_part = &#39;%n&#39; AND domain = &#39;%d&#39; </span></code></pre> <p>Allow TCP traffic on the IMAP and POP3 port (143 and 110) and port 4190:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow imap </span><span>ufw allow pop3 </span><span>ufw allow 4190/tcp </span></code></pre> <p>Check whether the Dovecot configuration is valid:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>dovecot -n </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/dovecot init </span><span>rc-update add dovecot </span></code></pre> <p>Check if Dovecot is listening on the IMAP and the POP3 port (143 and 110), as well as the <code>/var/spool/postfix/private/dovecot-lmtp</code> and <code>/var/spool/postfix/private/auth</code> UNIX sockets:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>netstat -anp | grep dovecot </span></code></pre> <h1 id="configuring-dns">Configuring DNS</h1> <p>Add the following DNS records:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>_imap._tcp SRV 143 imap.example.com. </span><span>_pop3._tcp SRV 110 pop3.example.com. </span></code></pre> <h1 id="thunderbird-add-on">Thunderbird Add-on</h1> <p>Download and install the <a href="https://addons.thunderbird.net/en-US/thunderbird/addon/sieve/">Sieve Add-on for Thunderbird</a>. If you go to Tools &gt; Sieve Message Filters, Thunderbird should open the page that lists your server connections. You should be able to connect to the Sieve server and manage the sieve scripts for your accounts.</p> Setting up Alpine Linux 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/general/ <h1 id="installation">Installation</h1> <p>Use any live environment (LiveISO or rescue image) to boot up the system you want to set up. The image doesn't necessarily have to be Alpine Linux, but if you can boot up Alpine linux, the installation may be slightly easier. Use <code>fdisk</code>, <code>gdisk</code> or <code>gparted</code> to partition the disk.</p> <p>Mount the partition that you want to use as the root filesystem:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir /mnt/chroot </span><span>mount /dev/vda1 /mnt/chroot </span></code></pre> <p>Download and extract the Alpine Linux rootfs:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>wget https://dl-cdn.alpinelinux.org/alpine/v3.12/releases/x86_64/alpine-minirootfs-3.12.3-x86_64.tar.gz </span><span>tar -xvpzf alpine-minirootfs-3.12.3-x86_64.tar.gz -C /mnt/chroot/ </span></code></pre> <p>Check <a href="https://alpinelinux.org/downloads/">https://alpinelinux.org/downloads/</a> for the latest version.</p> <p>Edit <code>/mnt/chroot/chroot.sh</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mount --rbind /dev /mnt/chroot/dev </span><span>mount --make-rslave /mnt/chroot/dev </span><span>mount -t proc /proc /mnt/chroot/proc </span><span>mount --rbind /sys /mnt/chroot/sys </span><span>mount --make-rslave /mnt/chroot/sys </span><span>mount --rbind /tmp /mnt/chroot/tmp </span><span>cp /etc/resolv.conf /mnt/chroot/etc/resolv.conf </span></code></pre> <p>Using this script you can easily <code>chroot</code> into the installation:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sh chroot.sh </span><span>chroot /mnt/chroot /bin/bash </span></code></pre> <p>Add the following lines to <code>/etc/apk/repositories</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@edge http://dl-cdn.alpinelinux.org/alpine/edge/main </span><span>@community http://dl-cdn.alpinelinux.org/alpine/edge/community </span><span>@testing http://dl-cdn.alpinelinux.org/alpine/edge/testing </span></code></pre> <p>Installing basic software:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add bash shadow openrc vim </span></code></pre> <p>Remove the <code>#</code> from the following line in <code>/etc/inittab</code> to enable the serial console at boot:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 </span></code></pre> <p>If you installed Alpine to a VPS or a container, then you may not need a boot loader, a kernel and the firmware to boot into Alpine. Otherwise, you can set up a Linux kernel:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add linux-firmware linux-lts grub grub-efi </span></code></pre> <p>Install GRUB:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>grub-install --target=x86_64-efi --efi-directory=/boot /dev/vda </span></code></pre> <p>Generate the GRUB config:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>grub-mkconfig -o /boot/grub/grub.cfg </span></code></pre> <h1 id="create-user">Create user</h1> <p>Create a <code>user</code> that you can use to log onto the server:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>useradd -mUG wheel user </span></code></pre> <p>Configure a password for the <code>user</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>passwd user </span></code></pre> <h1 id="setting-the-timezone">Setting the timezone</h1> <p>Install the <code>tzdata</code> package:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add tzdata </span></code></pre> <p>You can list the available timezones as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ls /usr/share/zoneinfo </span></code></pre> <p>Assuming you want to use the Europe/Amsterdam timezone, you can copy it as follow to the localtime file:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cp /usr/share/zoneinfo/Europe/Amsterdam /etc/localtime </span></code></pre> <p>Do also specify your timezone:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>echo &quot;Europe/Amsterdam&quot; &gt; /etc/timezone </span></code></pre> <p>You can check the current time and date:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>date </span></code></pre> <p>After configuring the timezone, you can remove the <code>tzdata</code> package:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk del tzdata </span></code></pre> <h1 id="setting-the-hostname">Setting the hostname</h1> <p>Edit <code>/etc/hostname</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>hostname </span></code></pre> <h1 id="networking">Networking</h1> <p>Edit <code>/etc/network/interfaces</code> and adjust the settings for IPv6 accordingly:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>auto lo </span><span>iface lo inet loopback </span><span> </span><span>auto eth0 </span><span>iface eth0 inet dhcp </span><span>iface eth0 inet6 static </span><span> address 2001:bc8:600:1236::1 </span><span> netmask 64 </span><span> gateway 2001:bc8:600:1236:: </span><span> pre-up echo 1 &gt; /proc/sys/net/ipv6/conf/eth0/accept_ra </span></code></pre> <p>Restart the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/networking restart </span></code></pre> <h1 id="firewall">Firewall</h1> <p>Install ufw:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add ip6tables ufw@testing </span></code></pre> <p>Block incoming traffic by default:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw default deny incoming </span><span>ufw default allow outgoing </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw enable </span><span>rc-update add ufw </span></code></pre> <p>Check the status of ufw:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw status </span></code></pre> <h1 id="ssh">SSH</h1> <p>Install OpenSSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add openssh-server </span></code></pre> <p>Edit <code>/etc/ssh/sshd_config</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>PasswordAuthentication no </span><span>AllowTcpForwarding yes </span></code></pre> <p>Open the SSH port, but limit the amount of possible traffic:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw limit ssh </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/sshd start </span><span>rc-update add sshd </span></code></pre> <p>Log in as <code>user</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>su user </span><span>mkdir .ssh </span><span>chmod 0700 </span></code></pre> <p>Upload your public key as <code>~/.ssh/authorized_keys</code>.</p> <h1 id="dns">DNS</h1> <p>Add an A-record and AAAA-record with your IPv4 and IPv6 addresses to your DNS records:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@ IN A ( &quot;163.172.149.186&quot; ); </span><span>@ IN AAAA ( &quot;2001:bc8:600:1236::1&quot; ); </span></code></pre> <p>Add a CNAME-record to your DNS record:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>* IN CNAME ( &quot;@&quot; ); </span></code></pre> <p>Finally, you also want to set up reverse DNS to map your IP addresses back to the hostname. This can typically be done through the provider of your server instance.</p> Setting up Gitea 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/gitea/ <h1 id="installation">Installation</h1> <p>Install Gitea:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add gitea </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Create the user and database:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>createuser -U postgres --pwprompt gitea </span><span>createdb -U postgres --owner=gitea gitea </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/gitea start </span><span>rc-update add gitea </span></code></pre> <p>Forward the port using SSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ssh -L 3000:localhost:3000 example.com </span></code></pre> <p>Point your browser to <a href="http://localhost:3000/">http://localhost:3000/</a> and configure Gitea.</p> <p>Edit <code>/etc/nginx/sites-available/01_git.example.com</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>server { </span><span> listen 80; </span><span> listen [::]:80; </span><span> server_name git.codentium.com; </span><span> </span><span> # Redirect HTTP traffic to HTTPS. </span><span> return 301 https://$host$request_uri; </span><span>} </span><span> </span><span>server { </span><span> listen 443 ssl; </span><span> listen [::]:443 ssl; </span><span> server_name git.codentium.com; </span><span>ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem # managed by Certbot </span><span>ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem # managed by Certbot </span><span> index index.html; </span><span> </span><span> # HTTP Strict Transport Security. </span><span> add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains&quot; always; </span><span> </span><span> location / { </span><span> proxy_pass http://localhost:3000; </span><span> } </span><span> </span><span> location /.well-known { </span><span> alias /var/www/example.com/htdocs/.well-known; </span><span> } </span><span>} </span></code></pre> <p>Create a symlink to enable the site:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ln -s /etc/nginx/sites-available/01_git.example.com /etc/nginx/sites-enabled/ </span></code></pre> <p>Reload the Nginx configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/nginx reload </span></code></pre> Setting up Let's Encrypt SSL 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/lets-encrypt/ <h1 id="installation">Installation</h1> <p>Install <code>certbot</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add certbot certbot-nginx </span></code></pre> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>certbot --nginx -d example.com -d www.example.com </span></code></pre> <h1 id="renewing-the-certificates">Renewing the Certificates</h1> <p>To test if the certificate renewal works, you can perform a dry run:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>certbot renew --dry-run </span></code></pre> <p>To renew the certificates, run the following:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>certbot renew </span></code></pre> <p>Add a DNS CAA record for your domain:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@ CAA 0 issue &quot;letsencrypt.org&quot; </span></code></pre> <h1 id="setting-up-nginx">Setting up Nginx</h1> <p>Generate the Diffie-Hellman parameters:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 </span></code></pre> <p>Edit <code>/etc/nginx/nginx.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>http { </span><span> include /etc/nginx/sites-enabled/*; </span><span> </span><span> ssl_protocols TLSv1.2 TLSv1.3; </span><span> ssl_ciphers !ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; </span><span> ssl_prefer_server_ciphers on; </span><span> ssl_session_cache shared:SSL:10m; </span><span> ssl_dhparam /etc/ssl/certs/dhparam.pem; </span><span> ssl_ecdh_curve secp384r1; </span><span>} </span></code></pre> <p>Edit your per site-configurations in <code>/etc/nginx/sites-available</code> to enable HTTPS:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>server { </span><span> listen 80; </span><span> listen [::]:80; </span><span> listen 443 ssl; </span><span> listen [::]:443 ssl; </span><span> server_name .example.com; </span><span>ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot </span><span>ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot </span></code></pre> <p>Allow TCP traffic on the HTTPS port (443):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow https </span></code></pre> <p>Reload the Nginx configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/nginx reload </span></code></pre> <h1 id="setting-up-postfix">Setting up Postfix</h1> <p>Edit <code>/etc/postfix/main.cf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>smtpd_use_tls = yes </span><span>smtpd_tls_key_file = /etc/letsencrypt/live/example.com/privkey.pem </span><span>smtpd_tls_cert_file = /etc/letsencrypt/live/example.com/fullchain.pem </span><span>smtpd_tls_security_level = may </span><span>smtpd_tls_auth_only = no </span><span>smtpd_tls_loglevel = 1 </span><span>smtpd_tls_received_header = yes </span><span>smtpd_tls_session_cache_timeout = 3600s </span><span>smtpd_tls_eecdh_grade = strong </span><span>smtpd_tls_protocols= !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 </span><span>smtpd_tls_mandatory_protocols= !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 </span><span>smtpd_tls_mandatory_ciphers = high </span><span>smtpd_tls_security_level=may </span><span>smtpd_tls_ciphers = high </span><span>tls_preempt_cipherlist = yes </span><span>smtpd_tls_mandatory_exclude_ciphers = aNULL, MD5 , DES, ADH, RC4, PSD, SRP, 3DES, eNULL </span><span>smtpd_tls_exclude_ciphers = aNULL, MD5 , DES, ADH, RC4, PSD, SRP, 3DES, eNULL </span><span>smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 </span><span>smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 </span><span>tls_random_source = dev:/dev/urandom </span></code></pre> <p>Edit <code>/etc/postfix/master.cf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>submission inet n - n - - smtpd </span><span> -o syslog_name=postfix/submission </span><span> -o smtpd_tls_security_level=encrypt </span><span> -o smtpd_sals_auth_enable=yes </span><span>smtps inet n - n - - smtpd </span><span> -o syslog_name=postfix/smtps </span><span> -o smtpd_tls_wrappermode=yes </span><span> -o smtpd_sasl_auth_enable=yes </span></code></pre> <p>Allow TCP traffic on the SMTPS and Submission ports (port 465 and 587):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow smtps </span><span>ufw allow 587/tcp </span></code></pre> <p>Reload the Postfix configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/postfix reload </span></code></pre> <p>Add the following DNS records:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>_submission._tcp SRV 587 smtp.example.com. </span></code></pre> <h1 id="setting-up-dovecot">Setting up Dovecot</h1> <p>Generate the Diffie-Hellman parameters:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>openssl dhparam -out /etc/dovecot/dh.pem 4096 </span></code></pre> <p>Edit <code>/etc/dovecot/conf.d/10-ssl.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ssl = required </span><span>ssl_cert = &lt;/etc/letsencrypt/live/example.com/fullchain.pem </span><span>ssl_key = &lt;/etc/letsencrypt/live/example.com/privkey.pem </span><span>ssl_dh = &lt;/etc/dovecot/dh.pem </span><span>ssl_min_protocol = TLSv1.2 </span><span>ssl_prefer_server_ciphers = yes </span></code></pre> <p>Allow TCP traffic on the IMAPS and POP3S ports (port 993 and 995):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow imaps </span><span>ufw allow pop3s </span></code></pre> <p>Reload the Dovecot configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/dovecot reload </span></code></pre> <p>Add the following DNS records:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>_imaps._tcp SRV 993 imap.example.com. </span><span>_pop3s._tcp SRV 995 pop3.example.com. </span></code></pre> <h1 id="testing">Testing</h1> <p>Use OpenSSL to test the connection:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>openssl s_client -connect imap -connect example.com:443 </span><span>openssl s_client -starttls smtp -connect mail.example.com:587 </span><span>openssl s_client -starttls imap -connect mail.example.com:143 </span><span>openssl s_client -connect mail.example.com:993 </span></code></pre> Setting up Murmur 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/murmur/ <h1 id="installation">Installation</h1> <p>Install Murmur:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add murmur </span></code></pre> <p>Edit <code>/etc/murmur.ini</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sslCert=/etc/letsencrypt/live/example.com/fullchain.pem </span><span>sslKey=/etc/letsencrypt/live/example.com/privkey.pem </span></code></pre> <p>Allow TCP traffic on port 64738:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow 64738 </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/murmur start </span><span>rc-update add murmur </span></code></pre> <p>Add the following DNS record:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>_mumble._tcp SRV 64738 mumble.example.com. </span></code></pre> Setting up HTTP Strict Transport Security 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/nginx-hsts/ <h1 id="configuration">Configuration</h1> <p>Set up <a href="../lets-encrypt">Let's Encrypt</a> first.</p> <p>Edit <code>/etc/nginx/sites-available/02_example.com</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>server { </span><span> listen 80; </span><span> listen [::]:80; </span><span> server_name .example.com; </span><span> </span><span> # Redirect HTTP traffic to HTTPS. </span><span> return 301 https://$host$request_uri; </span><span>} </span><span> </span><span>server { </span><span> listen 443 ssl; </span><span> listen [::]:443 ssl; </span><span> server_name .example.com; </span><span>ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot </span><span>ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot </span><span> </span><span> add_header Strict-Transport-Security &quot;max-age=31536000; includeSubDomains&quot;; </span><span>} </span></code></pre> <h1 id="references">References</h1> <ol> <li><a href="https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/">https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/</a></li> </ol> Setting up PHP 7 with Nginx 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/nginx-php/ <h1 id="installation">Installation</h1> <p>Install PHP 7:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add php7-fpm php7-mcrypt php7-soap php7-openssl php7-gmp php7-pdo_odbc php7-json php7-dom php7-pdo php7-zip php7-mysqli php7-sqlite3 php7-apcu php7-pdo_pgsql php7-bcmath php7-gd php7-odbc php7-pdo_mysql php7-pdo_sqlite php7-gettext php7-xmlreader php7-xmlrpc php7-bz2 php7-iconv php7-pdo_dblib php7-curl php7-ctype </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Edit <code>/etc/php7/php.ini</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[Date] </span><span>date.timezone = Europe/Amsterdam </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/php-fpm7 start </span><span>rc-update add php-fpm7 </span></code></pre> <p>Edit <code>/etc/nginx/sites-available/02_localhost</code> to add in the following within <code>server</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>server { </span><span> location ~\.php$ { </span><span> fastcgi_split_path_info ^(.+\.php)(.*)$; </span><span> include fastcgi.conf; </span><span> fastcgi_pass 127.0.0.1:9000; </span><span> } </span><span>} </span></code></pre> <p>Edit <code>/var/www/localhost/htdocs/version.php</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>&lt;?php </span><span> phpinfo(); </span><span>?&gt; </span></code></pre> <p>Reload the Nginx configuration:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/nginx reload </span></code></pre> Setting up uWSGI 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/nginx-uwsgi/ <h1 id="installation">Installation</h1> <p>Install uWSGI:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add python3 py3-pip py3-virtualenv uwsgi uwsgi-python3 </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/uwsgi start </span><span>rc-update add uwsgi </span></code></pre> <h1 id="per-application-setup">Per-Application Setup</h1> <p>Add a user for the Python application:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>useradd -m flask </span></code></pre> <p>Log in as the user:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>su flask </span><span>cd ~ </span></code></pre> <p>Create the virtual environment for the application:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>virtualenv env </span></code></pre> <p>Activate the virtual environment:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>source env/bin/activate </span></code></pre> <p>Install Flask;</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>pip install flask </span></code></pre> <p>Edit <code>~/app.py</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>from flask import Flask </span><span>app = Flask(__name__) </span><span> </span><span>@app.route(&#39;/&#39;) </span><span>def hello_world(): </span><span> return &#39;Hello, World!&#39; </span></code></pre> <p>Deactivate the virtual environment:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>deactivate </span></code></pre> <p>Press Ctrl+D or type <code>exit</code> to log out of the flask user.</p> <p>To test our uWSGI installation, we can first run <code>uwsgi</code> from the command line:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>uwsgi --socket 127.0.0.1:5000 --protocol=http --plugins python --uid flask --chdir /home/flask --virtualenv /home/flask/env -w app:app </span></code></pre> <p>Forward the port using SSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ssh -L 5000:localhost:5000 example.com </span></code></pre> <p>Point your browser to <a href="http://localhost:5000">http://localhost:5000</a> to test the Python application.</p> <h1 id="uwsgi-emperor">uWSGI Emperor</h1> <p>Create a directory for the uWSGI application sockets:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir /var/run/uwsgi-apps </span><span>chown -hR uwsgi: /var/run/uwsgi-apps </span></code></pre> <p>Edit <code>/etc/uwsgi/conf.d/flask</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>[uwsgi] </span><span>uid = flask </span><span>gid = flask </span><span>chown-socket = nginx </span><span>socket = /var/run/uwsgi-apps/flask.sock </span><span>chdir = /home/flask/app </span><span>virtualenv = /home/flask/app </span><span>module = app:app </span><span>plugin = python </span></code></pre> <p>Correct the permissions for /etc/uwsgi:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>chown -R uwsgi: /etc/uwsgi </span></code></pre> <p>Reload uWSGI emperor:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/uwsgi reload </span></code></pre> <h1 id="nginx">Nginx</h1> <p>If you want to add the application to a subdirectory, edit your configuration as follows:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span> location /flask { </span><span> try_files $uri @flask; </span><span> } </span><span> </span><span> location @flask { </span><span> rewrite /foo/(.*)$ /$1 break; </span><span> include uwsgi_params; </span><span> uwsgi_pass unix:/var/run/uwsgi-apps/flask.sock; </span><span> } </span></code></pre> <p>Otherwise, you can also host your application as part of a (sub)domain:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span> location / { </span><span> try_files $uri @flask; </span><span> } </span><span> </span><span> location @flask { </span><span> include uwsgi_params; </span><span> uwsgi_pass unix:/var/run/uwsgi-apps/flask.sock; </span><span> } </span></code></pre> Setting up nginx 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/nginx/ <h1 id="installation">Installation</h1> <p>Install <code>nginx</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add nginx </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Edit <code>/etc/nginx/nginx.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>user nginx nginx; </span><span>worker_processes 1; </span><span> </span><span>error_log /var/log/nginx/error_log info; </span><span> </span><span>events { </span><span> worker_connections 1024; </span><span> use epoll; </span><span>} </span><span> </span><span>http { </span><span> include /etc/nginx/mime.types; </span><span> default_type application/octet-stream; </span><span> </span><span> log_format main </span><span> &#39;$remote_addr - $remote_user [$time_local] &#39; </span><span> &#39;&quot;$request&quot; $status $bytes_sent &#39; </span><span> &#39;&quot;$http_referer&quot; &quot;$http_user_agent&quot; &#39; </span><span> &#39;&quot;$gzip_ratio&quot;&#39;; </span><span> </span><span> client_header_timeout 10m; </span><span> client_body_timeout 10m; </span><span> client_max_body_size 50m; </span><span> send_timeout 10m; </span><span> </span><span> connection_pool_size 256; </span><span> client_header_buffer_size 1k; </span><span> large_client_header_buffers 4 2k; </span><span> request_pool_size 4k; </span><span> </span><span> gzip on; </span><span> gzip_min_length 1100; </span><span> gzip_buffers 4 8k; </span><span> gzip_types text/plain; </span><span> </span><span> output_buffers 1 32k; </span><span> postpone_output 1460; </span><span> </span><span> sendfile on; </span><span> tcp_nopush on; </span><span> tcp_nodelay on; </span><span> </span><span> keepalive_timeout 75 20; </span><span> </span><span> ignore_invalid_headers on; </span><span> </span><span> include /etc/nginx/sites-enabled/*; </span><span>} </span></code></pre> <p>Create the directories for the per-site configurations:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p /etc/nginx/sites-available </span><span>mkdir -p /etc/nginx/sites-enabled </span></code></pre> <p>Edit <code>/etc/nginx/sites-available/02_localhost</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>server { </span><span> listen 8000; </span><span> listen [::]:8000; </span><span> server_name localhost; </span><span> </span><span> root /var/www/localhost/htdocs; </span><span> index index.html index.htm; </span><span> error_page 404 /404.htm; </span><span> </span><span> location / { </span><span> } </span><span> </span><span> location ~* &quot;.(htm)$&quot; { </span><span> try_files $uri &quot;${uri}l&quot; =404; </span><span> } </span><span>} </span></code></pre> <p>Edit <code>/etc/nginx/sites-available/02_example.com</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>server { </span><span> listen 80; </span><span> listen [::]:80; </span><span> server_name .example.com; </span><span> </span><span> root /var/www/example.com/htdocs; </span><span> index index.html index.htm; </span><span> error_page 404 /404.htm; </span><span> </span><span> location / { </span><span> } </span><span> </span><span> location ~* &quot;.(htm)$&quot; { </span><span> try_files &quot;${uri}l&quot; =404; </span><span> } </span><span>} </span></code></pre> <p>Create the symlinks to enable the per-site configurations:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ln -s /etc/nginx/sites-available/02_localhost /etc/nginx/sites-enabled/ </span><span>ln -s /etc/nginx/sites-available/02_example.com /etc/nginx/sites-enabled/ </span></code></pre> <p>Create the directories:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p /var/www/localhost/htdocs </span><span>mkdir -p /var/www/example.com/htdocs </span></code></pre> <p>Edit <code>/var/www/localhost/htdocs/index.htm</code> and <code>/var/www/example.com/htdocs/index.htm</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>&lt;html&gt; </span><span> &lt;head&gt; </span><span> &lt;title&gt;Test&lt;/title&gt; </span><span> &lt;/head&gt; </span><span> </span><span> &lt;body&gt; </span><span> &lt;p&gt;Hello, world!&lt;/p&gt; </span><span> &lt;/body&gt; </span><span>&lt;/html&gt; </span></code></pre> <p>Allow TCP traffic on the HTTP port (80):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow http </span></code></pre> <p>Check whether the Nginx configuration is valid:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>nginx -t </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/nginx start </span><span>rc-update add nginx </span></code></pre> <p>Check if Nginx is listening on the HTTP port (80):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>netstat -anp | grep nginx </span></code></pre> <p>You should now be able to access your website from <a href="http://example.com">http://example.com</a>. In addition, you can forward the port 8000 over SSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ssh -L 8000:localhost:8000 example.com </span></code></pre> <p>Then you can point your browser to <a href="http://localhost:8000">http://localhost:8000</a>.</p> Setting up OpenVPN 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/openvpn/ <h1 id="installation">Installation</h1> <p>Install OpenVPN:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add openvpn </span></code></pre> <h1 id="public-key-infrastructure">Public Key Infrastructure</h1> <p>Install EasyRSA:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add easy-rsa </span></code></pre> <p>Copy the template at <code>/usr/share/easy-rsa</code> to its own directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cp -a /usr/share/easy-rsa ~/certs </span><span>cd ~/certs </span><span>mv vars.example vars </span></code></pre> <p>Edit <code>~/certs/vars</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>set_var EASYRSA_DN &quot;org&quot; </span><span>set_var EASYRSA_REQ_COUNTRY &quot;US&quot; </span><span>set_var EASYRSA_REQ_PROVINCE &quot;CA&quot; </span><span>set_var EASYRSA_REQ_CITY &quot;SanFrancisco&quot; </span><span>set_var EASYRSA_REQ_ORG &quot;YourOrganization&quot; </span><span>set_var EASYRSA_REQ_EMAIL &quot;[email protected]&quot; </span></code></pre> <p>Set up the Public Key Infrastructure:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./easyrsa init-pki </span></code></pre> <p>Create the CA certificate:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./easyrsa build-ca </span></code></pre> <p>Generate the server certificate request and key:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./easyrsa gen-req games nopass </span></code></pre> <p>Sign the server certificate request:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./easyrsa sign-req server games </span></code></pre> <p>Generate the Diffie-Hellman parameters:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./easyrsa gen-dh </span></code></pre> <p>Generate the scret Hash-based message Authentication Code (HMAC):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./easyrsa --genkey --secret pki/ta.key </span></code></pre> <p>Upload the following files:</p> <ul> <li><code>~/certs/pki/ca.crt</code> =&gt; <code>/etc/openvpn/games/ca.crt</code></li> <li><code>~/certs/pki/dh.pem</code> =&gt; <code>/etc/openvpn/games/dh.crt</code></li> <li><code>~/certs/pki/ta.key</code> =&gt; <code>/etc/openvpn/games/ta.key</code></li> <li><code>~/certs/pki/issued/games.crt</code> =&gt; <code>/etc/openvpn/games/games.crt</code></li> <li><code>~/certs/pki/private/games.key</code> =&gt; <code>/etc/openvpn/games/games.key</code></li> </ul> <h1 id="configuration">Configuration</h1> <p>Edit <code>/etc/openvpn/games.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>port 1194 </span><span>proto udp </span><span>proto udp6 </span><span>dev tap </span><span> </span><span>user nobody </span><span>group nobody </span><span> </span><span>ca /etc/openvpn/games/ca.crt </span><span>cert /etc/openvpn/games/games.crt </span><span>key /etc/openvpn/games/games.key </span><span>dh /etc/openvpn/games/dh.pem </span><span>tls-auth /etc/openvpn/games/ta.key 0 </span><span> </span><span>server 10.42.42.0 255.255.255.0 </span><span>client-to-client </span><span>push &quot;route 10.42.42.0 255.255.255.0&quot; </span><span>push &quot;route-metric 512&quot; </span><span>push &quot;route 0.0.0.0 0.0.0.0&quot; </span><span>topology subnet </span><span> </span><span>persist-key </span><span>ifconfig-pool-persist games-ips.txt </span><span> </span><span>keepalive 10 120 </span><span>comp-lzo </span><span> </span><span>status /var/log/openvpn/games-status.log </span><span>log /var/log/openvpn/games.log </span><span>verb 4 </span></code></pre> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p /var/log/openvpn </span><span>chown -hR openvpn: /var/log/openvpn </span></code></pre> <p>Create the symlink for a game-specific service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ln -s /etc/init.d/openvpn /etc/init.d/openvpn.games </span></code></pre> <p>Allow TCP traffic on port 1194:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow 1194 </span></code></pre> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/openvpn.games start </span><span>rc-update add openvpn.games </span></code></pre> <h1 id="issuing-client-certificates">Issuing Client Certificates</h1> <p>Create a private key, create a certificate request and sign it for the client (replace client with a unique identifier for the client):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>./easyrsa build-client-full client nopass </span></code></pre> <p>Edit <code>games.ovpn</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>client </span><span>dev tap </span><span>proto udp </span><span> </span><span>remote example.com 1194 </span><span> </span><span>resolv-retry 30 </span><span>nobind </span><span> </span><span>persist-key </span><span> </span><span>ca ca.crt </span><span>cert client.crt </span><span>key client.key </span><span>tls-auth ta.key 1 </span><span> </span><span>comp-lzo </span><span>status /var/log/openvpn/games-status.log </span><span>log /var/log/openvpn/games.log </span><span>verb 3 </span></code></pre> <p>Provide the user with the following files:</p> <ul> <li><code>~/certs/psk/ca.crt</code></li> <li><code>~/certs/psk/ta.key</code></li> <li><code>~/certs/psk/private/client.key</code></li> <li><code>~/certs/psk/issued/client.crt</code></li> </ul> <p>You can then use OpenVPN to open the <code>games.ovpn</code> directly.</p> <p>On Linux, it should also be possible to use the <code>games.ovpn</code> as <code>/etc/openvpn/games.conf</code>. Adjust the paths to the keys accordingly (e.g. by storing them in <code>/etc/openvpn/games/</code>). Then create symlink and run the OpenVPN service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ln -s /etc/init.d/openvpn /etc/init.d/openvpn.games </span><span>/etc/init.d/openvpn.games start </span><span>rc-update add openvpn.games </span></code></pre> <h1 id="game-discovery">Game Discovery</h1> <p>One of the problems that many games have is that when you try to find LAN games, that they will end up using the wrong network interface by default. To fix this, the VPN network interface has to be priortised over the default one. Go to Network Connections from Networking and Sharing Center. There you should see a VPN Network TAN-Windows Adapter V9 interface. Right-click it and select &quot;Properties&quot;, you should be presented with the following window:</p> <center> <figure> <p><img src="https://codentium.com/guides/alpine/openvpn/openvpn1.png" alt="VPN Properties window" /></p> <figcaption> <p><strong>Figure 1:</strong> The VPN Properties window for the VPN Network TAN-Windows Adapter V9 interface.</p> </figcaption> </figure> </center> <p>Select &quot;Internet Protocol Version 4 (TCP/IPv4)&quot; and click on &quot;Properties&quot;:</p> <center> <figure> <p><img src="https://codentium.com/guides/alpine/openvpn/openvpn2.png" alt="Internet Protocol Version 4 Properties" /></p> <figcaption> <p><strong>Figure 2:</strong> The Internet Protocol Version 4 Properties window.</p> </figcaption> </figure> </center> <p>Click on &quot;Advanced&quot;:</p> <center> <figure> <p><img src="https://codentium.com/guides/alpine/openvpn/openvpn3.png" alt="Advanced TCP/IP Settings" /></p> <figcaption> <p><strong>Figure 3:</strong> The Advanced TCP/IP Settings window.</p> </figcaption> </figure> </center> <p>Set the interface metric from automatic to manual and input 5 as its value and close the windows. Now you should be able to find your friend's hosted game using LAN discovery.</p> Setting up Postfix 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/postfix/ <h1 id="installation">Installation</h1> <p>Install <code>postfix</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add postfix postfix-pgsql postfix-pcre </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Create the mail directory and assign vmail as the owner:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p /var/mail/domains </span><span>chown -R vmail:postdrop /var/mail/domains </span></code></pre> <p>Get the uid and gid of the <code>vmail</code> user (in our case the uid is 105 and the gid is 107):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>grep vmail /etc/passwd </span></code></pre> <p>Edit <code>/etc/postfix/main.cf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>inet_protocols = ipv4 ipv6 </span><span> </span><span>myhostname = example.com </span><span>mydomain = example.com </span><span>relayhost = </span><span>mynetworks = 127.0.0.0/8 </span><span>mydestination = </span><span> </span><span>mydestination = localhost.$mydomain, localhost </span><span>mynetworks_style = subnet </span><span>mynetworks = 127.0.0.0/8 </span><span> </span><span>virtual_mailbox_domains = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_domains_maps.cf </span><span>virtual_alias_maps = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_maps.cf, </span><span> proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_maps.cf, </span><span> proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_catchall_maps.cf </span><span> </span><span>virtual_mailbox_maps = proxy:pgsql:/etc/postfix/sql/pgsql_virtual_mailbox_maps.cf, </span><span> proxy:pgsql:/etc/postfix/sql/pgsql_virtual_alias_domain_mailbox_maps.cf </span><span> </span><span>virtual_mailbox_base = /var/mail/domains/ </span><span>virtual_gid_maps = static:107 </span><span>virtual_uid_maps = static:105 </span><span>virtual_minimum_uid = 100 </span><span>virtual_transport = lmtp:unix:private/dovecot-lmtp </span><span> </span><span>mailbox_transport = virtual </span><span>local_transport = virtual </span><span>local_transport_maps = $virtual_mailbox_maps </span><span> </span><span>smtpd_helo_required = yes </span><span>disable_vrfy_command = yes </span><span> </span><span>message_size_limit = 104857600 </span><span>virtual_mailbox_limit = 104857600 </span><span>queue_minfree = 51200000 </span><span> </span><span>smtpd_sender_restrictions = </span><span> permit_mynetworks, </span><span> reject_non_fqdn_sender, </span><span> reject_unknown_sender_domain </span><span> </span><span>smtpd_recipient_restrictions = </span><span> reject_non_fqdn_recipient, </span><span> reject_unknown_recipient_domain, </span><span> permit_mynetworks, </span><span> permit_sasl_authenticated, </span><span> reject_unauth_destination, </span><span> reject_rbl_client dnsbl.sorbs.net, </span><span> reject_rbl_client zen.spamhaus.org, </span><span> reject_rbl_client bl.spamcop.net </span><span> </span><span>smtpd_data_restrictions = reject_unauth_pipelining </span><span> </span><span>smtputf8_enable = no </span><span> </span><span>broken_sasl_auth_clients = no </span><span>smtpd_sasl_type = dovecot </span><span>smtpd_sasl_path = private/auth </span><span>smtpd_sasl_auth_enable = yes </span><span>smtpd_sasl_security_options = noanonymous </span><span>smtpd_sasl_local_domain = </span><span>smtpd_sasl_authenticated_header = no </span></code></pre> <p>Adjust the password accordingly and run the following sequence of commands to create the PostgreSQL scripts for Postfix to access the Postfix database:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cd /etc/postfix </span><span>mkdir sql </span><span>PASSWORD=&quot;ChangeMe&quot; </span><span> </span><span>cat - &lt;&lt;EOF &gt;sql/pgsql_virtual_alias_domain_catchall_maps.cf </span><span>user = postfix </span><span>password = $PASSWORD </span><span>hosts = localhost </span><span>dbname = postfix </span><span>query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = &#39;%d&#39; AND alias.address = &#39;@&#39; || alias_domain.target_domain AND alias.active = true AND alias_domain.active = true </span><span>EOF </span><span> </span><span>cat - &lt;&lt;EOF &gt;sql/pgsql_virtual_alias_domain_mailbox_maps.cf </span><span>user = postfix </span><span>password = $PASSWORD </span><span>hosts = localhost </span><span>dbname = postfix </span><span>query = SELECT maildir FROM mailbox,alias_domain WHERE alias_domain.alias_domain = &#39;%d&#39; AND mailbox.username = &#39;%u&#39; || &#39;@&#39; || alias_domain.target_domain AND mailbox.active = true AND alias_domain.active </span><span>EOF </span><span> </span><span>cat - &lt;&lt;EOF &gt;sql/pgsql_virtual_alias_domain_maps.cf </span><span>user = postfix </span><span>password = $PASSWORD </span><span>hosts = localhost </span><span>dbname = postfix </span><span>query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = &#39;%d&#39; AND alias.address = &#39;%u&#39; || &#39;@&#39; || alias_domain.target_domain AND alias.active = true AND alias_domain.active = true </span><span>EOF </span><span> </span><span>cat - &lt;&lt;EOF &gt;sql/pgsql_virtual_alias_maps.cf </span><span>user = postfix </span><span>password = $PASSWORD </span><span>hosts = localhost </span><span>dbname = postfix </span><span>query = SELECT goto FROM alias WHERE address = &#39;%s&#39; AND active = &#39;1&#39; </span><span>EOF </span><span> </span><span>cat - &lt;&lt;EOF &gt;sql/pgsql_virtual_domains_maps.cf </span><span>user = postfix </span><span>password = $PASSWORD </span><span>hosts = localhost </span><span>dbname = postfix </span><span>query = SELECT domain FROM domain WHERE domain = &#39;%s&#39; AND active=&#39;1&#39; </span><span>EOF </span><span> </span><span>cat - &lt;&lt;EOF &gt;sql/pgsql_virtual_mailbox_maps.cf </span><span>user = postfix </span><span>password = $PASSWORD </span><span>hosts = localhost </span><span>dbname = postfix </span><span>query = SELECT maildir FROM mailbox WHERE username = &#39;%s&#39; AND active = true </span><span>EOF </span><span> </span><span>chown -R postfix:postfix sql </span><span>chmod 640 sql/* </span></code></pre> <p>Allow TCP traffic on the SMTP port (port 25):</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ufw allow smtp </span></code></pre> <p>Check whether the Postfix configuration is valid:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>postconf -n </span></code></pre> <p>Start the Postfix service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>newaliases </span><span>/etc/init.d/postfix start </span><span>rc-update add postfix </span></code></pre> <h1 id="configuring-dns">Configuring DNS</h1> <p>Add the following DNS records:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@ MX 10 mail.example.com. </span></code></pre> Setting up Postfixadmin 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/postfixadmin/ <h1 id="installation">Installation</h1> <p>Install <code>postfixadmin</code>, <code>dovecot</code> (necessary for the SHA512-CRYPT) and the necessary PHP extensions:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add postfixadmin dovecot php-pgsql php-imap php-mbstring php-session </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Create the <code>/usr/share/webapps/postfixadmin/templates_c</code> directory:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p /usr/share/webapps/postfixadmin/templates_c </span><span>chown www: /usr/share/webapps/postfixadmin/templates_c </span></code></pre> <p>Copy <code>/usr/share/webapps/postfixadmin/config.inc.php</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>cp /etc/postfixadmin/config.inc.php /usr/share/webapps/postfixadmin/config.local.php </span></code></pre> <p>Edit <code>/usr/share/webapps/postfixadmin/config.local.php</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>$CONF[&#39;configured&#39;] = true; </span><span>$CONF[&#39;setup_password&#39;] = &quot;&quot;; // Don&#39;t change this yet. </span><span>$CONF[&#39;database_type&#39;] = &#39;pgsql&#39;; </span><span>$CONF[&#39;database_host&#39;] = &#39;localhost&#39;; </span><span>$CONF[&#39;database_user&#39;] = &#39;postfixadmin&#39;; </span><span>$CONF[&#39;database_password&#39;] = &#39;password&#39;; // Change this to your password. </span><span>$CONF[&#39;database_name&#39;] = &#39;postfix&#39;; </span><span>$CONF[&#39;database_prefix&#39;] = &quot;&quot;; </span><span>$CONF[&#39;admin_email&#39;] = &#39;[email protected]&#39;; // Replace with your e-mail address. </span><span>$CONF[&#39;encrypt&#39;] = &#39;dovecot:SHA512-CRYPT&#39;; </span><span>$CONF[&#39;authlib_default_flavor&#39;] = &#39;SHA&#39;; </span><span>$CONF[&#39;dovecotpw&#39;] = &quot;/usr/bin/doveadm pw&quot;; </span><span>$CONF[&#39;domain_path&#39;] = &#39;YES&#39;; </span><span>$CONF[&#39;domain_in_mailbox&#39;] = &#39;NO&#39;; </span><span>$CONF[&#39;aliases&#39;] = &#39;10&#39;; </span><span>$CONF[&#39;mailboxes&#39;] = &#39;10&#39;; </span><span>$CONF[&#39;maxquota&#39;] = &#39;10&#39;; </span><span>$CONF[&#39;quota&#39;] = &#39;YES&#39;; </span><span>$CONF[&#39;quota_multiplier&#39;] = &#39;1024000&#39;; </span><span>$CONF[&#39;vacation&#39;] = &#39;NO&#39;; </span><span>$CONF[&#39;vacation_control&#39;] =&#39;NO&#39;; </span><span>$CONF[&#39;vacation_control_admin&#39;] = &#39;NO&#39;; </span><span>$CONF[&#39;alias_control&#39;] = &#39;YES&#39;; </span><span>$CONF[&#39;alias_control_admin&#39;] = &#39;YES&#39;; </span><span>$CONF[&#39;special_alias_control&#39;] = &#39;YES&#39;; </span><span>$CONF[&#39;fetchmail&#39;] = &#39;NO&#39;; </span><span>$CONF[&#39;user_footer_link&#39;] = &quot;http://localhost:8000/postfixadmin&quot;; </span><span>$CONF[&#39;footer_link&#39;] = &#39;http://localhost:8000/postfixadmin/main.php&#39;; </span><span>$CONF[&#39;create_mailbox_subdirs_prefix&#39;]=&quot;&quot;; </span><span>$CONF[&#39;used_quotas&#39;] = &#39;YES&#39;; </span><span>$CONF[&#39;new_quota_table&#39;] = &#39;YES&#39;; </span></code></pre> <p>Replace <code>change-this-to-your-domain.tld</code> to your domain name:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sed -i -e &#39;s/change-this-to-your.domain.tld/example.com/g&#39; /usr/share/webapps/postfixadmin/config.local.php </span></code></pre> <p>Add the following to <code>/etc/nginx/sites-available/02_localhost</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span> location /postfixadmin { </span><span> alias /usr/share/webapps/postfixadmin/public; </span><span> } </span><span> </span><span> location ~ \.php$ { </span><span> location ~ ^/postfixadmin/ { </span><span> alias /usr/share/webapps/postfixadmin/public; </span><span> fastcgi_split_path_info ^/postfixadmin(.+?\.php)(.*)$; </span><span> include fastcgi.conf; </span><span> fastcgi_pass 127.0.0.1:9000; </span><span> } </span><span> </span><span> fastcgi_split_path_info ^(.+?\.php)(.*)$; </span><span> include fastcgi.conf; </span><span> fastcgi_pass 127.0.0.1:9000; </span><span> } </span></code></pre> <p>Forward the port using SSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ssh -L 8000:localhost:8000 example.com </span></code></pre> <p>Point your browser to <a href="http://localhost:8000/postfixadmin/setup.php">http://localhost:8000/postfixadmin/setup.php</a> and create the password hash. Then add it to <code>/usr/share/webapps/postfixadmin/config.local.php</code>. Go back to <a href="http://localhost:8000/postfixadmin/setup.php">http://localhost:8000/postfixadmin/setup.php</a> and create the superadmin account.</p> Setting up PostgreSQL 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/postgresql/ <h1 id="installation">Installation</h1> <p>Install PostgreSQL:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add postgresql postgresql-client </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Start the service:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/postgresql setup </span><span>/etc/init.d/postgresql start </span><span>rc-update add postgresql </span></code></pre> <p>Create the PostgreSQL users and database for the Postfix database:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>createuser -U postgres --pwprompt postfixadmin </span><span>createuser -U postgres --pwprompt postfix </span><span>createdb -U postgres --owner=postfixadmin postfix </span></code></pre> <p>Grant the <code>postfix</code> user access to the database:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>psql -U postfixadmin postfix </span><span>GRANT SELECT ON alias TO postfix; </span><span>GRANT SELECT ON domain TO postfix; </span><span>GRANT SELECT ON mailbox TO postfix; </span><span>GRANT SELECT on alias_domain TO postfix; </span><span>\q </span></code></pre> Setting up rspamd 2020-12-29T00:00:00+00:00 2020-12-29T00:00:00+00:00 https://codentium.com/guides/alpine/rspamd/ <h1 id="installation">Installation</h1> <p>Install <code>redis</code> and <code>rspamd</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>apk add redis rspamd rspamd-client rspamd-controller rspamd-fuzzy rspamd-proxy </span></code></pre> <h1 id="configuration">Configuration</h1> <p>Run the rspamd configuration wizard:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>rspamadm configwizard </span></code></pre> <p>Set the WebUI controller password, set up Redis with the defaults and set up the DKIM signing feature.</p> <p>In terms of DKIM, select option 1. Then press enter until it asks for a domain to sign. Now you can specify a domain, use the default selector, create a private key for each domain you want to add.</p> <p>Start the services:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/redis start </span><span>/etc/init.d/rspamd start </span><span>rc-update add redis </span><span>rc-update add rspamd </span></code></pre> <p>Check if the rspamd services are listening:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>netstat -anp | grep rspamd </span></code></pre> <p>Add the milter to <code>/etc/postfix/main.cf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>milter_protocol = 6 </span><span>milter_default_action = accept </span><span>smtpd_milters = inet:127.0.0.1:11332 </span><span>non_smtpd_milters = $smtpd_milters </span></code></pre> <p>Add <code>-o smtpd_milters=</code> to <code>/etc/postfix/master.cf</code> to prevent mail from being routed twice to rspamd:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span># For injecting mail back into postfix from the filter </span><span>127.0.0.1:10026 inet n - n - 16 smtpd </span><span> -o content_filter= </span><span> -o smtpd_milters= </span><span> -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks </span><span> -o smtpd_helo_restrictions= </span><span> -o smtpd_client_restrictions= </span><span> -o smtpd_sender_restrictions= </span><span> -o smtpd_recipient_restrictions=permit_mynetworks,reject </span><span> -o mynetworks_style=host </span><span> -o smtpd_authorized_xforward_hosts=127.0.0.0/8 </span></code></pre> <p>Reload the Postfix configuration to enable the milters:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/postfix reload </span></code></pre> <p>Configure the DNS records:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>@ TXT v=spf1 a mx mx:example.com -all </span><span>_adsp._domainkey TXT dkim=all </span><span>_dmarc TXT v=DMARC1; p=reject; sp=reject; rua=mailto:[email protected]; aspf=s; adkim=s; </span></code></pre> <p>Also add the DKIM records that can be found in <code>/var/lib/rspamd/dkim/example.com.dkim.key.pub</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>dkim._domainkey TXT v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLaiAmj5xUQ6s4AlEhwHwnW3JqNc0LZH2SEMZ9y7qIk+C7EvplDYkLf8tG6iVFSb1+ouPCESgRza6/sM4BZYdIYB5SUkM5bn+CqpTtBEWUPvaGawzqer+on1/+Y9pXFKgV3O8WaG223w+THrvfCj9g0FsRKff6lfWekQr2+8G70wIDAQAB </span></code></pre> <h1 id="dovecot-sieve">Dovecot Sieve</h1> <p>Now that rspamd is running, we will use Dovecot's sieving functionality to automatically move any mail marked as spam by rspamd to the spam folder. In addition, we will add two sieve scripts that monitor any transactions moving mail from or to the spam folder to use that mail to train rspamd </p> <p>Enable the imap_sieve plugin for the imap protocol in <code>/etc/dovecot/conf.d/20-imap.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>protocol imap { </span><span> # Space separated list of plugins to load (default is global mail_plugins). </span><span> mail_plugins = $mail_plugins imap_sieve </span><span> </span><span> ... </span><span>} </span></code></pre> <p>Add or edit the following options in <code>/etc/dovecot/conf.d/90-sieve.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span> sieve_before = /var/mail/sieve/global/spam-global.sieve </span><span> sieve_pipe_bin_dir = /usr/bin </span><span> sieve_global_extensions = +vnd.dovecot.pipe </span><span> sieve_plugins = sieve_imapsieve sieve_extprograms </span></code></pre> <p>Reload the Dovecot configuration to enable the Sieve extensions:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/dovecot reload </span></code></pre> <p>Create the directory for the sieve scripts:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>mkdir -p /var/mail/sieve/global </span></code></pre> <p>Edit <code>/var/mail/sieve/global/spam-global.sieve</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>require [&quot;fileinto&quot;, &quot;mailbox&quot;]; </span><span> </span><span>if anyof( </span><span> header :contains [&quot;X-Spam-Flag&quot;] &quot;YES&quot;, </span><span> header :contains [&quot;X-Spam&quot;] &quot;Yes&quot;, </span><span> header :contains [&quot;Subject&quot;] &quot;*** SPAM ***&quot; </span><span>) { </span><span> fileinto :create &quot;Spam&quot;; </span><span> stop; </span><span>} </span></code></pre> <p>Edit <code>/var/mail/sieve/global/learn-spam.sieve</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>require [&quot;vnd.dovecot.pipe&quot;, &quot;copy&quot;, &quot;imapsieve&quot;]; </span><span>pipe :copy &quot;rspamc&quot; [&quot;learn_spam&quot;]; </span></code></pre> <p>Edit <code>/var/mail/sieve/global/learn-ham.sieve</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>require [&quot;vnd.dovecot.pipe&quot;, &quot;copy&quot;, &quot;imapsieve&quot;]; </span><span>pipe :copy &quot;rspamc&quot; [&quot;learn_ham&quot;]; </span></code></pre> <p>Compile the scripts:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>sievec /var/mail/sieve/global/spam-global.sieve </span><span>sievec /var/mail/sieve/global/learn-spam.sieve </span><span>sievec /var/mail/sieve/global/learn-ham.sieve </span></code></pre> <p>Correct the permissions:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>chown -hR vmail: /var/mail/sieve </span></code></pre> <p>Add the following to <code>/etc/dovecot/conf.d/90-sieve.conf</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span> # Learn about spam when mail is moved from any mailbox into spam. </span><span> imapsieve_mailbox1_name = Spam </span><span> imapsieve_mailbox1_causes = COPY </span><span> imapsieve_mailbox1_before = file:/var/mail/sieve/global/learn-spam.sieve </span><span> </span><span> # Learn aboout ham when mail is moved from spam into any mailbox. </span><span> imapsieve_mailbox2_name = * </span><span> imapsieve_mailbox2_from = Spam </span><span> imapsieve_mailbox2_causes = COPY </span><span> imapsieve_mailbox2_before = file:/var/mail/sieve/global/learn-ham.sieve </span></code></pre> <p>Reload the Dovecot configuration to enable the sieve filters:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>/etc/init.d/dovecot reload </span></code></pre> <h1 id="web-interface">Web Interface</h1> <p>Add the following to <code>/etc/nginx/sites-available/02_localhost</code>:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span> location /rspamd { </span><span> alias /usr/share/rspamd/www; </span><span> try_files $uri @rspamd; </span><span> } </span><span> </span><span> location @rspamd { </span><span> rewrite /rspamd/(.*) /$1 break; </span><span> proxy_pass http://127.0.0.1:11334; </span><span> proxy_set_header Host $http_host; </span><span> proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; </span><span> } </span></code></pre> <p>Forward the port using SSH:</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>ssh -L 8000:localhost:8000 example.com </span></code></pre> <p>Point your browser to <a href="http://localhost:8000/rspamd/">http://localhost:8000/rspamd/</a>.</p> <h1 id="testing">Testing</h1> <p>To test your rspamd installation, you can send an e-mail from another domain to yourself and check the e-mail headers. They should contain an <code>Authentication-Results</code> field if SPF, DKIM and DMARC are operational.</p> <pre style="background-color:#272822;color:#f8f8f2;"><code><span>Authentication-Results: example.com; </span><span> dkim=pass header.d=gmail.com header.s=smtpapi header.b=XXXXXXXX; </span><span> dmarc=none; </span><span> spf=pass </span></code></pre> <p>Furthermore, if the spam filter is working, then the header should also contain a <code>X-Spam-Score</code> field.</p> <p>Vice versa, if you send an e-mail, rspamd should now sign the e-mail with the DKIM private key belonging to your domain name. You can use <a href="https://dkimvalidator.com">https://dkimvalidator.com</a> to fully verify your SPF, DKIM and DMARC setup when sending e-mails.</p>