GPL compliance auditing for embedded Linux devices means checking whether a vendor actually met the source-code disclosure and license-notice obligations of the GNU General Public License for the software shipped on a physical product. It sits at the intersection of firmware forensics, supply-chain verification, and bootloader analysis. For engineers working with industrial IoT gateways, consumer routers, single-board computers, and medical devices, an audit answers a concrete question: can I obtain, rebuild, and modify the exact GPL-licensed components running on this hardware? This article walks through a repeatable audit method using SPI flash extraction, U-Boot environment inspection, device tree review, and package manifest cross-checking. I have used this sequence on ARM-based routers, NXP i.MX6 industrial controllers, and a Rockchip RK3399 SBC, and the steps below reflect what actually worked in those sessions.

What a GPL Compliance Audit Actually Checks
An audit is not a legal review. It is a technical comparison between the binary firmware image and the source code a vendor publishes. The core checks are:
- Does the vendor offer complete corresponding source for the GPL components in the exact version shipped?
- Are build scripts, configuration files, and toolchain information included where the GPL requires them?
- Is the written offer for source code valid and reachable for the product’s support lifetime?
- Are license texts and copyright notices present in the firmware or accompanying documentation?
- Can the published source be built into a binary that matches the shipped firmware, or at least into a functionally equivalent image?
For embedded devices, the most common failure is not a missing tarball. It is a tarball that contains the upstream kernel source but omits the vendor’s board support patches, the out-of-tree kernel modules, the U-Boot modifications, or the exact configuration used for the build. A second common failure is a source offer that points to a dead URL or a generic download page with no product-specific archive.
Step 1: Inventory the Hardware and Identify Storage
Start with the physical device. Open the enclosure and photograph the board. Identify the main SoC, the boot flash, the RAM, and any removable storage. On most Linux-based embedded devices, the boot firmware and root filesystem live on one of these:
- SPI NOR flash, typically 8 to 64 MB, holding U-Boot, the device tree, and sometimes a recovery kernel
- eMMC, often 4 to 32 GB, holding the main root filesystem
- NAND flash, common on older routers and industrial controllers
- SD card or USB storage, common on SBCs and some medical devices
For a first audit pass, I focus on SPI NOR and eMMC. SPI NOR is the easiest to read non-destructively with a SOIC-8 clip and a programmer like a CH341A or a Dediprog SF100. eMMC requires either a vendor UART shell, a Linux boot from external media, or a BGA interposer, which is more invasive.
Reading SPI NOR Flash
Connect the clip to the flash chip, verify the pinout against the datasheet, and read the full chip. I use flashrom on a Linux host with a CH341A. A typical command is:
flashrom -p ch341a_spi -r router_spi.bin
Read the chip twice and compare SHA-256 hashes. If the hashes differ, check the clip seating and the power supply. A noisy read produces a corrupt image that will mislead every later step.
Reading eMMC
If the device boots to a Linux shell over UART, use dd to copy the eMMC partitions to an external USB drive or over the network. If the device has a locked bootloader and no shell, you may need to desolder the eMMC or use a vendor recovery mode. On one industrial gateway, I found a hidden U-Boot command that dumped the eMMC boot partition to a TFTP server, which avoided desoldering entirely.

