How to Audit a Device for GPL Compliance: A Firmware Engineer’s Field Guide

Close-up of embedded circuit board being probed with multimeter during GPL audit
A methodical hardware inspection often reveals the first clues about the software inside.

I don’t crack open a new router, IoT gateway, or industrial controller looking for build quality or antenna gain. I’m looking for the GPL. The GNU General Public License is the legal and ethical backbone of so many embedded Linux systems, and yet manufacturers violate it with a regularity that still manages to disappoint me. After years spent untangling proprietary firmware blobs from the open-source code they lean on, I’ve settled into a systematic way of auditing devices for compliance. This isn’t a legal process—it’s a technical one. My job is to collect verifiable evidence that a product ships with GPL-licensed components, then figure out whether the vendor actually meets its obligations to offer the matching source code.

This guide walks you through a complete hardware and software audit. We start the moment a device lands on your bench and don’t stop until you’re staring at a diff against a known upstream kernel. I’ll assume you’re comfortable with a soldering iron, a serial terminal, and a Linux shell. The methods apply to any embedded device running Linux, but the details lean toward ARM and MIPS-based consumer networking gear. That’s where the worst habits live.

Step 1: Physical Inspection and Firmware Extraction

Before any software analysis, I document the hardware. Pop the case, photograph the PCB from both sides. Note every IC, especially the flash memory chips—SPI NOR, NAND, eMMC—the main SoC, and any microcontroller that might be off running its own firmware. The markings on a flash chip tell you capacity and interface type, and that dictates your extraction method. For a typical SPI flash, a dirt-cheap CH341a programmer with a SOIC-8 clip will dump the contents without desoldering. If the device uses eMMC or managed NAND, you’ll probably need to get into the bootloader console and pull the image over serial or Ethernet.

Engineer holding SOIC-8 clip attached to flash memory chip on router board
A direct flash dump is the most reliable starting point for a binary firmware analysis.

Find the console pins. Most boards lay out a 4-pin or 6-pin UART header. Grab a multimeter, locate ground, then probe the remaining pins while the device powers up to find TX and RX at common baud rates—115200 is the safe bet. If you get clean boot logs, you’ve got a live feed into the bootloader and kernel startup. Save the whole thing. It’ll spill the kernel version, the U-Boot build string, and often the list of loaded drivers. That all becomes evidence later.

Identifying the Bootloader and Kernel Version

Inside the boot log, hunt for strings like U-Boot 2013.07 or Linux version 4.4.198. Write down the exact version. Many vendors tweak U-Boot to suppress output or lock out interactive access. If the bootloader is locked, you might still interrupt it by hammering a key sequence during those first seconds of power-on. Common ones: Ctrl+C, Esc, or something vendor-specific like “tpl“. Get a U-Boot prompt and you can dump the entire flash over TFTP or via the md command piped through your serial terminal. That’s gold when a clip-on programmer isn’t practical.

Step 2: Unpacking and Analyzing the Firmware Image

Once you’ve got a raw firmware dump, the real work starts: carving it into its pieces. Embedded firmware images are rarely one filesystem. They’re a layer cake of bootloader, kernel, and one or more filesystem partitions. I run binwalk against the binary to scan for magic bytes and known signatures. The command I reach for first:

binwalk -e --dd='.*' firmware_dump.bin

The -e flag auto-extracts recognized file types. The --dd option carves raw sections based on signatures, which helps when binwalk stumbles on a non-standard header. After extraction, you’ll typically find a compressed kernel image—zImage or uImage—and a root filesystem in SquashFS, JFFS2, or UBIFS. Mount it or run unsquashfs against it, and you’re browsing the filesystem exactly as it sits on the running device.

Dual monitor setup showing hex dump and terminal with binwalk analysis of firmware
Binwalk output quickly reveals the partition table and compression formats used in a firmware image.

Cataloging GPL-Licensed Components

With the root filesystem exposed, I go through every executable, library, and kernel module methodically. The kernel itself is GPLv2. Check /lib/modules/ for all the .ko files. Run modinfo on each and look at the license tag. A clean, GPL-compatible module shows license: GPL. A proprietary module might say proprietary or just leave the tag out. Any non-GPL kernel module that links against GPL-only symbols—check /proc/kallsyms on a live system if you’ve got shell access—is a serious problem. The kernel’s GPL-only symbol export mechanism exists precisely to stop proprietary modules from using certain internal interfaces. Find a module doing that, and the vendor is almost certainly in violation.

Beyond the kernel, I look for user-space binaries that are clearly derived from GPL projects. In networking gear, the usual suspects: BusyBox (GPLv2), iptables (GPLv2), dnsmasq (GPLv2 or v3), and udhcpd (GPLv2). Identify them by comparing the binary’s strings against known upstream versions. A simple check:

strings busybox | grep -i 'busybox v'

That usually spits out something like BusyBox v1.22.1. Record every GPL component and its version. This list is your compliance demand in raw form. The vendor has to provide source for these exact versions, plus any changes they made.

Step 3: Verifying Source Code Availability

