Free Linux Kernel Programming Course — Module 5, Part A
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.
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
makeworks - 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.
| 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.
| 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
| 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 cleanbefore 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, addingmemset()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.
| 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 (first line of .c file) | MODULE_LICENSE() macro | Taint Status |
|---|---|---|
// SPDX-License-Identifier: GPL-2.0-only | MODULE_LICENSE("GPL v2") | Clean |
// SPDX-License-Identifier: GPL-2.0-or-later | MODULE_LICENSE("GPL") | Clean |
// SPDX-License-Identifier: GPL-2.0 OR MIT | MODULE_LICENSE("Dual MIT/GPL") | Clean |
// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause | MODULE_LICENSE("Dual BSD/GPL") | Clean |
| Missing SPDX line | Any | Build warning |
Best Practices: Static Analysis and Licensing in Kernel Module Development
- Make static analysis part of your build routine, not an afterthought. Add
satargets 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};ormemset(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 rawstrcpy(),strcat(), orsprintf(). - 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
- 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
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.
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.
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.
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.
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.
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.
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.rstin 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