Blame SOURCES/0190-Add-grub-set-bootflag-utility.patch

d9d99f
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
d9d99f
From: Hans de Goede <hdegoede@redhat.com>
d9d99f
Date: Tue, 12 Jun 2018 13:25:16 +0200
d9d99f
Subject: [PATCH] Add grub-set-bootflag utility
d9d99f
d9d99f
This commit adds a new grub-set-bootflag utility, which can be used
d9d99f
to set known bootflags in the grubenv: boot_success or menu_show_once.
d9d99f
d9d99f
grub-set-bootflag is different from grub-editenv in 2 ways:
d9d99f
d9d99f
1) It is intended to be executed by regular users through pkexec, so
d9d99f
running as root if the polkit policy allows this. As such it is written
d9d99f
to not use any existing grubenv related code for easy auditing.
d9d99f
d9d99f
2) Since it can be executed by regular users it only allows setting
d9d99f
(assigning a value of 1 to) bootflags which it knows about. Currently
d9d99f
those are just boot_success and menu_show_once.
d9d99f
d9d99f
This commit also adds a couple of example systemd and polkit files which
d9d99f
show how this can be used to set boot_success from a user-session:
d9d99f
d9d99f
docs/grub-boot-success.service
d9d99f
docs/grub-boot-success.timer
d9d99f
docs/org.gnu.grub.policy
d9d99f
d9d99f
The 2 grub-boot-success.systemd files should be placed in /lib/systemd/user
d9d99f
and a symlink to grub-boot-success.timer should be added to
d9d99f
/lib/systemd/user/timers.target.wants.
d9d99f
d9d99f
The org.gnu.grub.policy polkit file should be placed in
d9d99f
/usr/share/polkit-1/actions.
d9d99f
d9d99f
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
d9d99f
---
d9d99f
 Makefile.util.def              |   7 ++
d9d99f
 util/grub-set-bootflag.c       | 158 +++++++++++++++++++++++++++++++++++++++++
d9d99f
 .gitignore                     |   2 +
d9d99f
 conf/Makefile.extra-dist       |   3 +
d9d99f
 docs/grub-boot-success.service |   6 ++
d9d99f
 docs/grub-boot-success.timer   |   5 ++
d9d99f
 docs/org.gnu.grub.policy       |  20 ++++++
d9d99f
 util/grub-set-bootflag.1       |  20 ++++++
d9d99f
 8 files changed, 221 insertions(+)
d9d99f
 create mode 100644 util/grub-set-bootflag.c
d9d99f
 create mode 100644 docs/grub-boot-success.service
d9d99f
 create mode 100644 docs/grub-boot-success.timer
d9d99f
 create mode 100644 docs/org.gnu.grub.policy
d9d99f
 create mode 100644 util/grub-set-bootflag.1
d9d99f
d9d99f
diff --git a/Makefile.util.def b/Makefile.util.def
d9d99f
index 5a8c390a1da..5da55393291 100644
d9d99f
--- a/Makefile.util.def
d9d99f
+++ b/Makefile.util.def
d9d99f
@@ -1435,3 +1435,10 @@ program = {
d9d99f
   ldadd = grub-core/gnulib/libgnu.a;
d9d99f
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
d9d99f
 };
