Static Analysis & Licensing in Linux Kernel Modules

Linux Kernel Module Static Analysis and Licensing | Free Linux Kernel Programming Course
Static Analysis & Licensing in Linux Kernel Modules

Free Linux Kernel Programming Course — Module 5, Part A

⏱ 25 min read
🎯 Beginner–Intermediate
🐧 Linux 6.x
🛡 Security Focused

Writing a Linux kernel module that compiles without errors is just the first step. In this free Linux kernel development course tutorial, you will learn two equally important skills: running static analysis on your kernel module source code to catch bugs before they become kernel panics, and correctly setting the license for your kernel module so the kernel community and tools treat it the right way.

These are not optional extras. In real-world embedded Linux driver development, skipping static analysis is how subtle buffer overflows and use-after-free bugs slip into production. Getting the license wrong means the kernel marks your module as “tainted” and the community will not help you debug issues. Both topics matter from day one.

📚 What You Will Learn
What static analysis means in kernel development How to integrate analysis tools into your Makefile How to read and act on static analysis warnings Why uninitialized variables cause real kernel bugs Linux kernel module licensing rules MODULE_LICENSE() macro and accepted values Dual licensing and upstream contribution rules SPDX identifiers in kernel source files
✅ Prerequisites

Before reading this tutorial, you should be comfortable with:

  • Writing and compiling a basic “Hello World” kernel module
  • Understanding what a Makefile does and how make works
  • Basic C programming — pointers, arrays, strings
  • Having a Linux 6.x system (physical or virtual) with kernel headers installed

Why Static Analysis Matters in Free Linux Kernel Development

User-space programs run inside a protected process boundary. If your user-space code has a buffer overflow, the operating system can catch it and kill the process. The damage is contained. The kernel has no such protection. A bug in a kernel module runs with full privilege — there is no safety net. A bad pointer dereference does not print an error and exit cleanly; it triggers an Oops or a full kernel panic.

This is exactly why static analysis — examining your source code for problems before it even runs — is treated seriously in kernel development. You want the tool to catch mistakes at compile time, not at 3 AM when a customer’s embedded device crashes in the field.

Static analysis tools read your C source files and look for patterns that are known to cause bugs: unsafe string functions, uninitialized variables, potential buffer overflows, incorrect type usage, and many more. They do not run your code. They reason about it.

How Static Analysis Fits Into the Kernel Module Build Workflow
Write C Source
(module.c)
Run Static Analyzer
(before compilation)
Fix Warnings Compile
make → .ko
insmod / Load
Module
Static analysis runs on source code — not on the compiled .ko binary. Catch bugs before they become kernel panics.

Adding Static Analysis Targets to Your Kernel Module Makefile

A well-structured kernel module Makefile does more than just build. In professional kernel development, you also add targets for cleaning, style checking, and static analysis. When you press Tab twice after make in a shell, a well-written Makefile reveals all its available targets — this is good practice.

A typical professional Makefile for a kernel module might expose targets like these:

$ make [tab][tab]
all        clean      help       install    sa_cppcheck   sa_gcc
tarxz-pkg  checkpatch code-style indent     sa            sa_flawfinder  sa_sparse

The sa prefix stands for static analysis. Each sa_* target runs a different analysis tool against your source files. This approach lets you run a quick check or a deep check independently from the build itself.

Here is what a minimal sa_flawfinder Makefile target looks like — not copied from any existing project, but written freshly to show the idea clearly:

# Makefile target for flawfinder static analysis
sa_flawfinder:
	@echo "--- Running flawfinder static analysis ---"
	flawfinder *.c

And a target for sparse, the kernel’s own static checker:

# Makefile target for sparse
sa_sparse:
	@echo "--- Running sparse ---"
	make C=2 CHECK="sparse" modules

Running make sa_flawfinder tells make to clean first (to ensure a fresh state), then invoke flawfinder on all .c files in the directory. The output tells you exactly which line has a potential problem and gives it a severity rating.

Understanding Static Analysis Warnings — A Real Kernel Bug Pattern

Let us walk through a concrete example of what a static analyzer actually catches and why it matters. This is the kind of thing that trips up developers new to kernel module programming — including very experienced C programmers coming from user-space backgrounds.

