Two bugs in 2.3.10-rc2 that cause segfaults

Rob Mueller robm at fastmail.fm
Tue Oct 16 03:49:11 EDT 2007


Hi Ken

After some testing on our test server, we rolled out 2.3.10-rc2 onto one of 
our production IMAP stores and almost immediately starting noticing 
segfaults for some users.

Bron and I spent quite a bit of time this afternoon debugging, and we're 
pretty sure we found both the causes.

1. In index_parse_sequence(), your realloc() doesn't multiply by 
sizeof(struct seq_range), so if you have a sequence list with more than 
about 8 items, it will start corrupting memory

2. The signed -> unsigned changes in the prot stream stuff weren't as 
innocent as they looked and created a subtle and nasty bug. You might want 
to audit the other code where you changed signed -> unsigned as well...

Here's an example of the problem if you use IDLE and close the connection on 
it. In that case, what happens is:

In cmd_idle(), we wait for data from the client by calling:

        c = getword(imapd_in, &arg);

getword() calls prot_getc(), which looks like this:

#define prot_getc(s) ((s)->cnt-- > 0 ? (int)*(s)->ptr++ : prot_fill(s))

Say there's no data in the prot buffer, then cnt == 0. However when we call 
the above macro, it does cnt--, which makes cnt wrap to 4294967295, and then 
calls prot_fill. prot_fill() calls read(), and if read() returns <= 0 (eg 
other end closed the connection) it does.

        if (n <= 0) {
            if (n) s->error = xstrdup(strerror(errno));
            else s->eof = 1;
            return EOF;
        }

So it sets the eof flag and returns EOF.

Back in cmd_idle, we get the EOF, and exit out of cmd_idle and back into the 
main loop, which does.

        /* Parse tag */
        c = getword(imapd_in, &tag);

Now getword calls the prot_getc() macro again... but uh, oh. cnt is 
definitely > 0 now (it's 4294967295) and so we reading through unitialised 
memory trying to interpret it as IMAP commands until we hit a segfault 
somewhere.

Our fix is to change prot_getc() to only decrement cnt if it's non-zero. 
Because it's an expression macro, it uses the C comma operator which I've 
found often even experienced C people don't know about 
(http://www.eskimo.com/~scs/cclass/int/sx4db.html).

#define prot_getc(s) ((s)->cnt > 0 ? (--(s)->cnt, (int)*(s)->ptr++) : 
prot_fill(s))

Both patches are here:

http://cyrus.brong.fastmail.fm/patches/cyrus-fix-segfaults-2.3.10.diff

Rob



More information about the Cyrus-devel mailing list