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)
 /* --- @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@ --- */
 
 
 /* --- @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\
 -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\
 -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;
   int i;
   char *p, *q, *path;
   struct checkpath cp;
+  gid_t gid;
   int f = 0;
 
 #define f_print 1u
   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' },
       { "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' },
       { "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 }
     };
 
       { 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) {
     if (i < 0)
       break;
     switch (i) {
@@ -147,6 +151,11 @@ int main(int argc, char *argv[])
       case 'u':
        usage(stdout);
        exit(0);
       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;
       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
 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.
 .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 variables --------------------------------------------------*/
 
 static uid_t me;
+static gid_t mygroup;
 static struct checkpath cp;
 static struct passwd *pw;
 static struct checkpath cp;
 static struct passwd *pw;
+static unsigned flags;
+#define F_PRIVGRP 1u
 
 /*----- Main code ---------------------------------------------------------*/
 
 
 /*----- 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);
     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
   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)
 /* --- @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@ --- */
 
 
 /* --- @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\
 -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\
 -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;
   int shell = 0;
   int duff = 0;
   int i;
+  gid_t gid;
   char *p;
 
   enum {
   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' },
       { "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' },
       { "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 }
     };
 
       { 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) {
     if (i < 0)
       break;
     switch (i) {
@@ -366,6 +376,13 @@ int main(int argc, char *argv[])
       case 'C':
        return (!fullcheck(optarg));
        break;
       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;
       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 <stdlib.h>
 
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <grp.h>
 
 #include <grp.h>
+#include <pwd.h>
 
 #include <mLib/macros.h>
 #include <mLib/report.h>
 
 #include <mLib/macros.h>
 #include <mLib/report.h>
@@ -84,4 +86,117 @@ insert:
   cp->cp_gid[cp->cp_gids++] = g;
 }
 
   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 -------------------------------------------------*/
 /*----- 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*/);
 
 
 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
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus