One Sysfs Write: What Lenovo's WWAN Unlock Binary Was Actually Doing
Source: lobsters
The Lenovo WWAN unlock binary has been a quiet irritant for ThinkPad Linux users for several years. The situation is described well in this article on hofstede.it: someone sat down, reverse-engineered what the binary was actually doing, and replaced it with a bash script. The script is about 100 lines. The binary was closed-source, tied to specific distribution releases, and had a habit of breaking on kernel updates.
That story is worth following in full, because what the binary was hiding reveals something specific about how hardware vendors approach Linux support, and what the Linux kernel’s rfkill subsystem already provides without any vendor intervention.
The Setup: ACPI-Blocked Modems
Modern ThinkPads with LTE modems do not have a BIOS hardware whitelist in the old sense. Older machines, like the T420 or X220, famously refused to POST if you installed a non-approved wireless card, displaying error 1802 and halting. That approach was crude and required BIOS mods, custom firmware patches via tools like 1vyrain, or raw hex editing to remove the whitelist table from the firmware image.
Current hardware takes a softer approach. The WWAN modem, typically a Fibocom L850-GL, Quectel EM05, or Sierra Wireless EM7455, is connected over PCIe or USB and will enumerate normally. It shows up in lspci or lsusb. But the firmware places it under an rfkill block at startup, and without an explicit unlock step, ModemManager never sees it as usable.
On Windows, Lenovo’s companion software handles this unlock automatically and silently. On Linux, Lenovo began distributing a binary package, variously named lenovo-wwan-gui or lnv-wwan-ready depending on the model, delivered from Lenovo’s own .deb/.rpm repository. It installed as a systemd service and ran at boot. It was closed-source, opaque, and tied to specific distribution versions, which meant kernel upgrades and distribution major releases regularly broke it.
The rfkill Subsystem
The Linux rfkill subsystem has existed in the kernel since 2.6.22. It provides a unified interface for managing radio transmitter kill switches, covering Wi-Fi, Bluetooth, WWAN, and NFC. Every registered rfkill device appears under /sys/class/rfkill/, with entries like:
/sys/class/rfkill/rfkill0/
name # e.g., "ideapad_wwan"
type # "wwan", "wlan", "bluetooth"
state # 0 = soft-blocked, 1 = unblocked
hard # 1 = hardware kill switch engaged
soft # 1 = software block active
A soft block can be cleared from userspace. Writing 1 to the state file, or calling rfkill unblock wwan, is sufficient. A hard block cannot be overridden in software; it represents a physical switch or a firmware-level prohibition that the kernel cannot bypass. Lenovo’s modem block is a soft block, established by the thinkpad_acpi or ideapad-laptop kernel driver at init time based on ACPI state read from the embedded controller.
The thinkpad_acpi driver has handled ThinkPad ACPI integration since the 2.6.x era. It reads ACPI methods like GSMC (get WWAN state) and SSMC (set WWAN state) from the machine’s DSDT, registers the modem with the rfkill subsystem, and exposes the soft block. By the time the system reaches userspace, the kernel has already done the complex part: parsing the DSDT, negotiating with the embedded controller, and surfacing a standard rfkill interface. The only remaining task is to tell the kernel to unblock the device.
What the Binary Was Actually Doing
Using strace, strings, and udev monitoring, the community established that Lenovo’s blob was performing a narrow sequence of operations:
- Waiting for udev to signal the WWAN device’s presence.
- Locating the rfkill index by iterating
/sys/class/rfkill/rfkill*and checking thetypefile forwwan. - Writing
1to that device’sstatefile, or invokingrfkill unblock. - Optionally sending a DBus message to
org.freedesktop.ModemManager1to trigger a re-scan, which ModemManager would have performed on its own via udev anyway. - Polling
mmcli -Luntil a modem object appeared.
The entire functional core was a loop over sysfs entries and a single write. The DBus interaction was unnecessary for normal ModemManager operation, because MM already monitors udev events and enumerates any newly unblocked modem on its own.
The replacement script handles this directly:
#!/bin/bash
set -euo pipefail
# Unblock any soft-blocked WWAN rfkill device
for rfkill_path in /sys/class/rfkill/rfkill*; do
type=$(cat "$rfkill_path/type" 2>/dev/null || continue)
if [[ "$type" == "wwan" ]]; then
echo 1 > "$rfkill_path/state"
fi
done
# Wait for ModemManager to enumerate the modem
for i in $(seq 1 30); do
mmcli -L 2>/dev/null | grep -q "Modem" && break
sleep 1
done
Paired with a systemd oneshot unit:
[Unit]
Description=WWAN Unlock
After=ModemManager.service
Wants=ModemManager.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/wwan-unlock.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
This is stable across kernel versions, readable, and carries no distribution dependencies. It works as long as the thinkpad_acpi driver loads and the rfkill subsystem exists, both of which have been true for over a decade.
Exceptions: When a Blob Serves a Real Purpose
It is worth distinguishing this case from situations where vendor binaries serve a genuine function. The Fibocom L850-GL modem used in the X1 Carbon Gen 6 and T480 uses an Intel XMM7360 chipset that requires firmware to be loaded into device RAM at startup. The kernel does not ship this firmware in its tree, and the out-of-tree xmm7360-pci driver was necessary for years to handle it. That is a firmware loading problem with real complexity behind it.
Similarly, Qualcomm-based modems in some devices require the QMI protocol stack to initialize properly, handled in userspace by libqmi and qmi-proxy. These involve genuine protocol negotiation over a control channel, not a toggle of a flag already managed by a kernel driver.
The Lenovo WWAN blob fell into neither category. The kernel driver had already done the protocol work; the blob existed to perform a step that the kernel had deliberately left to userspace as a policy decision, but that policy decision required only a single sysfs write. No firmware, no protocol negotiation, no hardware initialization that the kernel driver had not already completed.
The Broader Pattern
Lenovo is not unique in this regard. The pattern of wrapping kernel-provided interfaces in opaque binaries and distributing them as vendor packages appears across the industry. The justifications tend to combine QA processes that produce Windows tooling by default, legal caution around redistribution, and a testing surface that does not cover the diversity of Linux kernel versions in production use.
The cost falls on users who upgrade their kernel and find their modem soft-blocked because the blob’s systemd service failed due to a library version mismatch. The fix has always been available in the kernel’s own documentation, but locating it requires first understanding that the blob was a wrapper in the first place.
There is a secondary cost that is harder to measure: the chilling effect on distribution packagers who are reluctant to include hardware support that depends on a closed binary they cannot audit, modify, or build from source. Several major distributions have declined to ship Lenovo’s WWAN package in their official repositories for exactly this reason, leaving users to add Lenovo’s third-party repository and trust whatever it ships.
The community projects that reverse-engineered this problem arrived at the same conclusion independently: the unlock sequence is entirely expressible in terms of standard kernel interfaces, and the binary was providing no value that a well-written udev rule or systemd unit could not provide with full transparency.
The value of work like the hofstede.it article is precisely that it makes the wrapper visible. Once the actual operation is documented, the replacement is straightforward. The 100-line bash script is not the interesting part; establishing that 100 lines was all there ever was is.