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