amd64/138318: [patch] select(2) in i386 emulation can overwrite
user data
Peter Jeremy
peterjeremy at optushome.com.au
Sat Aug 29 23:10:02 UTC 2009
>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
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-amd64
mailing list