defend against malicious line in ut_line, which could cause unwanted

writes to anything under /dev.  revoke setuid/gid privs earlier.
From: xs@kittenz.org
This commit is contained in:
itojun 2002-08-16 20:21:48 +00:00
parent 39acbf03ed
commit 2abe377059
5 changed files with 173 additions and 86 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: ttymsg.c,v 1.15 2000/07/05 11:46:42 ad Exp $ */
/* $NetBSD: ttymsg.c,v 1.16 2002/08/16 20:21:48 itojun Exp $ */
/*
* Copyright (c) 1989, 1993
@ -38,7 +38,7 @@
#if 0
static char sccsid[] = "@(#)ttymsg.c 8.2 (Berkeley) 11/16/93";
#else
__RCSID("$NetBSD: ttymsg.c,v 1.15 2000/07/05 11:46:42 ad Exp $");
__RCSID("$NetBSD: ttymsg.c,v 1.16 2002/08/16 20:21:48 itojun Exp $");
#endif
#endif /* LIBC_SCCS and not lint */
@ -67,8 +67,8 @@ __RCSID("$NetBSD: ttymsg.c,v 1.15 2000/07/05 11:46:42 ad Exp $");
char *
ttymsg(struct iovec *iov, int iovcnt, const char *line, int tmout)
{
static char device[MAXNAMLEN] = _PATH_DEV;
static char errbuf[1024];
char device[MAXNAMLEN];
int cnt, fd, left, wret;
struct iovec localiov[6];
sigset_t nset;
@ -81,12 +81,16 @@ ttymsg(struct iovec *iov, int iovcnt, const char *line, int tmout)
if (iovcnt > sizeof(localiov) / sizeof(localiov[0]))
return ("too many iov's (change code in libutil/ttymsg.c)");
(void)strncpy(device + sizeof(_PATH_DEV) - 1, line,
sizeof(device) - sizeof(_PATH_DEV));
if (strchr(device + sizeof(_PATH_DEV) - 1, '/')) {
/* A slash is an attempt to break security... */
(void) snprintf(errbuf, sizeof(errbuf), "'/' in \"%s\"",
device);
if (strlcpy(device, _PATH_DEV, sizeof(device)) >= sizeof(device) ||
strlcat(device, line, sizeof(device)) >= sizeof(device)) {
(void) snprintf(errbuf, sizeof(errbuf), "%s: path too long",
line);
return (errbuf);
}
if (strcspn(line, "./") != strlen(line)) {
/* A slash or dot is an attempt to break security... */
(void) snprintf(errbuf, sizeof(errbuf), "'/' or '.' in \"%s\"",
line);
return (errbuf);
}
@ -101,6 +105,12 @@ ttymsg(struct iovec *iov, int iovcnt, const char *line, int tmout)
"%s: %s", device, strerror(errno));
return (errbuf);
}
if (!isatty(fd)) {
(void) snprintf(errbuf, sizeof(errbuf),
"%s: not a tty device", device);
(void) close(fd);
return (errbuf);
}
for (cnt = left = 0; cnt < iovcnt; ++cnt)
left += iov[cnt].iov_len;

View File

