amd64/138318: [patch] select(2) in i386 emulation can overwrite user data

John Baldwin jhb at freebsd.org
Mon Aug 31 12:30:09 UTC 2009


The following reply was made to PR amd64/138318; it has been noted by GNATS.

From: John Baldwin <jhb at freebsd.org>
To: freebsd-amd64 at freebsd.org,
 Peter Jeremy <peterjeremy at optushome.com.au>
Cc: FreeBSD-gnats-submit at freebsd.org
Subject: Re: amd64/138318: [patch] select(2) in i386 emulation can overwrite user data
Date: Mon, 31 Aug 2009 08:23:27 -0400

 On Saturday 29 August 2009 7:03:32 pm Peter Jeremy wrote:
 > 
 > >Number:         138318
 > >Category:       amd64
 > >Synopsis:       [patch] select(2) in i386 emulation can overwrite user data
 > >Confidential:   no
 > >Severity:       critical
 > >Priority:       high
 > >Responsible:    freebsd-amd64
 > >State:          open
 > >Quarter:        
 > >Keywords:       
 > >Date-Required:
 > >Class:          sw-bug
 > >Submitter-Id:   current-users
 > >Arrival-Date:   Sat Aug 29 23:10:01 UTC 2009
 > >Closed-Date:
 > >Last-Modified:
 > >Originator:     Peter Jeremy
 > >Release:        FreeBSD 8.0-BETA2 amd64
 > >Organization:
 > n/a
 > >Environment:
 > System: FreeBSD server.vk2pj.dyndns.org 8.0-BETA2 FreeBSD 8.0-BETA2 #8: Sat 
 Aug 8 21:54:17 EST 2009 
 root at server.vk2pj.dyndns.org:/var/obj/usr/src/sys/server amd64
 > 
 > Code inspection shows that this bug still exists in 9-current.
 > 
 > >Description:
 > 	The select() wrapper for freebsd32 and linux32 emulation does not
 > 	wrap the fd_set arguments.  fd_set is an array of fd_mask - which
 > 	is 'long' on all architectures.  This means that kern_select() on
 > 	64-bit kernels expects that the fd_set arguments are arrays of
 > 	8-byte objects whilst 32-bit code passes arrays of 4-byte objects.
 > 	As a result, the kernel can overwrite 4-bytes more than userland
 > 	expects.
 > 
 > 	This obviously breaks 32-bit sshd with PrivilegeSeparation enabled
 > 	but may have other less-obvious breakage.
 > 
 > >How-To-Repeat:
 > 
 > Run a FreeBSD/i386 sshd on FreeBSD/amd64:
 > 
 > server# file /tank/aspire/usr/sbin/sshd 
 > /tank/aspire/usr/sbin/sshd: ELF 32-bit LSB executable, Intel 80386, version 
 1 (FreeBSD), dynamically linked (uses shared libs), for FreeBSD 8.0 (800096), 
 stripped
 > server# /tank/aspire/usr/sbin/sshd -p 8022 -d -o UsePrivilegeSeparation=yes
 > debug1: sshd version OpenSSH_5.1p1 FreeBSD-20080801
 > ...
 > debug1: SSH2_MSG_KEX_DH_GEX_REQUEST received
 > debug1: SSH2_MSG_KEX_DH_GEX_GROUP sent
 > debug1: expecting SSH2_MSG_KEX_DH_GEX_INIT
 > buffer_put_bignum2_ret: BN too small
 > buffer_put_bignum2: buffer error
 > debug1: do_cleanup
 > debug1: do_cleanup
 > server# 
 > 
 > As a more contrived (but more obvious) example, compile the following
 > code on i386 and run it on amd64:
 > 
 > ---- 8-< ---- 8-< ---- 8-< ---- 8-< ---- 8-< ---- 8-< ---- 8-< ----
 > #include <sys/select.h>
 > #include <stdio.h>
 > #include <stdlib.h>
 > #include <string.h>
 > 
 > int main(void)
 > {
 >     fd_set *fd, *rd, *wr, *ex;
 >     int r;
 >     fd = malloc(sizeof(fd_mask) * 3 * 4);
 >     memset(fd, 0xa5, sizeof(fd_mask) * 3 * 4);
 >     rd = (fd_set *)&fd->fds_bits[1];
 >     wr = (fd_set *)&fd->fds_bits[5];
 >     ex = (fd_set *)&fd->fds_bits[9];
 >     rd->fds_bits[0] = wr->fds_bits[0] = ex->fds_bits[0] = 0;
 >     FD_SET(0, rd);
 >     FD_SET(1, wr);
 >     FD_SET(2, wr);
 >     FD_SET(0, ex);
 >     FD_SET(1, ex);
 >     FD_SET(2, ex);
 >     printf("read:   %08lx %08lx %08lx %08lx\n",
 > 	   fd->fds_bits[0], fd->fds_bits[1], fd->fds_bits[2], fd->fds_bits[3]);
 >     printf("write:  %08lx %08lx %08lx %08lx\n",
 > 	   fd->fds_bits[4], fd->fds_bits[5], fd->fds_bits[6], fd->fds_bits[7]);
 >     printf("except: %08lx %08lx %08lx %08lx\n",
 > 	 fd->fds_bits[8], fd->fds_bits[9], fd->fds_bits[10], fd->fds_bits[11]);
 >     r = select(3, rd, wr, ex, NULL);
 >     printf("select returns %d:\n", r);
 >     printf("read:   %08lx %08lx %08lx %08lx\n",
 > 	   fd->fds_bits[0], fd->fds_bits[1], fd->fds_bits[2], fd->fds_bits[3]);
 >     printf("write:  %08lx %08lx %08lx %08lx\n",
 > 	   fd->fds_bits[4], fd->fds_bits[5], fd->fds_bits[6], fd->fds_bits[7]);
 >     printf("except: %08lx %08lx %08lx %08lx\n",
 > 	 fd->fds_bits[8], fd->fds_bits[9], fd->fds_bits[10], fd->fds_bits[11]);
 >     return 0;
 > }
 > ---- 8-< ---- 8-< ---- 8-< ---- 8-< ---- 8-< ---- 8-< ---- 8-< ----
 > server# /tank/aspire/root/seltest 
 > read:   a5a5a5a5 00000001 a5a5a5a5 a5a5a5a5
 > write:  a5a5a5a5 00000006 a5a5a5a5 a5a5a5a5
 > except: a5a5a5a5 00000007 a5a5a5a5 a5a5a5a5
 > read:   a5a5a5a5 00000000 00000000 a5a5a5a5
 > write:  a5a5a5a5 00000006 00000000 a5a5a5a5
 > except: a5a5a5a5 00000000 00000000 a5a5a5a5
 > server# 
 > 
 > >Fix:
 > Either:
 > 1) Change the definition of fd_mask from ulong to uint32 (at least within
 >    the kernel)
 > 2) Wrap the fd_set arguments on freebsd32 and linux for 64-bit kernels.
 > 
 > The latter may appear stylistically cleaner but requires significantly
 > more effort because the fd_set copyin()s are all currently done within
 > kern_select() and are non-trivial blocks of code to optimise performance
 > whilst minimising kvm usage.  The attached patch therefore implements
 > the former behaviour:
 > Index: select.h
 > ===================================================================
 > RCS file: /usr/ncvs/src/sys/sys/select.h,v
 > retrieving revision 1.20
 > diff -u -r1.20 select.h
 > --- select.h	6 Jan 2006 22:12:46 -0000	1.20
 > +++ select.h	29 Aug 2009 23:00:08 -0000
 > @@ -39,7 +39,7 @@
 >  #include <sys/_timeval.h>
 >  #include <sys/timespec.h>
 >  
 > -typedef	unsigned long	__fd_mask;
 > +typedef	__uint32_t	__fd_mask;
 >  #if __BSD_VISIBLE
 >  typedef	__fd_mask	fd_mask;
 >  #endif
 
 I think this would break the ABI of select() for old binaries (compiled with 
 fd_mask == long) on 64-bit big-endian archs (i.e. sparc64).  I think you 
 could manage 2) by having kern_select() accept an 'int nfdbits' parameter 
 that replaces 'NFDBITS' when computing nfdbits.  That will work fine for now 
 as all our COMPAT32 archs are little-endian.  If we wanted to support 
 COMPAT32 on big endian then you could pass an operations vector to 
 kern_select() that has wrappers for copying in/out fd_set lists similar to 
 what is done with kern_kevent().
 
 -- 
 John Baldwin


More information about the freebsd-amd64 mailing list