From fdeeb62e28f7b79fd2222d14da454aab72af2f00 Mon Sep 17 00:00:00 2001
From: Michael Matz <matz@suse.de>
Date: Mon, 20 Jan 2020 05:31:09 +0100
Subject: [PATCH] Fix symbol versions with shared libs

ELF files that refer to shared libs containing sym-versions, but
don't refer to any dynamic symbols with symbol versions (should happen
only with very simple shared libs) would generate an empty .gnu.version_r
section.  Some dynamic linker contain bugs in that they don't check
the section size or DT_VERNEEDNUM (which are both zero for such files
we generate) before accessing the first entry, and then bail out with
a message like

./a.exe: error while loading shared libraries: ./a1.so: unsupported
 version 25960 of Verneed record

(where the "version number" actually comes from neighboring bytes
from different sections).

So, there's not much choice, we simply must not generate such section.
---
 tccelf.c | 86 ++++++++++++++++++++++++++++++--------------------------
 1 file changed, 46 insertions(+), 40 deletions(-)

diff --git a/tccelf.c b/tccelf.c
index fe00a30f..de81e33c 100644
--- a/tccelf.c
+++ b/tccelf.c
@@ -558,9 +558,7 @@ version_add (TCCState *s1)
         return;
     versym_section = new_section(s1, ".gnu.version", SHT_GNU_versym, SHF_ALLOC);
     versym_section->sh_entsize = sizeof(ElfW(Half));
-    verneed_section = new_section(s1, ".gnu.version_r", SHT_GNU_verneed, SHF_ALLOC);
     versym_section->link = s1->dynsym;
-    verneed_section->link = s1->dynsym->link;
 
     /* add needed symbols */
     symtab = s1->dynsym;
@@ -580,43 +578,50 @@ version_add (TCCState *s1)
         } else
           versym[sym_index] = 0;
     }
-    /* generate verneed section */
-    for (i = nb_sym_versions; i-- > 0;) {
-        struct sym_version *sv = &sym_versions[i];
-        int n_same_libs = 0, prev;
-        size_t vnofs;
-        ElfW(Vernaux) *vna = 0;
-        if (sv->out_index < 1)
-          continue;
-        vnofs = section_add(verneed_section, sizeof(*vn), 1);
-        vn = (ElfW(Verneed)*)(verneed_section->data + vnofs);
-        vn->vn_version = 1;
-        vn->vn_file = put_elf_str(verneed_section->link, sv->lib);
-        vn->vn_aux = sizeof (*vn);
-        do {
-            prev = sv->prev_same_lib;
-            if (sv->out_index > 0) {
-                vna = section_ptr_add(verneed_section, sizeof(*vna));
-                vna->vna_hash = elf_hash ((const unsigned char *)sv->version);
-                vna->vna_flags = 0;
-                vna->vna_other = sv->out_index;
-                sv->out_index = -2;
-                vna->vna_name = put_elf_str(verneed_section->link, sv->version);
-                vna->vna_next = sizeof (*vna);
-                n_same_libs++;
-            }
-            if (prev >= 0)
-                sv = &sym_versions[prev];
-        } while(prev >= 0);
-        vna->vna_next = 0;
-        vn = (ElfW(Verneed)*)(verneed_section->data + vnofs);
-        vn->vn_cnt = n_same_libs;
-        vn->vn_next = sizeof(*vn) + n_same_libs * sizeof(*vna);
-        nb_entries++;
+    /* generate verneed section, but not when it will be empty.  Some
+       dynamic linkers look at their contents even when DTVERNEEDNUM and
+       section size is zero.  */
+    if (nb_versions > 2) {
+        verneed_section = new_section(s1, ".gnu.version_r",
+                                      SHT_GNU_verneed, SHF_ALLOC);
+        verneed_section->link = s1->dynsym->link;
+        for (i = nb_sym_versions; i-- > 0;) {
+            struct sym_version *sv = &sym_versions[i];
+            int n_same_libs = 0, prev;
+            size_t vnofs;
+            ElfW(Vernaux) *vna = 0;
+            if (sv->out_index < 1)
+              continue;
+            vnofs = section_add(verneed_section, sizeof(*vn), 1);
+            vn = (ElfW(Verneed)*)(verneed_section->data + vnofs);
+            vn->vn_version = 1;
+            vn->vn_file = put_elf_str(verneed_section->link, sv->lib);
+            vn->vn_aux = sizeof (*vn);
+            do {
+                prev = sv->prev_same_lib;
+                if (sv->out_index > 0) {
+                    vna = section_ptr_add(verneed_section, sizeof(*vna));
+                    vna->vna_hash = elf_hash ((const unsigned char *)sv->version);
+                    vna->vna_flags = 0;
+                    vna->vna_other = sv->out_index;
+                    sv->out_index = -2;
+                    vna->vna_name = put_elf_str(verneed_section->link, sv->version);
+                    vna->vna_next = sizeof (*vna);
+                    n_same_libs++;
+                }
+                if (prev >= 0)
+                  sv = &sym_versions[prev];
+            } while(prev >= 0);
+            vna->vna_next = 0;
+            vn = (ElfW(Verneed)*)(verneed_section->data + vnofs);
+            vn->vn_cnt = n_same_libs;
+            vn->vn_next = sizeof(*vn) + n_same_libs * sizeof(*vna);
+            nb_entries++;
+        }
+        if (vn)
+          vn->vn_next = 0;
+        verneed_section->sh_info = nb_entries;
     }
-    if (vn)
-      vn->vn_next = 0;
-    verneed_section->sh_info = nb_entries;
     dt_verneednum = nb_entries;
 }
 #endif
@@ -2045,10 +2050,11 @@ static void fill_dynamic(TCCState *s1, struct dyn_inf *dyninf)
     put_dt(dynamic, DT_RELENT, sizeof(ElfW_Rel));
 #endif
 #endif
-    if (versym_section) {
+    if (versym_section)
+        put_dt(dynamic, DT_VERSYM, versym_section->sh_addr);
+    if (verneed_section) {
         put_dt(dynamic, DT_VERNEED, verneed_section->sh_addr);
         put_dt(dynamic, DT_VERNEEDNUM, dt_verneednum);
-        put_dt(dynamic, DT_VERSYM, versym_section->sh_addr);
     }
     s = find_section_create (s1, ".preinit_array", 0);
     if (s && s->data_offset) {