strace seg with select and -ve nfds

Dr. David Alan Gilbert dave at treblig.org
Tue Nov 5 12:33:55 UTC 2013


* Denys Vlasenko (dvlasenk at redhat.com) wrote:
> On 11/04/2013 10:39 PM, Dr. David Alan Gilbert wrote:

Hi Denys,
  Thanks for the reply,

> > The 'trinity' fuzz tester managed to trigger a seg of strace
> > when doing a select() with a -ve nfds value but pointing to a valid large
> > buffer (on x86 Linux).
> > 
> > My patch below fixes this; I'm not 100% happy because:
> >   1) It seems way too complicated - can't we quit earlier when we find
> >      the length is weird?
> 
> Yes, it can be simpler.
> 
> >   2) It's odd the way the code reads the arg for fdsize and then later
> >      reads it again for nfds, I think that's really the underlying
> >      reason this tripped.
> 
> I propose to do simply this:
> 
> +       nfds = fdsize;
>         fdsize = (((fdsize + 7) / 8) + sizeof(long)-1) & -sizeof(long);
> +       /* We had bugs a-la "while (j < args[0])" and "umoven(args[0])" below.
> +        * Instead of args[0], use nfds for fd count, fdsize for array lengths.
> +        */
> 
> and use nfds in those two places where we incorrectly use arg[0] now.

> >   3) I'd like some reassurance that my understanding of the way
> >      strace's arg types work is right.
> > 
> > (WTH does strace use int for nfds?)
> 
> Because having more than 2^31-1 file descriptors in one process is insanity.

Only twice as insane as having 2^30-1 file descriptors!

> > Thoughts?
> 
> I applied a slightly simplified version of your fix to strace git, please try it.

That still fails (this is FORTIFY detecting the fail).

I can see two things potentially; the simple one is that the types are still
wrong so that the -1 starts out as 2^32-1 and goes through the route of being
corrected to be 1024*1024 rather than 0.

However, the other thing I think is being caught here is that fortify is 
catching FD_ISSET on a value greater than the size of the fd_set,
so the added test below causes it to hit that failure - I guess the
only solution there is not to use FD_ISSET or to cap at max-fds rather than
the arbitrary 1024*1024 ?
(I guess you could argue that's a false positive from fortify, but there
again I think it is an illegal use of FD_ISSET).

Anyway, here is the signedness fix:

diff --git a/desc.c b/desc.c
index 384b147..92cdf48 100644
--- a/desc.c
+++ b/desc.c
@@ -481,16 +481,16 @@ static int
 decode_select(struct tcb *tcp, long *args, enum bitness_t bitness)
 {
        int i, j;
-       unsigned nfds, fdsize;
+       int nfds, fdsize;
        fd_set *fds;
        const char *sep;
        long arg;
 
-       fdsize = args[0];
+       fdsize = (int)args[0];
        /* Beware of select(2^31-1, NULL, NULL, NULL) and similar... */
-       if (args[0] > 1024*1024)
+       if (fdsize > 1024*1024)
                fdsize = 1024*1024;
-       if (args[0] < 0)
+       if (fdsize < 0)
                fdsize = 0;
        nfds = fdsize;
        fdsize = (((fdsize + 7) / 8) + sizeof(long)-1) & -sizeof(long);
@@ -502,7 +502,7 @@ decode_select(struct tcb *tcp, long *args, enum bitness_t bitness)
                fds = malloc(fdsize);
                if (!fds)
                        die_out_of_memory();
-               tprintf("%ld", args[0]);
+               tprintf("%d", (int)args[0]);
                for (i = 0; i < 3; i++) {
                        arg = args[i+1];
                        if (arg == 0) {

diff --git a/test/select.c b/test/select.c
index aee9f43..09f29c9 100644
--- a/test/select.c
+++ b/test/select.c
@@ -11,6 +11,8 @@ char buffer[1024*1024*2];
 int main()
 {
        fd_set rds;
+       struct timeval timeout;
+
        FD_ZERO(&rds);
        FD_SET(2, &rds);
        /* Start with a nice simple select */
@@ -18,6 +20,15 @@ int main()
        /* Now the crash case that trinity found, negative nfds
         * but with a pointer to a large chunk of valid memory.
         */
+       FD_ZERO((fd_set*)buffer);
+       FD_SET(2,(fd_set*)buffer);
        select(-1, (fd_set *)buffer, NULL, NULL, NULL);
+
+       /* Another variant, a large +ve value larger than
+        * the allowed number of fds.
+        */
+       timeout.tv_sec = 0;
+       timeout.tv_usec = 100;
+       select(10000, (fd_set *)buffer, NULL, NULL, &timeout);
        return 0;
 }

-- 
 -----Open up your eyes, open up your mind, open up your code -------   
/ Dr. David Alan Gilbert    |       Running GNU/Linux       | Happy  \ 
\ gro.gilbert @ treblig.org |                               | In Hex /
 \ _________________________|_____ http://www.treblig.org   |_______/




More information about the Strace-devel mailing list