Date: Fri, 2 Aug 2002 23:33:15 +0200 (CEST) From: Pawel Jakub Dawidek <nick@garage.freebsd.pl> To: FreeBSD-gnats-submit@FreeBSD.org Subject: bin/41271: Non-suid-crontab. Message-ID: <20020802213315.CA1943ABD6C@milla.ask33.net>
next in thread | raw e-mail | index | archive | help
>Number: 41271 >Category: bin >Synopsis: Non-suid-crontab. >Confidential: no >Severity: non-critical >Priority: medium >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: change-request >Submitter-Id: current-users >Arrival-Date: Fri Aug 02 14:40:01 PDT 2002 >Closed-Date: >Last-Modified: >Originator: Pawel Jakub Dawidek >Release: FreeBSD 4.6-STABLE i386 >Organization: >Environment: System: FreeBSD milla.ask33.net 4.6-STABLE FreeBSD 4.6-STABLE #9: Wed Jul 31 21:18:46 CEST 2002 root@milla.ask33.net:/usr/obj/usr/src/sys/MILLA i386 >Description: Now crontab(1) is a set-uid-root binary. This is not needed, crontab could be only set-gid on unprivileged group, for example ,,crontab''. I'm sending a patch that is a port of crontab from OpenBSD, but with serious hole fixed. This patch fix some little bugs in FreeBSD crontab version too. I've added also path size checks. >How-To-Repeat: >Fix: diff -ru /usr/src/usr.sbin/cron/cron/compat.h cron/cron/compat.h --- /usr/src/usr.sbin/cron/cron/compat.h Sat Aug 28 03:15:49 1999 +++ cron/cron/compat.h Thu Aug 1 20:32:19 2002 @@ -109,7 +109,7 @@ #ifdef POSIX #include <unistd.h> #ifdef _POSIX_SAVED_IDS -# define HAVE_SAVED_UIDS +# define HAVE_SAVED_GIDS #endif #endif @@ -133,8 +133,4 @@ #if !defined(UNICOS) && !defined(UNIXPC) # define HAS_FCHOWN -#endif - -#if !defined(UNICOS) && !defined(UNIXPC) -# define HAS_FCHMOD #endif diff -ru /usr/src/usr.sbin/cron/cron/cron.h cron/cron/cron.h --- /usr/src/usr.sbin/cron/cron/cron.h Tue May 29 01:37:26 2001 +++ cron/cron/cron.h Fri Aug 2 03:12:50 2002 @@ -74,6 +74,8 @@ #define MAX_TEMPSTR 100 /* obvious */ #define MAX_UNAME 20 /* max length of username, should be overkill */ #define ROOT_UID 0 /* don't change this, it really must be root */ +#define WHEEL_GID 0 /* don't change this, it really must be wheel */ +#define CRONTAB_GID 88 /* gid of "crontab" group */ #define ROOT_USER "root" /* ditto */ /* NOTE: these correspond to DebugFlagNames, @@ -219,7 +221,7 @@ set_debug_flags __P((char *)), get_char __P((FILE *)), get_string __P((char *, int, FILE *, char *)), - swap_uids __P((void)), + swap_gids __P((void)), load_env __P((char *, FILE *)), cron_pclose __P((FILE *)), strcmp_until __P((char *, char *, int)), diff -ru /usr/src/usr.sbin/cron/cron/database.c cron/cron/database.c --- /usr/src/usr.sbin/cron/cron/database.c Sat Aug 28 03:15:50 1999 +++ cron/cron/database.c Fri Aug 2 03:38:49 2002 @@ -87,7 +87,7 @@ new_db.head = new_db.tail = NULL; if (syscron_stat.st_mtime) { - process_crontab("root", "*system*", + process_crontab(ROOT_USER, "*system*", SYSCRONTAB, &syscron_stat, &new_db, old_db); } @@ -113,9 +113,13 @@ if (dp->d_name[0] == '.') continue; - (void) strncpy(fname, dp->d_name, sizeof(fname)); - fname[sizeof(fname)-1] = '\0'; + (void) strncpy(fname, dp->d_name, sizeof fname - 1); + fname[sizeof fname - 1] = '\0'; + if (strlen(fname) >= sizeof fname - 1) + continue; /* XXX log? */ (void) snprintf(tabname, sizeof tabname, CRON_TAB(fname)); + if (strlen(tabname) >= sizeof tabname - 1) + continue; /* XXX log? */ process_crontab(fname, fname, tabname, &statbuf, &new_db, old_db); @@ -182,7 +186,6 @@ cron_db *db; char *name; { - char *env_get(); user *u; for (u = db->head; u != NULL; u = u->next) @@ -204,6 +207,8 @@ struct passwd *pw = NULL; int crontab_fd = OK - 1; user *u; + uid_t pwuid; + gid_t pwgid; if (strcmp(fname, "*system*") && !(pw = getpwnam(uname))) { /* file doesn't have a user in passwd file. @@ -211,6 +216,14 @@ log_it(fname, getpid(), "ORPHAN", "no passwd entry"); goto next_crontab; } + /* this have to be done, because of fake "*system*" files */ + if (pw) { + pwuid = pw->pw_uid; + pwgid = pw->pw_gid; + } else { + pwuid = ROOT_UID; + pwgid = WHEEL_GID; + } if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) { /* crontab not accessible? @@ -221,6 +234,26 @@ if (fstat(crontab_fd, statbuf) < OK) { log_it(fname, getpid(), "FSTAT FAILED", tabname); + goto next_crontab; + } + if (!S_ISREG(statbuf->st_mode)) { + log_it(fname, getpid(), "NOT REGULAR", tabname); + goto next_crontab; + } + if ((statbuf->st_mode & 07777) != 0600) { + log_it(fname, getpid(), "BAD FILE MODE", tabname); + goto next_crontab; + } + if (statbuf->st_uid != pwuid) { + log_it(fname, getpid(), "WRONG FILE USER OWNER", tabname); + goto next_crontab; + } + if (statbuf->st_gid != pwgid) { + log_it(fname, getpid(), "WRONG FILE GROUP OWNER", tabname); + goto next_crontab; + } + if (statbuf->st_nlink != 1) { + log_it(fname, getpid(), "BAD LINK COUNT", tabname); goto next_crontab; } diff -ru /usr/src/usr.sbin/cron/crontab/Makefile cron/crontab/Makefile --- /usr/src/usr.sbin/cron/crontab/Makefile Wed Apr 25 14:09:24 2001 +++ cron/crontab/Makefile Fri Aug 2 03:47:20 2002 @@ -8,7 +8,8 @@ BINDIR= /usr/bin BINOWN= root -BINMODE=4555 +BINGRP= crontab +BINMODE=2555 INSTALLFLAGS=-fschg .include <bsd.prog.mk> diff -ru /usr/src/usr.sbin/cron/crontab/crontab.c cron/crontab/crontab.c --- /usr/src/usr.sbin/cron/crontab/crontab.c Sat Jun 16 05:18:37 2001 +++ cron/crontab/crontab.c Fri Aug 2 09:56:36 2002 @@ -57,7 +57,7 @@ static PID_T Pid; static char User[MAX_UNAME], RealUser[MAX_UNAME]; -static char Filename[MAX_FNAME]; +static char Filename[MAX_FNAME + 1], TempFilename[MAX_FNAME + 1]; static FILE *NewCrontab; static int CheckErrorCount; static enum opt_t Option; @@ -69,6 +69,7 @@ check_error __P((char *)), parse_args __P((int c, char *v[])); static int replace_cmd __P((void)); +static void clean_turds(int); static void @@ -101,7 +102,6 @@ setlinebuf(stderr); #endif parse_args(argc, argv); /* sets many globals, opens a file */ - set_cron_uid(); set_cron_cwd(); if (!allowed(User)) { warnx("you (%s) are not allowed to use this program", User); @@ -200,20 +200,12 @@ if (!strcmp(Filename, "-")) { NewCrontab = stdin; } else { - /* relinquish the setuid status of the binary during - * the open, lest nonroot users read files they should - * not be able to read. we can't use access() here - * since there's a race condition. thanks go out to - * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting - * the race. - */ - - if (swap_uids() < OK) - err(ERROR_EXIT, "swapping uids"); + if (swap_gids() < OK) + err(ERROR_EXIT, "swapping gids"); if (!(NewCrontab = fopen(Filename, "r"))) err(ERROR_EXIT, "%s", Filename); - if (swap_uids() < OK) - err(ERROR_EXIT, "swapping uids back"); + if (swap_gids() < OK) + err(ERROR_EXIT, "swapping gids back"); } } @@ -224,12 +216,14 @@ static void list_cmd() { - char n[MAX_FNAME]; + char n[MAX_FNAME + 1]; FILE *f; int ch, x; log_it(RealUser, Pid, "LIST", User); - (void) sprintf(n, CRON_TAB(User)); + (void) snprintf(n, sizeof n, CRON_TAB(User)); + if (strlen(n) >= sizeof n - 1) + err(ERROR_EXIT, "path too long"); if (!(f = fopen(n, "r"))) { if (errno == ENOENT) errx(ERROR_EXIT, "no crontab for %s", User); @@ -266,7 +260,7 @@ static void delete_cmd() { - char n[MAX_FNAME]; + char n[MAX_FNAME + 1]; int ch, first; if (isatty(STDIN_FILENO)) { @@ -279,7 +273,9 @@ } log_it(RealUser, Pid, "DELETE", User); - (void) sprintf(n, CRON_TAB(User)); + (void) snprintf(n, sizeof n, CRON_TAB(User)); + if (strlen(n) >= sizeof n - 1) + err(ERROR_EXIT, "path too long"); if (unlink(n)) { if (errno == ENOENT) errx(ERROR_EXIT, "no crontab for %s", User); @@ -301,17 +297,18 @@ static void edit_cmd() { - char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; + char n[MAX_FNAME + 1], q[MAX_TEMPSTR], *editor; FILE *f; int ch, t, x; struct stat statbuf, fsbuf; time_t mtime; WAIT_T waiter; PID_T pid, xpid; - mode_t um; log_it(RealUser, Pid, "BEGIN EDIT", User); - (void) sprintf(n, CRON_TAB(User)); + (void) snprintf(n, sizeof n, CRON_TAB(User)); + if (strlen(n) >= sizeof n - 1) + err(ERROR_EXIT, "path too long"); if (!(f = fopen(n, "r"))) { if (errno != ENOENT) err(ERROR_EXIT, "%s", n); @@ -320,20 +317,19 @@ err(ERROR_EXIT, _PATH_DEVNULL); } - um = umask(077); - (void) sprintf(Filename, "/tmp/crontab.XXXXXXXXXX"); - if ((t = mkstemp(Filename)) == -1) { - warn("%s", Filename); - (void) umask(um); - goto fatal; - } - (void) umask(um); + strncpy(Filename, "/tmp/crontab.XXXXXXXXXX", sizeof Filename - 1); + Filename[sizeof Filename - 1] = '\0'; + if (strlen(Filename) >= sizeof Filename - 1) + err(ERROR_EXIT, "path too long"); + if ((t = mkstemp(Filename)) == -1) + err(ERROR_EXIT, "%s", Filename); #ifdef HAS_FCHOWN if (fchown(t, getuid(), getgid()) < 0) { + warn("fchown"); #else if (chown(Filename, getuid(), getgid()) < 0) { + warn("chown"); #endif - warn("fchown"); goto fatal; } if (!(NewCrontab = fdopen(t, "r+"))) { @@ -402,8 +398,8 @@ goto fatal; case 0: /* child */ - if (setuid(getuid()) < 0) - err(ERROR_EXIT, "setuid(getuid())"); + if (setgid(getgid()) < 0) + err(ERROR_EXIT, "setgid(getgid())"); if (chdir("/tmp") < 0) err(ERROR_EXIT, "chdir(/tmp)"); if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) @@ -491,9 +487,10 @@ */ static int replace_cmd() { - char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; + char n[MAX_FNAME + 1], envstr[MAX_ENVSTR]; FILE *tmp; - int ch, eof; + int ch, eof, fd; + int error = 0; entry *e; time_t now = time(NULL); char **envp = env_init(); @@ -503,13 +500,28 @@ return (-2); } - (void) sprintf(n, "tmp.%d", Pid); - (void) sprintf(tn, CRON_TAB(n)); - if (!(tmp = fopen(tn, "w+"))) { - warn("%s", tn); + (void) snprintf(TempFilename, sizeof TempFilename, + CRON_TAB("tmp.XXXXXXXXX")); + if (strlen(TempFilename) >= sizeof TempFilename - 1) { + TempFilename[0] = '\0'; + warn("path too long"); + return (-2); + } + + if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) { + warn("%s", TempFilename); + if (fd != -1) { + close(fd); + unlink(TempFilename); + } + TempFilename[0] = '\0'; return (-2); } + (void) signal(SIGHUP, clean_turds); + (void) signal(SIGINT, clean_turds); + (void) signal(SIGQUIT, clean_turds); + /* write a signature at the top of the file. * * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. @@ -528,9 +540,10 @@ fflush(tmp); rewind(tmp); if (ferror(tmp)) { - warnx("error while writing new crontab to %s", tn); - fclose(tmp); unlink(tn); - return (-2); + warnx("error while writing new crontab to %s", TempFilename); + fclose(tmp); + error = -2; + goto done; } /* check the syntax of the file being installed. @@ -559,49 +572,54 @@ if (CheckErrorCount != 0) { warnx("errors in crontab file, can't install"); - fclose(tmp); unlink(tn); - return (-1); + fclose(tmp); + error = -1; + goto done; } #ifdef HAS_FCHOWN - if (fchown(fileno(tmp), ROOT_UID, -1) < OK) + if (fchown(fileno(tmp), getuid(), getgid()) < OK) { + warn("fchown"); #else - if (chown(tn, ROOT_UID, -1) < OK) -#endif - { + if (chown(TempFilename, getuid(), getgid()) < OK) { warn("chown"); - fclose(tmp); unlink(tn); - return (-2); - } - -#ifdef HAS_FCHMOD - if (fchmod(fileno(tmp), 0600) < OK) -#else - if (chmod(tn, 0600) < OK) #endif - { - warn("chown"); - fclose(tmp); unlink(tn); - return (-2); + fclose(tmp); + error = -2; + goto done; } if (fclose(tmp) == EOF) { warn("fclose"); - unlink(tn); - return (-2); + error = -2; + goto done; } - (void) sprintf(n, CRON_TAB(User)); - if (rename(tn, n)) { - warn("error renaming %s to %s", tn, n); - unlink(tn); - return (-2); + (void) snprintf(n, sizeof n, CRON_TAB(User)); + if (strlen(n) >= sizeof n - 1) { + warn("path too long"); + error = -2; + goto done; } + if (rename(TempFilename, n)) { + warn("error renaming %s to %s", TempFilename, n); + error = -2; + goto done; + } + TempFilename[0] = '\0'; log_it(RealUser, Pid, "REPLACE", User); poke_daemon(); - return (0); +done: + (void) signal(SIGHUP, SIG_DFL); + (void) signal(SIGINT, SIG_DFL); + (void) signal(SIGQUIT, SIG_DFL); + if (TempFilename[0]) { + (void) unlink(TempFilename); + TempFilename[0] = '\0'; + } + return (error); } @@ -623,4 +641,20 @@ return; } #endif /*USE_UTIMES*/ +} + + +static void +clean_turds(signo) + int signo; +{ + int save_errno = errno; + + if (TempFilename[0]) + (void) unlink(TempFilename); + if (signo) { + (void) signal(signo, SIG_DFL); + (void) raise(signo); + } + errno = save_errno; } diff -ru /usr/src/usr.sbin/cron/lib/misc.c cron/lib/misc.c --- /usr/src/usr.sbin/cron/lib/misc.c Mon Apr 29 00:45:53 2002 +++ cron/lib/misc.c Fri Aug 2 03:32:07 2002 @@ -210,7 +210,11 @@ */ if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) { warn("%s", SPOOL_DIR); - if (OK == mkdir(SPOOL_DIR, 0700)) { + if (OK == mkdir(SPOOL_DIR, 0730)) { + if (OK != chown(SPOOL_DIR, ROOT_UID, CRONTAB_GID)) { + rmdir(SPOOL_DIR); + err(ERROR_EXIT, "%s: chown", SPOOL_DIR); + } warnx("%s: created", SPOOL_DIR); stat(SPOOL_DIR, &sb); } else { @@ -219,6 +223,20 @@ } if (!(sb.st_mode & S_IFDIR)) err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR); + if (sb.st_uid != ROOT_UID || sb.st_gid != CRONTAB_GID) { + if (OK != chown(SPOOL_DIR, ROOT_UID, CRONTAB_GID)) { + rmdir(SPOOL_DIR); + err(ERROR_EXIT, "%s: chown", SPOOL_DIR); + } + warnx("%s: set owner user and group", SPOOL_DIR); + } + if ((sb.st_mode & 07777) != 0730) { + if (OK != chmod(SPOOL_DIR, 0730)) { + rmdir(SPOOL_DIR); + err(ERROR_EXIT, "%s: chmod", SPOOL_DIR); + } + warnx("%s: set permissions", SPOOL_DIR); + } } @@ -244,15 +262,19 @@ } if (!fp) { - char pidfile[MAX_FNAME]; + char pidfile[MAX_FNAME + 1]; char buf[MAX_TEMPSTR]; int fd, otherpid; - (void) sprintf(pidfile, PIDFILE, PIDDIR); + (void) snprintf(pidfile, sizeof pidfile, PIDFILE, PIDDIR); + if (strlen(pidfile) >= sizeof pidfile - 1) { + log_it("CRON", getpid(), "DEATH", "path too long"); + errx(ERROR_EXIT, "path too long"); + } if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644))) || (NULL == (fp = fdopen(fd, "r+"))) ) { - sprintf(buf, "can't open or create %s: %s", + snprintf(buf, sizeof buf, "can't open or create %s: %s", pidfile, strerror(errno)); log_it("CRON", getpid(), "DEATH", buf); errx(ERROR_EXIT, "%s", buf); @@ -262,7 +284,8 @@ int save_errno = errno; fscanf(fp, "%d", &otherpid); - sprintf(buf, "can't lock %s, otherpid may be %d: %s", + snprintf(buf, sizeof buf, + "can't lock %s, otherpid may be %d: %s", pidfile, otherpid, strerror(save_errno)); log_it("CRON", getpid(), "DEATH", buf); errx(ERROR_EXIT, "%s", buf); @@ -390,7 +413,7 @@ char line[MAX_TEMPSTR]; rewind(file); - while (fgets(line, MAX_TEMPSTR, file)) { + while (fgets(line, sizeof line, file)) { if (line[0] != '\0') if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = '\0'; @@ -410,20 +433,13 @@ allowed(username) char *username; { - static int init = FALSE; - static FILE *allow, *deny; + static FILE *allow = NULL, *deny = NULL; - if (!init) { - init = TRUE; #if defined(ALLOW_FILE) && defined(DENY_FILE) - allow = fopen(ALLOW_FILE, "r"); - deny = fopen(DENY_FILE, "r"); - Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) -#else - allow = NULL; - deny = NULL; + allow = fopen(ALLOW_FILE, "r"); + deny = fopen(DENY_FILE, "r"); + Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny)) #endif - } if (allow) return (in_file(username, allow)); @@ -591,7 +607,7 @@ *dst++ = '^'; *dst++ = '?'; } else { /* parity character */ - sprintf(dst, "\\%03o", ch); + snprintf(dst, 5, "\\%03o", ch); dst += 4; } } @@ -645,11 +661,9 @@ #endif /*MAIL_DATE*/ -#ifdef HAVE_SAVED_UIDS -static int save_euid; -int swap_uids() { save_euid = geteuid(); return seteuid(getuid()); } -int swap_uids_back() { return seteuid(save_euid); } -#else /*HAVE_SAVED_UIDS*/ -int swap_uids() { return setreuid(geteuid(), getuid()); } -int swap_uids_back() { return swap_uids(); } -#endif /*HAVE_SAVED_UIDS*/ +#ifdef HAVE_SAVED_GIDS +static int save_egid; +int swap_gids() { save_egid = getegid(); return setegid(getgid()); } +#else /*HAVE_SAVED_GIDS*/ +int swap_gids() { return setregid(getegid(), getgid()); } +#endif /*HAVE_SAVED_GIDS*/ >Release-Note: >Audit-Trail: >Unformatted: To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-bugs" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20020802213315.CA1943ABD6C>