"After" Security Advisory Title: OpenBSD chpass/chfn/chsh file content leak Affects: chpass/chfn/chsh from OpenBSD (from 2.0 to 3.2) Advisory ID: ASA-0001 Release Date: 2003-02-03 Author: Marc Bevand URL: http://www.epita.fr/~bevand_m/asa/asa-0001 --oOo-- 0. Table of Contents 0. Table of Contents 1. Introduction 2. Problem 3. Solution 4. Conclusion 5. References 6. Attached files 7. Revision history --oOo-- 1. Introduction OpenBSD [1] provides a setuid-root tool, chpass(1) (or chfn, or chsh, which are hard links to the same binary file), that allows editing of the user database information. This tool can be exploited to partially display the content of any file. But to make this happen, the content of the file has to match a very particular format, making the vulnerability practically useless in real-world situations. --oOo-- 2. Problem chpass writes user database information in a temporary file, and supplies it to an editor for changes. While the editor is running, the user can suspend it (^Z), replace the temporary file by a hard link to any file, resume the editor in the foreground, quit it without saving the file, and let chpass process the file for further operations. At this point, chpass will open the file (with root permissions since it is setuid-root), read it line by line and for each of them: - if it is longer than 2048 bytes, abort the reading - if it begins by '#', ignore it - else check the validity of the line Many conditions have to be respected to make a line valid, I will not list them here, they are too many. If the line is valid, chpass processes the next one. Else, if it is invalid and if it begins by "shell:" (whatever the case is) and if the rest of the line contains only printable characters (according to isprint(3)) and if none of them is ':' or ' ', the rest of the line is displayed in an error message. Here is a concrete example, create a file as root: # echo "shell: secret_data" >/tmp/sec # chmod 600 /tmp/sec Then run chpass under ordinary user privileges (lets say that the temporary filename you are editing is ``/var/tmp/pw.Loi22925''): $ chpass # ^Z in the editor [1]+ Stopped chpass $ rm /var/tmp/pw.Loi22925 $ ln /tmp/sec /var/tmp/pw.Loi22925 $ fg # then quit the editor chpass chpass: secret_data: non-standard shell ^^^^^^^^^^^ The string "secret_data" is contained in a file owned by root and readable only by root, but is displayed in this error message. FreeBSD and NetBSD implementations of chpass have been checked. They are not vulnerable since the temporary file is created in the directory ``/etc''. --oOo-- 3. Solution OpenBSD maintainers have been contacted on 2003-02-02 about this issue. The same day, a fix has been committed to the cvs (see the attached file ``asa-0001.openbsd-chpass.cvs-diff''). The new code solves the problem by requiring that the link count be one. But this check is not sufficient (reported by Frank v Waveren and Pavel Machek), so on 2003-02-07 another fix has been commited to the cvs (see the attached file ``asa-0001.openbsd-chpass.cvs-diff-2''). The owner of the temporary file is also checked. --oOo-- 4. Conclusion Two fixes has been applied to OpenBSD-current. The attached files contains the related cvs diff. --oOo-- 5. References [1] OpenBSD http://www.openbsd.org --oOo-- 6. Attached files The following file is also available at: http://www.epita.fr/~bevand_m/asa/asa-0001.openbsd-chpass.cvs-diff ---8<-------------- asa-0001.openbsd-chpass.cvs-diff ----------------- Index: edit.c =================================================================== RCS file: /cvs/src/usr.bin/chpass/edit.c,v retrieving revision 1.23 diff -u -r1.23 edit.c --- edit.c 31 Jul 2002 22:08:42 -0000 1.23 +++ edit.c 2 Feb 2003 18:34:02 -0000 @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -152,12 +153,14 @@ char *p, *q; ENTRY *ep; FILE *fp; + int fd; - if (!(fp = fopen(tempname, "r"))) + if ((fd = open(tempname, O_RDONLY|O_NOFOLLOW)) == -1 || + (fp = fdopen(fd, "r")) == NULL) pw_error(tempname, 1, 1); - if (fstat(fileno(fp), &sb)) + if (fstat(fd, &sb)) pw_error(tempname, 1, 1); - if (sb.st_size == 0) { + if (sb.st_size == 0 || sb.st_nlink != 1) { warnx("corrupted temporary file"); goto bad; } ---8<-------------- asa-0001.openbsd-chpass.cvs-diff ----------------- The following file is also available at: http://www.epita.fr/~bevand_m/asa/asa-0001.openbsd-chpass.cvs-diff-2 ---8<------------- asa-0001.openbsd-chpass.cvs-diff-2 ---------------- =================================================================== RCS file: /usr/OpenBSD/cvs/src/usr.bin/chpass/edit.c,v retrieving revision 1.24 retrieving revision 1.25 diff -u -r1.24 -r1.25 --- src/usr.bin/chpass/edit.c 2003/02/02 18:38:22 1.24 +++ src/usr.bin/chpass/edit.c 2003/02/07 23:28:18 1.25 @@ -1,4 +1,4 @@ -/* $OpenBSD: edit.c,v 1.24 2003/02/02 18:38:22 millert Exp $ */ +/* $OpenBSD: edit.c,v 1.25 2003/02/07 23:28:18 millert Exp $ */ /* $NetBSD: edit.c,v 1.6 1996/05/15 21:50:45 jtc Exp $ */ /*- @@ -38,7 +38,7 @@ #if 0 static char sccsid[] = "@(#)edit.c 8.3 (Berkeley) 4/2/94"; #else -static char rcsid[] = "$OpenBSD: edit.c,v 1.24 2003/02/02 18:38:22 millert Exp $"; +static char rcsid[] = "$OpenBSD: edit.c,v 1.25 2003/02/07 23:28:18 millert Exp $"; #endif #endif /* not lint */ @@ -160,7 +160,7 @@ pw_error(tempname, 1, 1); if (fstat(fd, &sb)) pw_error(tempname, 1, 1); - if (sb.st_size == 0 || sb.st_nlink != 1) { + if (sb.st_size == 0 || sb.st_nlink != 1 || sb.st_uid != uid) { warnx("corrupted temporary file"); goto bad; } ---8<------------- asa-0001.openbsd-chpass.cvs-diff-2 ---------------- --oOo-- 7. Revision history 2003-02-03 - First public release. 2003-02-08 - Second public release. - The owner of the temporary file is also checked.