Design and Implementation of ELF File Signature Verification Mechanism in Linux Kernel (C/C++ Code Implementation)

1. Introduction: Why is ELF Signature Verification Needed?

In Linux systems, ELF (Executable and Linkable Format) is the core format for executable files and shared libraries. By default, the kernel only checks the format validity of ELF files (such as magic number and architecture matching), without verifying the integrity and legitimacy of the file’s source— this means that attackers can tamper with ELF files (such as modifying the code segment), implant malicious code, or run unauthorized ELF programs, breaching the system’s security defenses.

To address this issue, the ELF signature verification kernel module (<span>binfmt_elf_signature_verification</span>) analyzed in this article enforces the verification of the signature integrity of ELF files and their dependent shared libraries (.so) through a kernel-level “pre-interception + signature verification” mechanism. Only files that pass verification can be executed by the kernel, fundamentally blocking the execution of malicious ELF files and establishing a “trusted execution entry” for the Linux system.

2. Core Functions: What Does the Module Do?

This module is essentially a Linux kernel binary format (binfmt) handler that intercepts all execution requests for ELF files by registering with the kernel’s binfmt mechanism, completing the following core tasks:

  1. ELF File Filtering and Skipping First, it checks whether the file is in ELF format (validating the ELF magic number, type, and architecture), while skipping ELF files in critical system paths (such as <span>/bin/</span> and <span>/usr/</span> system programs) — this is to avoid system crashes due to unsigned ELF files after the module is installed, representing a trade-off between “security and usability”.

  2. Key Section Signature Verification Only the core sections of ELF (currently the <span>.text</span> code segment, expandable to the <span>.data</span> data segment) are verified for signatures: ELF must additionally include the corresponding “signature section” (such as <span>.text_sig</span>), and the module will load the original section data and signature data, verifying integrity through the kernel’s cryptographic interface.

  3. Recursive Verification of Dynamic Libraries If the ELF is a dynamically linked program (depending on <span>.so</span><code><span> libraries), the module will read the system's </span><code><span>/etc/ld.so.cache</span> (dynamic link cache), find the absolute paths of the dependent libraries, and repeat the above signature verification process for each <span>.so</span> library, ensuring the entire chain of “main program + dependent libraries” is trusted.

  4. Verification Result Closure

  • Verification Passed: Returns <span>-ENOEXEC</span>, informing the kernel that “the current module does not handle execution, hand over to the native ELF handler (<span>binfmt_elf</span>) for subsequent loading”;
  • Verification Failed (no signature/invalid signature/dependent library not passed): Returns an error code, and the kernel directly blocks execution.

3. Implementation Principle: Full Process from Interception to Verification

The module’s workflow revolves around “kernel interception → layered verification → trusted chain extension”, without modifying the original ELF processing logic of the kernel, achieving lightweight integration by reusing system mechanisms:

1. Kernel Interception: Pre-access Based on Binfmt Mechanism

The Linux kernel manages different types of executable files (such as ELF, scripts, a.out) through the binfmt (binary format) framework. Each format corresponds to a <span>struct linux_binfmt</span> structure, where the <span>load_binary</span> function is the “file processing entry”.

The core access logic of the module:

  • During initialization, it registers a custom <span>elf_signature_verification_format</span> structure through <span>insert_binfmt</span>, pointing the <span>load_binary</span> to the module’s verification entry function;
  • When a user executes an ELF file, the kernel traverses all binfmt handlers, prioritizing the call to this module’s <span>load_binary</span> — achieving the interception effect of “verify before execution”;
  • After verification passes, it returns <span>-ENOEXEC</span> to let the kernel continue traversing, ultimately handled by the native <span>binfmt_elf</span> for execution (such as loading the code segment, initializing process space).

2. Layered Verification: Filtering from Format to Signature

The module adopts a strategy of “filtering invalid files first, then deep verification” to reduce unnecessary computational overhead:

(1) First Level: ELF Format Legitimacy Check

First, it excludes non-target files through the <span>elf_format_validation</span> function:

  • Validates the ELF magic number (<span>ELFMAG</span>, which is <span>0x7f + "ELF"</span>), confirming it is an ELF file;
  • Validates ELF type (only processes <span>ET_EXEC</span> executable files, <span>ET_DYN</span> shared libraries), architecture (<span>elf_check_arch</span>, ensuring it matches the kernel architecture);
  • Validates key fields (such as <span>e_shstrndx</span> cannot be <span>SHN_UNDEF</span>, to avoid missing section information).

