Emulating Windows system calls in Linux
lwn.net
lwn.net
I like the idea of using eBPF to run an emulation layer. That has potential to be more efficient and doesn't require the running process to cooperate so closely with the syscall emulation. Then this would probably lead to extensions to eBPF and its tooling which should be useful for other things!
https://j00ru.vexillium.org/syscalls/nt/64/ Has an excellent table of syscalls by version.
In order for this to be feasable on Windows, there needs to be some stability in the syscalls; even if that stability involves angry MS engineers complaining that major applications are doing something that they were explicitly told not to do.
It’s gonna be very hard to check integrity of code in RAM. The complications include relocation table (applied when loading; see also ASLR) and global variables (modified as the software runs).
The Win32 API's require certain operating system semantics, and not all of that semantics comes nicely from Linux syscalls.
A Win32 API library on Linux benefits from Windows-like syscalls underneath, even if nobody is bypassing that library to get to those syscalls.
Think about it; that is exactly like saying that if programs were just written to the C API described in the POSIX spec, we wouldn't need a Unix-like kernel underneath. While that isn't an outright false statement, it has implications for the nature and quality of the system.
Determining what table a program expects likely would be a black art, but I don’t think that would stop Microsoft from trying that for programs deemed important for the platform.
That said if you look at the list linked above some are "stable" meaning that MS probably knows this happens and kept those specific ones stable. But the rule still applies... never do direct system calls on NT.
I think when the article is talking about Windows system calls, it's about the interface between the kernel32.dll and the kernel, not about the Win32 API.
Nowadays, it's split up as it should always have been, with the path being user32.dll -> win32u.dll -> win32k.sys.
Found this article[1] which explained it nicely (and hopefully correctly).
[1]: https://www.codeguru.com/cpp/w-p/system/devicedriverdevelopm...
OpenBSD already does this to improve security: https://marc.info/?l=openbsd-tech&m=157488907117170&w=2
And why would, say a Lisp compiler use the C library (or to use a more popular example, why would a Java VM do the same)? It might be common practice (to ease FFI/JNI), but making this a requirement seems heading the wrong direction.
Linux syscalls are more stable at the actual binary level. Some "int $0x80" code you wrote in 1996 in x86 assembly language will work today, provided the binary format (like a.out) will load.
The libc which was used then is not even around any more.
It's not about defending the kernel from the user mode process. It's a mitigation to make exploiting usermode RCEs like buffer overflows a bit harder by possibly preventing some forms of ASLR bypass.
MS might do it in part for security, but likely aloo to prevent people from relying on unstable implementation details, which makes changing those details harder for MS because they try to avoid breaking popular applications.
> And why would, say a Lisp compiler use the C library
On unix based systems libc doubles as (part of) the OS's API. BSD in particular has no stable syscall interface, code must go through libc. Its libc doesn't even have a stable ABI across major versions, which is annoying if you're not using C. Non C languages are clearly second class citizens on BSD.
Linux on the other hand does have a stable documented syscall interface, so you are allowed to call the kernel directly, without relying on any OS provided usermode libraries.
On Windows it's similar to BSD, but you have to go through different OS provided libraries (at minimum ntdll, usually the Win32 API). At least these have a stable ABI. On Windows libc is little more than just another dll.
> but making this a requirement seems heading the wrong direction.
I disagree. IMO an OS provided dynamically linked library is a great abstraction boundary. It allows the OS to change its kernel interface or even turn operations that previously needed to transition into the kernel into purely usermode functions.
For example the OS might provide a mutex which in the first version transitions to the kernel on every lock attempt, while later it's replaced by a futex based implementation which only transitions on contented locks.
Or more relevant to the linked article, it allows implementing the same API on a completely different kernel (like Wine does), without increasing the kernel's API surface.
In theory, yes that's true. In practice it's a question of whether it actually reduces anybodies work load. If everybody respected the boundary I sure it would reduce Microsofts work load.
But as has been reported repeatedly here, that's isn't how it's worked out in practice. Not only do applications not respect it, Microsoft finds itself in the position of having to ensure some of those applications keep working on newer kernel versions despite the fact they've wilfully violated the rules. So we end up the Windows GUI having to expose with "compatibility levels", and users having to deal with it. That doesn't sound like a win to me.
There are other ways of doing it that wouldn't suffer from that fate. Trampolines (a page the kernel creates in user space that intercepts some syscalls) is one way that Linux uses. I don't think Linux does, but it wouldn't be hard to insist that any syscall come from those pages. Do that and you have your cake (low overhead user space mediating some calls), and get to eat it to (you can safely assume every one uses it).
If syscalls were stable, they'd just try to use some other means to obfuscate their OS calls and ensure it's sufficiently fragile as to break easily if something changes.
tl;dr the lack of stability is a feature not a bug for this use case.
I didn't know this. What applications are doing this and why?
The gnarlier antivirus will do a similar thing by just patching the IDT and syscall MSRs, but that's a bit frowned upon.
Ironically, this work might backfire in the long run if unscrupulous players are able to use cheats developed specifically to run under Linux.
[1]: https://lifeinhex.com/use-of-syscall-and-sysenter-in-vmprote...
Is this feasible or am I misunderstandig the feature?:
https://kernelnewbies.org/KernelProjects/usermode-helper-enh...
https://elixir.bootlin.com/linux/latest/source/security/Kcon...
The issue is that ptrace is unreasonably slow (orders of magnitude slower than just regular syscalls), so they'd want the fast normal path to not go through ptrace. But they don't have a way to communicate that to the kernel at the moment.
The Intel Binary Compatibility Standard (iBCS) is a standardized application binary interface (ABI) for Unix operating systems on Intel-386-compatible computers, published by AT&T, Intel and SCO in 1988, and updated in 1990.
https://www.tldp.org/HOWTO/WordPerfect-5.html
Same friend had a DEC Alpha and got Digital Unix Netscape to run under Linux on the Alpha.
Running 286 Xenix applications on a 386 or later Unix or Unix-like system is fairly straightforward as far as the 386 kernel is concerned. 286 Xenix used a different syscall mechanism than 386 Unixes did, so you didn't have the problem of the kernel needing to deal with 286 and 386 syscalls ending up in the same place.
My recollection, from being half the two person team that implemented 286 Unix binary compatibility for System V Release 3.2 on 386 at ISC, and then being half the different two person team that did 286 Xenix binary compatibility, was that this is what was required in the kernel:
1. Adding a mechanism to allow a process to set up 16-bit segments as aliases to portions of its address space.
2. Modifying exec to recognize a 286 Unix or Xenix binary, and turn the exec into an exec of /bin/i286emul or /bin/x286emul, with the path to the 286 binary as an argument.
3. I don't remember if we had to do anything special for the 286 system call mechanism, because I don't actually remember what that mechanism was. I don't remember if what the binaries would try to do for a syscall would simply cause a trapable signal, and then the signal handler would recognize the system call and take care of it, of if the system call mechanism used something like a call gate that the 386 code had to set up, and so we had to add a mechanism to allowed the 386 code to do that.
That was almost all the kernel work. Pretty much everything else could be handled in user mode code. When you would run a 286 Xenix binary, that turned into an exec of /bin/x286emul. x286emul would read the Xenix binary, allocate memory for it and load it, map segments to it, and jump to the code, switching the processor to 16-bit mode.
When the Xenix code did a system call and it ended up in the handler in x286emul, for most system calls it was simply a matter of copying arguments from where the 286 call put them to where the corresponding 386 call expected them, making the 386 system call, then putting results in the right place, and returning to the Xenix code.
For some system calls, more work was needed. Signal handling, for instance. If the 286 code wanted to trap a signal, the 386 code would have to set a trap of its own, and its handler would then have to deal with the stack fiddling and such to deliver it to the 286 code.
Speaking of signal handling, that led to the stupidest, most annoying meeting I've ever had to attend.
ISC had done the official 386 port of System V Release 3 under contract from AT&T, and we did the 286 Unix binary compatibility as part of that. Later, there was a deal between AT&T and Sun and Microsoft to make Unixes more compatible, which included Xenix compatibilty, and we were doing the Xenix compatibility as part of that, under contract from Microsoft.
For the 286 binary compatibility, ISC did the whole thing--all the user mode code in i286emul and all the supporting kernel changes. But for Xenix compatibilty, ISC was just doing x286emul. Microsoft was doing any kernel modifications needed, which wasn't very much because the mods already there for i286emul mostly worked find for x286emul.
There was one difference between Xenix and Unix signal handling that we could not fully address in x286emul. We needed kernel support--some sort of per-process flag that would tell it the processes wanted signals to behave like Xenix signals. We asked Microsoft to add such a flag.
A bit later they got back to us and said there was issues with such a flag that could not be settled by email or phone. We had to have an in-person meeting. So me and the other guy working no x286emul had to fly early one morning from Los Angeles to Seattle, take a rental car to Redmond, and attend a meeting with Microsoft.
At the meeting, we introduced ourselves and the half dozen or so Microsoft people introduced themselves, and then they presented this issue that could not be handled by email or phone: should this flag be controlled by an ioctl, or should it be an optional new parameter for the signal() call [1]. We gave our opinion, that was the end of the meeting, and we headed for the airport.
[1] or something like that. I don't remember the exact second option.
That's my project. I also did the port of iBCS to amd64, which is what ibcs-us is based on.
I moved the support out of the kernel because the rate of change of the internet kernel API's makes maintaining invasive kernel modules like this is just far too much work. Every time I looked at the kernel the way syscalls worked internally changed in some way, and broke ibcs64 as a consequence.
Decades ago Linux had full on support for other syscall interfaces via it's personality mechanism. See personality(2). Most of the functionality has been ripped out as kernel internal API evolved. I'm guessing maintainers noticed the personality stuff wasn't used and so it was easier to delete it than port it to their new shiny API.
At the time I thought that would be a disaster for iBCS. Now after porting ibcs64 to user space, I think personality(2) was the wrong way to do it. Turns out iBCS is mostly glue that emulates foreign syscalls using Linux equivalents, and as ibcs-us demonstrates that kernel space glue runs equally well in user space. It should not be in the kernel, where it bloats things and could introduce security holes.
Once you realise the glue code can run anywhere, there is only one problematic area remaining: directing the foreign syscalls to your user space glue code. Here ibcs-us could really use some kernel could really help to make it both fast and safe, but it's a use case that apparently hasn't crossed the Linux kernel dev's minds.
This is exactly the same problem the Wine developers are having, and are trying to solve in these LWN articles. To me their solution looks like a horrible kludge. That isn't a criticism. I don't see how anything that tries to make minimal changes to the kernel is going to be anything other than a kludge.
If you want to do it properly, you need to add some mechanism to the kernel that lets user space redirect whatever syscall method is in use to a user space trampoline page. There are any number of syscall methods out there: int's, sysenter's, lcalls. The kernel has to provide a way of trapping each and redirecting them to the trampoline, but once they are there they are the emulator's problem. Mostly. The emulator then needs a second mechanism to make the real Linux kernel syscall that isn't redirected. One way to do that is to say "syscalls from within this address range aren't to be emulated". I’m guessing that’s what OpenBSD does now, but replace “aren’t to be emulated” with “are allowed”.
This could be thought of as a way of the kernel providing a way to virtualise it’s own syscall interface. There is no need to stop at one level either, as there there is no good reason one virtualised interface shouldn't end in another trampoline, unbeknownst to it. So a Xenix 286 emulator could be running inside of a Xenix 386 emulator running on Linux. It would be lovely for ibcs-us of course, but LD_PRELOAD's, virtual machines and yes even Wine all want to do the same thing, so it is a broadly applicable use case.
Anyway, this seems like a very good idea. It provides a very general light-weight VM mechanism.
I don't know who they are, which is the normal situation in the open source world. The few I've interacted with seem to use Informix. I, well the company I work for, is the only exception I've met to that.