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