[Nouveau] [PATCH 9/9] dri2: Fix corner case crash for swaplimit > 1

Anssi Hannula anssi.hannula at iki.fi
Thu Jul 12 12:39:16 PDT 2012


Hi!

On 16.02.2012 02:45, Mario Kleiner wrote:
> If a swaplimit > 1 is set on a server which
> supports the swaplimit api (XOrg 1.12.0+),
> the following can happen:
>
> 1. Client calls glXSwapBuffersMscOML() with a
>    swap target > 1 vblank in the future, or a
>    client calls glXSwapbuffers() while the swap
>    interval is set to > 1 (unusual but possible).
>
> 2. nouveau_dri2_finish_swap() is therefore called
>    only at the target vblank, instead of immediately.
>
> 3. Because of the deferred execution of
>    nouveu_dri2_finish_swap(), the OpenGL client
>    can call x-servers DRI2GetBuffersWithFormat()
>    before nouveau_dri2_finish_swap() executes and
>    it deletes pixmaps that would be needed by
>    nouveau_dri2_finish_swap() --> Segfault --> Crash.
>
> Prevent this: When a swap is scheduled into the
> future, we temporarily reduce the swaplimit to 1
> until nouveau_dri2_finish_swap() is done, then
> restore it to its original value. This throttles
> the client inside the server in DRI2ThrottleClient()
> before it can call the evil DRI2GetbuffersWithFormat().
>
> The client will still be released one video refresh
> interval before swap completion, so there is still
> some potential win.
>
> This doesn't affect the common case of swapping at
> the next vblank, where this throttling is not needed
> or done.


After upgrading my system to X server 1.12.3, I've started to
experience some crashes/freezes related to apparent memory
corruption.
Debugging with valgrind and gdb, it seems to me like the "evil"
DRI2GetbuffersWithFormat() can be called before swap even when
target_msc == current_msc + 1. This results in writes+reads to
freed memory when the vblank event is finally being handled.

I'm not an Xorg/drm expert, but with a glance at the code I
didn't see anything that would/should prevent
DRI2GetbuffersWithFormat() from being called before the vblank
event is handled. Even if the event is immediately sent by the
kernel, isn't it possible that the X server services some other
requests before the event is dispatched?

Am I missing something, or is that a bug?

In any case, I'm running ddx 1.0.1, xserver 1.12.3, libdrm 2.4.37,
kernel 3.4.4, and I have Option "GLXVBlank" "true".

Here's one of the invalid write errors:

==24537== Invalid write of size 4
==24537==    at 0x8D45029: nouveau_bo_name_get (nouveau.c:426)
==24537==    by 0x8B1EFFD: nouveau_dri2_finish_swap 
(nouveau_dri2.c:173)
==24537==    by 0x8B1F65F: nouveau_dri2_vblank_handler 
(nouveau_dri2.c:598)
==24537==    by 0x8707BA0: drmHandleEvent (xf86drmMode.c:808)
==24537==    by 0x8B37BC5: drmmode_wakeup_handler 
(drmmode_display.c:1447)
==24537==    by 0x43EA15: WakeupHandler (dixutils.c:421)
==24537==    by 0x5D7429: WaitForSomething (WaitFor.c:224)
==24537==    by 0x430B8B: Dispatch (dispatch.c:357)
==24537==    by 0x421D6C: main (main.c:288)
==24537==  Address 0xdd7c6d4 is 4 bytes inside a block of size 40 
free'd
==24537==    at 0x4C25A9E: free (in 
/usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==24537==    by 0x890E5A4: update_dri2_drawable_buffers (dri2.c:415)
==24537==    by 0x890E97D: do_get_buffers (dri2.c:525)
==24537==    by 0x890EB60: DRI2GetBuffersWithFormat (dri2.c:582)
==24537==    by 0x8910A7E: ProcDRI2GetBuffersWithFormat (dri2ext.c:299)
==24537==    by 0x8911256: ProcDRI2Dispatch (dri2ext.c:564)
==24537==    by 0x430DDF: Dispatch (dispatch.c:428)
==24537==    by 0x421D6C: main (main.c:288)

I stored some additional data regarding the vblank event request in
nouveau_dri2_vblank_state, so that I could retrieve the information
at invalid write time:
(gdb) print *(struct nouveau_dri2_vblank_state*) event_data
$1 = {action = SWAP, client = 0x7740b20, draw = 37748775, dst = 
0xdd7c6d0, src = 0xbd1f5a0, func = 0x8910bf2 <DRI2SwapEvent>, data = 
0x99247b0, frame = 302185, current_msc = 302184, target_msc = 302185, 
remainder = 0, divisor = 0, hit = 0}

As you can see, in this event target_msc was current_msc + 1, but
still DRI2GetBuffersWithFormat() apparently managed to get called
first (and since swap limit was not altered, the call was not
throttled).


> Signed-off-by: Mario Kleiner <mario.kleiner at tuebingen.mpg.de>
> ---
>  src/nouveau_dri2.c |   26 ++++++++++++++++++++++++++
>  1 files changed, 26 insertions(+), 0 deletions(-)
>
> diff --git a/src/nouveau_dri2.c b/src/nouveau_dri2.c
> index f0c7fec..7878a5a 100644
> --- a/src/nouveau_dri2.c
> +++ b/src/nouveau_dri2.c
> @@ -445,6 +445,26 @@ nouveau_dri2_schedule_swap(ClientPtr client,
> DrawablePtr draw,
>  		if (*target_msc == 0)
>  			*target_msc = 1;
>
> +#if DRI2INFOREC_VERSION >= 6
> +		/* Is this a swap in the future, ie. the vblank event will
> +		 * not be immediately dispatched, but only at a future vblank?
> +		 * If so, we need to temporarily lower the swaplimit to 1, so
> +		 * that DRI2GetBuffersWithFormat() requests from the client get
> +		 * deferred in the x-server until the vblank event has been
> +		 * dispatched to us and nouveau_dri2_finish_swap() is done. If
> +		 * we wouldn't do this, DRI2GetBuffersWithFormat() would operate
> +		 * on wrong (pre-swap) buffers, and cause a segfault later on in
> +		 * nouveau_dri2_finish_swap(). Our vblank event handler restores
> +		 * the old swaplimit immediately after nouveau_dri2_finish_swap()
> +		 * is done, so we still get 1 video refresh cycle worth of
> +		 * triple-buffering. For a swap at next vblank, dispatch of the
> +		 * vblank event happens immediately, so there isn't any need
> +		 * for this lowered swaplimit.
> +		 */
> +		if (current_msc < *target_msc - 1)
> +			DRI2SwapLimit(draw, 1);
> +#endif
> +
>  		/* Request a vblank event one frame before the target */
>  		ret = nouveau_wait_vblank(draw, DRM_VBLANK_ABSOLUTE |
>  					  DRM_VBLANK_EVENT,
> @@ -557,6 +577,12 @@ nouveau_dri2_vblank_handler(int fd, unsigned int 
> frame,
>  	switch (s->action) {
>  	case SWAP:
>  		nouveau_dri2_finish_swap(draw, frame, tv_sec, tv_usec, s);
> +#if DRI2INFOREC_VERSION >= 6
> +		/* Restore real swap limit on drawable, now that it is safe. */
> +		ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum];
> +		DRI2SwapLimit(draw, NVPTR(scrn)->swap_limit);
> +#endif
> +
>  		break;
>
>  	case WAIT:

-- 
Anssi Hannula


More information about the Nouveau mailing list