The Uninitialized Buffer Problem

Consider a function inside a kernel module that declares a local character array to build up a status message string, then calls strlen() on it to find out how long it already is before appending more text. In user-space this might seem harmless. In the kernel it is a ticking time bomb.

The reason is straightforward: in C, a local variable that is declared but not initialized has random content. The stack memory it occupies contains whatever bytes happened to be there from previous function calls. When strlen() scans that buffer looking for a null terminator (\0), it may walk past the intended end of the array — this is called a buffer over-read.

Why Calling strlen() on an Uninitialized Buffer Is Dangerous
Scenario Buffer State strlen() Result Risk
Initialized: buf[0] = '\0' Known: empty string 0 — correct Safe ✓
Uninitialized: stack garbage Random bytes, may have no ‘\0’ in range Unpredictable — could be 300 Over-read ✗
Uninitialized in kernel context May read past array end into kernel memory Reads kernel stack data Kernel Oops / Leak ✗

Flawfinder correctly identifies this pattern and flags it as CWE-126 — the official Common Weakness Enumeration number for buffer over-read. When you see a CWE number in a static analysis report, you can look it up at cwe.mitre.org to understand exactly what class of security vulnerability you are dealing with.

The Fix: Always Initialize and Use Safe String Functions

The fix has two parts. First, always initialize your buffer at declaration time. Second, replace unsafe string functions with their length-bounded equivalents.

In kernel code, the safe alternatives to the standard C string functions are:

/* Instead of strlen() on potentially uninitialized data: */
/* Always initialize first */
char msg[256];
memset(msg, 0, sizeof(msg));   /* OR: char msg[256] = {0}; */

/* Instead of strcat() which has no bounds checking: */
strlcat(dst, src, sizeof(dst));   /* kernel's safe bounded version */

/* Instead of strcpy() which can overflow: */
strlcpy(dst, src, sizeof(dst));   /* kernel's safe bounded copy */

/* Instead of sprintf() which can overflow: */
snprintf(msg, sizeof(msg), "value: %d", val);  /* always use snprintf */

The key difference between strlcat() and strncat() is subtle but important. strncat()‘s third argument is the maximum number of bytes to append, not the total buffer size. This makes it easy to accidentally overflow. strlcat()‘s third argument is the total buffer size — much safer and clearer in intent.

Tools Available for Kernel Module Static Analysis

Static Analysis Tools Commonly Used in Linux Kernel Development
Tool What It Checks Kernel-Aware? Install
sparse Kernel-specific type mismatches, locking errors, address space violations Yes — designed for kernel apt install sparse
flawfinder Dangerous C functions, buffer issues, CWE-mapped risks No — general C/C++ apt install flawfinder
cppcheck Null pointer dereference, memory leaks, undefined behavior Partial apt install cppcheck
GCC -W flags Uninitialized vars, implicit conversions, unused results Partial Built into GCC
checkpatch.pl Kernel coding style violations Yes — kernel script In kernel source tree: scripts/checkpatch.pl

Recommendation for beginners: Start with sparse because it understands kernel-specific annotations (__user, __iomem, locking attributes) that general-purpose tools completely miss. Use flawfinder as a second pass to catch dangerous C function usage. Run checkpatch.pl before every commit to keep your code style consistent with the kernel community’s expectations.

Running Sparse on Your Kernel Module

# Install sparse
sudo apt install sparse

# Run sparse via the kernel build system (Linux 6.x)
make C=1 CHECK="sparse" -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# C=1 checks only modified files
# C=2 checks all files regardless of modification time

# Common sparse warning you will see if you mix user and kernel pointers:
# warning: incorrect type in argument 1 (different address spaces)
# expected void *
# got void [noderef] __user *

The [noderef] __user annotation is something sparse understands and GCC does not. It tells the checker that this pointer points to user-space memory and must go through copy_to_user() / copy_from_user() — never dereferenced directly in kernel code. If you accidentally dereference it, sparse catches it immediately. This is one reason sparse is irreplaceable in kernel development.

