A Lenovo ThinkPad ships with a WWAN modem, and on Linux, that modem might not work out of the box. Not because the kernel lacks a driver, not because ModemManager does not recognize it, but because Lenovo ships a proprietary binary blob whose sole job is to send a handful of AT commands to the modem before it agrees to register on a network. A post on Hofstede’s blog describes replacing that blob with a 100-line Bash script. The post is worth reading on its own terms, but the more interesting thing to think through is what this situation reveals about vendor tooling and the AT command interface that modems have been exposing for decades.
What WWAN Locking Actually Means
The Lenovo locking mechanism here is distinct from the older BIOS whitelist problem that plagued ThinkPad users for years. That older issue, well-documented in forums and the coreboot project, prevented users from installing third-party wireless cards because the UEFI firmware would check the card’s PCI subsystem ID against a hardcoded list and refuse to boot if it did not match. That was lock-in at the hardware initialization layer.
The WWAN unlock problem is different and, in some ways, more subtle. The modem is recognized. The kernel loads its driver. ModemManager sees the device. But the modem sits in a locked state and will not register on a network until it receives a specific initialization sequence. Lenovo ships that initialization sequence inside a compiled binary, and on affected systems the expectation is that this binary runs as a systemd service at boot before ModemManager is allowed to manage the modem.
The modems in question are typically Fibocom devices, specifically variants like the L850-GL and FM350-GL, which appear in ThinkPad X1 Carbon, T14, and X13 generations depending on year and region. Fibocom is a Chinese manufacturer that supplies cellular modules to laptop OEMs. Their modems expose a standard USB interface with multiple endpoints: one for AT commands, one for diagnostic traffic, and one for the data channel that ModemManager or NetworkManager eventually use.
AT Commands and the Longevity of a 1977 Interface
The AT command set was designed for the Hayes Smartmodem in 1977. The basic syntax, prefixing commands with “AT” and terminating with a carriage return, has survived every generation of mobile communications technology. GSM, HSPA, LTE, and 5G modems all speak some dialect of AT commands, often extending the base set with vendor-specific extensions prefixed with AT+, AT^, or AT$.
On Linux, when a USB modem enumerates, the kernel presents its AT command port as a character device, usually /dev/ttyUSB0 or a higher-numbered variant, or sometimes /dev/ttyACM0 depending on how the device describes its interface class. You can open that device with any terminal program and type AT followed by a carriage return. If the modem responds with OK, you have a working AT channel.
ModemManager, the daemon that handles modem management on most modern Linux desktops, probes these character devices, identifies the modem, and then takes control of the AT channel to query capabilities and configure the connection. It has built-in knowledge of vendor-specific AT extensions for hundreds of modems and maintains a plugin architecture for devices that need special handling.
The unlock sequence Lenovo encodes in its blob is a sequence of these AT commands. The bash script that replaces the blob sends the same commands using standard shell utilities, either by writing directly to the character device or using a tool like socat or microcom to manage the serial communication.
A minimal AT command exchange via shell looks like this:
exec 3<>/dev/ttyUSB2
echo -e 'AT\r' >&3
read -t 2 response <&3
echo "$response"
exec 3>&-
That opens the device as a file descriptor, sends an AT probe, reads the response with a timeout, and closes the connection. For a more reliable approach you configure the port first:
stty -F /dev/ttyUSB2 9600 cs8 -cstopb -parenb
USB serial devices often ignore baud rate settings since they run over USB bulk transfers rather than actual serial lines, but getting the line discipline right still matters for reliable reads.
What the Unlock Sequence Likely Contains
Vendor unlock sequences for Fibocom modems typically involve reading device-specific identifiers from the system and using them to derive or look up an authorization value. The IMEI of the modem is one common input. The system’s UUID, readable from /sys/class/dmi/id/product_uuid or via dmidecode, is another. Some implementations read values from UEFI NVRAM variables using efivarfs.
A typical unlock flow might look like:
- Query the modem’s IMEI with
AT+CGSN - Read the system UUID from DMI
- Compute a hash or look up a precomputed value in a table bundled with the blob
- Send a vendor-specific AT command with the derived value as a parameter
- Receive an acknowledgment and transition the modem to an unlocked state
The fact that a 100-line bash script can do this same job is revealing. It means the complexity that justified shipping a compiled binary was not cryptographic in any meaningful sense. If the unlock involves a simple hash of publicly accessible hardware identifiers, the “security” it provides is minimal. The binary blob serves primarily as an obstacle to understanding and modification, not as a genuine security boundary.
This is a pattern worth recognizing. Vendor tooling often gets compiled into binaries not because the logic requires it, but because binaries are less readable than scripts and therefore feel more defensible as intellectual property. The actual computation may be trivial.
ModemManager Integration and the Correct Boot Sequence
One reason this matters practically is that getting the boot sequence right is non-obvious. ModemManager probes and takes control of modem ports aggressively. If it grabs the AT channel before the unlock script runs, the unlock may fail or the port may be unavailable. The correct approach is to configure udev rules to block ModemManager from claiming the device until the unlock service has completed.
A udev rule to hold ModemManager off a specific device looks like:
SUBSYSTEM=="usb", ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0104", ENV{ID_MM_DEVICE_IGNORE}="1"
The ID_MM_DEVICE_IGNORE environment variable tells ModemManager to leave this device alone. After the unlock script completes, it can trigger ModemManager to re-probe with mmcli --scan-modems or by removing and re-adding the udev rule.
The systemd service ordering also matters. The unlock service needs After=systemd-udev-settle.service to ensure the device is present, and ModemManager needs to start after the unlock service. This kind of dependency chain is straightforward in systemd but is exactly the sort of thing that gets encoded in opaque startup scripts inside a vendor binary, making it hard to debug when something goes wrong.
The Broader Context of Blobs on Linux
The Linux ecosystem has a complicated relationship with binary blobs. Firmware blobs for WiFi cards, GPU microcode, Intel ME firmware, and modem unlock sequences occupy a spectrum from “genuinely necessary compiled hardware microcode” to “a shell script that someone compiled to make it feel more official.”
The genuinely necessary ones, like the firmware images that get uploaded to WiFi chipsets or the microcode that patches CPU errata, cannot reasonably be expressed as shell scripts because they run on separate processors with their own instruction sets. The Intel iwlwifi driver loads firmware images into the wireless chipset’s embedded processor; there is no Linux-native way to express that logic without the binary image.
Modem unlock sequences sit at the other end of this spectrum. The modem’s baseband processor is already running its own firmware. The AT command channel is a high-level text interface into that firmware. Sending AT commands via a bash script is functionally identical to sending them via a C program or a compiled Go binary. The choice of delivery mechanism is arbitrary.
This distinction matters because blobs impose real costs. They cannot be audited for security vulnerabilities. They cannot be patched by the distribution. They create dependency problems when the blob is built for a specific kernel ABI or glibc version. They fail silently in ways that are difficult to diagnose. And they make it harder for users to understand what their hardware is actually doing.
The Lenovo situation sits in an interesting middle ground: the company is not shipping a blob because the computation requires it, but because the unlock mechanism presumably involves some validation that Lenovo wants to control. Whether that control serves a legitimate purpose, such as preventing the modem from being used on networks it was not certified for, or whether it is primarily a business arrangement with cellular carriers, is not always clear from the outside.
What the Script’s Existence Demonstrates
The successful replacement of the blob with a bash script demonstrates at minimum that the unlock sequence is deterministic and reproducible given only publicly accessible system data. If it required a secret key stored in a secure enclave or a network round-trip to a Lenovo server, a shell script replacement would not be feasible without that key or server access.
The fact that it is feasible suggests the “lock” is more of a speed bump than a genuine barrier. Users determined enough to read the original article, understand the AT command interface, and write 100 lines of Bash can replace the vendor tooling entirely. The practical benefit is that this approach is transparent, auditable, easy to modify for edge cases, and not tied to whatever support lifecycle Lenovo assigns to its blob.
For anyone running a custom Linux setup on a ThinkPad with WWAN, the methodology documented by Hofstede is worth studying not just for the specific commands but for the general approach: identify what the blob does by observing its behavior, reproduce that behavior using standard interfaces, and wire it into the system service manager cleanly. That approach applies to any proprietary tooling situation where the underlying interface is open even when the vendor implementation is not.
The AT command interface has been publicly documented for nearly fifty years. Any modem that exposes it is, intentionally or not, offering a surface that can be scripted by anyone willing to read the specification. Vendors who build unlock mechanisms on top of that interface should probably expect that those mechanisms will eventually be understood and replicated.