From 42d359350510506b87101cf77202fefcbfc790cb Mon Sep 17 00:00:00 2001
From: Andreas Schwab <schwab@linux-m68k.org>
Date: Thu, 27 May 2021 12:49:47 +0200
Subject: [PATCH] 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.
 
  CVE-2021-33574: The mq_notify function has a potential use-after-free
  issue when using a notification type of SIGEV_THREAD and a thread
  attribute with a non-default affinity mask.


Index: glibc-2.11.1/nptl/Makefile
===================================================================
--- glibc-2.11.1.orig/nptl/Makefile
+++ glibc-2.11.1/nptl/Makefile
@@ -124,7 +124,8 @@ libpthread-routines = nptl-init vars eve
 		      pthread_mutexattr_setprotocol \
 		      pthread_mutexattr_getprioceiling \
 		      pthread_mutexattr_setprioceiling tpp \
-		      pthread_mutex_getprioceiling pthread_mutex_setprioceiling
+		      pthread_mutex_getprioceiling pthread_mutex_setprioceiling \
+		      pthread_attr_copy
 #		      pthread_setuid pthread_seteuid pthread_setreuid \
 #		      pthread_setresuid \
 #		      pthread_setgid pthread_setegid pthread_setregid \
Index: glibc-2.11.1/nptl/Versions
===================================================================
--- glibc-2.11.1.orig/nptl/Versions
+++ glibc-2.11.1/nptl/Versions
@@ -249,5 +249,6 @@ libpthread {
     __pthread_initialize_minimal;
     __pthread_clock_gettime; __pthread_clock_settime;
     __pthread_unwind;
+    __pthread_attr_copy;
   }
 }
Index: glibc-2.11.1/nptl/pthreadP.h
===================================================================
--- glibc-2.11.1.orig/nptl/pthreadP.h
+++ glibc-2.11.1/nptl/pthreadP.h
@@ -430,6 +430,8 @@ extern int __pthread_mutexattr_init (pth
 extern int __pthread_mutexattr_destroy (pthread_mutexattr_t *attr);
 extern int __pthread_mutexattr_settype (pthread_mutexattr_t *attr, int kind);
 extern int __pthread_attr_destroy (pthread_attr_t *attr);
+extern int __pthread_attr_setaffinity_np (pthread_attr_t *, size_t,
+					  const cpu_set_t *);
 extern int __pthread_attr_getdetachstate (const pthread_attr_t *attr,
 					  int *detachstate);
 extern int __pthread_attr_setdetachstate (pthread_attr_t *attr,
@@ -577,4 +579,9 @@ extern void __wait_lookup_done (void) at
 # define PTHREAD_STATIC_FN_REQUIRE(name) __asm (".globl " #name);
 #endif
 
+/* Make a deep copy of the attribute *SOURCE in *TARGET.  *TARGET is
+   not assumed to have been initialized.  Returns 0 on success, or a
+   positive error code otherwise.  */
+int __pthread_attr_copy (pthread_attr_t *target, const pthread_attr_t *source);
+
 #endif	/* pthreadP.h */
Index: glibc-2.11.1/nptl/pthread_attr_copy.c
===================================================================
--- /dev/null
+++ glibc-2.11.1/nptl/pthread_attr_copy.c
@@ -0,0 +1,56 @@
+/* Deep copy of a pthread_attr_t object.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <pthreadP.h>
+#include <stdlib.h>
+
+int
+__pthread_attr_copy (pthread_attr_t *target, const pthread_attr_t *source)
+{
+  /* Avoid overwriting *TARGET until all allocations have
+     succeeded.  */
+  union pthread_attr_transparent temp;
+  temp.external = *source;
+
+  /* Force new allocation.  This function has full ownership of temp.  */
+  temp.internal.cpuset = NULL;
+  temp.internal.cpusetsize = 0;
+
+  int ret = 0;
+
+  struct pthread_attr *isource = (struct pthread_attr *) source;
+
+  /* Propagate affinity mask information.  */
+  if (isource->cpusetsize > 0)
+    ret = __pthread_attr_setaffinity_np (&temp.external,
+					 isource->cpusetsize,
+					 isource->cpuset);
+
+  if (ret != 0)
+    {
+      /* Deallocate because we have ownership.  */
+      __pthread_attr_destroy (&temp.external);
+      return ret;
+    }
+
+  /* Transfer ownership.  *target is not assumed to have been
+     initialized.  */
+  *target = temp.external;
+  return 0;
+}
Index: glibc-2.11.1/nptl/sysdeps/unix/sysv/linux/internaltypes.h
===================================================================
--- glibc-2.11.1.orig/nptl/sysdeps/unix/sysv/linux/internaltypes.h
+++ glibc-2.11.1/nptl/sysdeps/unix/sysv/linux/internaltypes.h
@@ -49,6 +49,14 @@ struct pthread_attr
 #define ATTR_FLAG_POLICY_SET		0x0040
 
 
+/* Used to allocate a pthread_attr_t object which is also accessed
+   internally.  */
+union pthread_attr_transparent
+{
+  pthread_attr_t external;
+  struct pthread_attr internal;
+};
+
 /* Mutex attribute data structure.  */
 struct pthread_mutexattr
 {
Index: glibc-2.11.1/nptl/sysdeps/unix/sysv/linux/mq_notify.c
===================================================================
--- glibc-2.11.1.orig/nptl/sysdeps/unix/sysv/linux/mq_notify.c
+++ glibc-2.11.1/nptl/sysdeps/unix/sysv/linux/mq_notify.c
@@ -135,9 +135,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;
 }
@@ -285,8 +288,14 @@ mq_notify (mqd_t mqdes, const struct sig
       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.  */
@@ -299,8 +308,11 @@ mq_notify (mqd_t mqdes, const struct sig
   int retval = INLINE_SYSCALL (mq_notify, 2, mqdes, &se);
 
   /* If it failed, free the allocated memory.  */
-  if (__builtin_expect (retval != 0, 0))
-    free (data.attr);
+  if (retval != 0 && data.attr != NULL)
+    {
+      pthread_attr_destroy (data.attr);
+      free (data.attr);
+    }
 
   return retval;
 }
Index: glibc-2.11.1/nptl/sysdeps/unix/sysv/linux/pthread_attr_setaffinity.c
===================================================================
--- glibc-2.11.1.orig/nptl/sysdeps/unix/sysv/linux/pthread_attr_setaffinity.c
+++ glibc-2.11.1/nptl/sysdeps/unix/sysv/linux/pthread_attr_setaffinity.c
@@ -32,8 +32,8 @@ extern int __determine_cpumask_size (pid
 
 
 int
-__pthread_attr_setaffinity_new (pthread_attr_t *attr, size_t cpusetsize,
-				const cpu_set_t *cpuset)
+__pthread_attr_setaffinity_np (pthread_attr_t *attr, size_t cpusetsize,
+			       const cpu_set_t *cpuset)
 {
   struct pthread_attr *iattr;
 
@@ -79,7 +79,7 @@ __pthread_attr_setaffinity_new (pthread_
 
   return 0;
 }
-versioned_symbol (libpthread, __pthread_attr_setaffinity_new,
+versioned_symbol (libpthread, __pthread_attr_setaffinity_np,
 		  pthread_attr_setaffinity_np, GLIBC_2_3_4);
 
 
@@ -88,7 +88,7 @@ int
 __pthread_attr_setaffinity_old (pthread_attr_t *attr, cpu_set_t *cpuset)
 {
   /* The old interface by default assumed a 1024 processor bitmap.  */
-  return __pthread_attr_setaffinity_new (attr, 128, cpuset);
+  return __pthread_attr_setaffinity_np (attr, 128, cpuset);
 }
 compat_symbol (libpthread, __pthread_attr_setaffinity_old,
 	       pthread_attr_setaffinity_np, GLIBC_2_3_3);