Common Mistakes When Running Static Analysis on Kernel Modules

  • Ignoring all warnings as “noise”: Static analyzers do produce false positives. But the approach of ignoring everything means you will also ignore real bugs. Triage each warning; understand why it fired before dismissing it.
  • Running analysis without clean build: Always run make clean before analysis. Stale object files can cause the analyzer to miss files or process them incorrectly.
  • Fixing the warning without understanding it: If flawfinder warns about strlen() on an uninitialized buffer, adding memset() before it fixes the warning but you should also ask why the buffer was uninitialized in the first place — the design may need rethinking.
  • Not using sparse for kernel code: General-purpose C analyzers do not know about kernel address spaces, RCU locking, or interrupt context restrictions. Sparse was built specifically for the kernel and is the most valuable tool in your kit.

Linux Kernel Module Licensing — Why It Is Not Optional

The Linux kernel itself is licensed under the GNU General Public License version 2 (GPL-2.0). This is a well-established legal reality that the kernel project will maintain. When you write a kernel module, you need to declare its license using the MODULE_LICENSE() macro — and the value you choose has real consequences.

When a module is loaded, the kernel checks the license tag. If the license is not a recognized free/open-source license, the kernel prints a warning in dmesg and marks itself as tainted. A tainted kernel is one where the kernel community — and kernel bug reporters — will tell you “we cannot help you debug this because your loaded module may be causing the problem and we cannot see its source.” Taint is a serious mark in professional embedded Linux development.

Kernel Module License Decision: What Happens at insmod Time
MODULE_LICENSE(“GPL v2”)
or “GPL” or “Dual BSD/GPL”
MODULE_LICENSE(“Proprietary”) No MODULE_LICENSE()
✓ Kernel NOT tainted
✓ Access to GPL-only kernel symbols
✓ Community will help with bugs
✓ modinfo shows license cleanly
⚠ Kernel marked TAINTED (P flag)
✗ No access to GPL-only symbols
⚠ Community may decline to help
⚠ Legal and technical restrictions
✗ Build warning at compile time
✗ Kernel TAINTED when loaded
✗ No module metadata at all
✗ Not acceptable for any distribution

The MODULE_LICENSE() Macro and Accepted Values

The kernel header include/linux/module.h defines exactly which license strings are accepted. Here is a summary of the currently valid ones:

/* Valid MODULE_LICENSE() values — from include/linux/module.h */

MODULE_LICENSE("GPL");              /* GNU GPL v2 or later */
MODULE_LICENSE("GPL v2");          /* GNU GPL v2 exactly */
MODULE_LICENSE("GPL and additional rights"); /* GPL v2 plus extra permissions */
MODULE_LICENSE("Dual BSD/GPL");    /* Choose: GPL v2 OR BSD */
MODULE_LICENSE("Dual MIT/GPL");    /* Choose: GPL v2 OR MIT */
MODULE_LICENSE("Dual MPL/GPL");    /* Choose: GPL v2 OR Mozilla Public License */
MODULE_LICENSE("Proprietary");     /* Closed source — kernel will be tainted */

For any new kernel module you write for learning purposes or for contributing upstream, use MODULE_LICENSE("GPL v2"). It is the most precise declaration and exactly what the kernel project expects.

GPL-Only Kernel Symbols

Some kernel functions are exported with EXPORT_SYMBOL_GPL() rather than EXPORT_SYMBOL(). This means only GPL-licensed modules can call them. If your module has MODULE_LICENSE("Proprietary"), the linker will refuse to link against these symbols. You will see an error like:

ERROR: modpost: GPL-incompatible module your_module.ko uses GPL-only symbol 'some_kernel_function'

This is intentional. Many of the most useful internal kernel APIs — including certain tracing hooks, DMA mapping helpers, and scheduler interfaces — are GPL-only. Writing a proprietary kernel module means you lose access to a significant portion of the kernel’s internal API surface.

Dual Licensing Kernel Modules

Dual licensing means your code is available under two licenses simultaneously — the user picks whichever applies to their situation. For example, MODULE_LICENSE("Dual MIT/GPL") means someone building with the Linux kernel gets GPL-2.0 terms, while someone using the same C code in a non-Linux context can use it under the MIT license.