@ -1,4 +1,4 @@
/* $NetBSD: utmp_update.c,v 1.3 2002/08/09 10:01:53 soren Exp $ */
/* $NetBSD: utmp_update.c,v 1.4 2002/08/16 20:21:48 itojun Exp $ */
/*-
* Copyright (c) 2002 The NetBSD Foundation, Inc.
@ -41,6 +41,7 @@
#include <stdio.h>
#include <vis.h>
#include <err.h>
#include <fcntl.h>
#include <pwd.h>
#include <utmpx.h>
#include <stdlib.h>
@ -56,8 +57,14 @@ main(int argc, char *argv[])
size_t len;
struct passwd *pwd;
struct stat st;
int fd;
uid_t euid;
if (argc != 1) {
euid = geteuid();
if (seteuid(getuid()) == -1)
err(1, "seteuid");
if (argc != 2) {
(void)fprintf(stderr, "Usage: %s <vis-utmpx-entry>\n",
getprogname());
exit(1);
@ -90,18 +97,20 @@ main(int argc, char *argv[])
errx(1, "Current user `%s' does not match `%s' in utmpx entry",
pwd->pw_name, utx->ut_name);
if (stat(utx->ut_line, &st) == -1)
fd = open(utx->ut_line, O_RDONLY, 0);
if (fd == -1)
err(1, "Cannot open `%s'", utx->ut_line);
if (fstat(fd, &st) == -1)
err(1, "Cannot stat `%s'", utx->ut_line);
if (st.st_uid != getuid())
errx(1, "%s: Is not owned by you", utx->ut_line);
if (!isatty(fd))
errx(1, "%s: Not a tty device", utx->ut_line);
close(fd);
if (access(utx->ut_line, W_OK|R_OK) == -1)
err(1, "%s", utx->ut_line);
if (!S_ISCHR(st.st_mode))
errx(1, "%s: Not a character device", utx->ut_line);
/*
* to check if the tty is writable here is problematic. First we
* don't even know if it is a tty, secondly we are setuid so it
* is not trivial to use access
*/
(void)seteuid(euid);
pututxline(utx);
return 0;

View File

