From: B Horn <b...@horn.uk>

It was possible to overflow the value of mod->ref_count, a signed
integer, by repeatedly invoking insmod on an already loaded module.
This led to a use-after-free. As once ref_count was overflowed it became
possible to unload the module while there was still references to it.

This resolves the issue by using grub_add() to check if the ref_count
will overflow and then stops further increments. Further changes were
also made to grub_dl_unref() to check for the underflow condition and
the reference count was changed to an unsigned 64-bit integer.

Reported-by: B Horn <b...@horn.uk>
Signed-off-by: B Horn <b...@horn.uk>
Reviewed-by: Daniel Kiper <daniel.ki...@oracle.com>
---
 grub-core/commands/minicmd.c |  2 +-
 grub-core/kern/dl.c          | 17 ++++++++++++-----
 include/grub/dl.h            |  8 ++++----
 util/misc.c                  |  4 ++--
 4 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/grub-core/commands/minicmd.c b/grub-core/commands/minicmd.c
index fa498931e..286290866 100644
--- a/grub-core/commands/minicmd.c
+++ b/grub-core/commands/minicmd.c
@@ -167,7 +167,7 @@ grub_mini_cmd_lsmod (struct grub_command *cmd __attribute__ 
((unused)),
   {
     grub_dl_dep_t dep;
 
-    grub_printf ("%s\t%d\t\t", mod->name, mod->ref_count);
+    grub_printf ("%s\t%" PRIuGRUB_UINT64_T "\t\t", mod->name, mod->ref_count);
     for (dep = mod->dep; dep; dep = dep->next)
       {
        if (dep != mod->dep)
diff --git a/grub-core/kern/dl.c b/grub-core/kern/dl.c
index 8ad015b07..99bc12385 100644
--- a/grub-core/kern/dl.c
+++ b/grub-core/kern/dl.c
@@ -32,6 +32,7 @@
 #include <grub/env.h>
 #include <grub/cache.h>
 #include <grub/i18n.h>
+#include <grub/safemath.h>
 
 #ifdef GRUB_MACHINE_EFI
 #include <grub/efi/memory.h>
@@ -556,7 +557,7 @@ grub_dl_resolve_dependencies (grub_dl_t mod, Elf_Ehdr *e)
   return GRUB_ERR_NONE;
 }
 
-int
+grub_uint64_t
 grub_dl_ref (grub_dl_t mod)
 {
   grub_dl_dep_t dep;
@@ -567,10 +568,13 @@ grub_dl_ref (grub_dl_t mod)
   for (dep = mod->dep; dep; dep = dep->next)
     grub_dl_ref (dep->mod);
 
-  return ++mod->ref_count;
+  if (grub_add (mod->ref_count, 1, &mod->ref_count))
+    grub_fatal ("Module reference count overflow");
+
+  return mod->ref_count;
 }
 
-int
+grub_uint64_t
 grub_dl_unref (grub_dl_t mod)
 {
   grub_dl_dep_t dep;
@@ -581,10 +585,13 @@ grub_dl_unref (grub_dl_t mod)
   for (dep = mod->dep; dep; dep = dep->next)
     grub_dl_unref (dep->mod);
 
-  return --mod->ref_count;
+  if (grub_sub (mod->ref_count, 1, &mod->ref_count))
+    grub_fatal ("Module reference count underflow");
+
+  return mod->ref_count;
 }
 
-int
+grub_uint64_t
 grub_dl_ref_count (grub_dl_t mod)
 {
   if (mod == NULL)
diff --git a/include/grub/dl.h b/include/grub/dl.h
index 750fc8d3d..84509c5c1 100644
--- a/include/grub/dl.h
+++ b/include/grub/dl.h
@@ -174,7 +174,7 @@ typedef struct grub_dl_dep *grub_dl_dep_t;
 struct grub_dl
 {
   char *name;
-  int ref_count;
+  grub_uint64_t ref_count;
   int persistent;
   grub_dl_dep_t dep;
   grub_dl_segment_t segment;
@@ -203,9 +203,9 @@ grub_dl_t EXPORT_FUNC(grub_dl_load) (const char *name);
 grub_dl_t grub_dl_load_core (void *addr, grub_size_t size);
 grub_dl_t EXPORT_FUNC(grub_dl_load_core_noinit) (void *addr, grub_size_t size);
 int EXPORT_FUNC(grub_dl_unload) (grub_dl_t mod);
-extern int EXPORT_FUNC(grub_dl_ref) (grub_dl_t mod);
-extern int EXPORT_FUNC(grub_dl_unref) (grub_dl_t mod);
-extern int EXPORT_FUNC(grub_dl_ref_count) (grub_dl_t mod);
+extern grub_uint64_t EXPORT_FUNC(grub_dl_ref) (grub_dl_t mod);
+extern grub_uint64_t EXPORT_FUNC(grub_dl_unref) (grub_dl_t mod);
+extern grub_uint64_t EXPORT_FUNC(grub_dl_ref_count) (grub_dl_t mod);
 
 extern grub_dl_t EXPORT_VAR(grub_dl_head);
 
diff --git a/util/misc.c b/util/misc.c
index d545212d9..0f928e5b4 100644
--- a/util/misc.c
+++ b/util/misc.c
@@ -190,14 +190,14 @@ grub_xputs_real (const char *str)
 
 void (*grub_xputs) (const char *str) = grub_xputs_real;
 
-int
+grub_uint64_t
 grub_dl_ref (grub_dl_t mod)
 {
   (void) mod;
   return 0;
 }
 
-int
+grub_uint64_t
 grub_dl_unref (grub_dl_t mod)
 {
   (void) mod;
-- 
2.11.0


_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to