My first steps with eBPF. In this article I'm describing how I used bluetooth tracing with eBPF to handle locking of my laptop.
I heard “eBPF” so many times in recent days that I’ve decided to give it a try. I have very limited knowledge about kernel tracing so I thought it is good opportunity to learn something new. One particular talk (by Brendan Gregg) especially caught my attention and I recommend it if you want to get some general idea behind eBPF. At the beginning of his talk he is showing how he was able to monitor WiFi signal strength with eBPF. I seemed so easy that I wanted to mimic something similar. In this article I’m describing how I used eBPF tracing for locking and suspending my laptop using Bluetooth on my phone.
There are many good sources to explain it along with the history of BPF (some of them you will find in this article), so I will try to make it simple.
eBPF is a functionality of linux kernel that allows lightweight execution of user code as a response to kernel events. The events could be hardware/software events, tracing events both static (compiled into code) and dynamic (attached in runtime), etc. The code itself is limited in a sense that it is guaranteed to finish (no loops) and is verified before loading into kernel.
Some facts and how others describes it:
“Function-as-a-Service for kernel events” (Dan Wendlandt from his talk)
“Superpowers are coming to Linux” (Brendan Gregg from hist talk)
At the beginning the only thing I knew is that I need to trace Bluetooth. But how should I know what are the function names that are called? Recalling some basic knowledge about linux kernel: kernel is the interface between user programs and hardware > kernel uses kernel modules that are specific for particular hardware > I should probably find Bluetooth module then. So by listing loaded modules I could find at least some anchor to begin with.
Shockingly enough, the module I was looking for is named Bluetooth :). Turns out that with this information we can already limit tracing to specific module using trace-cmd. It is a wrapper around ftrace tracer, which in raw form requires writing values into filesystem. You can install it using something similar to apt-get install -y trace-cmd. Then you can actually use it for tracing functions of a specific module.
Seeing that there are non-zero bytes captured is always a good sign. To view the output you need to issue report command.
At this point I had a bunch of function calls, that I should use to somehow get the useful info about my phone’s Bluetooth state. You can find code of all the functions in kernel source code so in theory it should be matter of time to find the right one to trace. Well, it was pretty significant matter of time, spent on recalling C basics, HCI protocol events and bpftrace, which is what I wanted use in the end.
So how the trial and error part looked like? I had to first install bpftrace from snap and overcome lockdown (see this issue). You need to run all those commands as root too.
I used kprobe, which is the kernel debugging mechanism that can be attached to function execution. Whenever function is entered you can access all its arguments (in bpftrace). So for example let’s say we want to trace hci_cmd_complete_evt, defined as:
As you can see, there is hdev argument of type hci_dev which turns out to be structure holding a lot of information about the Bluetooth device. Let’s have a look just at the top fields of this struct.
Knowing all above, I could print the name of local device using below script.
So yes, bpftrace has its own language similar to C but consisting of only group of functions (see reference guide]). It is often used in a form of one-liners, but you can put it into file with shebang as I did.
Going through the code, we need to first include kernel headers (lines 3-4) which is used to cast arg0 into hci_dev struct (line 9). In line 7, I’m attaching the code to kprobe:hci_cmd_complete_evt, which is eBPF at its beauty - you can execute your own code in kernel as a reaction to kernel event. In this case the reaction is just printing device name (line 10) every time hci_cmd_complete_evt function is entered. After running it you should see new line with device name every few seconds.
I had to also make sure that the target function is triggered frequently enough, so I found more usable function called mgmt_device_found that had all I need: MAC address (arg1) and RSSI (Received Signal Strength Indication - arg5).
Even though bpftrace support array [] operator I had problems reading correctly bdaddr struct with its underlying array, so I end up using C trick of incrementing pointer to array directly. You can find more details in below snippet - I divided MAC into to halves and then put it back in printf. I’m also printing rssi after a space in same line.
The output from running this code looks as follows.
As a result I have per MAC signal strength that I can later on use to trigger lock and unlock of my Ubuntu system. That is great, but why not find disconnect function that would trigger system suspend whenever I disable Bluetooth on my phone. Fortunately it is there and its called mgmt_device_disconnected the bpftrace to output disconnected MAC can be found below.
We have now bt_rssi.bt showing MAC with corresponding signal power and bt_diconnect.bt showing MAC that just disconnected. It is time to shell-script it into actual system actions.
The script takes only one argument - target MAC address (i.e. your phone Bluetooth MAC) and uses LOCKFILE file to mark if system is locked already. bt_lock() function parses the output of bt_rssi.bt to decide on locking or unlocking the session. Because xargs in line 29 passes one argument in form “<MAC> <RSSI>”, it needs to be broken into two arguments using bash array in line 6.
Since xargs starts a separate process I had to export all the variables and function in lines 24-26. Last two lines of the script are just actual executions of bpftrace scripts.
After running the whole script with Bluetooth working on both your pc and phone, you should see system being locked whenever you go away for ~2 meters and unlocked when phone is back near the pc. Turing off the Bluetooth on your phone will result in suspend. The extra feature is that when you turn on the Bluetooth after suspend and wake pc up it becomes unlocked, all of that without typing the password.
Just remember that the script showed in this article was created for educational purposes (just for fun) and it shouldn’t be used in places where you are exposed to anybody that could reach your keyboard. This is mainly because of the fact that your Bluetooth MAC becomes effectively password to your system, and despite of techniques that hides Bluetooth MAC it should be treated as public information. It works ok when using it at home, during pandemic, when the only hackers nearby are 2 and 6 years old and mostly using brute-force keyboard attacks, not Bluetooth MAC spoofing.
As for eBPF, it is definitely very interesting concept that gains adoption in multiple fields. Vision behind it is far broader than tracing, performance and security cases. eBPF against current service mesh implementations with all its significant overhead is something worth looking at closely.