{
	"id": "61d21a1d-1af0-41ab-9ec8-967b25b9ed34",
	"created_at": "2026-04-06T02:11:23.862563Z",
	"updated_at": "2026-04-10T13:12:05.91805Z",
	"deleted_at": null,
	"sha1_hash": "d5d90f9631a94114c675e6ac1c9d4050aa4c0a5c",
	"title": "LKM loading kernel restrictions",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 148323,
	"plain_text": "LKM loading kernel restrictions\r\nPublished: 2018-02-17 · Archived: 2026-04-06 01:53:13 UTC\r\nLoading arbitrary kernel modules dynamically has always been a gray area between usability oriented and security\r\noriented Linux developers \u0026 users. In this post I will present what options are available today from the Linux\r\nkernel and the most popular kernel hardening patch, the grsecurity. Those will give you some ideas on how those\r\nprojects deal with the threat of Linux kernel’s LKMs (Loadable Kernel Modules).\r\nThreat\r\nThis can be split to two main categories, allowing dynamic LKM loading introduces the following two threats:\r\nMalicious LKMs. That’s more or less rootkits or similar malware that an adversary can load for various\r\noperations, most commonly to hide specific activities from the user-space.\r\nVulnerable LKM loading. Imagine that you have a 0day exploit on a specific network driver but this is not\r\nloaded by default. If you can trigger a dynamic loading then you can use your code to exploit it and\r\ncompromise the system. This is what this vector is about.\r\nLinux kernel and KSPP\r\nThe KSPP (Kernel Self-Protection Project) of the Linux kernel tried to fix this issue with the introduction of the\r\nkernel modules access restriction. Below you can see the exact description that Linux kernel’s documentation has\r\nfor this restriction.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\nRestricting access to kernel modules\r\nThe kernel should never allow an unprivileged user the ability to load specific\r\nkernel modules, since that would provide a facility to unexpectedly extend the\r\navailable attack surface. (The on-demand loading of modules via their predefined\r\nsubsystems, e.g. MODULE_ALIAS_*, is considered “expected” here, though additional\r\nconsideration should be given even to these.) For example, loading a filesystem\r\nmodule via an unprivileged socket API is nonsense: only the root or physically\r\nlocal user should trigger filesystem module loading. (And even this can be up\r\nfor debate in some scenarios.)\r\nTo protect against even privileged users, systems may need to either disable\r\nmodule loading entirely (e.g. monolithic kernel builds or modules_disabled\r\nsysctl), or provide signed modules (e.g. CONFIG_MODULE_SIG_FORCE, or dm-crypt\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 1 of 10\n\n13\r\n14\r\n15\r\n16\r\nwith LoadPin), to keep from having root load arbitrary kernel code via the\r\nmodule loader interface.\r\nThe most restrictive way is via modules_disabled sysctl variable which is available by default on the Linux kernel.\r\nThis can either be set dynamically as you see here.\r\n1 sysctl -w kernel.modules_disabled=1\r\nOr permanently as part of the runtime kernel configuration as you can see here.\r\n1 echo 'kernel.modules_disabled=1' \u003e\u003e /etc/sysctl .d /99-custom .conf\r\nIn both cases, the result is the same. Basically, the above change its default value from “0” to “1”. You can find the\r\nexact definition of this variable in kernel/sysctl.c.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\nkernel/sysctl.c\r\n{\r\n.procname = \"modules_disabled\" ,\r\n.data = \u0026modules_disabled,\r\n.maxlen = sizeof ( int ),\r\n.mode = 0644,\r\n.proc_handler = proc_dointvec_minmax,\r\n.extra1 = \u0026one,\r\n.extra2 = \u0026one,\r\n},\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 2 of 10\n\nIf we look into kernel/module.c we will see that if modules_disabled has a non-zero value it is not allowing LKM\r\nloading (may_init_module()) or even unloading (delete_module() system call) of any LKM. Below you can see\r\nthe module initialization code that requires both the SYS_MODULE POSIX capability, and modules_disabled to\r\nbe zero.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\nstatic int may_init_module( void )\r\n{\r\nif (!capable(CAP_SYS_MODULE) || modules_disabled)\r\nreturn -EPERM;\r\nreturn 0;\r\n}\r\nSimilarly, the delete_module() system call has the exact same check as shown below. In both cases, failure of\r\nthose will result in a Permission Denied (EPERM) error.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\nSYSCALL_DEFINE2(delete_module, const char __user *, name_user,\r\nunsigned int , flags)\r\n{\r\nstruct module *mod;\r\nchar name[MODULE_NAME_LEN];\r\nint ret, forced = 0;\r\nif (!capable(CAP_SYS_MODULE) || modules_disabled)\r\nreturn -EPERM;\r\nLooking in kernel/kmod.c we can also see another check, before the kernel module loading request is passed to\r\ncall_modprobe() to get loaded in the kernel, the __request_module() function verifies that modprobe_path is set,\r\nmeaning the LKM is not loaded via an API or socket instead of /sbin/modprobe command.\r\n1 int __request_module( bool wait, const char *fmt, ...)\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 3 of 10\n\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\n15\r\n16\r\n{\r\nva_list args;\r\nchar module_name[MODULE_NAME_LEN];\r\nint ret;\r\nWARN_ON_ONCE(wait \u0026\u0026 current_is_async());\r\nif (!modprobe_path[0])\r\nreturn 0;\r\nThe above were the features that Linux kernel had for years to protect against this threat. The downside though is\r\nthat completely disabling loading and unloading of LKMs can break some legitimate operations such as system\r\nupgrades, reboots on systems that load modules after boot, automation configuring software RAID devices after\r\nboot, etc.\r\nTo deal with the above, on 22 May 2017 the KSPP team proposed a patch to __request_module() (still to be added\r\nto the kernel) which follows a different approach.\r\n1\r\n2\r\n3\r\n4\r\n5\r\nint __request_module( bool wait, int allow_cap, const char *fmt, ...)\r\n{\r\n...\r\nif (!modprobe_path[0])\r\nreturn 0;\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 4 of 10\n\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n...\r\nret = security_kernel_module_request(module_name, allow_cap);\r\nif (ret)\r\nWhat you see here is that in the very early stage of the kernel module loading security_kernel_module_request() is\r\ninvoked with the module to be loaded as well as allow_cap variable which can be set to either “0” or “1”. If its\r\nvalue is positive, the security subsystem will trust the caller to load modules with specific predifned (hardcoded)\r\naliases. This should allow auto-loading of specific aliases. This was done to close a design flaw of the Linux\r\nkernel where although all modules required the CAP_SYS_MODULE capability to load modules (which is\r\nalready checked as shown earlier), the network modules required the CAP_NET_ADMIN capability which\r\ncompletely bypassed the previously described controls. Using this modified __request_module() it is ensured that\r\nonly specific modules that are allowed by the security subsystem will be able to auto-load. However, it is also\r\ncrucial to note that to this date, the only security subsystem that utilizes security_kernel_module_request() hook is\r\nthe SELinux.\r\nBefore we move on with grsecurity, it is important to note that in 07 November 2010 Dan Rosenberg proposed a\r\nreplacement of modules_disabled, the modules_restrict which was a copy of grsecurity’s logic. It had three values,\r\n0 (disabled), 1 (only root can load/unload LKMs), 2 (no one can load/unload – same as modules_disabled). You\r\ncan see the check that it was adding to __request_module() below.\r\n1\r\n2\r\n3\r\nif (current_uid() \u0026\u0026 modules_restrict)\r\nreturn -EPERM;\r\nHowever, this was never added to the upstream kernel so there is no need to dive more into the details behind it.\r\nJust as an overview, here is the proposed kernel configuration option documentation for modules_restrict.\r\n1\r\n2\r\n3\r\n4\r\nmodules_restrict:\r\nA toggle value indicating if modules are allowed to be loaded\r\nin an otherwise modular kernel. This toggle defaults to off\r\n(0), but can be set true (1). Once true, modules can be\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 5 of 10\n\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\nneither loaded nor unloaded, and the toggle cannot be set back\r\nto false.\r\nA value indicating if module loading is restricted in an\r\notherwise modular kernel. This value defaults to off (0),\r\nbut can be set to (1) or (2). If set to (1), modules cannot\r\nbe auto-loaded by non-root users, for example by creating a\r\nsocket using a packet family that is compiled as a module and\r\nnot already loaded. If set to (2), modules can neither be\r\nloaded nor unloaded, and the value can no longer be changed.\r\ngrsecurity\r\nUnfortunately, grsecurity stable patches are no longer publicly available. For this reason, in this article I will be\r\nusing the grsecurity patch for kernel releases 3.1 to 4.9.24. For the LKM loading hardening grsecurity offers a\r\nkernel configuration option known as MODHARDEN (Harden Module Auto-loading). If we go back to\r\n___request_module() in kernel/kmod.c we will see how this feature works.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\nret = security_kernel_module_request(module_name);\r\nif (ret)\r\nreturn ret;\r\n#ifdef CONFIG_GRKERNSEC_MODHARDEN\r\nif (uid_eq(current_uid(), GLOBAL_ROOT_UID)) {\r\nread_lock(\u0026tasklist_lock);\r\nif (! strcmp (current-\u003ecomm, \"mount\" ) \u0026\u0026\r\ncurrent-\u003ereal_parent \u0026\u0026 ! strncmp (current-\u003ereal_parent-\u003ecomm, \"udisk\" ,\r\n5)) {\r\nread_unlock(\u0026tasklist_lock);\r\nprintk(KERN_ALERT \"grsec: denied attempt to auto-load fs module %.64s by\r\nudisks\\n\" , module_name);\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 6 of 10\n\n13\r\n14\r\n15\r\n16\r\n17\r\nreturn -EPERM;\r\n}\r\nread_unlock(\u0026tasklist_lock);\r\n}\r\n#endif\r\nThe check in this case is relatively simple, it verifies that the caller’s UID is the same as the static global UID of\r\nroot user. This ensure that only users with UID=0 can load kernel modules which completely eliminates the cases\r\nof unprivileged users exploiting flaws that are allowing them to request kernel module loading. To overcome the\r\nnetwork kernel modules issue grsecurity followed a different approach which maintains the capability check\r\n(which is currently used by a very limited amount of security subsystems) but redirects all loading to the\r\n___request_module() function to ensure that only root can load them.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n12\r\n13\r\n14\r\nvoid dev_load( struct net *net, const char *name)\r\n{\r\n...\r\nno_module = !dev;\r\nif (no_module \u0026\u0026 capable(CAP_NET_ADMIN))\r\nno_module = request_module( \"netdev-%s\" , name);\r\nif (no_module \u0026\u0026 capable(CAP_SYS_MODULE)) {\r\n#ifdef CONFIG_GRKERNSEC_MODHARDEN\r\n___request_module( true , \"grsec_modharden_netdev\" , \"%s\" , name);\r\n#else\r\nrequest_module( \"%s\" , name);\r\n#endif\r\n}\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 7 of 10\n\nFurthermore, grsecurity identified that a similar security design flaw also exists in the filesystem modules loading\r\n(still to be identified and fixed in the upstream kernel), which was fixed in a similar manner. Below is the\r\ngrsecurity version of fs/filesystems.c’s get_fs_type() function which is ensuring that filesystem modules are\r\nloaded only by root user.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\nfs = __get_fs_type(name, len);\r\n#ifdef CONFIG_GRKERNSEC_MODHARDEN\r\nif (!fs \u0026\u0026 (___request_module( true , \"grsec_modharden_fs\" , \"fs-%.*s\" , len,\r\nname) == 0))\r\n#else\r\nif (!fs \u0026\u0026 (request_module( \"fs-%.*s\" , len, name) == 0))\r\n#endif\r\nfs = __get_fs_type(name, len);\r\nThis Linux kernel design flaw allows loading of non-filesystem kernel modules via mount. How grsecurity detects\r\nthose is quite clever and can be found in simplify_symbols() function of kernel/module.c. What it does is ensuring\r\nthat the the arguments of the module are copied to the kernel side, and then checks the module’s loading\r\ninformation in the symbol table to ensure that the loaded module is trying to register a filesystem instead of any\r\narbitrary kernel module.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\r\n10\r\n11\r\n#ifdef CONFIG_GRKERNSEC_MODHARDEN\r\nint is_fs_load = 0;\r\nint register_filesystem_found = 0;\r\nchar *p;\r\np = strstr (mod-\u003eargs, \"grsec_modharden_fs\" );\r\nif (p) {\r\nchar *endptr = p + sizeof ( \"grsec_modharden_fs\" ) - 1;\r\nmemmove (p, endptr, strlen (mod-\u003eargs) - (unsigned int )(endptr - mod-\r\n\u003eargs) + 1);\r\nis_fs_load = 1;\r\n}\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 8 of 10\n\n12\r\n13\r\n14\r\n15\r\n16\r\n17\r\n18\r\n19\r\n20\r\n21\r\n22\r\n23\r\n24\r\n25\r\n26\r\n#endif\r\nfor (i = 1; i \u003c symsec-\u003esh_size / sizeof (Elf_Sym); i++) {\r\nconst char *name = info-\u003estrtab + sym[i].st_name;\r\n#ifdef CONFIG_GRKERNSEC_MODHARDEN\r\nif (is_fs_load \u0026\u0026 ! strcmp (name, \"register_filesystem\" ))\r\nregister_filesystem_found = 1;\r\n#endif\r\nswitch (sym[i].st_shndx) {\r\nTo help in detection of malicious users trying to exploit this Linux kernel design flaw, grsecurity has also an\r\nalerting mechanism in place which immediately logs any attempts to load Linux kernel modules that are not\r\nfilesystems using this design flaw. Meaning loading a kernel module via “mount” without that being an actual\r\nfilesystem module.\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n#ifdef CONFIG_GRKERNSEC_MODHARDEN\r\nif (is_fs_load \u0026\u0026 !register_filesystem_found) {\r\nprintk(KERN_ALERT \"grsec: Denied attempt to load non-fs module %.64s through\r\nmount\\n\" , mod-\u003ename);\r\nret = -EPERM;\r\n}\r\n#endif\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 9 of 10\n\n8\r\n9\r\nreturn ret;\r\n}\r\nSecurity wise grsecurity’s approach is by far the most complete but unfortunately, by default the Linux kernel\r\ndoesn’t have anything even close to this.\r\nSource: https://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nhttps://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://xorl.wordpress.com/2018/02/17/lkm-loading-kernel-restrictions/"
	],
	"report_names": [
		"lkm-loading-kernel-restrictions"
	],
	"threat_actors": [],
	"ts_created_at": 1775441483,
	"ts_updated_at": 1775826725,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/d5d90f9631a94114c675e6ac1c9d4050aa4c0a5c.pdf",
		"text": "https://archive.orkl.eu/d5d90f9631a94114c675e6ac1c9d4050aa4c0a5c.txt",
		"img": "https://archive.orkl.eu/d5d90f9631a94114c675e6ac1c9d4050aa4c0a5c.jpg"
	}
}