Now you’ve got your list. Go to the manufacturer’s website. Look for an open-source or legal page—often a tiny text link buried in the footer or a support section. If you find a source download, grab it and compare the archive against what’s on the device. The laziest dodge is to offer a pristine upstream tarball that doesn’t match the modified binary actually shipped. To check, extract their source, build it with the right cross-compiler, and compare the resulting binary against the device’s binary. A byte-for-byte match won’t happen—timestamps and toolchain differences make that impossible—but the symbol table and functionality should line up. Use diff on the output of objdump -d. Any significant mismatch points to hidden changes.

If no source is offered at all, the violation is obvious. But the GPL requires a written offer valid for three years, not necessarily a website download. Check the physical box, the user manual, any printed insert. If a written offer exists but the vendor ignores it or it’s unreachable, document your attempts to contact them and request the source. Save the dates, the emails, every response. That paper trail is what carries weight if this ever reaches a copyright holder or a lawyer.

Checking for Copyleft Compliance in Derivative Works

Some vendors modify GPL code and then release their changes under a proprietary license, or they jam GPL and non-GPL code into a single binary without offering the full corresponding source. Use readelf or objdump to check the dynamic linking of user-space executables. If a proprietary binary links against a GPL library—say, a closed-source management daemon tied to libgpl.so—the entire work must be distributed under the GPL. The vendor has to provide source for that daemon too. This is a tricky corner of GPL interpretation, but the Free Software Foundation’s position is settled: dynamic linking creates a derivative work. Flag any such binary in your report.

Step 4: Scripted Verification with Buildroot and Yocto

For a tighter comparison, I build a known clean rootfs using Buildroot or Yocto, matching the same versions of BusyBox, the kernel, and other GPL packages. Boot that clean rootfs on an equivalent dev board, or in QEMU. Then rsync the device’s rootfs to a local directory and recursively diff the two. I pay attention to config files under /etc, init scripts, and any custom binaries. Differences in /etc/inittab, /etc/init.d/rcS, or odd *.so files often point to vendor changes that need to be disclosed. If the vendor’s GPL source archive doesn’t include those changes, the offering is incomplete.

This step takes time, but the evidence is bulletproof. I’ve caught vendors shipping a modified kernel with custom drivers for a proprietary wireless chipset, where the driver source was simply missing from their source drop. A diff against a clean Buildroot kernel showed a new drivers/net/wireless/vendor_chip directory. That directory’s absence from the source archive is a direct violation. No ambiguity.

Step 5: Documenting and Reporting Findings

Pull everything into a structured audit report. It should cover:

  • Device identification: Model, hardware revision, firmware version, purchase date.
  • Extraction method: How you got the firmware—flash dump, TFTP, serial.
  • GPL component inventory: Name, version, license, and location on the filesystem.
  • Source availability: URL or written offer details, date of request, and the response you got.
  • Compliance gaps: Specific missing source files, unreachable written offers, proprietary modules linked to GPL code.
  • Evidence: Boot logs, binwalk output, objdump diffs, email threads.

The report does two things. It hands the vendor a clear, actionable list of what needs fixing. And it gives a copyright holder—often the Free Software Foundation, Software Freedom Conservancy, or an individual kernel developer—the technical basis for a copyright infringement claim. Without a detailed audit, enforcement has no teeth.

A Note on Ethical Boundaries

An audit is a fact-finding mission, not a weapon. I start from a place of good faith until the evidence shows otherwise. Plenty of smaller vendors genuinely don’t understand their GPL obligations. A well-documented audit gives them room to come into compliance on their own. I always send the report to the vendor’s engineering or legal contact before I publish anything or share it with enforcement groups. That’s professional courtesy, and it often speeds up the fix.

FAQ: GPL Compliance Audits for Embedded Devices

What is the most common GPL violation you find in consumer routers?

The biggest one, by far, is shipping a modified Linux kernel or BusyBox with zero source code offered. Many manufacturers grab an upstream SDK from a chip vendor, make small tweaks, and never set up a public repo. The second most common? Offering source code that’s incomplete—usually missing the kernel config or those “proprietary” drivers that link right into the GPL kernel anyway.

Do I need legal training to perform a GPL audit?

No. The technical side needs no legal background. You’re collecting objective facts about what software sits on the device and what source the vendor actually makes available. Leave the legal interpretation to copyright holders and their attorneys. But a solid technical audit is the foundation for any successful legal action, so your work as an engineer is what makes it all possible.

How can I tell if a binary is statically linked against a GPL library?

Run ldd on the target binary, or check the dynamic section with readelf -d. If the binary doesn’t list the library as a shared dependency but its strings clearly come from that library, static linking is likely. Even better, disassemble the binary and look for function names that match the library’s API. Find those, and the binary was distributed without source—that’s almost certainly a violation of the GPL’s requirement to provide complete corresponding source for the whole work.

What if the vendor offers source code only for the exact GPL packages but not their build scripts?

The GPL requires “scripts used to control compilation and installation of the executable.” So if the vendor’s build process uses custom Makefiles, configuration scripts, or toolchain wrappers, those belong in the corresponding source. This gets overlooked a lot. If you can’t reproduce their binaries from the provided source with reasonable effort, the source offering is incomplete.