eeeca9c
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
eeeca9c
From: Hans de Goede <hdegoede@redhat.com>
eeeca9c
Date: Wed, 13 Nov 2019 13:02:01 +0100
eeeca9c
Subject: [PATCH] grub-set-bootflag: Write new env to tmpfile and then rename
eeeca9c
eeeca9c
Make the grubenv writing code in grub-set-bootflag more robust by
eeeca9c
writing the modified grubenv to a tmpfile first and then renaming the
eeeca9c
tmpfile over the old grubenv (following symlinks).
eeeca9c
eeeca9c
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
eeeca9c
---
eeeca9c
 util/grub-set-bootflag.c | 87 +++++++++++++++++++++++++++++++++++++++++++-----
eeeca9c
 1 file changed, 78 insertions(+), 9 deletions(-)
eeeca9c
eeeca9c
diff --git a/util/grub-set-bootflag.c b/util/grub-set-bootflag.c
e622855
index 3eb04beb5e..3b4c25ca2a 100644
eeeca9c
--- a/util/grub-set-bootflag.c
eeeca9c
+++ b/util/grub-set-bootflag.c
eeeca9c
@@ -28,7 +28,9 @@
eeeca9c
 #include <grub/err.h>
eeeca9c
 #include <grub/lib/envblk.h> /* For GRUB_ENVBLK_DEFCFG define */
eeeca9c
 #include <errno.h>
eeeca9c
+#include <limits.h>
eeeca9c
 #include <stdio.h>
eeeca9c
+#include <stdlib.h>
eeeca9c
 #include <string.h>
eeeca9c
 #include <unistd.h>
eeeca9c
 
bd73b85
@@ -56,8 +58,10 @@ int main(int argc, char *argv[])
eeeca9c
 {
eeeca9c
   /* NOTE buf must be at least the longest bootflag length + 4 bytes */
eeeca9c
   char env[GRUBENV_SIZE + 1], buf[64], *s;
eeeca9c
+  /* +1 for 0 termination, +6 for "XXXXXX" in tmp filename */
eeeca9c
+  char env_filename[PATH_MAX + 1], tmp_filename[PATH_MAX + 6 + 1];
eeeca9c
   const char *bootflag;
eeeca9c
-  int i, len, ret;
eeeca9c
+  int i, fd, len, ret;
eeeca9c
   FILE *f;
eeeca9c
 
eeeca9c
   if (argc != 2)
bd73b85
@@ -89,7 +93,32 @@ int main(int argc, char *argv[])
eeeca9c
   bootflag = bootflags[i];
eeeca9c
   len = strlen (bootflag);
eeeca9c
 
eeeca9c
-  f = fopen (GRUBENV, "r");
eeeca9c
+  /*
eeeca9c
+   * Really become root. setuid avoids an user killing us, possibly leaking
eeeca9c
+   * the tmpfile. setgid avoids the new grubenv's gid being that of the user.
eeeca9c
+   */
eeeca9c
+  ret = setuid(0);
eeeca9c
+  if (ret)
eeeca9c
+    {
eeeca9c
+      perror ("Error setuid(0) failed");
eeeca9c
+      return 1;
eeeca9c
+    }
eeeca9c
+
eeeca9c
+  ret = setgid(0);
eeeca9c
+  if (ret)
eeeca9c
+    {
eeeca9c
+      perror ("Error setgid(0) failed");
eeeca9c
+      return 1;
eeeca9c
+    }
eeeca9c
+
eeeca9c
+  /* Canonicalize GRUBENV filename, resolving symlinks, etc. */
eeeca9c
+  if (!realpath(GRUBENV, env_filename))
eeeca9c
+    {
eeeca9c
+      perror ("Error canonicalizing " GRUBENV " filename");
eeeca9c
+      return 1;
eeeca9c
+    }
eeeca9c
+
eeeca9c
+  f = fopen (env_filename, "r");
eeeca9c
   if (!f)
eeeca9c
     {
eeeca9c
       perror ("Error opening " GRUBENV " for reading");
bd73b85
@@ -144,30 +173,70 @@ int main(int argc, char *argv[])
eeeca9c
   snprintf(buf, sizeof(buf), "%s=1\n", bootflag);
eeeca9c
   memcpy(s, buf, len + 3);
eeeca9c
 
eeeca9c
-  /* "r+", don't truncate so that the diskspace stays reserved */
eeeca9c
-  f = fopen (GRUBENV, "r+");
eeeca9c
+
eeeca9c
+  /*
eeeca9c
+   * Create a tempfile for writing the new env.  Use the canonicalized filename
eeeca9c
+   * for the template so that the tmpfile is in the same dir / on same fs.
eeeca9c
+   */
eeeca9c
+  snprintf(tmp_filename, sizeof(tmp_filename), "%sXXXXXX", env_filename);
eeeca9c
+  fd = mkstemp(tmp_filename);
eeeca9c
+  if (fd == -1)
eeeca9c
+    {
eeeca9c
+      perror ("Creating tmpfile failed");
eeeca9c
+      return 1;
eeeca9c
+    }
eeeca9c
+
eeeca9c
+  f = fdopen (fd, "w");
eeeca9c
   if (!f)
eeeca9c
     {
eeeca9c
-      perror ("Error opening " GRUBENV " for writing");
eeeca9c
+      perror ("Error fdopen of tmpfile failed");
eeeca9c
+      unlink(tmp_filename);
eeeca9c
       return 1;     
eeeca9c
     }
eeeca9c
 
eeeca9c
   ret = fwrite (env, 1, GRUBENV_SIZE, f);
eeeca9c
   if (ret != GRUBENV_SIZE)
eeeca9c
     {
eeeca9c
-      perror ("Error writing to " GRUBENV);
eeeca9c
+      perror ("Error writing tmpfile");
eeeca9c
+      unlink(tmp_filename);
eeeca9c
       return 1;     
eeeca9c
     }
eeeca9c
 
eeeca9c
   ret = fflush (f);
eeeca9c
   if (ret)
eeeca9c
     {
eeeca9c
-      perror ("Error flushing " GRUBENV);
eeeca9c
+      perror ("Error flushing tmpfile");
eeeca9c
+      unlink(tmp_filename);
eeeca9c
       return 1;     
eeeca9c
     }
eeeca9c
 
eeeca9c
-  fsync (fileno (f));
eeeca9c
-  fclose (f);
eeeca9c
+  ret = fsync (fileno (f));
eeeca9c
+  if (ret)
eeeca9c
+    {
eeeca9c
+      perror ("Error syncing tmpfile");
eeeca9c
+      unlink(tmp_filename);
eeeca9c
+      return 1;
eeeca9c
+    }
eeeca9c
+
eeeca9c
+  ret = fclose (f);
eeeca9c
+  if (ret)
eeeca9c
+    {
eeeca9c
+      perror ("Error closing tmpfile");
eeeca9c
+      unlink(tmp_filename);
eeeca9c
+      return 1;
eeeca9c
+    }
eeeca9c
+
eeeca9c
+  /*
eeeca9c
+   * And finally rename the tmpfile with the new env over the old env, the
eeeca9c
+   * linux kernel guarantees that this is atomic (from a syscall pov).
eeeca9c
+   */
eeeca9c
+  ret = rename(tmp_filename, env_filename);
eeeca9c
+  if (ret)
eeeca9c
+    {
eeeca9c
+      perror ("Error renaming tmpfile to " GRUBENV " failed");
eeeca9c
+      unlink(tmp_filename);
eeeca9c
+      return 1;
eeeca9c
+    }
eeeca9c
 
eeeca9c
   return 0;
eeeca9c
 }