This is a legitimate and well-understood pattern in open-source kernel development. Several real kernel drivers use dual licensing. However, the key rule is: when the module is running inside the Linux kernel, the GPL-2.0 is the applicable license. The dual tag does not make your module proprietary — it is still GPL-2.0 while running in the kernel.

Upstream Contribution: GPL-2.0 Only

If your goal is to contribute your kernel module to the mainline Linux kernel, the answer is simple: MODULE_LICENSE("GPL v2") with no dual licensing. The Linux kernel project only accepts contributions under GPL-2.0. This is not negotiable and not likely to change.

SPDX License Identifiers in Linux 6.x Kernel Files

Modern kernels enforce an additional rule: every source file must have an SPDX license identifier as its very first line. SPDX (Software Package Data Exchange) is a standard format for license declarations that tools can parse automatically. This has been mandatory in the Linux kernel since around kernel 4.14 and is fully standard in Linux 6.x.

Here is what the first line of your kernel module source file should look like:

// SPDX-License-Identifier: GPL-2.0-only
/*
 * my_driver.c - Example kernel module for EmbeddedPathashala free Linux kernel course
 *
 * Author: Your Name
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example module for free Linux kernel development course");
MODULE_VERSION("1.0");

The SPDX line and the MODULE_LICENSE() macro should agree. GPL-2.0-only corresponds to "GPL v2". Other common combinations:

SPDX Identifier ↔ MODULE_LICENSE() Mapping for Linux 6.x
SPDX Identifier (first line of .c file) MODULE_LICENSE() macro Taint Status
// SPDX-License-Identifier: GPL-2.0-onlyMODULE_LICENSE("GPL v2")Clean
// SPDX-License-Identifier: GPL-2.0-or-laterMODULE_LICENSE("GPL")Clean
// SPDX-License-Identifier: GPL-2.0 OR MITMODULE_LICENSE("Dual MIT/GPL")Clean
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-ClauseMODULE_LICENSE("Dual BSD/GPL")Clean
Missing SPDX lineAnyBuild warning

Best Practices: Static Analysis and Licensing in Kernel Module Development

  • Make static analysis part of your build routine, not an afterthought. Add sa targets to your Makefile from the beginning of a project, not when something goes wrong.
  • Zero-initialize all local buffers in kernel code. Use char buf[SIZE] = {0}; or memset(buf, 0, sizeof(buf)); at the top of the function before any string operations.
  • Always use length-bounded string functions. In the kernel: strlcpy(), strlcat(), snprintf(). Never raw strcpy(), strcat(), or sprintf().
  • Treat sparse warnings as seriously as compiler errors. Sparse catches kernel-specific bugs that no general tool finds — address space mismatches, missing lock annotations, RCU violations.
  • Add // SPDX-License-Identifier: as line 1 of every .c and .h file you write. This is now standard practice in Linux 6.x and required for upstream submissions.
  • Use MODULE_LICENSE("GPL v2") for new modules unless you have a specific legal reason to use something else and have consulted your organization’s legal team.

Security Considerations in Kernel Module Code Quality

Static analysis is ultimately a security practice as much as a code quality practice. In kernel development, bugs are not just crashes — they are potential privilege escalation vectors. A buffer over-read in a kernel module could leak kernel stack contents to a user-space process. A buffer overflow could allow an attacker to overwrite kernel function pointers.

The CWE (Common Weakness Enumeration) system that flawfinder references is maintained by MITRE and is used by the CVE (Common Vulnerabilities and Exposures) system. When a kernel security bug gets a CVE number, there is almost always a corresponding CWE root cause. Understanding these mappings helps you think like a security reviewer, not just a feature developer.

Key security-relevant static analysis findings to always act on in free Linux kernel development:

  • CWE-119 / CWE-120: Buffer overflows — any use of unbounded string functions
  • CWE-126: Buffer over-read — strlen on uninitialized or non-null-terminated data
  • CWE-476: NULL pointer dereference — dereferencing a pointer without checking it first
  • CWE-416: Use after free — accessing memory after it has been freed (kfree’d)
  • CWE-362: Race condition — accessing shared kernel data without proper locking
📝 Key Takeaways
  • Static analysis runs on source code before compilation and catches bugs that would otherwise cause kernel panics or security vulnerabilities at runtime.
  • Sparse is the most valuable static analysis tool for kernel module development because it understands kernel-specific address space and locking annotations.
  • Always initialize local buffers before use. Uninitialized buffers with string functions like strlen() cause CWE-126 buffer over-read bugs.
  • Use kernel-safe string functions: strlcpy(), strlcat(), snprintf() instead of their unbounded equivalents.
  • MODULE_LICENSE() is mandatory. For new modules targeting the mainline kernel, use "GPL v2".
  • Proprietary modules taint the kernel and lose access to GPL-only exported symbols.
  • Every source file in Linux 6.x must begin with an SPDX license identifier on the first line.

Frequently Asked Questions — Free Linux Kernel Development Course

Q: Is static analysis the same as unit testing for kernel modules?

No. Static analysis examines source code without running it. Unit testing (which the kernel does have, via KUnit) runs test cases against the actual code. They complement each other. Static analysis is faster and catches a different class of bugs. You should do both.

Q: Can I use AddressSanitizer (ASan) with kernel modules?

Yes — the kernel has KASAN (Kernel Address Sanitizer), which is a runtime memory error detector similar to ASan. It is enabled via CONFIG_KASAN=y in the kernel config. KASAN is excellent for finding buffer overflows and use-after-free bugs that static analysis might miss. For a development or test kernel, always enable KASAN. Do not use it in production due to memory and performance overhead.

Q: My module has no MODULE_LICENSE() and still loads. Is that okay?

It may load, but the kernel will print a warning and mark itself as tainted. In newer kernel versions and distributions, loading a module without a license declaration may be blocked entirely depending on kernel lockdown mode settings. Always add MODULE_LICENSE() — there is no valid reason to omit it.

Q: Does dual licensing mean I can keep my driver source code private?

No. Dual licensing in the context of a kernel module means the module is available under two open-source licenses. It does not mean one option is “proprietary.” If you need a genuinely proprietary kernel module, the only way is MODULE_LICENSE("Proprietary"), which comes with the kernel taint penalty and loss of GPL-only symbols. The legal complexity of proprietary kernel modules is significant — consult a lawyer.

Q: How do I check if a loaded module is tainted?

Check /proc/sys/kernel/tainted — a value of 0 means no taint. You can also run dmesg | grep -i taint or cat /proc/sys/kernel/tainted. Each bit in the taint value represents a different taint reason. The kernel documentation explains each bit at Documentation/admin-guide/tainted-kernels.rst in the kernel source tree.

Q: Is flawfinder still worth using in Linux 6.x development?

Yes. Flawfinder is not kernel-specific, but it catches dangerous C function patterns that are equally dangerous in kernel code — often more so. Think of it as a complement to sparse: sparse catches kernel-specific issues, flawfinder catches general unsafe C patterns. Run both as part of your analysis pipeline.

Q: Where can I find the full list of SPDX identifiers for kernel source files?

The kernel source tree has a LICENSES/ directory at the top level. Under it you will find preferred/ (recommended licenses), dual/ (for dual-licensed code), and deprecated/ (older identifiers no longer recommended). For new files, always pick from preferred/. The SPDX organization also maintains the full list at spdx.org.

Authoritative References

  • MITRE CWE Database: https://cwe.mitre.org — look up any CWE number from flawfinder output
  • Linux Kernel LICENSES/ directory: In the kernel source tree — definitive list of acceptable license identifiers
  • GNU GPL FAQ: https://www.gnu.org/licenses/gpl-faq.html — answers common licensing questions
  • SPDX Standard: https://spdx.org — the SPDX license identifier standard
  • Kernel Documentation — Tainted Kernels: Documentation/admin-guide/tainted-kernels.rst in your kernel source

Continue Your Free Linux Kernel Programming Journey

EmbeddedPathashala offers 100% free courses on Linux kernel development, Linux device drivers, and embedded systems — no registration required.

Visit EmbeddedPathashala

Leave a Reply

Your email address will not be published. Required fields are marked *