From: Mark Wooding Date: Wed, 17 Jul 2024 12:24:58 +0000 (+0100) Subject: chkpath.c, tmpdir.c, utils.c: Add option to trust private groups. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/checkpath/commitdiff_plain/refs/heads/mdw/privgrp?hp=972ca7cda65ce3fc9f150c20a56ad4836f3a7414 chkpath.c, tmpdir.c, utils.c: Add option to trust private groups. --- diff --git a/chkpath.c b/chkpath.c index a7158d3..a3f6bf3 100644 --- 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; diff --git a/tmpdir.1 b/tmpdir.1 index be786bd..9459a44 100644 --- 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. diff --git a/tmpdir.c b/tmpdir.c index 18eb440..4c6f4b2 100644 --- a/tmpdir.c +++ b/tmpdir.c @@ -53,8 +53,11 @@ /*----- 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 6182d08..e18602b 100644 --- a/utils.c +++ b/utils.c @@ -32,8 +32,10 @@ #include #include +#include #include +#include #include #include @@ -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 fc29aee..0b2a74a 100644 --- a/utils.h +++ b/utils.h @@ -51,6 +51,21 @@ 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