Step 2: Extract and Identify the Bootloader
Once you have a flash image, locate the bootloader. On most ARM devices, U-Boot is the bootloader. Search the image for the U-Boot magic string or the version banner. I use binwalk and strings:
binwalk router_spi.bin
strings -a router_spi.bin | grep -i "U-Boot"
Typical output includes a line like U-Boot 2017.09 (Oct 12 2018 - 14:22:31 +0800). Record the exact version and build date. This is the first concrete data point for the audit. If the vendor’s published source archive contains U-Boot 2017.09 but the binary reports 2017.09 with a different build date, that is not automatically a violation, but it means the vendor’s source may not match the shipped binary. You need to dig further.
Checking U-Boot Environment and Commands
If you can interrupt U-Boot over the serial console, run printenv and help. The environment variables often reveal the boot command, the kernel load address, the device tree load address, and the root filesystem location. The command list shows whether the vendor added custom commands. Custom commands are a strong signal that the vendor modified U-Boot. Those modifications are GPL-covered and must be in the source archive.
On a consumer router I audited, the vendor’s U-Boot had a custom factory_reset command that wrote calibration data to a specific NAND offset. The vendor’s published U-Boot source did not include that command. That was a clear source-completeness failure.
Step 3: Extract the Kernel and Device Tree
The kernel and device tree are usually stored as separate partitions or as a combined FIT image. Use binwalk to find the kernel image. Common formats are zImage, uImage, and FIT. For a uImage, the header contains the load address, entry point, and a CRC. For a FIT image, use dumpimage from U-Boot tools to list the components:
dumpimage -l kernel.itb
The device tree blob (DTB) is a binary file that describes the hardware to the kernel. Extract it and decompile it with dtc:
dtc -I dtb -O dts device.dtb -o device.dts
The decompiled device tree shows the exact hardware configuration: which UARTs are enabled, which GPIOs are assigned, which drivers are bound, and which vendor-specific compatible strings are used. Compare the compatible strings against the kernel source the vendor published. If the vendor’s kernel source does not contain a driver matching a compatible string in the shipped DTB, the source is incomplete.
Kernel Version and Configuration
Extract the kernel version string from the binary. On a running device, uname -a gives the version and build date. On a static image, search for the version banner with strings. Then check whether the vendor published the kernel configuration. The GPL requires the scripts and configuration used to control compilation. A vendor that publishes only the vanilla kernel source without the .config file has not met the obligation.
I audited a medical device controller that shipped a 4.9 kernel with a vendor-specific .config that enabled a proprietary out-of-tree driver for a sensor interface. The vendor’s source archive contained the vanilla 4.9 kernel and a tarball of the out-of-tree driver, but no .config. Rebuilding the kernel without the configuration produced a binary that did not match the shipped image and did not support the sensor. That is a practical compliance failure even if the vendor believed it had published enough.
Step 4: Inventory the Root Filesystem and Packages
Mount the root filesystem image or extract it from the flash dump. On many devices, the root filesystem is a SquashFS or UBIFS image. Use unsquashfs or ubireader_extract_files to unpack it. Then inventory the installed packages. On Debian-based systems, check /var/lib/dpkg/status. On OpenWrt-based systems, check /usr/lib/opkg/status. On Yocto-based systems, look for the package feed manifest or the /etc/version file.
For each GPL-licensed package, record the exact version. Then compare that list against the vendor’s published source archive. The most common mismatch is a vendor that publishes the source for the base distribution but not for the additional GPL packages it added. On one router, the vendor added busybox applets, a modified hostapd, and a GPL-licensed iperf build, but the source archive contained only the kernel and U-Boot. The root filesystem inventory made the gap obvious.
Checking for Modified GPL Binaries
A binary can be modified without a version bump. To detect this, compare the shipped binary against the binary built from the vendor’s published source. If the vendor publishes a build script, run it in a clean container and compare the resulting binary’s hash against the shipped binary. If the hashes differ, disassemble or diff the binaries to see what changed. On one industrial controller, the vendor’s published busybox source built to a binary that differed from the shipped binary by a single added applet. The vendor had patched busybox to add a custom diagnostic command and had not published the patch. That is a GPL violation.

Step 5: Verify the Written Offer and License Notices
The GPL allows a vendor to satisfy the source-code obligation in two main ways: ship the source with the product, or provide a written offer valid for at least three years. For embedded devices, the written offer is usually in the user manual, on a paper insert, or on a label on the device. Check the offer carefully:
- Does it name the exact product and firmware version?
- Does it give a physical address or a working URL?
- Does the URL resolve to a product-specific source archive, or just a generic page?
- Is the offer still valid, or has the vendor removed the archive after a product refresh?
I have seen written offers that point to a corporate download page with no product-specific link. The user must search a support portal, and the search returns nothing. That is a practical failure of the written offer. I have also seen offers that list a URL that redirects to a marketing page. The offer must be meaningful, not a dead end.
License Texts and Copyright Notices
Check the firmware image and the device packaging for the GPL license text and the copyright notices. On many devices, the license text is in /usr/share/doc or in a licenses directory on the root filesystem. If the vendor ships a device with no GPL text and no written offer, the distribution is not compliant. The GPL requires that recipients receive a copy of the license and that the source offer be conspicuous.
Step 6: Attempt a Rebuild
The strongest audit step is to rebuild the GPL components from the vendor’s published source and compare the result to the shipped binaries. This is time-consuming but decisive. Set up a clean build environment matching the vendor’s stated toolchain. For a Yocto-based device, the vendor should publish the BSP layer, the machine configuration, and the build manifest. For a Buildroot-based device, the vendor should publish the Buildroot tree and the defconfig. For a Debian-based device, the vendor should publish the source packages and the build scripts.
On a Rockchip RK3399 SBC I audited, the vendor published a Yocto BSP layer that built successfully, but the resulting kernel binary differed from the shipped kernel by a set of out-of-tree patches for a camera interface. The vendor had not published those patches. The rebuild made the gap visible in a way that a source-archive inspection alone could not.
Common Audit Findings and How to Document Them
Document every finding with the exact binary hash, the source archive hash, the tool versions, and the commands used. A useful audit report includes:
- Device identification: vendor, model, hardware revision, firmware version
- Flash layout: partition table, offsets, sizes, filesystem types
- Bootloader version and any custom commands
- Kernel version, configuration, and device tree compatible strings
- Root filesystem package inventory with versions
- Source archive contents and any missing components
- Rebuild results and binary comparison hashes
- Written offer text and URL status
This documentation is useful whether you are reporting a compliance issue to a vendor, preparing a request for source code, or building an internal compliance database for devices your organization deploys.
FAQ
What is the minimum source code a vendor must provide for a GPL-compliant embedded device?
The vendor must provide the complete corresponding source for all GPL-licensed components in the exact version shipped, including any modifications, plus the scripts and configuration files used to control compilation and installation. For a typical Linux device, that means the kernel source with the vendor’s patches and .config, the U-Boot source with board support, the device tree sources, and the source for all GPL-licensed userspace packages. A vanilla upstream tarball without the vendor’s changes is not sufficient.
How can I tell if a vendor modified U-Boot or the kernel without publishing the changes?
Compare the shipped binary against a binary built from the vendor’s published source. For U-Boot, check the version banner, the command list, and the environment variables for custom entries. For the kernel, check the device tree compatible strings against the drivers in the published source, and compare the kernel configuration. A custom U-Boot command or a device tree node with no matching driver in the published source is a strong signal of unpublished modifications.
What should I do if a vendor’s written offer for source code points to a dead URL?
Document the URL, the date you checked it, and the HTTP response. Contact the vendor in writing and request the source code for the specific product and firmware version. If the vendor does not respond or cannot provide the source, the device is not GPL-compliant. You can then decide whether to report the issue to the copyright holders of the GPL components, to a compliance organization, or to your own legal team if the device is part of your supply chain.
Is a binary firmware image enough to prove a GPL violation?
A binary image alone is usually not enough. You need the vendor’s published source archive, the build environment, and a comparison. The binary can show the version strings, the device tree, the package inventory, and the custom commands, but the violation is the gap between the binary and the source the vendor offers. Document both sides of that gap before drawing a conclusion.
This audit method is the foundation for a deeper topic: reconstructing a vendor’s build system from a firmware image when no source is published at all. That reverse-engineering path is the next article in this series.