linux
module_core is referenced after being freed during error cleanup
load_module() allocates memory for the core executable (module_core) and the initialization code (module_init) of the new module, then copies the image of the module object included in the module text segment to module_core. A pointer `mod' to the module object is mantained across the function. If an error occurs, it may happen that load_module() frees module_core and then dereferences `mod', but since `mod' points to the area allocated for module_core, it dereferences already freed memory.
Bug fixed by commit 6e2b75740be
| Type | UseAfterFree |
| Config | "MODULE_UNLOAD && SMP" (2nd degree) |
| Fix-in | code |
| Location | kernel/ |
#define ENOMEM 12 /* Out of memory */
#define NULL (void*)0
extern void *malloc (unsigned long __size) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) ;
extern void free (void *__ptr) __attribute__ ((__nothrow__ , __leaf__));
__attribute__ ((noinline)) int nondet() { return 42; }
extern void *memcpy (void *__restrict __dest, const void *__restrict __src,
unsigned long __n) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2)));
#define refptr(mod) (mod[0])
#define module_core(mod) (mod[1])
int load_module()
{
void *hdr;
void **mod;
long err = 0;
void *ptr = NULL;
if ((hdr = malloc(2*sizeof(void*))) == NULL) // (2)
return -ENOMEM;
mod = (void *)hdr; // (3)
#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
refptr(mod) = malloc(32);
if (!refptr(mod))
{
err = -ENOMEM;
goto free_mod;
}
#endif
ptr = malloc(512); // (4)
if (!ptr) {
err = -ENOMEM;
goto free_percpu;
}
module_core(mod) = ptr; // (5)
while (nondet()) {
void *dest = module_core(mod); // (6)
memcpy(dest, mod, sizeof(2*sizeof(void*))); // (7)
mod = module_core(mod); // (8) `mod' and `module_core(mod)' are aliases
if (nondet())
break;
}
if (nondet())
goto free_unload; // (9)
return 0;
free_unload:
free_core:
free(module_core(mod)); // (10) frees `module_core(mod)' and thus `mod' too
free_percpu:
#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
free(refptr(mod)); // (11) ERROR: dereferencing already freed `mod'.
#endif
free_mod:
free_hdr:
free(hdr);
return err;
}
int main(void) {
load_module(); // (1)
return 0;
}
diff --git a/simple/6e2b757.c b/simple/6e2b757.c
--- a/simple/6e2b757.c
+++ b/simple/6e2b757.c
@@ -27,15 +27,6 @@
mod = (void *)hdr; // (3)
-#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
- refptr(mod) = malloc(32);
- if (!refptr(mod))
- {
- err = -ENOMEM;
- goto free_mod;
- }
-#endif
-
ptr = malloc(512); // (4)
if (!ptr) {
err = -ENOMEM;
@@ -51,18 +42,28 @@
break;
}
+#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
+ refptr(mod) = malloc(32);
+ if (!refptr(mod))
+ {
+ err = -ENOMEM;
+ goto free_init;
+ }
+#endif
+
if (nondet())
goto free_unload; // (9)
return 0;
free_unload:
+ free_init:
+#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
+ free(refptr(mod)); // (11) ERROR: dereferencing already freed `mod'.
+#endif
free_core:
free(module_core(mod)); // (10) frees `module_core(mod)' and thus `mod' too
free_percpu:
-#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
- free(refptr(mod)); // (11) ERROR: dereferencing already freed `mod'.
-#endif
free_mod:
free_hdr:
free(hdr);
#include <errno.h>
#include <stdlib.h>
#include <string.h>
struct module {
void *refptr;
void *module_core;
};
int main(int argc, char** argv) {
// load_module(64 * 1024);
void *hdr;
struct module *mod;
long err = 0;
void *ptr = NULL;
if ((hdr = malloc(len)) == NULL)
return -ENOMEM;
mod = (void *)hdr;
#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
mod->refptr = malloc(32);
if (!mod->refptr) {
err = -ENOMEM;
goto free_mod;
}
#endif
ptr = malloc(512);
if (!ptr) {
err = -ENOMEM;
goto free_percpu;
}
mod->module_core = ptr;
while (1) {
void *dest = mod->module_core;
memcpy(dest, mod, sizeof(struct module));
mod = mod->module_core;
break;
}
if (rand() % 2)
goto free_unload;
return 0;
free_unload:
free_core:
free(mod->module_core);
free_percpu:
#if defined(CONFIG_MODULE_UNLOAD) && defined(CONFIG_SMP)
free(mod->refptr); // ERROR
#endif
free_mod:
free_hdr:
free(hdr);
return 0;
}
. call kernel/module.c:1861:load_module()
. 1888: if (len > 64 * 1024 * 1024 || (hdr = vmalloc(len)) == NULL)
// allocates temporary memory area in hdr
. 1897: if (copy_from_user(hdr, umod, len) != 0) {
// copies module's object code from umod (user mode buffer)
. 1942: modindex = find_sec(hdr, sechdrs, secstrings,
// find the index of the '.gnu.linkonce.this_module' section, which contains an image of the module structure
. 1950: mod = (void *)sechdrs[modindex].sh_addr;
// and make mod point to it
. 2019: mod->refptr = percpu_modalloc(sizeof(local_t), __alignof__(local_t),
. 2045: ptr = module_alloc_update_bounds(mod->core_size);
. 2051: mod->module_core = ptr;
. 2053: ptr = module_alloc_update_bounds(mod->init_size);
. 2059: mod->module_init = ptr;
. 2063: for (i = 0; i < hdr->e_shnum; i++) {
// transfer sections to module_core and module_init
// mod is set to point to the modindex section.
// an init section is identified by INIT_OFFSET_MASK.
// .gnu.linkonce.this_module belongs to core.
. 2091: goto free_unload;
. 2291: module_free(mod, mod->module_init);
. 2293: module_free(mod, mod->module_core);
// mod was pointing to module_core, which is now freed
. ERROR 2298: percpu_modfree(mod->refptr);