Much of modern operating system functionality happens in and around the kernel. That’s a problem when you’re implementing monitoring and observability tools or adding low-level security tools because hooking into kernel functions is complex. Even Linux, readily accessible and with its system of run-time-loaded kernel modules and modifiable source code, makes it hard.
Once you started rolling your own kernel-level tools, you’d quickly end up with a nearly unmaintainable stack of modules and a kernel that only worked for your application. Then there’s the problem of upgrading: Would your modifications work with a new kernel release, or would you have to build everything from scratch again, or worse still, would it force you to prevent any updates at all?
Enter extended Berkeley Packet Filters
It was clearly an untenable position, until the development of eBPF, the extended Berkeley Packet Filter. By putting a sandbox inside the kernel, you can add code that hooks into kernel functions without requiring any changes to the kernel itself. Like the traditional Berkeley Packet Filter, eBPF provides an interface to kernel-level events, which then launch eBPF programs that run in a secure virtual machine in the Linux kernel.
That’s fine if you’re running a purely Linux environment, but most organizations now have heterogeneous systems, mixing Windows and Linux. That’s even more true of the cloud, where it’s the APIs that matter rather than the underlying OS. With cloud-native development focused on scalable, distributed systems, traditional monitoring technologies are hard to justify and eBPF-based observability tools become increasingly important.
If we’re to use eBPF-powered APIs to examine low-level OS performance in distributed systems, then getting it to run on Windows systems is important. This is where Microsoft’s recent reorganization of its operating systems group begins to make more sense, as it puts both Windows and Linux kernel development teams in the same group, allowing them to share ideas and tools. One of the first major collaborations between the teams is the Windows port of eBPF, announced in May.
Running eBPF on Windows
Currently being developed on GitHub, eBPF on Windows offers many of the same features as on Linux; however, architectural differences between Windows and Linux mean that it has needed to be implemented quite differently. Microsoft has implemented eBPF in a way that crosses the Windows usermode and kernel boundary safely. eBPF code from a standard eBPF toolchain is compiled to bytecode, ready for use by security or monitoring tools. You can verify and test eBPF code, calling it from the familiar netsh.exe Windows command, allowing you to build it into scripted actions from PowerShell.
eBPF code works with a user-mode library to deliver bytecode to a protected service running in userspace. Here code is checked before being run using a standard eBPF verifier, PREVAIL. This is a static code analyzer that checks code to ensure that it terminates, that code is type and memory safe, and that it does not access kernel data structures. PREVAIL is a second-generation verifier, which can work with complex eBPF code, including support for loops.
Windows’ protected services are signed by a key that allows code running in the protected space to be trusted by the kernel. It’s a way of ensuring that malicious code can’t enter the kernel while still allowing trusted eBPF extensions to be used. It’s a key part of the Windows design philosophy to keep code out of the kernel. By hosting the eBPF JIT in a driver, if it crashes, Windows will carry on running, and the driver can be reloaded automatically.
Once verified, code is either passed to a JIT compiler or handed over to a Windows kernel-mode interpreter. Compiled code and interpreted code both run in a Windows driver, ebpfcore.sys, which acts as a sink for events from another eBPF driver that acts as a shim for hooks from the Windows network driver subsystem and the TCP/IP stack. It then allows complex verifier functions to run in a safe environment where computationally intensive operations don’t affect other applications and services.
Building on eBPF in Windows tools
Much of the Windows eBPF stack builds on existing open source tools, making it easy to port code already running on Linux systems to Windows. By using familiar environments and contexts, Windows can quickly become part of an existing eBPF-based monitoring environment, either for testing code running on Windows desktop development systems or in production on Windows servers on-premises or in Azure.
That’s not to say eBPF For Windows is directly compatible with Linux eBPF systems. The two operating systems have very specific ways of working, and many Linux eBPF hooks don’t translate directly to Windows equivalents. If you’re using eBPF to monitor specific internal structs, that code is unlikely to work on Windows, where kernel memory is handled differently. Instead, it’s best to think of the Windows version of eBPF as a place to use common hooks, with a focus on the network stack rather than on kernel operations.
Microsoft aims to simplify eBPF ports by offering libbpf APIs as part of its implementation. The public APIs are there from the start, with drivers that work on Windows out the box. Under the hood, the tooling uses Windows syntax and calls, exposing them as generic hooks to eBPF clients. As a result, there’s no need for Microsoft to sign all your kernel-level code; it’s already signed the eBPF components that run your code after it’s been verified in a secure environment. That’s a big saving in both time and flexibility.
Initially, Microsoft is supporting access to the networking stack, but there’s actually support for anything with a driver, so eBPF could be integrated with a file system filter as a tool for monitoring file system operations. It’s possible to imagine a tool like this running across all the PCs in an organization monitoring for ransomware behaviors at a file-system level, and able to rapidly shut down operations as soon as malware activity is detected.
Giving Windows a user-programmable kernel
These are early days for eBPF on Windows. What’s shipping is more than a proof of concept but less than what’s possible. There’s a lot of community interest and a lot of demand for features. The project is open, like the Linux eBPF, so it’s going to be up to the wider community to have these available, giving Windows the user-programmable kernel that it’s never had without opening that kernel up to security vulnerabilities.
Keeping the Windows eBPF in userland seems to be a contradiction in terms, but marrying it with a kernel driver and a secure sandbox gives you the security you need with the flexibility you want. Microsoft has even demonstrated eBPF running in HVCI, Windows’ HyperVisor-enforced Code Integrity tool. Here, kernel-mode processes run virtualized to increase isolation, protecting the rest of the kernel from untrusted code. Although you can’t run compiled eBPF code in HVCI, it is suitable for using the interpreter, adding an extra layer of protection from third-party applications.
Adding support for eBPF in Windows makes a lot of sense. As we scale out heterogeneous systems, we need cross-platform monitoring and security tools, and having a common framework and APIs across Windows and Linux is helpful. Even if the same code won’t run on both platforms, a shared way of developing components should simplify operations and development.