chiark / gitweb /
chkpath.c, tmpdir.c, utils.c: Add option to trust private groups. mdw/privgrp
authorMark Wooding <mdw@distorted.org.uk>
Wed, 17 Jul 2024 12:24:58 +0000 (13:24 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 17 Jul 2024 12:26:44 +0000 (13:26 +0100)
chkpath.c
tmpdir.1
tmpdir.c
utils.c
utils.h

index a7158d3c7efb3978fc7813e0f9dd957b630c3e03..a3f6bf3aecd42d42c4088dc5499c0f0793fc6619 100644 (file)
--- a/chkpath.c
+++ b/chkpath.c
@@ -57,7 +57,7 @@ static void report(unsigned what, int verbose,
 /* --- @usage@ --- */
 
 static void usage(FILE *fp)
-  { fprintf(fp, "Usage: %s [-pqstv] [-g NAME] [PATH...]\n", QUIS); }
+  { fprintf(fp, "Usage: %s [-Tpqstv] [-g NAME] [PATH...]\n", QUIS); }
 
 /* --- @version@ --- */
 
@@ -83,6 +83,8 @@ Options provided are:\n\
 -V, --version          Display the program's version number.\n\
 -u, --usage            Show a terse usage summary.\n\
 \n\
+-T, --private-group    Accept paths writable by primary group if it has\n\
+                       no other members.\n\
 -g, --group NAME       Consider members of group NAME trustworthy.\n\
 -p, --print            Write the secure path elements to standard output.\n\
 -q, --quiet            Be quiet about the search progress (cumulative).\n\
@@ -100,6 +102,7 @@ int main(int argc, char *argv[])
   int i;
   char *p, *q, *path;
   struct checkpath cp;
+  gid_t gid;
   int f = 0;
 
 #define f_print 1u
@@ -125,6 +128,7 @@ int main(int argc, char *argv[])
       { "help",                0,              0,      'h' },
       { "version",     0,              0,      'V' },
       { "usage",       0,              0,      'u' },
+      { "private-group", 0,            0,      'T' },
       { "group",       OPTF_ARGREQ,    0,      'g' },
       { "print",       0,              0,      'p' },
       { "quiet",       0,              0,      'q' },
@@ -134,7 +138,7 @@ int main(int argc, char *argv[])
       { 0,             0,              0,      0 }
     };
 
-    i = mdwopt(argc, argv, "hVu" "g:pqstv", opts, 0, 0, 0);
+    i = mdwopt(argc, argv, "hVu" "Tg:pqstv", opts, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
@@ -147,6 +151,11 @@ int main(int argc, char *argv[])
       case 'u':
        usage(stdout);
        exit(0);
+      case 'T':
+       if (!private_group(&gid, cp.cp_verbose) &&
+           checkpath_addgid(&cp, gid))
+         die(1, "too many groups");
+       break;
       case 'g':
        allowgroup(&cp, optarg);
        break;
index be786bd3e4dbe7a2376be047740848c859747559..9459a444762c0cf82fc18322138c5163b28047c3 100644 (file)
--- a/tmpdir.1
+++ b/tmpdir.1
@@ -72,6 +72,9 @@ Don't try to find a temporary directory; just see whether
 is secure, and exit successfully if it is (and unsuccessfully if it
 isn't).
 .TP
+.B "\-T, \-\-private-group"
+Accept directories which are accessible by the user's primary group
+.TP
 .B "\-b, \-\-bourne"
 Output an assignment using Bourne shell syntax.  The default is to
 examine the user's shell and decide which syntax to use based on that.
index 18eb4400c34b8ba95c36489fd9850de9c1fff76a..4c6f4b2d8cb8248c95a739cb002a033b45868ca9 100644 (file)
--- a/tmpdir.c
+++ b/tmpdir.c
 /*----- Static variables --------------------------------------------------*/
 
 static uid_t me;
+static gid_t mygroup;
 static struct checkpath cp;
 static struct passwd *pw;
+static unsigned flags;
+#define F_PRIVGRP 1u
 
 /*----- Main code ---------------------------------------------------------*/
 
@@ -122,8 +125,11 @@ static int ok(const char *p, int *f)
     complain(p, "not a directory", 0);
   else if (st.st_uid != me)
     complain(p, "not owner", 0);
-  else if (st.st_mode & (S_IRWXG | S_IRWXO))
-    complain(p, "non-owner access permitted", 0);
+  else if (st.st_mode & S_IRWXO)
+    complain(p, "other access permitted", 0);
+  else if (!((flags & F_PRIVGRP) && st.st_gid != mygroup) &&
+          (st.st_mode & S_IRWXG))
+    complain(p, "other-group access permitted", 0);
   else if (~st.st_mode & S_IRWXU)
     complain(p, "owner lacks permissions", 0);
   else
@@ -258,7 +264,7 @@ static void report(unsigned what, int verbose,
 /* --- @usage@ --- */
 
 static void usage(FILE *fp)
-  { fprintf(fp, "Usage: %s [-bcv] [-C PATH] [-g NAME]\n", QUIS); }
+  { fprintf(fp, "Usage: %s [-Tbcv] [-C PATH] [-g NAME]\n", QUIS); }
 
 /* --- @version@ --- */
 
@@ -286,6 +292,8 @@ Options supported:\n\
 -u, --usage            Display a terse usage summary.\n\
 \n\
 -C, --check PATH       Check whether PATH is good, setting exit status.\n\
+-T, --private-group    Accept paths writable by primary group if it has\n\
+                       no other members.\n\
 -b, --bourne           Output a `TMPDIR' setting for Bourne shell users.\n\
 -c, --cshell           Output a `TMPDIR' setting for C shell users.\n\
 -g, --group NAME       Trust group NAME to be honest and true.\n\
@@ -312,6 +320,7 @@ int main(int argc, char *argv[])
   int shell = 0;
   int duff = 0;
   int i;
+  gid_t gid;
   char *p;
 
   enum {
@@ -342,6 +351,7 @@ int main(int argc, char *argv[])
       { "usage",       0,              0,      'u' },
       { "check",       OPTF_ARGREQ,    0,      'C' },
       { "verify",      OPTF_ARGREQ,    0,      'C' },
+      { "private-group", 0,            0,      'T' },
       { "bourne",      0,              0,      'b' },
       { "cshell",      0,              0,      'c' },
       { "group",       OPTF_ARGREQ,    0,      'g' },
@@ -350,7 +360,7 @@ int main(int argc, char *argv[])
       { 0,             0,              0,      0 }
     };
 
-    i = mdwopt(argc, argv, "hVu" "C:bcg:v", opts, 0, 0, 0);
+    i = mdwopt(argc, argv, "hVu" "C:Tbcg:qv", opts, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
@@ -366,6 +376,13 @@ int main(int argc, char *argv[])
       case 'C':
        return (!fullcheck(optarg));
        break;
+      case 'T':
+       if (!private_group(&gid, cp.cp_verbose)) {
+         mygroup = gid; flags |= F_PRIVGRP;
+         if (checkpath_addgid(&cp, gid))
+           die(1, "too many groups");
+       }
+       break;
       case 'b':
        shell = sh_bourne;
        break;
diff --git a/utils.c b/utils.c
index 6182d08ea966ffa12d58a629427e45b1fd9ce4a9..e18602b9c3daccb297c19338339bb203f44147a7 100644 (file)
--- a/utils.c
+++ b/utils.c
 #include <stdlib.h>
 
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <grp.h>
+#include <pwd.h>
 
 #include <mLib/macros.h>
 #include <mLib/report.h>
@@ -84,4 +86,117 @@ insert:
   cp->cp_gid[cp->cp_gids++] = g;
 }
 
+/* --- @private_group@ --- *
+ *
+ * Arguments:  @gid_t *gid_out@ = where to put the group id
+ *             @int verbose@ = verbosity level
+ *
+ * Returns:    Zero on success, %$-1$% if the user's group is not private.
+ *
+ * Use:                If the user's primary group has no other configured members,
+ *             then set @*gid_out@ to its gid and return zero.  Otherwise,
+ *             report a message if the verbosity level is sufficiently high,
+ *             and return %$-1$%,
+ */
+
+static int check_private_group_members(uid_t uid, struct group *gr,
+                                      int verbose)
+{
+  struct passwd *pw;
+  char *const *p;
+
+  for (p = gr->gr_mem; *p; p++) {
+    pw = getpwnam(*p);
+    if (!pw) {
+      if (verbose >= 0)
+       moan("can't find `passwd' entry for primary group `%s' member `%s'",
+            gr->gr_name, *p);
+      return (-1);
+    }
+    if (pw->pw_uid != uid) {
+      if (verbose >= 1)
+       moan("primary group `%s' has `%s' (uid %ld) as additional member",
+            gr->gr_name, *p, (long)pw->pw_uid);
+      return (-1);
+    }
+  }
+
+  return (0);
+}
+
+int private_group(gid_t *gid_out, int verbose)
+{
+  struct group *gr;
+  struct passwd *pw;
+  uid_t uid = getuid();
+  gid_t gid;
+  int rc;
+  unsigned f = 0;
+#define f_pwiter 1u
+#define f_griter 2u
+
+  /* Look up the user's primary group. */
+  pw = getpwuid(uid);
+  if (!pw) {
+    if (verbose >= 0)
+      moan("can't find `passwd' entry for real uid %ld", (long)uid);
+    rc = -1; goto end;
+  }
+  gid = pw->pw_gid;
+
+  /* Check /all/ groups in case there's another with the same gid which lists
+   * a member other than us.
+   */
+  setgrent(); f |= f_griter;
+  for (;;) {
+    gr = getgrent(); if (!gr) break;
+    if (gr->gr_gid == gid && check_private_group_members(uid, gr, verbose))
+      { rc = -1; goto end; }
+  }
+  endgrent(); f &= ~f_griter;
+
+  /* Check the group members directly.  The above check may well not work in
+   * YP environment, for example.
+   */
+  gr = getgrgid(gid);
+  if (!gr) {
+    if (verbose >= 1)
+      moan("can't find `group' entry for real gid %ld", (long)gid);
+    rc = -1; goto end;
+  }
+  if (check_private_group_members(uid, gr, verbose))
+    { rc = -1; goto end; }
+
+  /* Finally, check all other users to see if they list our group as their
+   * primary group.
+   */
+  setpwent(); f |= f_pwiter;
+  for (;;) {
+    pw = getpwent(); if (!pw) break;
+    if (pw->pw_gid == gid && pw->pw_uid != uid) {
+      if (verbose >= 1)
+       moan("user `%s' (uid %ld) shares primary group `%s' (gid %ld)",
+            pw->pw_name, (long)pw->pw_uid,
+            gr->gr_name, (long)gr->gr_gid);
+      rc = -1; goto end;
+    }
+  }
+  endpwent(); f &= ~f_pwiter;
+
+  /* All is well. */
+  if (verbose >= 2)
+    moan("trusting private group `%s' (gid %ld)",
+        gr->gr_name, (long)gr->gr_gid);
+  *gid_out = gid;
+  rc = 0;
+
+end:
+  if (f&f_pwiter) endpwent();
+  if (f&f_griter) endgrent();
+  return (rc);
+
+#undef f_pwiter
+#undef f_griter
+}
+
 /*----- That's all, folks -------------------------------------------------*/
diff --git a/utils.h b/utils.h
index fc29aee246cee40c70374746c2ea0fd72fefef3a..0b2a74a643f385edaabd9ec094ec1893f927a020 100644 (file)
--- a/utils.h
+++ b/utils.h
 
 extern void allowgroup(struct checkpath */*cp*/, const char */*gname*/);
 
+/* --- @private_group@ --- *
+ *
+ * Arguments:  @gid_t *gid_out@ = where to put the group id
+ *             @int verbose@ = verbosity level
+ *
+ * Returns:    Zero on success, %$-1$% if the user's group is not private.
+ *
+ * Use:                If the user's primary group has no other configured members,
+ *             then set @*gid_out@ to its gid and return zero.  Otherwise,
+ *             report a message if the verbosity level is sufficiently high,
+ *             and return %$-1$%,
+ */
+
+extern int private_group(gid_t */*gid_out*/, int /*verbose*/);
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus