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);