077c9d
commit d524fa6c35e675eedbd8fe6cdf4db0b49c658026
077c9d
Author: H.J. Lu <hjl.tools@gmail.com>
077c9d
Date:   Thu Nov 8 10:06:58 2018 -0800
077c9d
077c9d
    Check multiple NT_GNU_PROPERTY_TYPE_0 notes [BZ #23509]
077c9d
    
077c9d
    Linkers group input note sections with the same name into one output
077c9d
    note section with the same name.  One output note section is placed in
077c9d
    one PT_NOTE segment.  Since new linkers merge input .note.gnu.property
077c9d
    sections into one output .note.gnu.property section, there is only
077c9d
    one NT_GNU_PROPERTY_TYPE_0 note in one PT_NOTE segment with new linkers.
077c9d
    Since older linkers treat input .note.gnu.property section as a generic
077c9d
    note section and just concatenate all input .note.gnu.property sections
077c9d
    into one output .note.gnu.property section without merging them, we may
077c9d
    see multiple NT_GNU_PROPERTY_TYPE_0 notes in one PT_NOTE segment with
077c9d
    older linkers.
077c9d
    
077c9d
    When an older linker is used to created the program on CET-enabled OS,
077c9d
    the linker output has a single .note.gnu.property section with multiple
077c9d
    NT_GNU_PROPERTY_TYPE_0 notes, some of which have IBT and SHSTK enable
077c9d
    bits set even if the program isn't CET enabled.  Such programs will
077c9d
    crash on CET-enabled machines.  This patch updates the note parser:
077c9d
    
077c9d
    1. Skip note parsing if a NT_GNU_PROPERTY_TYPE_0 note has been processed.
077c9d
    2. Check multiple NT_GNU_PROPERTY_TYPE_0 notes.
077c9d
    
077c9d
            [BZ #23509]
077c9d
            * sysdeps/x86/dl-prop.h (_dl_process_cet_property_note): Skip
077c9d
            note parsing if a NT_GNU_PROPERTY_TYPE_0 note has been processed.
077c9d
            Update the l_cet field when processing NT_GNU_PROPERTY_TYPE_0 note.
077c9d
            Check multiple NT_GNU_PROPERTY_TYPE_0 notes.
077c9d
            * sysdeps/x86/link_map.h (l_cet): Expand to 3 bits,  Add
077c9d
            lc_unknown.
077c9d
077c9d
diff --git a/sysdeps/x86/dl-prop.h b/sysdeps/x86/dl-prop.h
077c9d
index 26c3131ac5e2d080..9ab890d12bb99133 100644
077c9d
--- a/sysdeps/x86/dl-prop.h
077c9d
+++ b/sysdeps/x86/dl-prop.h
077c9d
@@ -49,6 +49,10 @@ _dl_process_cet_property_note (struct link_map *l,
077c9d
 			      const ElfW(Addr) align)
077c9d
 {
077c9d
 #if CET_ENABLED
077c9d
+  /* Skip if we have seen a NT_GNU_PROPERTY_TYPE_0 note before.  */
077c9d
+  if (l->l_cet != lc_unknown)
077c9d
+    return;
077c9d
+
077c9d
   /* The NT_GNU_PROPERTY_TYPE_0 note must be aliged to 4 bytes in
077c9d
      32-bit objects and to 8 bytes in 64-bit objects.  Skip notes
077c9d
      with incorrect alignment.  */
077c9d
@@ -57,6 +61,9 @@ _dl_process_cet_property_note (struct link_map *l,
077c9d
 
077c9d
   const ElfW(Addr) start = (ElfW(Addr)) note;
077c9d
 
077c9d
+  unsigned int feature_1 = 0;
077c9d
+  unsigned int last_type = 0;
077c9d
+
077c9d
   while ((ElfW(Addr)) (note + 1) - start < size)
077c9d
     {
077c9d
       /* Find the NT_GNU_PROPERTY_TYPE_0 note.  */
077c9d
@@ -64,10 +71,18 @@ _dl_process_cet_property_note (struct link_map *l,
077c9d
 	  && note->n_type == NT_GNU_PROPERTY_TYPE_0
077c9d
 	  && memcmp (note + 1, "GNU", 4) == 0)
077c9d
 	{
077c9d
+	  /* Stop if we see more than one GNU property note which may
077c9d
+	     be generated by the older linker.  */
077c9d
+	  if (l->l_cet != lc_unknown)
077c9d
+	    return;
077c9d
+
077c9d
+	  /* Check CET status now.  */
077c9d
+	  l->l_cet = lc_none;
077c9d
+
077c9d
 	  /* Check for invalid property.  */
077c9d
 	  if (note->n_descsz < 8
077c9d
 	      || (note->n_descsz % sizeof (ElfW(Addr))) != 0)
077c9d
-	    break;
077c9d
+	    return;
077c9d
 
077c9d
 	  /* Start and end of property array.  */
077c9d
 	  unsigned char *ptr = (unsigned char *) (note + 1) + 4;
077c9d
@@ -78,9 +93,15 @@ _dl_process_cet_property_note (struct link_map *l,
077c9d
 	      unsigned int type = *(unsigned int *) ptr;
077c9d
 	      unsigned int datasz = *(unsigned int *) (ptr + 4);
077c9d
 
077c9d
+	      /* Property type must be in ascending order.  */
077c9d
+	      if (type < last_type)
077c9d
+		return;
077c9d
+
077c9d
 	      ptr += 8;
077c9d
 	      if ((ptr + datasz) > ptr_end)
077c9d
-		break;
077c9d
+		return;
077c9d
+
077c9d
+	      last_type = type;
077c9d
 
077c9d
 	      if (type == GNU_PROPERTY_X86_FEATURE_1_AND)
077c9d
 		{
077c9d
@@ -89,14 +110,18 @@ _dl_process_cet_property_note (struct link_map *l,
077c9d
 		     we stop the search regardless if its size is correct
077c9d
 		     or not.  There is no point to continue if this note
077c9d
 		     is ill-formed.  */
077c9d
-		  if (datasz == 4)
077c9d
-		    {
077c9d
-		      unsigned int feature_1 = *(unsigned int *) ptr;
077c9d
-		      if ((feature_1 & GNU_PROPERTY_X86_FEATURE_1_IBT))
077c9d
-			l->l_cet |= lc_ibt;
077c9d
-		      if ((feature_1 & GNU_PROPERTY_X86_FEATURE_1_SHSTK))
077c9d
-			l->l_cet |= lc_shstk;
077c9d
-		    }
077c9d
+		  if (datasz != 4)
077c9d
+		    return;
077c9d
+
077c9d
+		  feature_1 = *(unsigned int *) ptr;
077c9d
+
077c9d
+		  /* Keep searching for the next GNU property note
077c9d
+		     generated by the older linker.  */
077c9d
+		  break;
077c9d
+		}
077c9d
+	      else if (type > GNU_PROPERTY_X86_FEATURE_1_AND)
077c9d
+		{
077c9d
+		  /* Stop since property type is in ascending order.  */
077c9d
 		  return;
077c9d
 		}
077c9d
 
077c9d
@@ -112,6 +137,12 @@ _dl_process_cet_property_note (struct link_map *l,
077c9d
 	      + ELF_NOTE_NEXT_OFFSET (note->n_namesz, note->n_descsz,
077c9d
 				      align));
077c9d
     }
077c9d
+
077c9d
+  /* We get here only if there is one or no GNU property note.  */
077c9d
+  if ((feature_1 & GNU_PROPERTY_X86_FEATURE_1_IBT))
077c9d
+    l->l_cet |= lc_ibt;
077c9d
+  if ((feature_1 & GNU_PROPERTY_X86_FEATURE_1_SHSTK))
077c9d
+    l->l_cet |= lc_shstk;
077c9d
 #endif
077c9d
 }
077c9d
 
077c9d
diff --git a/sysdeps/x86/link_map.h b/sysdeps/x86/link_map.h
077c9d
index ef1206a9d2396a6f..9367ed08896794a4 100644
077c9d
--- a/sysdeps/x86/link_map.h
077c9d
+++ b/sysdeps/x86/link_map.h
077c9d
@@ -19,8 +19,9 @@
077c9d
 /* If this object is enabled with CET.  */
077c9d
 enum
077c9d
   {
077c9d
-    lc_none = 0,			 /* Not enabled with CET.  */
077c9d
-    lc_ibt = 1 << 0,			 /* Enabled with IBT.  */
077c9d
-    lc_shstk = 1 << 1,			 /* Enabled with STSHK.  */
077c9d
+    lc_unknown = 0,			 /* Unknown CET status.  */
077c9d
+    lc_none = 1 << 0,			 /* Not enabled with CET.  */
077c9d
+    lc_ibt = 1 << 1,			 /* Enabled with IBT.  */
077c9d
+    lc_shstk = 1 << 2,			 /* Enabled with STSHK.  */
077c9d
     lc_ibt_and_shstk = lc_ibt | lc_shstk /* Enabled with both.  */
077c9d
-  } l_cet:2;
077c9d
+  } l_cet:3;