d9d99f
+
d9d99f
+program = {
d9d99f
+  name = grub-set-bootflag;
d9d99f
+  installdir = sbin;
d9d99f
+  mansection = 1;
d9d99f
+  common = util/grub-set-bootflag.c;
d9d99f
+};
d9d99f
diff --git a/util/grub-set-bootflag.c b/util/grub-set-bootflag.c
d9d99f
new file mode 100644
d9d99f
index 00000000000..f8dc310909a
d9d99f
--- /dev/null
d9d99f
+++ b/util/grub-set-bootflag.c
d9d99f
@@ -0,0 +1,158 @@
d9d99f
+/* grub-set-bootflag.c - tool to set boot-flags in the grubenv. */
d9d99f
+/*
d9d99f
+ *  GRUB  --  GRand Unified Bootloader
d9d99f
+ *  Copyright (C) 2018 Free Software Foundation, Inc.
d9d99f
+ *
d9d99f
+ *  GRUB is free software: you can redistribute it and/or modify
d9d99f
+ *  it under the terms of the GNU General Public License as published by
d9d99f
+ *  the Free Software Foundation, either version 3 of the License, or
d9d99f
+ *  (at your option) any later version.
d9d99f
+ *
d9d99f
+ *  GRUB is distributed in the hope that it will be useful,
d9d99f
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
d9d99f
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
d9d99f
+ *  GNU General Public License for more details.
d9d99f
+ *
d9d99f
+ *  You should have received a copy of the GNU General Public License
d9d99f
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
d9d99f
+ */
d9d99f
+
d9d99f
+/*
d9d99f
+ * NOTE this gets run by users as root (through pkexec), so this does not
d9d99f
+ * use any grub library / util functions to allow for easy auditing.
d9d99f
+ * The grub headers are only included to get certain defines.
d9d99f
+ */
d9d99f
+
d9d99f
+#include <config-util.h>     /* For *_DIR_NAME defines */
d9d99f
+#include <grub/types.h>
d9d99f
+#include <grub/lib/envblk.h> /* For GRUB_ENVBLK_DEFCFG define */
d9d99f
+#include <stdio.h>
d9d99f
+#include <string.h>
d9d99f
+#include <unistd.h>
d9d99f
+
d9d99f
+#define GRUBENV "/" GRUB_BOOT_DIR_NAME "/" GRUB_DIR_NAME "/" GRUB_ENVBLK_DEFCFG
d9d99f
+#define GRUBENV_SIZE 1024
d9d99f
+
d9d99f
+const char *bootflags[] = {
d9d99f
+  "boot_success",
d9d99f
+  "menu_show_once",
d9d99f
+  NULL
d9d99f
+};
d9d99f
+
d9d99f
+static void usage(void)
d9d99f
+{
d9d99f
+  int i;
d9d99f
+
d9d99f
+  fprintf (stderr, "Usage: 'grub-set-bootflag <bootflag>', where <bootflag> is one of:\n");
d9d99f
+  for (i = 0; bootflags[i]; i++)
d9d99f
+    fprintf (stderr, "  %s\n", bootflags[i]);
d9d99f
+}
d9d99f
+
d9d99f
+int main(int argc, char *argv[])
d9d99f
+{
d9d99f
+  /* NOTE buf must be at least the longest bootflag length + 4 bytes */
d9d99f
+  char env[GRUBENV_SIZE + 1], buf[64], *s;
d9d99f
+  const char *bootflag;
d9d99f
+  int i, len, ret;
d9d99f
+  FILE *f;
d9d99f
+
d9d99f
+  if (argc != 2)
d9d99f
+    {
d9d99f
+      usage();
d9d99f
+      return 1;
d9d99f
+    }
d9d99f
+
d9d99f
+  for (i = 0; bootflags[i]; i++)
d9d99f
+    if (!strcmp (argv[1], bootflags[i]))
d9d99f
+      break;
d9d99f
+  if (!bootflags[i])
d9d99f
+    {
d9d99f
+      fprintf (stderr, "Invalid bootflag: '%s'\n", argv[1]);
d9d99f
+      usage();
d9d99f
+      return 1;
d9d99f
+    }
d9d99f
+
d9d99f
+  bootflag = bootflags[i];
d9d99f
+  len = strlen (bootflag);
d9d99f
+
d9d99f
+  f = fopen (GRUBENV, "r");
d9d99f
+  if (!f)
d9d99f
+    {
d9d99f
+      perror ("Error opening " GRUBENV " for reading");
d9d99f
+      return 1;     
d9d99f
+    }
d9d99f
+
d9d99f
+  ret = fread (env, 1, GRUBENV_SIZE, f);
d9d99f
+  fclose (f);
d9d99f
+  if (ret != GRUBENV_SIZE)
d9d99f
+    {
d9d99f
+      perror ("Error reading from " GRUBENV);
d9d99f
+      return 1;     
d9d99f
+    }
d9d99f
+
d9d99f
+  /* 0 terminate env */
d9d99f
+  env[GRUBENV_SIZE] = 0;
d9d99f
+
d9d99f
+  if (strncmp (env, GRUB_ENVBLK_SIGNATURE, strlen (GRUB_ENVBLK_SIGNATURE)))
d9d99f
+    {
d9d99f
+      fprintf (stderr, "Error invalid environment block\n");
d9d99f
+      return 1;
d9d99f
+    }
d9d99f
+
d9d99f
+  /* Find a pre-existing definition of the bootflag */
d9d99f
+  s = strstr (env, bootflag);
d9d99f
+  while (s && s[len] != '=')
d9d99f
+    s = strstr (s + len, bootflag);
d9d99f
+
d9d99f
+  if (s && ((s[len + 1] != '0' && s[len + 1] != '1') || s[len + 2] != '\n'))
d9d99f
+    {
d9d99f
+      fprintf (stderr, "Pre-existing bootflag '%s' has unexpected value\n", bootflag);
d9d99f
+      return 1;     
d9d99f
+    }
d9d99f
+
d9d99f
+  /* No pre-existing bootflag? -> find free space */
d9d99f
+  if (!s)
d9d99f
+    {
d9d99f
+      for (i = 0; i < (len + 3); i++)
d9d99f
+        buf[i] = '#';
d9d99f
+      buf[i] = 0;
d9d99f
+      s = strstr (env, buf);
d9d99f
+    }
d9d99f
+
d9d99f
+  if (!s)
d9d99f
+    {
d9d99f
+      fprintf (stderr, "No space in grubenv to store bootflag '%s'\n", bootflag);
d9d99f
+      return 1;     
d9d99f
+    }
d9d99f
+
d9d99f
+  /* The grubenv is not 0 terminated, so memcpy the name + '=' , '1', '\n' */
d9d99f
+  snprintf(buf, sizeof(buf), "%s=1\n", bootflag);
d9d99f
+  memcpy(s, buf, len + 3);
d9d99f
+
d9d99f
+  /* "r+", don't truncate so that the diskspace stays reserved */
d9d99f
+  f = fopen (GRUBENV, "r+");
d9d99f
+  if (!f)
d9d99f
+    {
d9d99f
+      perror ("Error opening " GRUBENV " for writing");
d9d99f
+      return 1;     
d9d99f
+    }
d9d99f
+
d9d99f
+  ret = fwrite (env, 1, GRUBENV_SIZE, f);
d9d99f
+  if (ret != GRUBENV_SIZE)
d9d99f
+    {
d9d99f
+      perror ("Error writing to " GRUBENV);
d9d99f
+      return 1;     
d9d99f
+    }
d9d99f
+
d9d99f
+  ret = fflush (f);
d9d99f
+  if (ret)
d9d99f
+    {
d9d99f
+      perror ("Error flushing " GRUBENV);
d9d99f
+      return 1;     
d9d99f
+    }
d9d99f
+
d9d99f
+  fsync (fileno (f));
d9d99f
+  fclose (f);
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
diff --git a/.gitignore b/.gitignore
d9d99f
index 42475592123..6c4cfc53781 100644
d9d99f
--- a/.gitignore
d9d99f
+++ b/.gitignore
d9d99f
@@ -111,6 +111,8 @@ grub-*.tar.*
d9d99f
 /grub*-rpm-sort.8
d9d99f
 /grub*-script-check
d9d99f
 /grub*-script-check.1
d9d99f
+/grub*-set-bootflag
d9d99f
+/grub*-set-bootflag.1
d9d99f
 /grub*-set-default
d9d99f
 /grub*-set-default.8
d9d99f
 /grub*-setsetpassword
d9d99f
diff --git a/conf/Makefile.extra-dist b/conf/Makefile.extra-dist
d9d99f
index 39eb94bded6..5946ec24a65 100644
d9d99f
--- a/conf/Makefile.extra-dist
d9d99f
+++ b/conf/Makefile.extra-dist
d9d99f
@@ -14,6 +14,9 @@ EXTRA_DIST += util/import_unicode.py
d9d99f
 EXTRA_DIST += docs/autoiso.cfg
d9d99f
 EXTRA_DIST += docs/grub.cfg
d9d99f
 EXTRA_DIST += docs/osdetect.cfg
d9d99f
+EXTRA_DIST += docs/org.gnu.grub.policy
d9d99f
+EXTRA_DIST += docs/grub-boot-success.service
d9d99f
+EXTRA_DIST += docs/grub-boot-success.timer
d9d99f
 
d9d99f
 EXTRA_DIST += conf/i386-cygwin-img-ld.sc
d9d99f
 
d9d99f
diff --git a/docs/grub-boot-success.service b/docs/grub-boot-success.service
d9d99f
new file mode 100644
d9d99f
index 00000000000..c8c91c34d49
d9d99f
--- /dev/null
d9d99f
+++ b/docs/grub-boot-success.service
d9d99f
@@ -0,0 +1,6 @@
d9d99f
+[Unit]
d9d99f
+Description=Mark boot as successful
d9d99f
+
d9d99f
+[Service]
d9d99f
+Type=oneshot
d9d99f
+ExecStart=/usr/bin/pkexec /usr/sbin/grub2-set-bootflag boot_success
d9d99f
diff --git a/docs/grub-boot-success.timer b/docs/grub-boot-success.timer
d9d99f
new file mode 100644
d9d99f
index 00000000000..221b532781b
d9d99f
--- /dev/null
d9d99f
+++ b/docs/grub-boot-success.timer
d9d99f
@@ -0,0 +1,5 @@
d9d99f
+[Unit]
d9d99f
+Description=Mark boot as successful after the user session has run 2 minutes
d9d99f
+
d9d99f
+[Timer]
d9d99f
+OnActiveSec=2min
d9d99f
diff --git a/docs/org.gnu.grub.policy b/docs/org.gnu.grub.policy
d9d99f
new file mode 100644
d9d99f
index 00000000000..18391efc8e7
d9d99f
--- /dev/null
d9d99f
+++ b/docs/org.gnu.grub.policy
d9d99f
@@ -0,0 +1,20 @@
d9d99f
+
d9d99f
+
d9d99f
+<policyconfig>
d9d99f
+  <vendor>GNU GRUB</vendor>
d9d99f
+  <vendor_url>https://www.gnu.org/software/grub/</vendor_url>
d9d99f
+  <action id="org.gnu.grub.set-bootflag">
d9d99f
+    
d9d99f
+          - A normal active user on the local machine does not need permission
d9d99f
+            to set bootflags to show the menu / mark current boot successful.
d9d99f
+     -->
d9d99f
+    <description>Set GRUB bootflags</description>
d9d99f
+    <message>Authentication is required to modify the bootloaders bootflags</message>
d9d99f
+    <defaults>
d9d99f
+      <allow_any>no</allow_any>
d9d99f
+      <allow_inactive>no</allow_inactive>
d9d99f
+      <allow_active>yes</allow_active>
d9d99f
+    </defaults>
d9d99f
+    <annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/grub2-set-bootflag</annotate>
d9d99f
+  </action>
d9d99f
+</policyconfig>
d9d99f
diff --git a/util/grub-set-bootflag.1 b/util/grub-set-bootflag.1
d9d99f
new file mode 100644
d9d99f
index 00000000000..57801da22a0
d9d99f
--- /dev/null
d9d99f
+++ b/util/grub-set-bootflag.1
d9d99f
@@ -0,0 +1,20 @@
d9d99f
+.TH GRUB-SET-BOOTFLAG 1 "Tue Jun 12 2018"
d9d99f
+.SH NAME
d9d99f
+\fBgrub-set-bootflag\fR \(em Set a bootflag in the GRUB environment block.
d9d99f
+
d9d99f
+.SH SYNOPSIS
d9d99f
+\fBgrub-set-bootflag\fR <\fIBOOTFLAG\fR>
d9d99f
+
d9d99f
+.SH DESCRIPTION
d9d99f
+\fBgrub-set-bootflag\fR is a command line to set bootflags in GRUB's
d9d99f
+stored environment.
d9d99f
+
d9d99f
+.SH COMMANDS
d9d99f
+.TP
d9d99f
+\fBBOOTFLAG\fR
d9d99f
+.RS 7
d9d99f
+Bootflag to set, one of \fIboot_success\fR or \fIshow_menu_once\fR.
d9d99f
+.RE
d9d99f
+
d9d99f
+.SH SEE ALSO
d9d99f
+.BR "info grub"