From 28a9bb2d70b3dbc7b703b373556f8b56ab327e70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michel=20D=C3=A4nzer?= <mdaenzer@redhat.com>
Date: Fri, 15 May 2026 17:47:51 +0200
Subject: [PATCH xserver 9/9] dri2: Deduplicate attachments in do_get_buffer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It was always the intention of the DRI2 protocol that there's at most
one instance of each attachment, and that's how it was implemented in
Mesa.

Since that wasn't enforced though, there might be other clients in the
wild which (e.g. accidentally) request the same attachment multiple
times. So starting to a raise a protocol error in this case now risks
breaking such clients.

Instead, just deduplicate the attachments using a bit-set.

This has a couple of desirable side effects:

* destroy_buffer cannot be called multiple times for the same
  DRI2BufferPtr.
* The client cannot cause the server to allocate a buffers array with
  more entries than there are attachments (currently 11).

Signed-off-by: Michel Dänzer <mdaenzer@redhat.com>
---
 hw/xfree86/dri2/dri2.c | 36 ++++++++++++++++++++++--------------
 1 file changed, 22 insertions(+), 14 deletions(-)

diff --git ./hw/xfree86/dri2/dri2.c ../hw/xfree86/dri2/dri2.c
index 7756aeedf114..d3b2eec88c74 100644
--- ./hw/xfree86/dri2/dri2.c
+++ ../hw/xfree86/dri2/dri2.c
@@ -558,45 +558,56 @@ update_dri2_drawable_buffers(DRI2DrawablePtr pPriv, DrawablePtr pDraw,
 static DRI2BufferPtr *
 do_get_buffers(DrawablePtr pDraw, int *width, int *height,
                unsigned int *attachments, int count, int *out_count,
                int has_format)
 {
     DRI2DrawablePtr pPriv = DRI2GetDrawable(pDraw);
     DRI2ScreenPtr ds;
     DRI2BufferPtr *buffers;
+    unsigned attachments_bitset = 0;
     Bool need_real_front = FALSE;
-    Bool have_real_front = FALSE;
     Bool need_fake_front = FALSE;
-    Bool have_fake_front = FALSE;
     int front_format = 0;
     int dimensions_match;
     int buffers_changed = 0;
     int i;
 
-    if (!pPriv) {
+    if (!pPriv ||
+        count > DRI2BufferHiz + 1) {
         *width = pDraw->width;
         *height = pDraw->height;
         *out_count = 0;
         return NULL;
     }
 
     ds = DRI2GetScreenPrime(pDraw->pScreen, pPriv->prime_id);
 
     dimensions_match = (pDraw->width == pPriv->width)
         && (pDraw->height == pPriv->height);
 
-    buffers = calloc((count + 1), sizeof(buffers[0]));
+    /* Since we deduplicate attachments in the buffers array, there cannot be
+     * more entries than there are attachments.
+     */
+    buffers = calloc((min(count, DRI2BufferHiz) + 1), sizeof(buffers[0]));
     if (!buffers)
         goto err_out;
 
     for (i = 0; i < count; i++) {
         const unsigned attachment = *(attachments++);
         const unsigned format = (has_format) ? *(attachments++) : 0;
 
+        if (attachment > DRI2BufferHiz)
+            goto err_out;
+
+        if (attachments_bitset & (1u << attachment))
+            continue;
+
+        attachments_bitset |= 1u << attachment;
+
         if (allocate_or_reuse_buffer(pDraw, ds, pPriv, attachment,
                                      format, dimensions_match, &buffers[i]))
             buffers_changed = 1;
 
         if (buffers[i] == NULL)
             goto err_out;
 
         /* In certain cases the (fake) front buffer is always needed, so return
@@ -606,63 +617,60 @@ do_get_buffers(DrawablePtr pDraw, int *width, int *height,
          * times.
          */
         if (attachment == DRI2BufferBackLeft) {
             need_real_front = TRUE;
             front_format = format;
         }
 
         if (attachment == DRI2BufferFrontLeft) {
-            have_real_front = TRUE;
             front_format = format;
 
             if (pDraw->type == DRAWABLE_WINDOW)
                 need_fake_front = TRUE;
         }
-
-        if (pDraw->type == DRAWABLE_WINDOW) {
-            if (attachment == DRI2BufferFakeFrontLeft)
-                have_fake_front = TRUE;
-        }
     }
 
-    if (need_real_front && !have_real_front) {
+    if (need_real_front &&
+        !(attachments_bitset & (1u << DRI2BufferFrontLeft))) {
         if (allocate_or_reuse_buffer(pDraw, ds, pPriv, DRI2BufferFrontLeft,
                                      front_format, dimensions_match,
                                      &buffers[i]))
             buffers_changed = 1;
 
         if (buffers[i] == NULL)
             goto err_out;
         i++;
     }
 
-    if (need_fake_front && !have_fake_front) {
+    if (need_fake_front &&
+        !(attachments_bitset & (1u << DRI2BufferFakeFrontLeft))) {
         if (allocate_or_reuse_buffer(pDraw, ds, pPriv, DRI2BufferFakeFrontLeft,
                                      front_format, dimensions_match,
                                      &buffers[i]))
             buffers_changed = 1;
 
         if (buffers[i] == NULL)
             goto err_out;
 
         i++;
-        have_fake_front = TRUE;
+        attachments_bitset |= 1u << DRI2BufferFakeFrontLeft;
     }
 
     *out_count = i;
 
     update_dri2_drawable_buffers(pPriv, pDraw, buffers, *out_count, width,
                                  height);
 
     /* If the client is getting a fake front-buffer, pre-fill it with the
      * contents of the real front-buffer.  This ensures correct operation of
      * applications that call glXWaitX before calling glDrawBuffer.
      */
-    if (have_fake_front && buffers_changed) {
+    if (buffers_changed &&
+        (attachments_bitset & (1u << DRI2BufferFakeFrontLeft))) {
         BoxRec box;
         RegionRec region;
 
         box.x1 = 0;
         box.y1 = 0;
         box.x2 = pPriv->width;
         box.y2 = pPriv->height;
         RegionInit(&region, &box, 0);
-- 
2.53.0

