Writing a simple windows driver in Rust
95 comments
·February 8, 2025mastax
ryandrake
Windows is not the only operating system treating the user's filesystem as a dumping ground. I don't know how many times I keep deleting .DS_Store, .fseventsd and extra files named ._xxxx but that doesn't keep Apple from dumping them all over my filesystem.
At least macOS has one place to install applications, one place for the user's documents, and most apps somehow respect them. Then, you have designated dumping grounds like ~/Library that contains tons of junk I have no idea whether I need or not.
KronisLV
> At least macOS has one place to install applications, one place for the user's documents, and most apps somehow respect them.
I actually like that simplicity more than how many things FHS describes for the various Linux distros: https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
Like, when a developer just wants to ship a product and doesn't have that much free time, it's understandable that tidy things will be overlooked.
Maybe something like https://www.gobolinux.org/at_a_glance.html would help with that.
miki123211
There are basically two ways to organize a file system, by subject and by purpose.
Linux prefers by-purpose organization, so executables go in /bin (or at least something ending in /bin), configuration goes into /etc, libraries go into /lib and so on. This simplifies discovery and cross-application data management, the answer to "how do I find a library" is just "search those 5 directories, if it's not there then it's missing", but it complicates package management and makes it too easy for garbage to accumulate. It's also better for GUI apps, it's far easier to make a music app that lets you think of artists, albums and playlists, not directories, if all your music is in one place.
Mac and Windows (mostly) prefer by-package organization, where everything related to a single application is contained inside that application's folder. This makes discovery harder, as now e.g. finding all applications that can open a particular file format requires you to check every single one. However, it makes package maintenance easier, removing an app just removes all its files. This approach complicates GUI applications and makes it impossible to escape the directory hierarchy, you can no longer make a nice music app if music files are strewn all around.
No system clearly falls in one bucket or the other, the Windows registry is partially organized by purpose, so are parts of Mac OS's Library folders, while Linux has package managers like Nix, which organize by package.
Ideally, we'd do by-package organization, and have some sort of per-user / per-process "union directories" (a bit like the /bin in Plan9) that would provide easy lookups for downstream applications.
p_ing
Two places. /Applications and ~/Applications.
More, if you count UN*X binaries.
maccard
And then all of the actual stuff that they put in Library Support
filleokus
I didn't even realise I still had stuff in ~/Applications. Probably got there via some Installer wizard...
ls ~/Applications
Autodesk Fusion Service Utility.app
Autodesk Fusion.app
Chrome Apps.localized
Edge Apps.localized
Remove Autodesk Fusion.app
ls -ls /Applications | wc -l
94
XajniN
You all forgot about /System/Applications
_blk
...And you'll start having dot files and dot folders in your home when you start using brew...
patchguard
There are conventions, but like anything that isn't explicitly enforced, developers will do whatever they can/wish.
rewgs
> one place for the user's documents
And yet far too many apps dare to put their files there. Drives me absolutely insane.
rad_gruchalski
> I don't know how many times I keep deleting .DS_Store
You must be using finder a lot. When working from terminal, none of that happens. Frankly, no clue why it bothers people.
mastax
Huh my Documents folder is weirdly empty, where did-
> Shortcut to Documents (OneDrive - Personal)
Oh good, now all that garbage is being synced across machines. Lovely.
jiggawatts
There are several ways of accomplishing this in user mode.
The Detours library lets you attach a custom DLL during the launch of any process, intercepting Win32 calls to file system APIs by writing a custom hook.
There's the built-in "compatibility shims", which are borderline undocumented, but they work similarly. They can toggle compatibility flags and redirect file and registry paths. Conveniently, these are designed to trigger only for specific EXEs based on heuristics.
To support technology like App-V and later Docker containers, Windows has an entire API surface for "virtualising" the filesystem and the registry, as well as any other NT kernel namespace.
Then there's the User Mode Filesystem and probably more approaches I'm not aware of or that I forgot about...
davikr
Can you describe those options further or send references to where I can read more, specially about the API for App-V you mention? I was looking, like OP, into writing a minifilter to overlay files and folders for game modding purposes, mostly (I know of Detours - but I think injecting is somewhat dirty), but I'd rather it be done in usermode given driver signing costs and all.
EvanAnderson
You're looking for AppCompat shims. I used them to make old applications work on newer versions of Windows (in the context of educational programs that K-12 teachers wanted to keep as they moved from Win9X to NT-based Windows). Here's a decent starting place: https://techcommunity.microsoft.com/blog/askperf/demystifyin...
mastax
Thanks. A looong time ago I looked into accomplishing it using AppContainers but that approach looked even more confusing and difficult than the kernel driver.
rkagerer
I just completely ignore the My Documents toxic waste dump, and use a different place (with a shorter path) for files I actually care about. I might have created directory junctions to redirect one or two programs that hardcode their data location.
dist-epoch
Microsoft/Windows itself puts a bunch of junk in the home folder, and to make things worse, they have extra long names:
ntuser.ini
ntuser.dat.LOG1
ntuser.dat.LOG2
NTUSER.DAT
NTUSER.DAT{b2352f18-cdbf-1122-8680-002248483d79}.TM.blf
NTUSER.DAT{b2352f18-cdbf-1122-8680-002248483d79}.TMContainer00000000000000000001.regtrans-ms
NTUSER.DAT{b2352f18-cdbf-1122-8680-002248483d79}.TMContainer00000000000000000002.regtrans-ms
zamadatix
You have to go pretty out of your way as a user to actually see those transaction logs though vs what GP is talking about with blatant pollution the user is actually expected to see and interact with.
wink
By "go out of your way" you mean "trying to copy the contents of any "system" folder in your HOME to a new machine? :)
delta_p_delta_x
These files correspond to the HKCU registry hive. There is really no better place to put them, because even the location of %LOCALAPPDATA% and %APPDATA% can be controlled... in the registry.
eviks
Of course there is a better way, don't give up so easily. One immediate improvement - use a subfolder for organization, so just 1 "garbage" item instead of many.
dist-epoch
they could have shorter names
int_19h
Funnily enough, Windows itself does stuff like this to allow applications written for Win9x (where e.g. Program Files were writable, so many apps would create files in their install folder). This is what the stuff in ~/AppData/Local/VirtualStore is.
jeroenhd
Marking files as hidden seems easier on Windows. I find it much more annoying to hide files on Linux (especially folders like ~/snap which can't be renamed).
If there's anything I've heard about drivers, though, it's that filesystem drivers are particularly annoying to write.
If you want to simply hide the files from view, you may have an easier time writing a shell plug-in. You have to deal with COM, but you're less likely to take down the whole system.
null
yonatan8070
I wish there was something like that for Linux so programs would stop littering my home directory with stuff that should clearly go into .config or .local.
gpm
Semi-related, anyone have any up to date information on rust usage in the windows kernel?
Almost 2 years ago they said "36,000 lines of code including a systemcall" [1], I'm curious how that project has progressed.
[1] https://www.thurrott.com/windows/282471/microsoft-is-rewriti...
jsheard
I haven't heard anything about the kernel but they're still trucking along with using Rust for other sensitive low-level stuff, most recently their secure enclave firmware:
https://techcommunity.microsoft.com/blog/windows-itpro-blog/...
dralley
And IIRC the UEFI implementation on Microsoft Surface devices
rs186
Changes in 24H2: https://learn.microsoft.com/en-us/windows/whats-new/whats-ne...
the__alchemist
Interesting! This looks very different from embedded drivers, which I've done a lot of in rust. Those are mostly reg reads, writes, bit shifting, DMA, and data sheet references.
AndrewGaspar
The code here looks to be essentially C with different syntax - every function marked unsafe, all resources manually managed. Sorry to be blunt, but what's the point of this?
AndrewGaspar
And I don't know how I missed this, but attempting to use the `UNICODE_STRING` returned in `string_to_ustring` is a guaranteed use after free. If you're interested in writing Windows kernel code, this is not the place to start.
AndrewGaspar
I'm glad to see that Microsoft is investing in Rust bindings for WDK[0], but browsing the repo, there's really no point in using this over C since they haven't bothered to invest in safe, Rust native bindings. The kmdf example[1] is like 50% "SAFETY:" comments because they're stuck using the straight C bindings for every WDK API.
[0] https://github.com/microsoft/windows-drivers-rs/
[1] https://github.com/microsoft/windows-drivers-rs/blob/main/ex...
haileys
`boost_write` doesn't appear to validate the length of the user supplied buffer before casting and dereferencing either, so that's a kernel-mode OOB read. Not sure how exploitable this actually is though.
AndrewGaspar
lol yep, you're 100% right.
dwattttt
It's unfortunate: a simple example is effectively all plumbing, and those would touch all the (unsafe) native interfaces.
A driver that actually does something interesting would be able to have unsafe interfaces at the boundary, and more typical Rust code inside.
AndrewGaspar
Yeah, I totally get that, but I suppose my argument would be is if you're going to bother writing your driver in Rust, until there are more mature Rust bindings for the OS interfaces, you might as well only write your most safety-sensitive business logic in Rust, and then write all the interfacing with the OS in C.
gpm
If you look at the readme they have
> wdk: Safe idiomatic bindings to APIs available in the Windows Development Kit (WDK)
And then if you look at that crate they've only implemented dbg_break, print, spinlock, and timer.
I'd take this as a sign that the bindings are a work in progress.
If you're going to write your driver in Rust... honestly I'd recommend just writing the `wdk` crate bindings you need for your driver and probably even upstreaming them. Wrapping FFI bindings in safe abstractions is usually pretty easy - but it's definitely the case that without them rust doesn't give you much.
This sample is using `wdk_sys` bindings directly without wrapping them... which is basically never the recommended way of interacting with the FFI in rust.
queuebert
Also a ton of ALL CAPS TYPES. Are we seriously throwing out all of the standard Rust naming conventions to adopt the ancient Windows naming-as-typing crap?
AndrewGaspar
That doesn't bother me per-se - those all caps names are pretty much all directly from the standard C bindings, and it makes sense to preserve that naming for the sake of having that 1:1 mapping with the ground truth C definition.
The actual issue here is that this "simple driver in Rust" is having to touch those direct C bindings at all - if Microsoft is going to advertise that they have support for writing drivers in Rust, that should presumably mean an API surface that's native to the language.
dwattttt
I believe Rust has added the ability to mark FFI functions as safe to call at their definition site (https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html#safe-...)
That way functions can be marked as accurate/"ok" to call in safe code by the author of the bindings. They could absolutely not be safe; in that case, the binding author is in error marking it so.
taurknaut
> that should presumably mean an API surface that's native to the language.
It reads pretty naturally to me as referring to the implementation of the driver.
queuebert
I see your point, but it makes it hard for Rust programmers to grok the code, as all caps denotes a constant. Even enums are camel case.
magicalhippo
In Delphi the Windows types are declared as-is, ie with all caps, but then most of them are aliased to more Pascal-like names.
So you can use TRect and TPoint and pass those to PtInRect[1] which expects RECT and POINT respectively.
[1]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/...
justmarc
Some 25 years ago I was tasked with writing a certain driver for Windows.
Being totally migrated to Linux by then I refused to use Windows for writing as well as building it, so I worked hard to build it with MSYS.
Long story short, I made it, and the driver worked great.
I think I had to write a patcher for the resulting PE (.sys) to get it to actually load.
Fun times.
null
ilrwbwrkhv
Great article and even more impressive design of the blog. Just clean straight forward easy on the eyes and loads instantly.
hu3
Same for me. I had to double-check the tech behind the blog.
It seems to be WordPress.com.
They probably rely heavily on caching and CDN.
Makes me sad that I just didn't stick to my WordPress blog 10 years ago.
I had an idea to write a filesystem filter driver which would let you configure path remapping rules of sorts, depending on the application. Things like:
- %userprofile%\.vscode -> %appdata%\vscode
- %CSIDL_MYDOCUMENTS%\Call of Duty -> %userprofile%\Saved Games\Call of Duty
Because my documents and home directories filling up with a bunch of garbage which has a designated place on the filesystem filled me with impotent rage. I scaffolded out a project to write a filter driver in rust, read through the minifilter documentation, realized how much work it was going to be, and gave up.
I have made my peace with the fact that a windows system is just going to be filled with garbage.