local-CVE-2021-33574-mq_notify-use-after-free
authorGNU Libc Maintainers <debian-glibc@lists.debian.org>
Wed, 30 Apr 2025 01:01:35 +0000 (09:01 +0800)
committerSean Whitton <spwhitton@spwhitton.name>
Wed, 30 Apr 2025 01:01:35 +0000 (09:01 +0800)
This is basically a backport of the following upstream commits below with
__pthread_attr_copy implemented as a static function in mq_notify.c Indeed it
has been added upstream in glibc 2.32 and is exported as a GLIBC_PRIVATE symbol
from libpthread.so while __mq_notify is exported by librt.so. As Debian
supports online upgrades, We need to support the case where 1) a process is
started loading libpthread.so, 2) the glibc libraries are upgraded 3) librt.so
is dlopened.  This scenario might happens for instance with OpenJDK.

commit 331c6e8a184167dd21a9f0b3fc165aeefea6eeca
Author: Florian Weimer <fweimer@redhat.com>
Date:   Tue May 19 12:32:39 2020 +0200

    nptl: Add __pthread_attr_copy for copying pthread_attr_t objects

commit 79474303223c5665bec75ffbdb2a86ee04a2514b
Author: Nikita Popov <npv1310@gmail.com>
Date:   Mon Aug 9 20:17:34 2021 +0530

    librt: fix NULL pointer dereference (bug 28213)

    Helper thread frees copied attribute on NOTIFY_REMOVED message
    received from the OS kernel.  Unfortunately, it fails to check whether
    copied attribute actually exists (data.attr != NULL).  This worked
    earlier because free() checks passed pointer before actually
    attempting to release corresponding memory.  But
    __pthread_attr_destroy assumes pointer is not NULL.

    So passing NULL pointer to __pthread_attr_destroy will result in
    segmentation fault.  This scenario is possible if
    notification->sigev_notify_attributes == NULL (which means default
    thread attributes should be used).

Signed-off-by: Nikita Popov <npv1310@gmail.com>
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
    (cherry picked from commit b805aebd42364fe696e417808a700fdb9800c9e8)

commit 42d359350510506b87101cf77202fefcbfc790cb
Author: Andreas Schwab <schwab@linux-m68k.org>
Date:   Thu May 27 12:49:47 2021 +0200

    Use __pthread_attr_copy in mq_notify (bug 27896)

    Make a deep copy of the pthread attribute object to remove a potential
    use-after-free issue.

commit 217b6dc298156bdb0d6aea9ea93e7e394a5ff091
Author: Florian Weimer <fweimer@redhat.com>
Date:   Tue Jun 1 17:51:41 2021 +0200

    Fix use of __pthread_attr_copy in mq_notify (bug 27896)

    __pthread_attr_copy can fail and does not initialize the attribute
    structure in that case.

    If __pthread_attr_copy is never called and there is no allocated
    attribute, pthread_attr_destroy should not be called, otherwise
    there is a null pointer dereference in rt/tst-mqueue6.

    Fixes commit 42d359350510506b87101cf77202fefcbfc790cb
    ("Use __pthread_attr_copy in mq_notify (bug 27896)").

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Gbp-Pq: Topic any
Gbp-Pq: Name local-CVE-2021-33574-mq_notify-use-after-free.diff

sysdeps/unix/sysv/linux/mq_notify.c

index f288bac477df8c846c6b2498376f5a67f5907e80..e78ae74f0bd697ca9dc1a0fa230fc5b1499eec5f 100644 (file)
@@ -134,9 +134,12 @@ helper_thread (void *arg)
               to wait until it is done with it.  */
            (void) __pthread_barrier_wait (&notify_barrier);
        }
-      else if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_REMOVED)
-       /* The only state we keep is the copy of the thread attributes.  */
-       free (data.attr);
+      else if (data.raw[NOTIFY_COOKIE_LEN - 1] == NOTIFY_REMOVED && data.attr != NULL)
+       {
+         /* The only state we keep is the copy of the thread attributes.  */
+         pthread_attr_destroy (data.attr);
+         free (data.attr);
+       }
     }
   return NULL;
 }
@@ -214,6 +217,42 @@ init_mq_netlink (void)
     }
 }
 
+static int
+__pthread_attr_copy (pthread_attr_t *target, const pthread_attr_t *source)
+{
+  /* Avoid overwriting *TARGET until all allocations have
+     succeeded.  */
+  union
+  {
+    pthread_attr_t external;
+    struct pthread_attr internal;
+  } temp;
+
+
+  temp.external = *source;
+
+  /* Force new allocation.  This function has full ownership of temp.  */
+  temp.internal.cpuset = NULL;
+  temp.internal.cpusetsize = 0;
+
+  struct pthread_attr *isource = (struct pthread_attr *) source;
+
+  /* Propagate affinity mask information.  */
+  if (isource->cpuset != NULL && isource->cpusetsize > 0)
+    {
+      temp.internal.cpuset = (cpu_set_t *) malloc (isource->cpusetsize);
+      if (temp.internal.cpuset == NULL)
+        return ENOMEM;
+
+      temp.internal.cpusetsize = isource->cpusetsize;
+      memcpy (temp.internal.cpuset, isource->cpuset, isource->cpusetsize);
+    }
+
+  /* Transfer ownership.  *target is not assumed to have been
+     initialized.  */
+  *target = temp.external;
+  return 0;
+}
 
 /* Register notification upon message arrival to an empty message queue
    MQDES.  */
@@ -257,8 +296,14 @@ mq_notify (mqd_t mqdes, const struct sigevent *notification)
       if (data.attr == NULL)
        return -1;
 
-      memcpy (data.attr, notification->sigev_notify_attributes,
-             sizeof (pthread_attr_t));
+      int ret = __pthread_attr_copy (data.attr,
+                                    notification->sigev_notify_attributes);
+      if (ret != 0)
+       {
+         free (data.attr);
+         __set_errno (ret);
+         return -1;
+       }
     }
 
   /* Construct the new request.  */
@@ -271,8 +316,11 @@ mq_notify (mqd_t mqdes, const struct sigevent *notification)
   int retval = INLINE_SYSCALL (mq_notify, 2, mqdes, &se);
 
   /* If it failed, free the allocated memory.  */
-  if (__glibc_unlikely (retval != 0))
-    free (data.attr);
+  if (retval != 0 && data.attr != NULL)
+    {
+      pthread_attr_destroy (data.attr);
+      free (data.attr);
+    }
 
   return retval;
 }