[Xcb] [PATCH libX11] xcb_io: Fix Xlib 32-bit request number wrapping bug

Jonas Petersen jnsptrsn1 at gmail.com
Mon Sep 15 12:31:03 PDT 2014

Hi Keith,

(2nd try)

I went back in time and found your last post regarding this topic. You 
did make some suggestions that got lost, probably due to my 
discontinuing. Sorry about that. So let me pick it up again from there, 
in order to hopefully get the case going.

I will post an updated patch including your suggestions. Please see my 
inline comments in this mail, particularly on your last point.

I tested the patch against a fresh Ubuntu Desktop 14.04.1 i386 
(libx11-1.6.2). I verified that your test (nop.c) will fail before and 
succeed after applying the patch.

Am 30.12.2013 um 21:04 schrieb Keith Packard:
> Jonas Petersen<jnsptrsn1 at gmail.com>  writes:
>> By design, on 32-bit systems, the Xlib internal 32-bit request sequence
>> numbers may wrap. There is two locations within xcb_io.c that are not
>> wrap-safe though. The value of last_flushed relies on request to be
>> sequential all the time. This is not given in the moment when the sequence
>> has just wrapped. Applications may then crash with a "Fatal IO error 11
>> (Resource temporarily unavailable)".
>> This patch fixes this by "unwrapping" the sequence number when needed to
>> retain the sequence relative to last_flushed.
> Reviewed-by: Keith Packard<keithp at keithp.com>
> There are some subtleties here which might be mentioned in comments:
>> +	unwrapped_request = dpy->request;
>> +	/* If there was a sequence number wrap since our last flush,
>> +	 * make sure the sequence number we use, stays in sequence
>> +	 * with dpy->xcb->last_flush. */
>> +	if (sizeof(uint64_t) > sizeof(unsigned long) && dpy->request < dpy->xcb->last_flushed)
>> +		unwrapped_request += UINT64_C(1) << 32;
> The sizeof(uint64_t) > sizeof (unsigned long) test is an optimization;
> dpy->request will *never* be less than last_flushed on a 64-bit system
> (well, until we have to deal with 64-bit wrap). However, the check is
> also useful to understand the append_pending_request call below.

Yes, it's an optimization. It will have an effect at compile time and it 
will make it end up only in 32-bit binaries. Actually there was some 
discussion about that at some other branch of this topic.

>>   		uint64_t sequence;
>> -		for(sequence = dpy->xcb->last_flushed + 1; sequence <= dpy->request; ++sequence)
>> +		for(sequence = dpy->xcb->last_flushed + 1; sequence <= unwrapped_request; ++sequence)
>>   			append_pending_request(dpy, sequence);
> Here, we're passing a 64-bit sequence which may be 'unwrapped', in that
> it can contain values above the maximum possible unsigned long sequence
> number. However, that only happens (see check above) where unsigned long
> is 32 bits. The 'sequence' formal in append_pending_request is an
> unsigned long, and so any time the sequence passed might have a high
> sequence value, it will get trimmed off by the type conversion.
> Do we want an '(unsigned long)' cast in the actual here? It would be
> strictly for documentation. Probably best to just mention what's
> happening in a comment instead

Agreed, I will add a comment.

>>   	}
>> -	requests = dpy->request - dpy->xcb->last_flushed;
>> +	requests = unwrapped_request - dpy->xcb->last_flushed;
>>   	dpy->xcb->last_flushed = dpy->request;
> I think last_flushed should be changed from uint64_t to unsigned long to
> match all of the Xlib types.

By examining the usages of last_flushed, I'd say it makes a lot of sense 
changing it to unsigned long. Added it to the patch.

> That would require a cast in the loop
> above:
> +	for(sequence = (uint64_t) dpy->xcb->last_flushed + 1; sequence <= unwrapped_request; ++sequence)

Added that as well.

> I think there's also a problem in require_socket -- it compares a 64-bit
> value from xcb with a 32-bit local value. I think we need to change that
> to:
>          uint64_t        sent64;
>          unsigned long   sent;
>          sent = sent64
>          if ((long) (sent - dpy->last_request_read) < 0)
>                  throw_thread_fail_assert("sequence wrapped")
> This would only allow for 31-bit differences.

Here I'm not sure if I really get what you mean. So you would introduce 
another local variable instead of casting where appropriate? Please see 
my patch if I applied that right.

By the way, the line following next...

         dpy->xcb->last_flushed = dpy->request = sent;

...now also is much more obvious. Because before it was a 64-32-64-bit 
assignment. Now it is 32-32-32. In my ealier patches I had added a 
comment to that line (which now would be obsolete):

     /* Note: The following line will truncate the 64bit 'sent'
      * to 32bit for 'dpy->request'. This truncated value will
      * then be assigned to the 64-bit 'dpy->xcb->last_flushed'
      * (which is intended). */
     dpy->xcb->last_flushed = dpy->request = sent;

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freedesktop.org/archives/xcb/attachments/20140915/cff8ca60/attachment.html>

More information about the Xcb mailing list