(2) Second Level: ELF Section and Signature Parsing

Loads key data structures from the ELF header (<span>elfhdr</span>) to prepare for signature verification:

  • Loads the section header table (<span>elf_shdr</span>): reads the ELF’s section description information (such as section name, offset, size) through <span>load_elf_shdrs</span>;
  • Loads the section string table (<span>shstrtab</span>): stores the names of all sections (such as <span>.text</span> and <span>.text_sig</span>), used to match “original section and signature section”;
  • Matches the signature section: through <span>scn_name_match</span> function, finds the section with a name suffix of <span>_sig</span> (such as <span>.text</span> corresponding to <span>.text_sig</span>), ensuring both have the same prefix and the length difference equals the length of <span>_sig</span>.

(3) Third Level: PKCS#7 Signature Verification

The module reuses the kernel’s native <span>verify_pkcs7_signature</span> function (the kernel’s built-in PKCS#7 signature verification interface) to complete core security checks:

  • Loads the original section data (such as <span>.text</span> code data) and signature section data (such as <span>.text_sig</span> PKCS#7 signature data);
  • Calls the kernel interface, based on the built-in kernel key ring (Key Ring) to verify the signature — the key ring is stored in kernel space and cannot be tampered with by user space, ensuring the trustworthiness of the “verification root”;
  • If verification passes, updates the “section verification checklist” (<span>scn_checklist</span><code><span>), marking that section as verified.</span>

(4) Fourth Level: Recursive Verification of Dependent Libraries

Dynamically linked ELF must verify all dependent <span>.so</span> libraries, and the module improves efficiency by reusing system caches:

  • Loads dynamic link information: extracts the list of dependent libraries from the <span>.dynamic</span> section (which stores dynamic link parameters) and the <span>.dynstr</span> section (which stores dependent library names, such as <span>libcrypto.so.1.1</span>);
  • Reuses <span>ld.so.cache</span>: loads the system’s <span>/etc/ld.so.cache</span> (the dynamic linker cache file, which stores the mapping of <span>.so</span> names to absolute paths), avoiding traversing the file system to find <span>.so</span>, reducing IO overhead;
  • Recursive verification: for each dependent library, creates a new <span>linux_binprm</span> structure (reusing ELF verification logic), repeating all the above verification steps to ensure the dependency chain is untampered.

3. Result Handling: Trustworthy Passes, Untrustworthy Blocks

After the verification process ends, the module ensures security through two key checks:

  • Checklist Check: Confirms through <span>lookup_checklist</span> that all sections needing verification (such as <span>.text</span>) have passed signature checks, avoiding omissions;
  • Dependency Chain Check: Ensures all <span>.so</span> libraries have passed verification, with no unauthorized dependencies.

The final return results:

  • All Pass: Returns <span>-ENOEXEC</span>, the kernel calls the native ELF handler to execute the file;
  • Any Failure: Returns an error code (such as <span>-ENODATA</span> for no signature, <span>-EBADMSG</span> for invalid signature), the kernel directly terminates execution, and logs output through <span>dmesg</span> for troubleshooting.

4. Design Philosophy: The Art of Trade-offs and Reuse

struct scn_checklist {
 unsigned char s_name[8]; 
 int s_nlen;     
 int s_check;    
};

struct ld_cache_header {
 char magic[sizeof(LD_CACHE_MAGIC_OLD) - 1];
 unsigned int n_libs;
};

struct ld_cache_entry {
 int e_flags;   
 unsigned int e_key;  
 unsigned e_value;  
};

struct ld_so_cache {
 char *l_buf;      
 loff_t l_len;      

 unsigned int l_entrynum;   
 struct ld_cache_entry *l_entries; 
 char *l_strtab;      
};
...
int init_so_caches(struct ld_so_cache *so_cache);
char *get_so_file_path(struct ld_so_cache *so_cache, char *so_key);
unsigned char *load_elf_sdata(struct elf_shdr *elf_shdata,struct file *elf_file);
int verify_scn_signature(unsigned char *scn_data,int scn_data_len, unsigned char *sig_scn_data,int sig_scn_data_len);
int elf_signature_verification(struct linux_binprm *bprm,struct ld_so_cache *so_cache);
int elf_format_validation(struct linux_binprm *bprm);
int load_elf_signature_verification_binary(struct linux_binprm *bprm);

static struct linux_binfmt elf_signature_verification_format = {
 .module = THIS_MODULE,
 .load_binary = load_elf_signature_verification_binary,
};

static int __init init_elf_signature_verification_binfmt(void)
{
 insert_binfmt(&amp;elf_signature_verification_format);
 return 0;
}

static void __exit exit_elf_signature_verification_binfmt(void)
{
 unregister_binfmt(&amp;elf_signature_verification_format);
}

module_init(init_elf_signature_verification_binfmt);
module_exit(exit_elf_signature_verification_binfmt);
...

If you need the complete source code, please add the WeChat number (c17865354792)

By default, the KDIR value in the Makefile points to the source code directory of the currently running kernel, and the kernel module will be installed in that directory.

KDIR := /lib/modules/$(shell uname -r)/build

Additionally, you can override the KDIR variable to build the module for a different kernel. Assuming your directory is a submodule under linux-kernel-elf-sig-verify, with a structure similar to linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module, you can modify KDIR to:

KDIR := ../

Then, use the <span>make</span> command to build the kernel module:

$ make
make -C /lib/modules/5.3.0-53-generic/build M=/home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module modules
make[1]: Entering directory '/usr/src/linux-headers-5.3.0-53-generic'
  CC [M]  /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.mod.o
  LD [M]  /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.3.0-53-generic'

binfmt_elf_signature_verification.ko is the kernel module. You can verify the basic information of this module:

$ modinfo binfmt_elf_signature_verification.ko 
filename:       /home/mrdrivingduck/Desktop/linux-kernel-elf-sig-verify/linux-kernel-elf-sig-verify-module/binfmt_elf_signature_verification.ko
alias:          fs-binfmt_elf_signature_verification
version:        1.0
description:    Binary handler for verifying signature in ELF section
...
license:        Dual MIT/GPL
srcversion:     24C778301DE1DD13C1BB3CF
depends:        
retpoline:      Y
name:           binfmt_elf_signature_verification
vermagic:       5.3.0-53-generic SMP mod_unload

Install the module using the insmod command:

$ sudo insmod binfmt_elf_signature_verification.ko

Remove the module using the rmmod command:

$ sudo rmmod binfmt_elf_signature_verification

If the module is successfully installed, you will no longer be able to run unsigned ELF files. You can use the dmesg command to see more information.

Generate your own key and use the openssl tool to create a key:

$ cd certs
$ openssl req -new -nodes -utf8 -sha256 -days 36500 -batch -x509 \
    -config x509.genkey -outform PEM
    -out kernel_key.pem -keyout kernel_key.pem
Generating a RSA private key
........+++++
........................................+++++
writing new private key to 'kernel_key.pem'
-----
$ cd ..

The core design of this module is “minimal invasiveness, high compatibility, balancing security and efficiency“, specifically reflected in the following points:

1. Based on Existing Kernel Mechanisms, Avoiding Reinventing the Wheel

The module does not modify the kernel’s native ELF processing logic but instead “plugs in” verification functionality through the binfmt mechanism — the advantages of this design are:

  • Strong compatibility: adaptable to different Linux kernel versions (as long as the binfmt interface remains unchanged), no need to recompile the kernel;
  • Low maintenance cost: when the kernel’s ELF processing logic is updated, the module does not need to be modified;
  • Secure and trustworthy: reuses the kernel’s <span>verify_pkcs7_signature</span> and key ring mechanisms, avoiding security vulnerabilities introduced by custom encryption logic.

2. Staged Verification: Balancing Efficiency and Security

The module does not perform signature verification on the “entire ELF file” but adopts a layered strategy of “format → key section → dependent libraries”:

  • First, filter out non-ELF files to reduce invalid computations;
  • Only verify key sections (such as <span>.text</span>), rather than the entire file — ensuring code integrity (as .text is the execution core) while reducing data loading and verification overhead;
  • Reuses <span>ld.so.cache</span><code><span> to find dependent libraries, avoiding IO-intensive path searches, improving verification efficiency.</span>

3. Usability First: Skipping Mechanism for Critical System ELFs

The module by default skips ELF files in <span>/bin/</span>, <span>/usr/</span>, and other system paths, which is a necessary trade-off between “security and usability”:

  • Most Linux distributions’ system programs do not have built-in signatures, and enforcing verification would cause the system to fail to boot;
  • This design allows the module to be “plug-and-play”, enabling users to expand the verification scope as needed (for example, after signing system ELFs, removing the skip logic).

4. Extensibility Design: Supporting More Scenarios

The module reserves clear extension interfaces:

  • Verifiable sections can be extended: the <span>scn_checklist</span> structure can add <span>.data</span>, <span>.rodata</span>, and other sections, requiring only one line of configuration;
  • Signature algorithms can be extended: based on the kernel’s <span>verify_pkcs7_signature</span>, if support for other signature standards (such as ED25519) is needed, only the kernel cryptographic interface needs to be extended;
  • Dependency lookup can be extended: currently dependent on <span>ld.so.cache</span><code><span>, future support can include searching for </span><code><span>.so</span> outside the cache (such as traversing <span>LD_LIBRARY_PATH</span>).

5. Related Knowledge Points: Understanding the Technical Background of the Module

To deeply understand this module, one must be familiar with the following core concepts in the Linux kernel and security fields:

1. Linux Kernel Binfmt Mechanism

The kernel manages executable file formats through <span>struct linux_binfmt</span>, with each format corresponding to a “handler”. When executing a file, the kernel calls <span>search_binary_handler</span> to traverse all handlers until it finds a <span>load_binary</span> function that can handle that format — this is the core principle that allows the module to “intercept ELF execution”.

2. Core Structure of ELF Files

The “signature verification” of ELF files relies on its structured design:

  • ELF Header (elfhdr): stores global information such as file type (executable/shared library), architecture, section header table offset, etc.;
  • Section Header Table (elf_shdr): each entry corresponds to a description of a section (name index, type, offset, size);
  • Key Sections:
    • <span>.text</span>: code segment, stores executable instructions, is the core object of signature verification;
    • <span>.dynamic</span>: dynamic link information, stores the list of dependent libraries (<span>DT_NEEDED</span>);
    • <span>.dynstr</span>: dynamic link string table, stores dependent library names (such as <span>libc.so.6</span>);
    • <span>_sig</span> suffixed section: custom signature section, stores the PKCS#7 signature data for the corresponding section.

3. Dynamic Linking and ld.so.cache

  • Dynamic Linker (ld.so): responsible for loading the dependent <span>.so</span> libraries and resolving symbols when the program starts;
  • ld.so.cache: the cache file for ld.so, generated by <span>ldconfig</span>, stores the mapping of <span>.so</span> names to absolute paths — the module reuses this cache to avoid redundant file system lookups, improving the efficiency of locating dependent libraries.

4. Kernel Key Ring

The key management mechanism in the Linux kernel, used for securely storing encryption keys, certificates, and other sensitive data. The key ring is divided into different namespaces (such as <span>user</span>, <span>system</span>, <span>kernel</span>), and the “built-in keys” used by the module are stored in the <span>kernel</span> namespace, accessible only by the kernel, ensuring that the root certificate used for verification cannot be tampered with by user space, which is the core guarantee of the “trusted root”.

5. PKCS#7 Signature Standard

A cryptographic message syntax commonly used in digital signatures, data encryption, and other scenarios. The kernel’s <span>verify_pkcs7_signature</span> function supports verifying PKCS#7 format signatures, ensuring data integrity and legitimacy of the source — the module implements cross-platform, standardized signature verification based on this standard.

6. Conclusion and Future Directions

This ELF signature verification module constructs a “security gate for ELF execution” for Linux systems through the design of “kernel interception + layered verification + full chain trust”, especially suitable for embedded devices, security-sensitive servers, and other scenarios (where executable file sources need to be strictly controlled).

Existing Limitations

  1. Only verifies the <span>.text</span> section, not covering <span>.data</span>, <span>.rodata</span>, and other key data segments;
  2. Depends on <span>ld.so.cache</span>, unable to handle <span>.so</span> libraries outside the cache (such as custom path <span>.so</span>);
  3. Skips a large number of system ELFs, leaving security not fully covered across the system (subsequent signing of system files is required).

Future Expansion Directions

  1. Expand verification scope: add verification for <span>.data</span>, <span>.rodata</span>, and other sections to enhance integrity assurance;
  2. Support custom keys: allow users to dynamically import root certificates through kernel interfaces instead of hardcoding built-in ones;
  3. Integrate SELinux/AppArmor: link with existing mandatory access control mechanisms to achieve dual security of “signature verification + permission control”;
  4. Support incremental verification: only re-sign the modified parts of the ELF file without needing full file verification, improving efficiency.

Through the design and implementation of this module, we can see that the development of security features in the Linux kernel often requires finding a balance between “security, usability, and efficiency”, while maximizing the reuse of existing kernel mechanisms to reduce complexity and security risks.

Leave a Comment