@ -1,4 +1,4 @@
/* $NetBSD: optr.c,v 1.26 2002/08/02 02:07:09 christos Exp $ */
/* $NetBSD: optr.c,v 1.27 2002/08/16 20:21:49 itojun Exp $ */
/*-
* Copyright (c) 1980, 1988, 1993
@ -38,7 +38,7 @@
#if 0
static char sccsid[] = "@(#)optr.c 8.2 (Berkeley) 1/6/94";
#else
__RCSID("$NetBSD: optr.c,v 1.26 2002/08/02 02:07:09 christos Exp $");
__RCSID("$NetBSD: optr.c,v 1.27 2002/08/16 20:21:49 itojun Exp $");
#endif
#endif /* not lint */
@ -272,11 +272,16 @@ sendmes(char *tty, char *message)
int lmsg = 1;
FILE *f_tty;
if (strcspn(tty, "./") != strlen(tty))
return;
(void)strncpy(t, _PATH_DEV, sizeof(t) - 1);
(void)strncat(t, tty, sizeof(t) - sizeof(_PATH_DEV) - 1);
t[sizeof(t) - 1] = '\0';
if ((f_tty = fopen(t, "w")) != NULL) {
if (!isatty(fileno(f_tty)))
return;
setbuf(f_tty, buf);
(void) fprintf(f_tty,
"\n\

View File

@ -1,4 +1,4 @@
/* $NetBSD: wall.c,v 1.19 2002/08/02 01:52:13 christos Exp $ */
/* $NetBSD: wall.c,v 1.20 2002/08/16 20:21:49 itojun Exp $ */
/*
* Copyright (c) 1988, 1990, 1993
@ -43,7 +43,7 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 1990, 1993\n\
#if 0
static char sccsid[] = "@(#)wall.c 8.2 (Berkeley) 11/16/93";
#endif
__RCSID("$NetBSD: wall.c,v 1.19 2002/08/02 01:52:13 christos Exp $");
__RCSID("$NetBSD: wall.c,v 1.20 2002/08/16 20:21:49 itojun Exp $");
#endif /* not lint */
/*
@ -57,6 +57,7 @@ __RCSID("$NetBSD: wall.c,v 1.19 2002/08/02 01:52:13 christos Exp $");
#include <sys/uio.h>
#include <err.h>
#include <errno.h>
#include <paths.h>
#include <pwd.h>
#include <stdio.h>
@ -71,7 +72,7 @@ void makemsg(const char *);
int main(int, char **);
int nobanner;
int mbufsize;
size_t mbufsize;
char *mbuf;
/* ARGSUSED */
@ -81,8 +82,14 @@ main(int argc, char **argv)
int ch;
struct iovec iov;
char *p;
struct passwd *pep = getpwnam("nobody");
struct passwd *pep;
struct utmpentry *ep;
gid_t egid;
egid = getegid();
if (setegid(getgid()) == -1)
err(1, "setegid");
pep = getpwnam("nobody");
while ((ch = getopt(argc, argv, "n")) != -1)
switch (ch) {
@ -107,6 +114,7 @@ usage:
iov.iov_base = mbuf;
iov.iov_len = mbufsize;
(void)getutentries(NULL, &ep);
(void)setegid(egid);
for (; ep; ep = ep->next)
if ((p = ttymsg(&iov, 1, ep->line, 60*5)) != NULL)
warnx("%s", p);
@ -116,7 +124,7 @@ usage:
void
makemsg(const char *fname)
{
register int ch, cnt;
int ch, cnt;
struct tm *lt;
struct passwd *pw;
struct stat sbuf;
@ -127,9 +135,11 @@ makemsg(const char *fname)
char *p, *tty, tmpname[32], lbuf[100], hostname[MAXHOSTNAMELEN+1];
(void)snprintf(tmpname, sizeof tmpname, "%s/wall.XXXXXX", _PATH_TMP);
if ((fd = mkstemp(tmpname)) == -1 || !(fp = fdopen(fd, "r+")))
if ((fd = mkstemp(tmpname)) == -1)
err(1, "can't open temporary file");
(void)unlink(tmpname);
if (!(fp = fdopen(fd, "r+")))
err(1, "can't open temporary file");
if (!nobanner) {
if (!(whom = getlogin()))
@ -179,10 +189,12 @@ makemsg(const char *fname)
if (fstat(fd, &sbuf))
err(1, "can't stat temporary file");
if (sbuf.st_size > SIZE_T_MAX)
errx(1, "file too big");
mbufsize = sbuf.st_size;
if (!(mbuf = malloc((u_int)mbufsize)))
if (!(mbuf = malloc(mbufsize)))
err(1, "malloc");
if (fread(mbuf, sizeof(*mbuf), mbufsize, fp) != mbufsize)
if (fread(mbuf, 1, mbufsize, fp) != mbufsize)
err(1, "can't read temporary file");
(void)close(fd);
(void)fclose(fp);
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: write.c,v 1.20 2002/08/02 01:59:44 christos Exp $ */
/* $NetBSD: write.c,v 1.21 2002/08/16 20:21:49 itojun Exp $ */
/*
* Copyright (c) 1989, 1993
@ -46,7 +46,7 @@ __COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\
#if 0
static char sccsid[] = "@(#)write.c 8.2 (Berkeley) 4/27/95";
#else
__RCSID("$NetBSD: write.c,v 1.20 2002/08/02 01:59:44 christos Exp $");
__RCSID("$NetBSD: write.c,v 1.21 2002/08/16 20:21:49 itojun Exp $");
#endif
#endif /* not lint */
@ -64,25 +64,34 @@ __RCSID("$NetBSD: write.c,v 1.20 2002/08/02 01:59:44 christos Exp $");
#include <pwd.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include "utmpentry.h"
void done(int);
void do_write(const char *, const char *, const uid_t);
void do_write(int, const char *, const uid_t);
void wr_fputs(char *);
void search_utmp(char *, char *, char *, uid_t, int);
int term_chk(const char *, int *, time_t *, int);
int search_utmp(char *, char *, uid_t);
int term_chk(uid_t, const char *, int *, time_t *, int);
int utmp_chk(const char *, const char *);
int main(int, char **);
static gid_t saved_egid;
int
main(int argc, char **argv)
{
char *cp;
time_t atime;
uid_t myuid;
int msgsok, myttyfd;
char tty[MAXPATHLEN], *mytty;
uid_t myuid, uid;
int msgsok, myttyfd, ttyfd;
char *mytty;
saved_egid = getegid();
if (setegid(getgid()) == -1)
err(1, "setegid");
myuid = getuid();
ttyfd = -1;
/* check that sender has write enabled */
if (isatty(fileno(stdin)))
@ -97,39 +106,41 @@ main(int argc, char **argv)
errx(1, "can't find your tty's name");
if ((cp = strrchr(mytty, '/')) != NULL)
mytty = cp + 1;
if (term_chk(mytty, &msgsok, &atime, 1))
exit(1);
if (term_chk(myuid, mytty, &msgsok, &atime, 1) == -1)
err(1, "%s%s", _PATH_DEV, mytty);
if (!msgsok) {
(void)fprintf(stderr,
"warning: you have write permission turned off; "
"no reply possible\n");
}
myuid = getuid();
/* check args */
switch (argc) {
case 2:
search_utmp(argv[1], tty, mytty, myuid, sizeof tty);
do_write(tty, mytty, myuid);
ttyfd = search_utmp(argv[1], mytty, myuid);
break;
case 3:
if (!strncmp(argv[2], _PATH_DEV, strlen(_PATH_DEV)))
argv[2] += strlen(_PATH_DEV);
if (uid_from_user(argv[1], &uid) == -1)
errx(1, "%s: unknown user", argv[1]);
if (utmp_chk(argv[1], argv[2]))
errx(1, "%s is not logged in on %s",
argv[1], argv[2]);
if (term_chk(argv[2], &msgsok, &atime, 1))
exit(1);
ttyfd = term_chk(uid, argv[2], &msgsok, &atime, 0);
if (ttyfd == -1)
err(1, "%s%s", _PATH_DEV, argv[2]);
if (myuid && !msgsok)
errx(1, "%s has messages disabled on %s",
argv[1], argv[2]);
do_write(argv[2], mytty, myuid);
break;
default:
(void)fprintf(stderr, "usage: write user [tty]\n");
exit(1);
}
if (setgid(getgid()) == -1)
err(1, "setgid");
do_write(ttyfd, mytty, myuid);
done(0);
/* NOTREACHED */
#ifdef __GNUC__
@ -165,49 +176,63 @@ utmp_chk(const char *user, const char *tty)
* Special case for writing to yourself - ignore the terminal you're
* writing from, unless that's the only terminal with messages enabled.
*/
void
search_utmp(char *user, char *tty, char *mytty, uid_t myuid, int ttylen)
int
search_utmp(char *user, char *mytty, uid_t myuid)
{
char tty[MAXPATHLEN];
time_t bestatime, atime;
int nloggedttys, nttys, msgsok, user_is_me;
struct utmpentry *ep;
int fd, nfd;
uid_t uid;
if (uid_from_user(user, &uid) == -1)
errx(1, "%s: unknown user", user);
(void)getutentries(NULL, &ep);
nloggedttys = nttys = 0;
bestatime = 0;
user_is_me = 0;
fd = -1;
for (; ep; ep = ep->next)
if (strcmp(user, ep->name) == 0) {
++nloggedttys;
if (term_chk(ep->line, &msgsok, &atime, 0))
nfd = term_chk(uid, ep->line, &msgsok, &atime, 0);
if (nfd == -1)
continue; /* bad term? skip */
if (myuid && !msgsok)
if (myuid && !msgsok) {
close(nfd);
continue; /* skip ttys with msgs off */
}
if (strcmp(ep->line, mytty) == 0) {
user_is_me = 1;
if (fd == -1)
fd = nfd;
else
close(nfd);
continue; /* don't write to yourself */
}
++nttys;
if (atime > bestatime) {
bestatime = atime;
(void)strncpy(tty, ep->line, ttylen - 1);
tty[ttylen - 1] = '\0';
}
(void)strlcpy(tty, ep->line, sizeof(tty));
close(fd);
fd = nfd;
} else
close(nfd);
}
if (nloggedttys == 0)
errx(1, "%s is not logged in", user);
if (nttys == 0) {
if (user_is_me) { /* ok, so write to yourself! */
(void)strncpy(tty, mytty, ttylen - 1);
tty[ttylen - 1] = '\0';
return;
}
if (user_is_me) /* ok, so write to yourself! */
return fd;
errx(1, "%s has messages disabled", user);
} else if (nttys > 1)
warnx("%s is logged in more than once; writing to %s",
user, tty);
return fd;
}
/*
@ -215,46 +240,71 @@ search_utmp(char *user, char *tty, char *mytty, uid_t myuid, int ttylen)
* and the access time
*/
int
term_chk(const char *tty, int *msgsokP, time_t *atimeP, int showerror)
term_chk(uid_t uid, const char *tty, int *msgsokP, time_t *atimeP, int ismytty)
{
struct stat s;
char path[MAXPATHLEN];
struct stat s;
int i, fd, serrno;
(void)snprintf(path, sizeof path, _PATH_DEV "%s", tty);
if (stat(path, &s) < 0) {
if (showerror)
warn("%s", path);
return(1);
if (strcspn(tty, "./") != strlen(tty)) {
errno = EINVAL; return(-1);
}
i = snprintf(path, sizeof path, _PATH_DEV "%s", tty);
if (i < 0 || i >= sizeof(path)) {
errno = ENOMEM; return(-1);
}
(void)setegid(saved_egid);
fd = open(path, O_WRONLY, 0);
serrno = errno;
(void)setegid(getgid());
errno = serrno;
if (fd == -1)
return(-1);
if (fstat(fd, &s) == -1)
goto error;
if (!isatty(fd) || s.st_uid != uid)
goto error;
*msgsokP = (s.st_mode & S_IWGRP) != 0; /* group write bit */
*atimeP = s.st_atime;
return(0);
if (ismytty)
(void) close(fd);
return(ismytty? 0: fd);
error:
if (fd != -1) {
serrno = errno;
close(fd);
errno = serrno;
}
return(-1);
}
/*
* do_write - actually make the connection
*/
void
do_write(const char *tty, const char *mytty, const uid_t myuid)
do_write(int ttyfd, const char *mytty, const uid_t myuid)
{
const char *login;
char *nows;
struct passwd *pwd;
time_t now;
char path[MAXPATHLEN], host[MAXHOSTNAMELEN + 1], line[512];
char host[MAXHOSTNAMELEN + 1], line[512];
/* Determine our login name before the we reopen() stdout */
/* Determine our login name before we re-open stdout */
if ((login = getlogin()) == NULL) {
if ((pwd = getpwuid(myuid)) != NULL)
login = pwd->pw_name;
else login = "???";
}
(void)snprintf(path, sizeof path, _PATH_DEV "%s", tty);
if ((freopen(path, "w", stdout)) == NULL)
err(1, "%s", path);
if (dup2(ttyfd, STDOUT_FILENO) == -1)
err(1, "dup2");
(void)signal(SIGINT, done);
(void)signal(SIGHUP, done);
(void)close(ttyfd);
/* print greeting */
if (gethostname(host, sizeof(host)) < 0)
@ -264,7 +314,7 @@ do_write(const char *tty, const char *mytty, const uid_t myuid)
now = time((time_t *)NULL);
nows = ctime(&now);
nows[16] = '\0';
(void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
(void)printf("\r\n\a\a\aMessage from %s@%s on %s at %s ...\r\n",
login, host, mytty, nows + 11);
while (fgets(line, sizeof(line), stdin) != NULL)
@ -275,11 +325,14 @@ do_write(const char *tty, const char *mytty, const uid_t myuid)
* done - cleanup and exit
*/
void
done(int dummy)
done(int signo)
{
(void)printf("EOF\r\n");
exit(0);
(void)write(STDOUT_FILENO, "EOF\r\n", sizeof("EOF\r\n") - 1);
if (signo == 0)
exit(0);
else
_exit(0);
}
/*
@ -289,7 +342,7 @@ done(int dummy)
void
wr_fputs(char *s)
{
char c;
unsigned char c;
#define PUTC(c) if (putchar(c) == EOF) goto err;
@ -297,13 +350,11 @@ wr_fputs(char *s)
c = toascii(*s);
if (c == '\n') {
PUTC('\r');
PUTC('\n');
} else if (!isprint((unsigned char)c) &&
!isspace((unsigned char)c) && c != '\007') {
} else if (!isprint(c) && !isspace(c) && c != '\a') {
PUTC('^');
PUTC(c^0x40); /* DEL to ?, others to alpha */
} else
PUTC(c);
c ^= 0x40; /* DEL to ?, others to alpha */
}
PUTC(c);
}
return;